libcruft-util/test/uri.cpp

434 lines
12 KiB
C++

#include "uri.hpp"
#include "tap.hpp"
#include <fmt/ostream.h>
#include <fmt/std.h>
#include <ostream>
///////////////////////////////////////////////////////////////////////////////
static void
test_parse (cruft::TAP::logger &tap)
{
static const struct {
const char *src;
const char *scheme;
const char *hierarchical;
const char *authority;
const char *user;
const char *host;
const char *port;
const char *path;
const char *query;
const char *fragment;
} GOOD[] = {
// examples from rfc3986
{
.src = "ftp://ftp.is.co.za/rfc/rfc1808.txt",
.scheme = "ftp",
.hierarchical = "ftp.is.co.za/rfc/rfc1808.txt",
.authority = "ftp.is.co.za",
.user = "",
.host = "ftp.is.co.za",
.port = "",
.path = "/rfc/rfc1808.txt",
.query = "",
.fragment = "" },
{
.src = "http://www.ietf.org/rfc/rfc2396.txt",
.scheme = "http",
.hierarchical = "www.ietf.org/rfc/rfc2396.txt",
.authority = "www.ietf.org",
.user = "",
.host = "www.ietf.org",
.port = "",
.path = "/rfc/rfc2396.txt",
.query = "",
.fragment = "" },
{
.src = "ldap://[2001:db8::7]/c=GB?objectClass?one",
.scheme = "ldap",
.hierarchical = "[2001:db8::7]/c=GB",
.authority = "[2001:db8::7]",
.user = "",
.host = "[2001:db8::7]",
.port = "",
.path = "/c=GB",
.query = "objectClass?one",
.fragment = "" },
{
.src = "mailto:John.Doe@example.com",
.scheme= "mailto",
.hierarchical = "John.Doe@example.com",
.authority= "",
.user = "",
.host = "",
.port = "",
.path= "John.Doe@example.com",
.query= "",
.fragment= "" },
{
.src = "news:comp.infosystems.www.servers.unix",
.scheme= "news",
.hierarchical = "comp.infosystems.www.servers.unix",
.authority= "",
.user = "",
.host = "",
.port = "",
.path= "comp.infosystems.www.servers.unix",
.query= "",
.fragment= "" },
{
.src = "tel:+1-816-555-1212",
.scheme= "tel",
.hierarchical = "+1-816-555-1212",
.authority= "",
.user = "",
.host = "",
.port = "",
.path= "+1-816-555-1212",
.query= "",
.fragment= "" },
{
.src = "telnet://192.0.2.16:80/",
.scheme= "telnet",
.hierarchical = "192.0.2.16:80/",
.authority= "192.0.2.16:80",
.user = "",
.host = "192.0.2.16",
.port = "80",
.path= "/",
.query= "",
.fragment= "" },
{
.src = "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
.scheme= "urn",
.hierarchical = "oasis:names:specification:docbook:dtd:xml:4.1.2",
.authority= "",
.user = "",
.host = "",
.port = "",
.path= "oasis:names:specification:docbook:dtd:xml:4.1.2",
.query= "",
.fragment= "" },
// a case with all possible components
{
.src = "https://user:password@example.com:80/path/to?foo=bar#fragment",
.scheme = "https",
.hierarchical = "user:password@example.com:80/path/to",
.authority = "user:password@example.com:80",
.user = "user:password",
.host = "example.com",
.port = "80",
.path = "/path/to",
.query = "foo=bar",
.fragment = "fragment" },
};
for (auto t: GOOD) {
tap.expect_nothrow ([t] (void) { cruft::uri foo (t.src); }, "nothrow parsing '{:s}'", t.src);
cruft::uri u (t.src);
tap.expect_eq (u.scheme (), t.scheme, "scheme for '{:s}'", t.src);
tap.expect_eq (u.heirarchical (), t.hierarchical, "hierarchical for '{:s}'", t.src);
tap.expect_eq (u.authority (), t.authority, "authority for '{:s}'", t.src);
tap.expect_eq (u.host (), t.host, "host for '{:s}'", t.src);
tap.expect_eq (u.user (), t.user, "user for '{:s}'", t.src);
tap.expect_eq (u.port (), t.port, "port for '{:s}'", t.src);
tap.expect_eq (u.path (), t.path, "path for '{:s}'", t.src);
tap.expect_eq (u.query (), t.query, "query for '{:s}'", t.src);
tap.expect_eq (u.fragment (), t.fragment, "fragment for '{:s}'", t.src);
}
static const char* BAD[] = {
// "www.google.com.au",
};
for (auto i: BAD)
tap.expect_throw<cruft::uri::parse_error> (
[i] (void) { cruft::uri foo (i); }, "throw parsing '{:s}'", i
);
}
///////////////////////////////////////////////////////////////////////////////
static void
test_normalise (cruft::TAP::logger &tap)
{
struct {
char const *init;
char const *expected;
} TESTS[] = {
// {
// // RFC 3986 example
// "/a/b/c/./../../g",
// "/a/g"
// },
// {
// // RFC 3986 example
// "mid/content=5/../6",
// "mid/6"
// },
{
"http://example.com/",
"http://example.com/",
},
{
"http://example.com/./",
"http://example.com/",
},
{
"http://example.com/../",
"http://example.com/",
},
{
"http://example.com/a/../b",
"http://example.com/b",
},
{
"http://example.com/a/../b/",
"http://example.com/b/",
},
{
"http://example.com/a/./b",
"http://example.com/a/b",
},
{
"http://example.com/a/./b/",
"http://example.com/a/b/",
},
{
"http://example.com/a/b/c/./d/e",
"http://example.com/a/b/c/d/e",
},
{
"http://example.com/a/b/c/../d/e",
"http://example.com/a/b/d/e",
},
{
"http://example.com/a/b/c/../../d/e",
"http://example.com/a/d/e",
},
{
"http://example.com/a/b/c/.././../d/e",
"http://example.com/a/d/e",
},
};
for (auto const [init, expected]: TESTS) {
cruft::uri init_obj (init);
cruft::uri expected_obj (expected);
auto const res = normalise (init_obj);
if (res != expected_obj)
fmt::print (stderr, "# '{}' != '{}'\n", res, expected_obj);
tap.expect_eq (res, expected_obj, "normalise('{}')", init);
}
}
///////////////////////////////////////////////////////////////////////////////
static void
test_rfc_resolve (cruft::TAP::logger &tap)
{
static constexpr char const *BASE = "http://a/b/c/d;p?q";
struct {
char const *relative;
char const *resolved;
} TESTS[] = {
{ "g:h", "g:h" },
{ "g", "http://a/b/c/g" },
{ "./g", "http://a/b/c/g" },
{ "g/", "http://a/b/c/g/" },
{ "/g", "http://a/g" },
{ "//g", "http://g" },
{ "?y", "http://a/b/c/d;p?y" },
{ "g?y", "http://a/b/c/g?y" },
{ "#s", "http://a/b/c/d;p?q#s" },
{ "g#s", "http://a/b/c/g#s" },
{ "g?y#s", "http://a/b/c/g?y#s" },
{ ";x", "http://a/b/c/;x" },
{ "g;x", "http://a/b/c/g;x" },
{ "g;x?y#s", "http://a/b/c/g;x?y#s" },
{ "", "http://a/b/c/d;p?q" },
{ ".", "http://a/b/c/" },
{ "./", "http://a/b/c/" },
{ "..", "http://a/b/" },
{ "../", "http://a/b/" },
{ "../g", "http://a/b/g" },
{ "../..", "http://a/" },
{ "../../", "http://a/" },
{ "../../g", "http://a/g" },
};
cruft::uri const base (BASE);
for (auto const [relative, expected]: TESTS) {
cruft::uri const relative_obj (relative);
cruft::uri const expected_obj (expected);
cruft::uri const resolved_obj = resolve (base, cruft::uri (relative));
if (resolved_obj != expected_obj)
fmt::print (stderr, "# '{}' != '{}'\n", expected_obj, resolved_obj);
tap.expect_eq (
resolved_obj,
expected_obj,
"resolve '{}', '{}'",
base, relative
);
}
}
///////////////////////////////////////////////////////////////////////////////
void
test_resolve (cruft::TAP::logger &tap)
{
struct {
char const *base;
char const *relative;
char const *expected;
} TESTS[] = {
{
"http://example.com",
".",
"http://example.com/",
},
{
"http://example.com",
"./",
"http://example.com/",
},
};
for (auto const [base, relative, expected]: TESTS) {
cruft::uri base_obj (base);
cruft::uri relative_obj (relative);
cruft::uri expected_obj (expected);
cruft::uri computed_obj = resolve (base_obj, relative_obj);
tap.expect_eq (
resolve (base_obj, relative_obj),
expected_obj,
"resolve '{}', '{}'",
base, relative
);
}
}
///////////////////////////////////////////////////////////////////////////////
static
void
test_validity (cruft::TAP::logger &tap)
{
static char const *GOOD[] = {
"http://example.com/",
"http://example.com",
"http://[2001:db8::7]/",
"http://[2001:db8::7]:80/",
"http://user@example.com:10/",
"http://user@example.com",
"http://example.com:443/",
"https://example.com:80/",
"http://example.com/foo?bar=qux#xyz",
"http://example.com/?bar=qux#xyz",
"http://example.com/#xyz",
"http://example.com/???",
"http://example.com/?#?",
"http://example.com/%22",
// Out of spec, but required
"/topic/emmanuel-jean-michel-frédéric-macron-2wc",
};
for (auto const &good: GOOD)
tap.expect_nothrow ([&] (void) { (void) cruft::uri (good); }, "parse: {}", good);
static char const *BAD[] = {
// "http://example.com/?foo=\"bar\"",
};
for (auto const &bad: BAD)
tap.expect_throw<std::exception> ([&] (void) { (void) cruft::uri (bad); }, "parse: {}", bad);
}
///////////////////////////////////////////////////////////////////////////////
static void
test_pqf (cruft::TAP::logger &tap)
{
static constexpr struct {
char const *uri;
char const *pq;
char const *pqf;
} TESTS[] = {
{
"http://example.com/foo?bar#qux",
"/foo?bar",
"/foo?bar#qux"
},
{
"http://example.com/foo",
"/foo",
"/foo",
},
{
"http://example.com/foo?#qux",
"/foo?",
"/foo?#qux"
},
{
"http://example.com/foo?bar#",
"/foo?bar",
"/foo?bar#"
}
};
for (auto const &t: TESTS) {
cruft::uri const obj (t.uri);
tap.expect_eq (t.pq, obj.pq (), "pq: {}", t.uri);
tap.expect_eq (t.pqf, obj.pqf (), "pqf: {}", t.uri);
}
}
///////////////////////////////////////////////////////////////////////////////
int
main (void)
{
cruft::TAP::logger tap;
test_validity (tap);
test_parse (tap);
test_normalise (tap);
test_rfc_resolve (tap);
test_resolve (tap);
test_pqf (tap);
return tap.status ();
}