diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib-http/test-http-url.c | 950 |
1 files changed, 950 insertions, 0 deletions
diff --git a/src/lib-http/test-http-url.c b/src/lib-http/test-http-url.c new file mode 100644 index 0000000..a9fdf24 --- /dev/null +++ b/src/lib-http/test-http-url.c @@ -0,0 +1,950 @@ +/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "net.h" +#include "http-url.h" +#include "test-common.h" + +struct valid_http_url_test { + const char *url; + enum http_url_parse_flags flags; + struct http_url url_base; + + struct http_url url_parsed; +}; + +/* Valid HTTP URL tests */ +static struct valid_http_url_test valid_url_tests[] = { + /* Generic tests */ + { + .url = "http://localhost", + .url_parsed = { + .host = { .name = "localhost" }, + }, + }, + { + .url = "http://www.%65%78%61%6d%70%6c%65.com", + .url_parsed = { + .host = { .name = "www.example.com" }, + }, + }, + { + .url = "http://www.dovecot.org:8080", + .url_parsed = { + .host = { .name = "www.dovecot.org" }, + .port = 8080, + }, + }, + { + .url = "http://127.0.0.1", + .url_parsed = { + .host = { + .name = "127.0.0.1", + .ip = { .family = AF_INET }, + }, + }, + }, + { + .url = "http://[::1]", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 }, + }, + }, + }, + { + .url = "http://[::1]:8080", + .url_parsed = { + .host = { + .name = "[::1]", + .ip = { .family = AF_INET6 }, + }, + .port = 8080, + }, + }, + { + .url = "http://user@api.dovecot.org", + .flags = HTTP_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "user", + }, + }, + { + .url = "http://userid:secret@api.dovecot.org", + .flags = HTTP_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "userid", + .password = "secret", + }, + }, + { + .url = "http://su%3auserid:secret@api.dovecot.org", + .flags = HTTP_URL_ALLOW_USERINFO_PART, + .url_parsed = { + .host = { .name = "api.dovecot.org" }, + .user = "su:userid", + .password = "secret", + }, + }, + { + .url = "http://www.example.com/" + "?question=What%20are%20you%20doing%3f&answer=Nothing.", + .url_parsed = { + .path = "/", + .host = { .name = "www.example.com" }, + .enc_query = "question=What%20are%20you%20doing%3f&answer=Nothing.", + }, + }, + /* Empty path segments */ + { + .url = "http://target//index.php", + .url_parsed = { + .path = "//index.php", + .host = { .name = "target" }, + }, + }, + { + .url = "http://target//path//index.php", + .url_parsed = { + .path = "//path//index.php", + .host = { .name = "target" }, + }, + }, + { + .url = "http://target//path/", + .url_parsed = { + .path = "//path/", + .host = { .name = "target" }, + }, + }, + { + .url = "http://target//path//", + .url_parsed = { + .path = "//path//", + .host = { .name = "target" }, + }, + }, + { + .url = "http://target//path//to//./index.php", + .url_parsed = { + .path = "//path//to//index.php", + .host = { .name = "target" }, + }, + }, + { + .url = "http://target//path//to//../index.php", + .url_parsed = { + .path = "//path//to/index.php", + .host = { .name = "target" }, + }, + }, + { + .url = "/index.php", + .url_base = { + .host = { .name = "target" }, + }, + .url_parsed = { + .host = { .name = "target" }, + .path = "/index.php", + }, + }, + { + .url = "//index.php", + .url_base = { + .host = { .name = "target" }, + }, + .url_parsed = { + .host = { .name = "index.php" }, + }, + }, + { + .url = "/path/to/index.php", + .url_base = { + .host = { .name = "target" }, + }, + .url_parsed = { + .host = { .name = "target" }, + .path = "/path/to/index.php", + }, + }, + { + .url = "//path//to//index.php", + .url_base = { + .host = { .name = "target" }, + }, + .url_parsed = { + .host = { .name = "path" }, + .path = "//to//index.php", + }, + }, + /* These next 2 URLs don't follow the recommendations in + http://tools.ietf.org/html/rfc1034#section-3.5 and + http://tools.ietf.org/html/rfc3696 + However they satisfy the grammar in + http://tools.ietf.org/html/rfc1123#section-2 and + http://tools.ietf.org/html/rfc952 + so we should parse them. + */ + { + .url = "http://256.0.0.1/that/reverts/to/DNS", + .url_parsed = { + .path = "/that/reverts/to/DNS", + .host = { .name = "256.0.0.1" }, + }, + }, + { + .url = "http://127.0.0.284/this/also/reverts/to/DNS", + .url_parsed = { + .path = "/this/also/reverts/to/DNS", + .host = { .name = "127.0.0.284" }, + }, + }, + { + .url = "http://www.example.com/#Status%20of%20development", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_parsed = { + .path = "/", + .host = { .name = "www.example.com" }, + .enc_fragment = "Status%20of%20development", + }, + }, + /* RFC 3986, Section 5.4. Reference Resolution Examples + * + * Within a representation with a well defined base URI of + * + * http://a/b/c/d;p?q + * + * a relative reference is transformed to its target URI as follows. + * + * 5.4.1. Normal Examples + */ + { // "g" = "http://a/b/c/g" + .url = "g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + }, + }, + { // "./g" = "http://a/b/c/g" + .url = "./g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + }, + }, + { // "g/" = "http://a/b/c/g/" + .url = "g/", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g/", + }, + }, + { // "/g" = "http://a/g" + .url = "/g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/g", + }, + }, + { // "//g" = "http://g" + .url = "//g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "g" }, + }, + }, + { // "?y" = "http://a/b/c/d;p?y" + .url = "?y", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "y", + }, + }, + { // "g?y" = "http://a/b/c/g?y" + .url = "g?y", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_query = "y", + }, + }, + { // "#s" = "http://a/b/c/d;p?q#s" + .url = "#s", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + .enc_fragment = "s", + }, + }, + { // "g#s" = "http://a/b/c/g#s" + .url = "g#s", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_fragment = "s", + }, + }, + { // "g?y#s" = "http://a/b/c/g?y#s" + .url = "g?y#s", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_query = "y", + .enc_fragment = "s", + }, + }, + { // ";x" = "http://a/b/c/;x" + .url = ";x", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/;x", + }, + }, + { // "g;x" = "http://a/b/c/g;x" + .url = "g;x", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g;x", + }, + + }, + { // "g;x?y#s" = "http://a/b/c/g;x?y#s" + .url = "g;x?y#s", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g;x", + .enc_query = "y", + .enc_fragment = "s", + }, + }, + { // "" = "http://a/b/c/d;p?q" + .url = "", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + }, + { // "." = "http://a/b/c/" + .url = ".", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/", + }, + }, + { // "./" = "http://a/b/c/" + .url = "./", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/", + }, + }, + { // ".." = "http://a/b/" + .url = "..", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/", + }, + }, + { // "../" = "http://a/b/" + .url = "../", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/", + }, + }, + { // "../g" = "http://a/b/g" + .url = "../g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/g", + }, + }, + { // "../.." = "http://a/" + .url = "../..", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/", + }, + }, + { // "../../" = "http://a/" + .url = "../../", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/", + }, + }, + { // "../../g" = "http://a/g" + .url = "../../g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/g", + }, + }, + /* 5.4.2. Abnormal Examples + */ + { // "../../../g" = "http://a/g" + .url = "../../../g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/g", + }, + }, + { // "../../../../g" = "http://a/g" + .url = "../../../../g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/g", + }, + }, + { // "/./g" = "http://a/g" + .url = "/./g", + .url_base = { + .host = {.name = "a"}, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = {.name = "a"}, + .path = "/g", + }, + }, + { // "/../g" = "http://a/g" + .url = "/../g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/g", + }, + }, + { // "g." = "http://a/b/c/g." + .url = "g.", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g.", + }, + }, + { // ".g" = "http://a/b/c/.g" + .url = ".g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/.g", + }, + }, + { // "g.." = "http://a/b/c/g.." + .url = "g..", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g..", + }, + }, + { // "..g" = "http://a/b/c/..g" + .url = "..g", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/..g", + }, + }, + { // "./../g" = "http://a/b/g" + .url = "./../g", + .url_base = { + .host = {.name = "a"}, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = {.name = "a"}, + .path = "/b/g", + }, + }, + { // "./g/." = "http://a/b/c/g/" + .url = "./g/.", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g/", + }, + }, + { // "g/./h" = "http://a/b/c/g/h" + .url = "g/./h", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g/h", + }, + }, + { // "g/../h" = "http://a/b/c/h" + .url = "g/../h", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/h", + }, + }, + { // "g;x=1/./y" = "http://a/b/c/g;x=1/y" + .url = "g;x=1/./y", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g;x=1/y", + }, + }, + { // "g;x=1/../y" = "http://a/b/c/y" + .url = "g;x=1/../y", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/y", + }, + }, + { // "g?y/./x" = "http://a/b/c/g?y/./x" + .url = "g?y/./x", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_query = "y/./x", + }, + }, + { // "g?y/../x" = "http://a/b/c/g?y/../x" + .url = "g?y/../x", + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_query = "y/../x", + }, + }, + { // "g#s/./x" = "http://a/b/c/g#s/./x" + .url = "g#s/./x", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = + { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_fragment = "s/./x", + }, + }, + { // "g#s/../x" = "http://a/b/c/g#s/../x" + .url = "g#s/../x", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART, + .url_base = { + .host = { .name = "a" }, + .path = "/b/c/d;p", + .enc_query = "q", + }, + .url_parsed = + { + .host = { .name = "a" }, + .path = "/b/c/g", + .enc_fragment = "s/../x", + }, + } +}; + +static unsigned int valid_url_test_count = N_ELEMENTS(valid_url_tests); + +static void +test_http_url_equal(struct http_url *urlt, struct http_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->path == NULL || urlt->path == NULL) { + test_assert(urlp->path == urlt->path); + } else { + test_assert(strcmp(urlp->path, urlt->path) == 0); + } + if (urlp->enc_query == NULL || urlt->enc_query == NULL) { + test_assert(urlp->enc_query == urlt->enc_query); + } else { + test_assert(strcmp(urlp->enc_query, urlt->enc_query) == 0); + } + if (urlp->enc_fragment == NULL || urlt->enc_fragment == NULL) { + test_assert(urlp->enc_fragment == urlt->enc_fragment); + } else { + test_assert(strcmp(urlp->enc_fragment, + urlt->enc_fragment) == 0); + } +} + +static void test_http_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 http_url_parse_flags flags = valid_url_tests[i].flags; + struct http_url *urlt = &valid_url_tests[i].url_parsed; + struct http_url *urlb = &valid_url_tests[i].url_base; + struct http_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("http url valid [%d]", i)); + + if (urlb->host.name == NULL) urlb = NULL; + if (http_url_parse(url, urlb, flags, pool_datastack_create(), + &urlp, &error) < 0) + urlp = NULL; + + test_out_reason(t_strdup_printf("http_url_parse(%s)", + valid_url_tests[i].url), urlp != NULL, error); + if (urlp != NULL) + test_http_url_equal(urlt, urlp); + + test_end(); + } T_END; +} + +struct invalid_http_url_test { + const char *url; + enum http_url_parse_flags flags; + struct http_url url_base; +}; + +static struct invalid_http_url_test invalid_url_tests[] = { + { + .url = "imap://example.com/INBOX" + }, + { + .url = "http:/www.example.com" + }, + { + .url = "" + }, + { + .url = "/index.html" + }, + { + .url = "http://www.example.com/index.html\"" + }, + { + .url = "http:///dovecot.org" + }, + { + .url = "http://[]/index.html" + }, + { + .url = "http://[v08.234:232:234:234:2221]/index.html" + }, + { + .url = "http://[1::34a:34:234::6]/index.html" + }, + { + .url = "http://example%a.com/index.html" + }, + { + .url = "http://example.com%/index.html" + }, + { + .url = "http://example%00.com/index.html" + }, + { + .url = "http://example.com:65536/index.html" + }, + { + .url = "http://example.com:72817/index.html" + }, + { + .url = "http://example.com/settings/%00/" + }, + { + .url = "http://example.com/settings/%0r/" + }, + { + .url = "http://example.com/settings/misc/%/" + }, + { + .url = "http://example.com/?%00" + }, + { + .url = "http://www.example.com/network.html#IMAP_Server" + }, + { + .url = "http://example.com/#%00", + .flags = HTTP_URL_ALLOW_FRAGMENT_PART + } +}; + +static unsigned int invalid_url_test_count = N_ELEMENTS(invalid_url_tests); + +static void test_http_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 http_url_parse_flags flags = invalid_url_tests[i].flags; + struct http_url *urlb = &invalid_url_tests[i].url_base; + struct http_url *urlp; + const char *error = NULL; + + if (urlb->host.name == NULL) + urlb = NULL; + + test_begin(t_strdup_printf("http url invalid [%d]", i)); + + if (http_url_parse(url, urlb, 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[] = { + "http://www.example.com/", + "http://10.0.0.1/", + "http://[::1]/", + "http://www.example.com:993/", + "http://www.example.com/index.html", + "http://www.example.com/settings/index.html", + "http://www.example.com/%23shared/news", + "http://www.example.com/query.php?name=Hendrik%20Visser", + "http://www.example.com/network.html#IMAP%20Server", +}; + +static unsigned int +parse_create_url_test_count = N_ELEMENTS(parse_create_url_tests); + +static void test_http_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 http_url *urlp; + const char *error = NULL; + + test_begin(t_strdup_printf("http url parse/create [%d]", i)); + + if (http_url_parse + (url, NULL, HTTP_URL_ALLOW_FRAGMENT_PART, + 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 = http_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_http_url_valid, + test_http_url_invalid, + test_http_url_parse_create, + NULL + }; + return test_run(test_functions); +} + |