/* 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",
		},
	},
	/* Encoded paths */
	{ // "http://a/%2f"
		.url = "http://a/%2f",
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//",
			.enc_path = "/%2f",
		},
	},
	{ // ""
		.url = "",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2f",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//",
			.enc_path = "/%2f",
		},
	},
	{ // ""
		.url = "",
		.url_base = {
			.host = { .name = "a" },
			.path = "//",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//",
			.enc_path = "//",
		},
	},
	{ // "."
		.url = ".",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2f",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "/",
			.enc_path = "/",
		},
	},
	{ // "./%2fc"
		.url = "./%2fc",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2fa/%2fb/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//a//b//c",
			.enc_path = "/%2fa/%2fb/%2fc",
		},
	},
	{ // "../%2fc"
		.url = "../%2fc",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2fa/%2fb/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//a//c",
			.enc_path = "/%2fa/%2fc",
		},
	},
	{ // "./%2fc"
		.url = "./%2fc",
		.url_base = {
			.host = { .name = "a" },
			.path = "/%2fa/%2fb/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "/%2fa/%2fb//c",
			.enc_path = "/%252fa/%252fb/%2fc",
		},
	},
	{ // "../%2fc"
		.url = "../%2fc",
		.url_base = {
			.host = { .name = "a" },
			.path = "/%2fa/%2fb/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "/%2fa//c",
			.enc_path = "/%252fa/%2fc",
		},
	},
	{ // "./%2fc%3f"
		.url = "./%2fc%3f",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2fa%3f/%2fb%3f/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//a?//b?//c?",
			.enc_path = "/%2fa%3f/%2fb%3f/%2fc%3f",
		},
	},
	{ // "../%2fc"
		.url = "../%2fc%3f",
		.url_base = {
			.host = { .name = "a" },
			.enc_path = "/%2fa%3f/%2fb%3f/",
		},
		.url_parsed = {
			.host = { .name = "a" },
			.path = "//a?//c?",
			.enc_path = "/%2fa%3f/%2fc%3f",
		},
	},
};

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);
	}
	const char *urlt_enc_path = (urlt->enc_path == NULL ?
				     urlt->path : urlt->enc_path);
	if (urlp->enc_path == NULL || urlt_enc_path == NULL) {
		test_assert(urlp->enc_path == urlt_enc_path);
	} else {
		test_assert(strcmp(urlp->enc_path, urlt_enc_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 = "http://%3a%3a1",
	},
	{
		.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",
	"http://www.example.com/%2f/frop.html",
	"http://www.example.com/%3f%2f%3f/frop.html",
};

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);
}

