#include #include #include #include #include /////////////////////////////////////////////////////////////////////////////// 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 ( [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 ([&] (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 (); }