/* 
   URI handling tests
   Copyright (C) 2001-2005, Joe Orton <joe@manyfish.co.uk>

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
  
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
  
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "config.h"

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "ne_uri.h"
#include "ne_alloc.h"

#include "tests.h"

static int simple(void)
{
    ne_uri p = {0};
    ON(ne_uri_parse("http://www.webdav.org/foo", &p));
    ON(strcmp(p.host, "www.webdav.org"));
    ON(strcmp(p.path, "/foo"));
    ON(strcmp(p.scheme, "http"));
    ON(p.port);
    ON(p.authinfo != NULL);
    ne_uri_free(&p);
    return 0;
}

static int simple_ssl(void)
{
    ne_uri p = {0};
    ON(ne_uri_parse("https://webdav.org/", &p));
    ON(strcmp(p.scheme, "https"));
    ON(p.port);
    ne_uri_free(&p);
    return OK;
}

static int no_path(void)
{
    ne_uri p = {0};
    ON(ne_uri_parse("https://webdav.org", &p));
    ON(strcmp(p.path, "/"));
    ne_uri_free(&p);
    return OK;
}

#define STR "/a¹²³¼½/"
static int escapes(void)
{
    char *un, *esc;
    esc = ne_path_escape(STR);
    ON(esc == NULL);
    un = ne_path_unescape(esc);
    ON(un == NULL);
    ON(strcmp(un, STR));
    ne_free(un);
    ne_free(esc);
    ONN("unescape accepted invalid URI", 
        ne_path_unescape("/foo%zzbar") != NULL);
    /* no-escape path */
    esc = ne_path_escape("/foobar");
    ON(strcmp(esc, "/foobar"));
    ne_free(esc);       
    return OK;
}    

static int parents(void)
{
    static const struct {
	const char *path, *parent;
    } ps[] = {
	{ "/a/b/c", "/a/b/" },
	{ "/a/b/c/", "/a/b/" },
	{ "/alpha/beta", "/alpha/" },
	{ "/foo", "/" },
	{ "norman", NULL },
	{ "/", NULL },
	{ "", NULL },
	{ NULL, NULL }
    };
    int n;
    
    for (n = 0; ps[n].path != NULL; n++) {
	char *p = ne_path_parent(ps[n].path);
	if (ps[n].parent == NULL) {
	    ONV(p != NULL, ("parent of `%s' was `%s' not NULL", 
			    ps[n].path, p));
	} else {
	    ONV(p == NULL, ("parent of `%s' was NULL", ps[n].path));
	    ONV(strcmp(p, ps[n].parent),
		("parent of `%s' was `%s' not `%s'",
		 ps[n].path, p, ps[n].parent));
	    ne_free(p);
	}
    }

    return OK;
}

static int compares(void)
{
    const char *alpha = "/alpha";

    ON(ne_path_compare("/a", "/a/") != 0);
    ON(ne_path_compare("/a/", "/a") != 0);
    ON(ne_path_compare("/ab", "/a/") == 0);
    ON(ne_path_compare("/a/", "/ab") == 0);
    ON(ne_path_compare("/a/", "/a/") != 0);
    ON(ne_path_compare("/alpha/", "/beta/") == 0);
    ON(ne_path_compare("/alpha", "/b") == 0);
    ON(ne_path_compare("/alpha/", "/alphash") == 0);
    ON(ne_path_compare("/fish/", "/food") == 0);
    ON(ne_path_compare(alpha, alpha) != 0);
    ON(ne_path_compare("/a/b/c/d", "/a/b/c/") == 0);
    return OK;
}

/* Checks that a URI comparison of 'u1' and 'u2', which have differing
 * 'field', doesn't compare to equal; and that they are ordered
 * correctly. */
static int cmp_differ(const char *field,
		      const ne_uri *u1, const ne_uri *u2)
{
    ONV(ne_uri_cmp(u1, u2) == 0,
	("URIs with different %s were equal", field));

    ONV(ne_uri_cmp(u2, u1) == 0,
	("URIs with different %s were equal (reversed)", field));

    /* relies on strcmp return value being of equal magnitude when
     * arguments are reversed; not sure if this is portable
     * assumption. */
    ONV(ne_uri_cmp(u1, u2) + ne_uri_cmp(u2, u1) != 0,
	("relative ordering of URIs with different %s incorrect", field));

    return OK;
}

static int cmp(void)
{
    ne_uri alpha, beta;
    
    alpha.path = "/alpha";
    alpha.scheme = "http";
    alpha.host = "example.com";
    alpha.port = 80;

    beta = alpha; /* structure copy. */

    ONN("equal URIs not equal", ne_uri_cmp(&alpha, &beta) != 0);

    beta.path = "/beta";
    CALL(cmp_differ("path", &alpha, &beta));

    beta = alpha; beta.scheme = "https";
    CALL(cmp_differ("scheme", &alpha, &beta));
    
    beta = alpha; beta.port = 433;
    CALL(cmp_differ("port", &alpha, &beta));

    beta = alpha; beta.host = "fish.com";
    CALL(cmp_differ("host", &alpha, &beta));

    beta = alpha; beta.host = "EXAMPLE.CoM";
    ONN("hostname comparison not case-insensitive",
	ne_uri_cmp(&alpha, &beta) != 0);
    
    beta = alpha; beta.scheme = "HtTp";
    ONN("scheme comparison not case-insensitive",
	ne_uri_cmp(&alpha, &beta) != 0);
    
    beta = alpha; beta.path = ""; alpha.path = "/";
    ONN("empty abspath doesn't match '/'",
	ne_uri_cmp(&alpha, &beta) != 0);
    ONN("'/' doesn't match empty abspath",
	ne_uri_cmp(&beta, &alpha) != 0);

    beta = alpha; alpha.path = ""; beta.path = "/foo";
    ONN("empty abspath matched '/foo'", ne_uri_cmp(&alpha, &beta) == 0);
    ONN("'/foo' matched empty abspath ", ne_uri_cmp(&beta, &alpha) == 0);

    return OK;
}

static int children(void)
{
    ON(ne_path_childof("/a", "/a/b") == 0);
    ON(ne_path_childof("/a/", "/a/b") == 0);
    ON(ne_path_childof("/aa/b/c", "/a/b/c/d/e") != 0);
    ON(ne_path_childof("////", "/a") != 0);
    return OK;
}

static int slash(void)
{
    ON(ne_path_has_trailing_slash("/a/") == 0);
    ON(ne_path_has_trailing_slash("/a") != 0);
    {
	/* check the uri == "" case. */
	char *foo = "/";
	ON(ne_path_has_trailing_slash(&foo[1]));
    }
    return OK;
}

static int just_hostname(void)
{
    ne_uri p = {0};
    ON(ne_uri_parse("host.name.com", &p));
    ON(strcmp(p.host, "host.name.com"));
    ne_uri_free(&p);
    return 0;
}

static int just_path(void)
{
    ne_uri p = {0};
    ON(ne_uri_parse("/argh", &p));
    ON(strcmp(p.path, "/argh"));
    ne_uri_free(&p);
    return 0;
}

static int default_port(void)
{
    ONN("default http: port incorrect", ne_uri_defaultport("http") != 80);
    ONN("default https: port incorrect", ne_uri_defaultport("https") != 443);
    ONN("unspecified scheme: port incorrect", ne_uri_defaultport("ldap") != 0);
    return OK;
}

static int parse(void)
{
    static const struct test_uri {
	const char *uri, *scheme, *host;
	unsigned int port;
	const char *path, *authinfo;
    } uritests[] = {
	{ "http://webdav.org/norman", "http", "webdav.org", 0, "/norman", NULL },
	{ "http://webdav.org:/norman", "http", "webdav.org", 0, "/norman", NULL },
	{ "https://webdav.org/foo", "https", "webdav.org", 0, "/foo", NULL },
	{ "http://webdav.org:8080/bar", "http", "webdav.org", 8080, "/bar", NULL },
	{ "http://a/b", "http", "a", 0, "/b", NULL },
	{ "http://webdav.org/bar:fish", "http", "webdav.org", 0, "/bar:fish", NULL },
	{ "http://webdav.org", "http", "webdav.org", 0, "/", NULL },
        { "http://webdav.org/fish@food", "http", "webdav.org", 0, "/fish@food", NULL },
        /* authinfo */
        { "ftp://jim:bob@jim.com", "ftp", "jim.com", 0, "/", "jim:bob" },
        { "ldap://fred:bloggs@fish.com/foobar", "ldap", "fish.com", 0, 
          "/foobar", "fred:bloggs" },
	/* relativeURIs accepted for dubious legacy reasons. */
	{ "a/b", NULL, "a", 0, "/b", NULL },
	{ "a:8080/b", NULL, "a", 8080, "/b", NULL },
	{ "/fish", NULL, NULL, 0, "/fish", NULL },
	{ "webdav.org:8080", NULL, "webdav.org", 8080, "/", NULL },
	/* IPv6 hex strings allowed even if IPv6 not supported. */
	{ "http://[::1]/foo", "http", "[::1]", 0, "/foo", NULL },
	{ "http://[a:a:a:a::0]/foo", "http", "[a:a:a:a::0]", 0, "/foo", NULL },
	{ "http://[::1]:8080/bar", "http", "[::1]", 8080, "/bar", NULL },
	{ "ftp://[feed::cafe]:555", "ftp", "[feed::cafe]", 555, "/", NULL },
	{ "http://fish/[foo]/bar", "http", "fish", 0, "/[foo]/bar", NULL },
	/* and some dubious ones: */
	{ "[::1]/foo", NULL, "[::1]", 0, "/foo", NULL },
	{ "[::1]:8000/foo", NULL, "[::1]", 8000, "/foo", NULL },
	{ NULL }
    };
    int n;

    for (n = 0; uritests[n].uri != NULL; n++) {
	ne_uri res;
	const struct test_uri *e = &uritests[n];
	ONV(ne_uri_parse(e->uri, &res) != 0,
	    ("%s: parse failed", e->uri));
	ONV(res.port != e->port,
	    ("%s: parsed port was %d not %d", e->uri, res.port, e->port));
	ONCMP(e->scheme, res.scheme, e->uri, "scheme");
	ONCMP(e->host, res.host, e->uri, "host");
	ONV(strcmp(res.path, e->path),
	    ("%s: parsed path was %s not %s", e->uri, res.path, e->path));
        ONCMP(e->authinfo, res.authinfo, e->uri, "authinfo");
	ne_uri_free(&res);
    }

    return OK;
}

static int failparse(void)
{
    static const char *uris[] = {
	"",
	"http://[::1/",
	NULL
    };
    int n;
    
    for (n = 0; uris[n] != NULL; n++) {
	ne_uri p;
	ONV(ne_uri_parse(uris[n], &p) == 0,
	    ("`%s' did not fail to parse", uris[n]));
	ne_uri_free(&p);
    }

    return 0;
}

static int unparse(void)
{
    const char *uris[] = {
	"http://foo.com/bar",
	"https://bar.com/foo/wishbone",
	"http://www.random.com:8000/",
	"http://[::1]:8080/",
	"ftp://ftp.foo.bar/abc/def",
	NULL
    };
    int n;

    for (n = 0; uris[n] != NULL; n++) {
	ne_uri parsed;
	char *unp;

	ONV(ne_uri_parse(uris[n], &parsed),
	    ("failed to parse %s", uris[n]));
	
        if (parsed.port == 0)
            parsed.port = ne_uri_defaultport(parsed.scheme);

	unp = ne_uri_unparse(&parsed);

	ONV(strcmp(unp, uris[n]),
	    ("unparse got %s from %s", unp, uris[n]));
	
        ne_uri_free(&parsed);
	ne_free(unp);
    }

    return OK;    
}

ne_test tests[] = {
    T(simple),
    T(simple_ssl),
    T(no_path),
    T(escapes),
    T(parents),
    T(compares),
    T(cmp),
    T(children),
    T(slash),
    T(just_hostname),
    T(just_path),
    T(default_port),
    T(parse),
    T(failparse),
    T(unparse),
    T(NULL)
};
