393 lines
11 KiB
C++
393 lines
11 KiB
C++
#include "uri.hpp"
|
|
|
|
#include "tap.hpp"
|
|
|
|
#include <fmt/ostream.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);
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
int
|
|
main (void)
|
|
{
|
|
cruft::TAP::logger tap;
|
|
|
|
test_validity (tap);
|
|
test_parse (tap);
|
|
test_normalise (tap);
|
|
test_rfc_resolve (tap);
|
|
test_resolve (tap);
|
|
|
|
return tap.status ();
|
|
}
|