/* URI handling tests Copyright (C) 2001-2006, Joe Orton 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 #endif #ifdef HAVE_STRING_H #include #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.userinfo != 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; } static int escapes(void) { const struct { const char *plain, *escaped; } paths[] = { { "/foo%", "/foo%25" }, { "/foo bar", "/foo%20bar" }, { "/foo_bar", "/foo_bar" }, { "/foobar", "/foobar" }, { "/a\xb9\xb2\xb3\xbc\xbd/", "/a%b9%b2%b3%bc%bd/" }, { NULL, NULL} }; size_t n; for (n = 0; paths[n].plain; n++) { char *esc = ne_path_escape(paths[n].plain), *un; ONCMP(paths[n].escaped, esc, paths[n].plain, "escape"); un = ne_path_unescape(esc); ONCMP(paths[n].plain, un, paths[n].plain, "unescape"); ne_free(un); ne_free(esc); } ONN("unescape accepted invalid URI", ne_path_unescape("/foo%zzbar") != NULL); ONN("unescape accepted invalid URI", ne_path_unescape("/foo%1zbar") != NULL); 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; } static int cmp(void) { static const struct { const char *left, *right; } eq[] = { { "http://example.com/alpha", "http://example.com/alpha" }, { "//example.com/alpha", "//example.com/alpha" }, { "http://example.com/alpha#foo", "http://example.com/alpha#foo" }, { "http://example.com/alpha?bar", "http://example.com/alpha?bar" }, { "http://jim@example.com/alpha", "http://jim@example.com/alpha" }, { "HTTP://example.com/alpha", "http://example.com/alpha" }, { "http://example.com/", "http://example.com" }, { "http://Example.Com/", "http://example.com" }, { NULL, NULL} }, diff[] = { { "http://example.com/alpha", "http://example.com/beta" }, { "http://example.com/alpha", "https://example.com/alpha" }, { "http://example.com/alpha", "http://www.example.com/alpha" }, { "http://example.com:443/alpha", "http://example.com:8080/alpha" }, { "http://example.com/alpha", "http://jim@example.com/alpha" }, { "http://bob@example.com/alpha", "http://jim@example.com/alpha" }, { "http://example.com/alpha", "http://example.com/alpha?fish" }, { "http://example.com/alpha?fish", "http://example.com/alpha?food" }, { "http://example.com/alpha", "http://example.com/alpha#foo" }, { "http://example.com/alpha#bar", "http://example.com/alpha#foo" }, { "http://example.com/alpha", "//example.com/alpha" }, { "http://example.com/alpha", "///alpha" }, { NULL, NULL} }; size_t n; for (n = 0; eq[n].left; n++) { ne_uri alpha, beta; int r1, r2; ONV(ne_uri_parse(eq[n].left, &alpha), ("could not parse left URI '%s'", eq[n].left)); ONV(ne_uri_parse(eq[n].right, &beta), ("could not parse right URI '%s'", eq[n].right)); r1 = ne_uri_cmp(&alpha, &beta); r2 = ne_uri_cmp(&beta, &alpha); ONV(r1 != 0, ("cmp('%s', '%s') = %d not zero", eq[n].left, eq[n].right, r1)); ONV(r2 != 0, ("cmp('%s', '%s') = %d not zero", eq[n].right, eq[n].left, r2)); ne_uri_free(&alpha); ne_uri_free(&beta); } for (n = 0; diff[n].left; n++) { ne_uri alpha, beta; int r1, r2; ONV(ne_uri_parse(diff[n].left, &alpha), ("could not parse left URI '%s'", diff[n].left)); ONV(ne_uri_parse(diff[n].right, &beta), ("could not parse right URI '%s'", diff[n].right)); r1 = ne_uri_cmp(&alpha, &beta); r2 = ne_uri_cmp(&beta, &alpha); ONV(r1 == 0, ("'%s' and '%s' did not compare as different", diff[n].left, diff[n].right)); ONV(((r1 > 0) != (r2 < 0) || (r1 < 0) != (r2 > 0)), ("'%s' and '%s' did not compare reflexively (%d vs %d)", diff[n].left, diff[n].right, r1, r2)); ne_uri_free(&alpha); ne_uri_free(&beta); } 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 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, *userinfo, *query, *fragment; } uritests[] = { { "http://webdav.org/norman", "http", "webdav.org", 0, "/norman", NULL, NULL, NULL }, { "http://webdav.org:/norman", "http", "webdav.org", 0, "/norman", NULL, NULL, NULL }, { "https://webdav.org/foo", "https", "webdav.org", 0, "/foo", NULL, NULL, NULL }, { "http://webdav.org:8080/bar", "http", "webdav.org", 8080, "/bar", NULL, NULL, NULL }, { "http://a/b", "http", "a", 0, "/b", NULL, NULL, NULL }, { "http://webdav.org/bar:fish", "http", "webdav.org", 0, "/bar:fish", NULL, NULL, NULL }, { "http://webdav.org", "http", "webdav.org", 0, "/", NULL, NULL, NULL }, { "http://webdav.org/fish@food", "http", "webdav.org", 0, "/fish@food", NULL, NULL, NULL }, /* query/fragments */ { "http://foo/bar?alpha", "http", "foo", 0, "/bar", NULL, "alpha", NULL }, { "http://foo/bar?alpha#beta", "http", "foo", 0, "/bar", NULL, "alpha", "beta" }, { "http://foo/bar#alpha?beta", "http", "foo", 0, "/bar", NULL, NULL, "alpha?beta" }, { "http://foo/bar#beta", "http", "foo", 0, "/bar", NULL, NULL, "beta" }, { "http://foo/bar?#beta", "http", "foo", 0, "/bar", NULL, "", "beta" }, { "http://foo/bar?alpha?beta", "http", "foo", 0, "/bar", NULL, "alpha?beta", NULL }, /* Examples from RFC3986§1.1.2: */ { "ftp://ftp.is.co.za/rfc/rfc1808.txt", "ftp", "ftp.is.co.za", 0, "/rfc/rfc1808.txt", NULL, NULL, NULL }, { "http://www.ietf.org/rfc/rfc2396.txt", "http", "www.ietf.org", 0, "/rfc/rfc2396.txt", NULL, NULL, NULL }, { "ldap://[2001:db8::7]/c=GB?objectClass?one", "ldap", "[2001:db8::7]", 0, "/c=GB", NULL, "objectClass?one", NULL }, { "mailto:John.Doe@example.com", "mailto", NULL, 0, "John.Doe@example.com", NULL, NULL, NULL }, { "news:comp.infosystems.www.servers.unix", "news", NULL, 0, "comp.infosystems.www.servers.unix", NULL, NULL, NULL }, { "tel:+1-816-555-1212", "tel", NULL, 0, "+1-816-555-1212", NULL, NULL, NULL }, { "telnet://192.0.2.16:80/", "telnet", "192.0.2.16", 80, "/", NULL, NULL, NULL }, { "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", "urn", NULL, 0, "oasis:names:specification:docbook:dtd:xml:4.1.2", NULL}, /* userinfo */ { "ftp://jim:bob@jim.com", "ftp", "jim.com", 0, "/", "jim:bob", NULL, NULL }, { "ldap://fred:bloggs@fish.com/foobar", "ldap", "fish.com", 0, "/foobar", "fred:bloggs", NULL, NULL }, /* IPv6 literals: */ { "http://[::1]/foo", "http", "[::1]", 0, "/foo", NULL, NULL, NULL }, { "http://[a:a:a:a::0]/foo", "http", "[a:a:a:a::0]", 0, "/foo", NULL, NULL, NULL }, { "http://[::1]:8080/bar", "http", "[::1]", 8080, "/bar", NULL, NULL, NULL }, { "ftp://[feed::cafe]:555", "ftp", "[feed::cafe]", 555, "/", NULL, NULL, NULL }, { "DAV:", "DAV", NULL, 0, "", NULL, NULL, NULL }, /* Some odd cases: heir-part and relative-ref will both match * with a zero-length expansion of "authority" (since * * reg-name can be zero-length); so a triple-slash URI-ref * will be matched as "//" followed by a zero-length authority * followed by a path of "/". */ { "foo:///", "foo", "", 0, "/", NULL, NULL, NULL }, { "///", NULL, "", 0, "/", NULL, NULL, NULL }, /* port grammar is "*DIGIT" so may be empty: */ { "ftp://[feed::cafe]:", "ftp", "[feed::cafe]", 0, "/", NULL, NULL, NULL }, { "ftp://[feed::cafe]:/", "ftp", "[feed::cafe]", 0, "/", NULL, NULL, NULL }, { "http://foo:/", "http", "foo", 0, "/", NULL, NULL, NULL }, /* URI-references: */ { "//foo.com/bar", NULL, "foo.com", 0, "/bar", NULL, NULL, NULL }, { "//foo.com", NULL, "foo.com", 0, "/", NULL, NULL, NULL }, { "//[::1]/foo", NULL, "[::1]", 0, "/foo", NULL, NULL, NULL }, { "/bar", NULL, NULL, 0, "/bar", NULL, NULL, NULL }, /* path-absolute */ { "foo/bar", NULL, NULL, 0, "foo/bar", NULL, NULL, NULL }, /* path-noscheme */ { "", NULL, NULL, 0, "", NULL, NULL, NULL }, /* path-empty */ /* CVE-2007-0157: buffer under-read in 0.26.[012]. */ { "http://webdav.org\xFF", "http", "webdav.org\xFF", 0, "/", NULL, NULL, 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->userinfo, res.userinfo, e->uri, "userinfo"); ONCMP(e->query, res.query, e->uri, "query"); ONCMP(e->fragment, res.fragment, e->uri, "fragment"); ne_uri_free(&res); } return OK; } static int failparse(void) { static const char *uris[] = { "http://[::1/", "http://[::1]f:80/", "http://[::1]]:80/", "http://foo/bar asda", "http://fish/[foo]/bar", 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", "ftp://joe@bar.com/abc/def", "http://a/b?c#d", "http://a/b?c", "http://a/b#d", "mailto:foo@bar.com", "//foo.com/bar", "//foo.com:8080/bar", 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.scheme) 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; } #define BASE "http://a/b/c/d;p?q" static int resolve(void) { static const struct { const char *base, *relative, *expected; } ts[] = { /* Examples from RFC3986§5.4: */ { BASE, "g:h", "g:h" }, { BASE, "g", "http://a/b/c/g" }, { BASE, "./g", "http://a/b/c/g" }, { BASE, "g/", "http://a/b/c/g/" }, { BASE, "/g", "http://a/g" }, { BASE, "//g", "http://g/" }, /* NOTE: modified to mandate non-empty path */ { BASE, "?y", "http://a/b/c/d;p?y" }, { BASE, "g?y", "http://a/b/c/g?y" }, { BASE, "#s", "http://a/b/c/d;p?q#s" }, { BASE, "g#s", "http://a/b/c/g#s" }, { BASE, "g?y#s", "http://a/b/c/g?y#s" }, { BASE, ";x", "http://a/b/c/;x" }, { BASE, "g;x", "http://a/b/c/g;x" }, { BASE, "g;x?y#s", "http://a/b/c/g;x?y#s" }, { BASE, "", "http://a/b/c/d;p?q" }, { BASE, ".", "http://a/b/c/" }, { BASE, "./", "http://a/b/c/" }, { BASE, "..", "http://a/b/" }, { BASE, "../", "http://a/b/" }, { BASE, "../g", "http://a/b/g" }, { BASE, "../..", "http://a/" }, { BASE, "../../", "http://a/" }, { BASE, "../../g", "http://a/g" }, { BASE, "../../../g", "http://a/g" }, { BASE, "../../../../g", "http://a/g" }, { BASE, "/./g", "http://a/g" }, { BASE, "/../g", "http://a/g" }, { BASE, "g.", "http://a/b/c/g." }, { BASE, ".g", "http://a/b/c/.g" }, { BASE, "g..", "http://a/b/c/g.." }, { BASE, "..g", "http://a/b/c/..g" }, { BASE, "./../g", "http://a/b/g" }, { BASE, "./g/.", "http://a/b/c/g/" }, { BASE, "g/./h", "http://a/b/c/g/h" }, { BASE, "g/../h", "http://a/b/c/h" }, { BASE, "g;x=1/./y", "http://a/b/c/g;x=1/y" }, { BASE, "g;x=1/../y", "http://a/b/c/y" }, { BASE, "g?y/./x", "http://a/b/c/g?y/./x" }, { BASE, "g?y/../x", "http://a/b/c/g?y/../x" }, { BASE, "g#s/./x", "http://a/b/c/g#s/./x" }, { BASE, "g#s/../x", "http://a/b/c/g#s/../x" }, { BASE, "http:g", "http:g" }, /* Additional examples: */ { BASE, ".", "http://a/b/c/" }, { "http://foo.com/alpha/beta", "../gamma", "http://foo.com/gamma" }, { "http://foo.com/alpha//beta", "../gamma", "http://foo.com/alpha/gamma" }, { "http://foo.com", "../gamma", "http://foo.com/gamma" }, { "", "zzz:.", "zzz:" }, { "", "zzz:./foo", "zzz:foo" }, { "", "zzz:../foo", "zzz:foo" }, { "", "zzz:/./foo", "zzz:/foo" }, { "", "zzz:/.", "zzz:/" }, { "", "zzz:/../", "zzz:/" }, { "", "zzz:.", "zzz:" }, { "", "zzz:..", "zzz:" }, { "", "zzz://foo@bar/", "zzz://foo@bar/" }, { "", "zzz://foo/?bar", "zzz://foo/?bar" }, { "zzz://foo/?bar", "//baz/?jam", "zzz://baz/?jam" }, { "zzz://foo/baz?biz", "", "zzz://foo/baz?biz" }, { "zzz://foo/baz", "", "zzz://foo/baz" }, { "//foo/baz", "", "//foo/baz" }, { NULL, NULL, NULL } }; size_t n; for (n = 0; ts[n].base; n++) { ne_uri base, relative, resolved; char *actual; ONV(ne_uri_parse(ts[n].base, &base), ("could not parse base URI '%s'", ts[n].base)); ONV(ne_uri_parse(ts[n].relative, &relative), ("could not parse input URI '%s'", ts[n].relative)); ONN("bad pointer was returned", ne_uri_resolve(&base, &relative, &resolved) != &resolved); ONN("NULL path after resolve", resolved.path == NULL); actual = ne_uri_unparse(&resolved); ONCMP(ts[n].expected, actual, ts[n].relative, "output mismatch"); ne_uri_free(&relative); ne_uri_free(&resolved); ne_uri_free(&base); ne_free(actual); } return OK; } static int copy(void) { static const char *ts[] = { "http://jim@foo.com:8080/bar?baz#bee", "", NULL, }; size_t n; for (n = 0; ts[n]; n++) { ne_uri parsed, parsed2; char *actual; ONV(ne_uri_parse(ts[n], &parsed), ("could not parse URI '%s'", ts[n])); ONN("ne_uri_copy returned wrong pointer", ne_uri_copy(&parsed2, &parsed) != &parsed2); actual = ne_uri_unparse(&parsed2); ONCMP(ts[n], actual, "copied URI", "unparsed URI"); ne_uri_free(&parsed2); ne_uri_free(&parsed); ne_free(actual); } 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(default_port), T(parse), T(failparse), T(unparse), T(resolve), T(copy), T(NULL) };