Mercurial > dovecot > core-2.2
changeset 15195:70305d850220
Adds HTTP URL parse support.
author | Stephan Bosch <stephan@rename-it.nl> |
---|---|
date | Wed, 10 Oct 2012 23:57:56 +0300 |
parents | b8929da80876 |
children | d927aaaf9252 |
files | configure.in src/Makefile.am src/lib-http/Makefile.am src/lib-http/http-url.c src/lib-http/http-url.h src/lib-http/test-http-url.c |
diffstat | 6 files changed, 834 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/configure.in Wed Oct 10 23:56:01 2012 +0300 +++ b/configure.in Wed Oct 10 23:57:56 2012 +0300 @@ -2754,6 +2754,7 @@ src/lib-dict/Makefile src/lib-dns/Makefile src/lib-fs/Makefile +src/lib-http/Makefile src/lib-imap/Makefile src/lib-imap-storage/Makefile src/lib-imap-client/Makefile
--- a/src/Makefile.am Wed Oct 10 23:56:01 2012 +0300 +++ b/src/Makefile.am Wed Oct 10 23:57:56 2012 +0300 @@ -9,6 +9,7 @@ lib-imap \ lib-imap-storage \ lib-master \ + lib-http \ lib-dict \ lib-settings \ lib-ssl-iostream
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-http/Makefile.am Wed Oct 10 23:57:56 2012 +0300 @@ -0,0 +1,37 @@ +noinst_LTLIBRARIES = libhttp.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-test \ + -I$(top_srcdir)/src/lib-dns \ + -I$(top_srcdir)/src/lib-ssl-iostream + +libhttp_la_SOURCES = \ + http-url.c + +headers = \ + http-url.h + +pkginc_libdir=$(pkgincludedir) +pkginc_lib_HEADERS = $(headers) + +test_programs = \ + test-http-url + +noinst_PROGRAMS = $(test_programs) + +test_libs = \ + ../lib-test/libtest.la \ + ../lib/liblib.la + +test_deps = $(noinst_LTLIBRARIES) $(test_libs) + +test_http_url_SOURCES = test-http-url.c +test_http_url_LDADD = http-url.lo $(test_libs) +test_http_url_DEPENDENCIES = $(test_deps) + +check: check-am check-test +check-test: all-am + for bin in $(test_programs); do \ + if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \ + done
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-http/http-url.c Wed Oct 10 23:57:56 2012 +0300 @@ -0,0 +1,287 @@ +/* Copyright (c) 2012 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "strfuncs.h" +#include "net.h" +#include "uri-util.h" +#include "http-url.h" + +/* + * HTTP URL parser + */ + +struct http_url_parser { + struct uri_parser parser; + + enum http_url_parse_flags flags; + + struct http_url *url; + struct http_url *base; + + unsigned int relative:1; +}; + +static bool http_url_do_parse(struct http_url_parser *url_parser) +{ + struct uri_parser *parser = &url_parser->parser; + struct http_url *url = url_parser->url, *base = url_parser->base; + struct uri_authority auth; + const char *const *path; + bool relative = TRUE, have_path = FALSE; + int path_relative; + const char *part; + int ret; + + /* RFC 2616 - Hypertext Transfer Protocol, Section 3.2: + * + * http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]] + * + * Translated to RFC 3986: + * + * absolute-http-URL = "http:" "//" host [ ":" port ] path-absolute + * ["?" query] [ "#" fragment ] + * relative-http-ref = relative-http-part [ "?" query ] [ "#" fragment ] + * relative-http-part = "//" host [ ":" port ] path-abempty + * / path-absolute + * / path-noscheme + * / path-empty + */ + + /* "http:" / "https:" */ + if ((url_parser->flags & HTTP_URL_PARSE_SCHEME_EXTERNAL) == 0) { + const char *scheme; + + if ((ret = uri_parse_scheme(parser, &scheme)) < 0) + return FALSE; + else if (ret > 0) { + if (strcasecmp(scheme, "https") == 0) { + if (url != NULL) + url->have_ssl = TRUE; + } else if (strcasecmp(scheme, "http") != 0) { + parser->error = "Not an HTTP URL"; + return FALSE; + } + relative = FALSE; + } + } else { + relative = FALSE; + } + + /* "//" host [ ":" port ] */ + if ((ret = uri_parse_authority(parser, &auth)) < 0) + return FALSE; + if (ret > 0) { + if (auth.enc_userinfo != NULL) { + /* http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-20 + + Section 2.8.1: + + {...} Senders MUST NOT include a userinfo subcomponent (and its "@" + delimiter) when transmitting an "http" URI in a message. Recipients + of HTTP messages that contain a URI reference SHOULD parse for the + existence of userinfo and treat its presence as an error, likely + indicating that the deprecated subcomponent is being used to + obscure the authority for the sake of phishing attacks. + */ + parser->error = "HTTP URL does not allow `userinfo@' part"; + return FALSE; + } + relative = FALSE; + } else if (!relative) { + parser->error = "Absolute HTTP URL requires `//' after `http:'"; + return FALSE; + } + + if (ret > 0 && url != NULL) { + url->host_name = auth.host_literal; + url->host_ip = auth.host_ip; + url->have_host_ip = auth.have_host_ip; + url->port = auth.port; + url->have_port = auth.have_port; + } + + /* path-abempty / path-absolute / path-noscheme / path-empty */ + if ((ret = uri_parse_path(parser, &path_relative, &path)) < 0) + return FALSE; + + /* Relative URLs are only valid when we have a base URL */ + if (relative) { + if (base == NULL) { + parser->error = "Relative URL not allowed"; + return FALSE; + } else if (url != NULL) { + url->host_name = p_strdup_empty(parser->pool, base->host_name); + url->host_ip = base->host_ip; + url->have_host_ip = base->have_host_ip; + url->port = base->port; + url->have_port = base->have_port; + } + + url_parser->relative = TRUE; + } + + /* Resolve path */ + if (ret > 0) { + string_t *fullpath; + + have_path = TRUE; + + if (url != NULL) + fullpath = t_str_new(256); + + if (relative && path_relative > 0 && base->path != NULL) { + const char *pbegin = base->path; + const char *pend = base->path + strlen(base->path); + const char *p = pend - 1; + + i_assert(*pbegin == '/'); + + /* discard trailing segments of base path based on how many effective + leading '..' segments were found in the relative path. + */ + while (path_relative > 0 && p > pbegin) { + while (p > pbegin && *p != '/') p--; + if (p >= pbegin) { + pend = p; + path_relative--; + } + if (p > pbegin) p--; + } + + if (url != NULL && pend > pbegin) + str_append_n(fullpath, pbegin, pend-pbegin); + } + + /* append relative path */ + while (*path != NULL) { + if (!uri_data_decode(parser, *path, NULL, &part)) + return FALSE; + + if (url != NULL) { + str_append_c(fullpath, '/'); + str_append(fullpath, part); + } + path++; + } + + if (url != NULL) + url->path = str_c(fullpath); + } else if (relative && url != NULL) { + url->path = base->path; + } + + /* [ "?" query ] */ + if ((ret = uri_parse_query(parser, &part)) < 0) + return FALSE; + if (ret > 0) { + if (!uri_data_decode(parser, part, NULL, NULL)) // check only + return FALSE; + if (url != NULL) + url->enc_query = p_strdup(parser->pool, part); + } else if (relative && !have_path && url != NULL) { + url->enc_query = p_strdup(parser->pool, base->enc_query); + } + + /* [ "#" fragment ] */ + if ((ret = uri_parse_fragment(parser, &part)) < 0) + return FALSE; + if (ret > 0) { + if ((url_parser->flags & HTTP_URL_ALLOW_FRAGMENT_PART) == 0) { + parser->error = "URL fragment not allowed for HTTP URL in this context"; + return FALSE; + } + if (!uri_data_decode(parser, part, NULL, NULL)) // check only + return FALSE; + if (url != NULL) + url->enc_fragment = p_strdup(parser->pool, part); + } else if (relative && !have_path && url != NULL) { + url->enc_fragment = p_strdup(parser->pool, base->enc_fragment); + } + + if (parser->cur != parser->end) { + parser->error = "HTTP URL contains invalid character."; + return FALSE; + } + return TRUE; +} + +/* Public API */ + +int http_url_parse(const char *url, struct http_url *base, + enum http_url_parse_flags flags, + struct http_url **url_r, const char **error_r) +{ + struct http_url_parser url_parser; + + /* base != NULL indicates whether relative URLs are allowed. However, certain + flags may also dictate whether relative URLs are allowed/required. */ + i_assert((flags & HTTP_URL_PARSE_SCHEME_EXTERNAL) == 0 || base == NULL); + + memset(&url_parser, '\0', sizeof(url_parser)); + uri_parser_init(&url_parser.parser, pool_datastack_create(), url); + + url_parser.url = t_new(struct http_url, 1); + url_parser.base = base; + url_parser.flags = flags; + + if (!http_url_do_parse(&url_parser)) { + *error_r = url_parser.parser.error; + return -1; + } + *url_r = url_parser.url; + return 0; +} + +/* + * HTTP URL construction + */ + +const char *http_url_create(const struct http_url *url) +{ + string_t *urlstr = t_str_new(512); + + /* scheme */ + uri_append_scheme(urlstr, "http"); + str_append(urlstr, "//"); + + /* host:port */ + if (url->host_name != NULL) { + /* assume IPv6 literal if starts with '['; avoid encoding */ + if (*url->host_name == '[') + str_append(urlstr, url->host_name); + else + uri_append_host_name(urlstr, url->host_name); + } else if (url->have_host_ip) { + uri_append_host_ip(urlstr, &url->host_ip); + } else + i_unreached(); + if (url->have_port) + uri_append_port(urlstr, url->port); + + if (url->path == NULL || *url->path == '\0') { + /* Older syntax of RFC 2616 requires this slash at all times for an + absolute URL + */ + str_append_c(urlstr, '/'); + } else { + uri_append_path_data(urlstr, "", url->path); + } + + /* query (pre-encoded) */ + if (url->enc_query != NULL) { + str_append_c(urlstr, '?'); + str_append(urlstr, url->enc_query); + } + + /* fragment */ + if (url->enc_fragment != NULL) { + str_append_c(urlstr, '#'); + str_append(urlstr, url->enc_fragment); + } + + return str_c(urlstr); +} + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-http/http-url.h Wed Oct 10 23:57:56 2012 +0300 @@ -0,0 +1,46 @@ +#ifndef HTTP_URL_H +#define HTTP_URL_H + +struct http_url { + /* server */ + const char *host_name; + struct ip_addr host_ip; + in_port_t port; + + /* path */ + const char *path; + + /* ?query (still encoded) */ + const char *enc_query; + + /* #fragment (still encoded) */ + const char *enc_fragment; + + unsigned int have_host_ip:1; /* URL uses IP address */ + unsigned int have_port:1; + unsigned int have_ssl:1; +}; + +/* + * HTTP URL parsing + */ + +enum http_url_parse_flags { + /* Scheme part 'http:' is already parsed externally. This implies that + this is an absolute HTTP URL. */ + HTTP_URL_PARSE_SCHEME_EXTERNAL = 0x01, + /* Allow '#fragment' part in URL */ + HTTP_URL_ALLOW_FRAGMENT_PART = 0x02 +}; + +int http_url_parse(const char *url, struct http_url *base, + enum http_url_parse_flags flags, + struct http_url **url_r, const char **error_r); + +/* + * HTTP URL construction + */ + +const char *http_url_create(const struct http_url *url); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-http/test-http-url.c Wed Oct 10 23:57:56 2012 +0300 @@ -0,0 +1,462 @@ +/* Copyright (c) 2009-2012 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, .have_port = TRUE } + },{ + .url = "http://127.0.0.1", + .url_parsed = { + .host_name = "127.0.0.1", + .have_host_ip = TRUE } +#ifdef HAVE_IPV6 + },{ + .url = "http://[::1]", + .url_parsed = { + .host_name = "[::1]", + .have_host_ip = TRUE } + },{ + .url = "http://[::1]:8080", + .url_parsed = { + .host_name = "[::1]", + .have_host_ip = TRUE, + .port = 8080, .have_port = TRUE } +#endif + },{ + .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." } + },{ + .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_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, &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) { + if (urlp->host_name == NULL || urlt->host_name == NULL) { + test_out(t_strdup_printf("url->host_name = %s", urlp->host_name), + urlp->host_name == urlt->host_name); + } else { + test_out(t_strdup_printf("url->host_name = %s", urlp->host_name), + strcmp(urlp->host_name, urlt->host_name) == 0); + } + if (!urlp->have_port) { + test_out("url->port = (unspecified)", + urlp->have_port == urlt->have_port); + } else { + test_out(t_strdup_printf("url->port = %u", urlp->port), + urlp->have_port == urlt->have_port && urlp->port == urlt->port); + } + if (!urlp->have_host_ip) { + test_out("url->host_ip = (unspecified)", + urlp->have_host_ip == urlt->have_host_ip); + } else { + test_out("url->host_ip = (valid)", + urlp->have_host_ip == urlt->have_host_ip); + } + if (urlp->path == NULL || urlt->path == NULL) { + test_out(t_strdup_printf("url->path = %s", urlp->path), + urlp->path == urlt->path); + } else { + test_out(t_strdup_printf("url->path = %s", urlp->path), + strcmp(urlp->path, urlt->path) == 0); + } + if (urlp->enc_query == NULL || urlt->enc_query == NULL) { + test_out(t_strdup_printf( + "url->enc_query = %s", urlp->enc_query), + urlp->enc_query == urlt->enc_query); + } else { + test_out(t_strdup_printf( + "url->enc_query = %s", urlp->enc_query), + strcmp(urlp->enc_query, urlt->enc_query) == 0); + } + if (urlp->enc_fragment == NULL || urlt->enc_fragment == NULL) { + test_out(t_strdup_printf( + "url->enc_fragment = %s", urlp->enc_fragment), + urlp->enc_fragment == urlt->enc_fragment); + } else { + test_out(t_strdup_printf( + "url->enc_fragment = %s", urlp->enc_fragment), + strcmp(urlp->enc_fragment, urlt->enc_fragment) == 0); + } + } + + 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" +#ifdef HAVE_IPV6 + },{ + .url = "http://[1::34a:34:234::6]/index.html" +#endif + },{ + .url = "http://example%a.com/index.html" + },{ + .url = "http://example.com%/index.html" + },{ + .url = "http://example%00.com/index.html" + },{ + .url = "http://example.com:65539/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, &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/", +#ifdef HAVE_IPV6 + "http://[::1]/", +#endif + "http://www.example.com:993/", + "http://www.example.com/index.html", + "http://www.example.com/settings/index.html", + "http://ww.%23example.com/", + "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, &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 (*test_functions[])(void) = { + test_http_url_valid, + test_http_url_invalid, + test_http_url_parse_create, + NULL + }; + return test_run(test_functions); +} +