summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2022-04-18 13:15:33 -0400
committerEdward Thomson <ethomson@edwardthomson.com>2022-06-17 13:49:34 -0400
commit373a3c9ab17ce9e7b9eb1cdc61390b78a61215e6 (patch)
treef9e15d9f3729e4e55ca22a64c27e584691551255
parentffef8e01d3a2d7802d32d452f123fd2b97af3d38 (diff)
downloadlibgit2-373a3c9ab17ce9e7b9eb1cdc61390b78a61215e6.tar.gz
url_parse: introduce our own url parsing
Provide our own url parser, so that we can handle Google Code's "fun" URLs that have a userinfo with an `@` in it. :cry:
-rw-r--r--src/util/net.c408
-rw-r--r--src/util/net.h1
-rw-r--r--tests/util/url/parse.c217
3 files changed, 542 insertions, 84 deletions
diff --git a/src/util/net.c b/src/util/net.c
index b2236daf8..9eddca916 100644
--- a/src/util/net.c
+++ b/src/util/net.c
@@ -93,121 +93,360 @@ int git_net_url_dup(git_net_url *out, git_net_url *in)
return 0;
}
-int git_net_url_parse(git_net_url *url, const char *given)
+static int url_invalid(const char *message)
{
- struct http_parser_url u = {0};
- bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo;
- git_str scheme = GIT_STR_INIT,
- host = GIT_STR_INIT,
- port = GIT_STR_INIT,
- path = GIT_STR_INIT,
- username = GIT_STR_INIT,
- password = GIT_STR_INIT,
- query = GIT_STR_INIT;
- int error = GIT_EINVALIDSPEC;
-
- if (http_parser_parse_url(given, strlen(given), false, &u)) {
- git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
- goto done;
- }
+ git_error_set(GIT_ERROR_NET, "invalid url: %s", message);
+ return GIT_EINVALIDSPEC;
+}
- has_scheme = !!(u.field_set & (1 << UF_SCHEMA));
- has_host = !!(u.field_set & (1 << UF_HOST));
- has_port = !!(u.field_set & (1 << UF_PORT));
- has_path = !!(u.field_set & (1 << UF_PATH));
- has_query = !!(u.field_set & (1 << UF_QUERY));
- has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
+static int url_parse_authority(
+ const char **user_start, size_t *user_len,
+ const char **password_start, size_t *password_len,
+ const char **host_start, size_t *host_len,
+ const char **port_start, size_t *port_len,
+ const char *authority_start, size_t len)
+{
+ const char *c, *hostport_end, *host_end = NULL,
+ *userpass_end, *user_end = NULL;
- if (has_scheme) {
- const char *url_scheme = given + u.field_data[UF_SCHEMA].off;
- size_t url_scheme_len = u.field_data[UF_SCHEMA].len;
- git_str_put(&scheme, url_scheme, url_scheme_len);
- git__strntolower(scheme.ptr, scheme.size);
- } else {
- git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
- goto done;
- }
+ enum {
+ HOSTPORT, HOST, IPV6, HOST_END, USERPASS, USER
+ } state = HOSTPORT;
- if (has_host) {
- const char *url_host = given + u.field_data[UF_HOST].off;
- size_t url_host_len = u.field_data[UF_HOST].len;
- git_str_decode_percent(&host, url_host, url_host_len);
- }
+ if (len == 0)
+ return 0;
- if (has_port) {
- const char *url_port = given + u.field_data[UF_PORT].off;
- size_t url_port_len = u.field_data[UF_PORT].len;
- git_str_put(&port, url_port, url_port_len);
- } else {
- const char *default_port = default_port_for_scheme(scheme.ptr);
+ /*
+ * walk the authority backwards so that we can parse google code's
+ * ssh urls that are not rfc compliant and allow @ in the username
+ */
+ for (hostport_end = authority_start + len, c = hostport_end - 1;
+ c >= authority_start && !user_end;
+ c--) {
+ switch (state) {
+ case HOSTPORT:
+ if (*c == ':') {
+ *port_start = c + 1;
+ *port_len = hostport_end - *port_start;
+ host_end = c;
+ state = HOST;
+ break;
+ }
- if (default_port == NULL) {
- git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given);
- goto done;
- }
+ /*
+ * if we've only seen digits then we don't know
+ * if we're parsing just a host or a host and port.
+ * if we see a non-digit, then we're in a host,
+ * otherwise, fall through to possibly match the
+ * "@" (user/host separator).
+ */
+
+ if (*c < '0' || *c > '9') {
+ host_end = hostport_end;
+ state = HOST;
+ }
- git_str_puts(&port, default_port);
- }
+ /* fall through */
- if (has_path) {
- const char *url_path = given + u.field_data[UF_PATH].off;
- size_t url_path_len = u.field_data[UF_PATH].len;
- git_str_put(&path, url_path, url_path_len);
- } else {
- git_str_puts(&path, "/");
+ case HOST:
+ if (*c == ']' && host_end == c + 1) {
+ host_end = c;
+ state = IPV6;
+ }
+
+ else if (*c == '@') {
+ *host_start = c + 1;
+ *host_len = host_end ? host_end - *host_start :
+ hostport_end - *host_start;
+ userpass_end = c;
+ state = USERPASS;
+ }
+
+ else if (*c == '[' || *c == ']' || *c == ':') {
+ return url_invalid("malformed hostname");
+ }
+
+ break;
+
+ case IPV6:
+ if (*c == '[') {
+ *host_start = c + 1;
+ *host_len = host_end - *host_start;
+ state = HOST_END;
+ }
+
+ else if ((*c < '0' || *c > '9') &&
+ (*c < 'a' || *c > 'f') &&
+ (*c < 'A' || *c > 'F') &&
+ (*c != ':')) {
+ return url_invalid("malformed hostname");
+ }
+
+ break;
+
+ case HOST_END:
+ if (*c == '@') {
+ userpass_end = c;
+ state = USERPASS;
+ break;
+ }
+
+ return url_invalid("malformed hostname");
+
+ case USERPASS:
+ if (*c == ':') {
+ *password_start = c + 1;
+ *password_len = userpass_end - *password_start;
+ user_end = c;
+ state = USER;
+ break;
+ }
+
+ break;
+
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
}
- if (has_query) {
- const char *url_query = given + u.field_data[UF_QUERY].off;
- size_t url_query_len = u.field_data[UF_QUERY].len;
- git_str_decode_percent(&query, url_query, url_query_len);
+ switch (state) {
+ case HOSTPORT:
+ *host_start = authority_start;
+ *host_len = (hostport_end - *host_start);
+ break;
+ case HOST:
+ *host_start = authority_start;
+ *host_len = (host_end - *host_start);
+ break;
+ case IPV6:
+ return url_invalid("malformed hostname");
+ case HOST_END:
+ break;
+ case USERPASS:
+ *user_start = authority_start;
+ *user_len = (userpass_end - *user_start);
+ break;
+ case USER:
+ *user_start = authority_start;
+ *user_len = (user_end - *user_start);
+ break;
+ default:
+ GIT_ASSERT(!"unhandled state");
}
- if (has_userinfo) {
- const char *url_userinfo = given + u.field_data[UF_USERINFO].off;
- size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
- const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
+ return 0;
+}
+
+int git_net_url_parse(git_net_url *url, const char *given)
+{
+ const char *c, *scheme_start, *authority_start, *user_start,
+ *password_start, *host_start, *port_start, *path_start,
+ *query_start, *fragment_start, *default_port;
+ git_str scheme = GIT_STR_INIT, user = GIT_STR_INIT,
+ password = GIT_STR_INIT, host = GIT_STR_INIT,
+ port = GIT_STR_INIT, path = GIT_STR_INIT,
+ query = GIT_STR_INIT, fragment = GIT_STR_INIT;
+ size_t scheme_len = 0, user_len = 0, password_len = 0, host_len = 0,
+ port_len = 0, path_len = 0, query_len = 0, fragment_len = 0;
+ bool hierarchical = false;
+ int error = 0;
+
+ enum {
+ SCHEME,
+ AUTHORITY_START, AUTHORITY,
+ PATH_START, PATH,
+ QUERY,
+ FRAGMENT
+ } state = SCHEME;
+
+ memset(url, 0, sizeof(git_net_url));
+
+ for (c = scheme_start = given; *c; c++) {
+ switch (state) {
+ case SCHEME:
+ if (*c == ':') {
+ scheme_len = (c - scheme_start);
+
+ if (*(c+1) == '/' && *(c+2) == '/') {
+ c += 2;
+ hierarchical = true;
+ state = AUTHORITY_START;
+ } else {
+ state = PATH_START;
+ }
+ } else if ((*c < 'A' || *c > 'Z') &&
+ (*c < 'a' || *c > 'z') &&
+ (*c < '0' || *c > '9') &&
+ (*c != '+' && *c != '-' && *c != '.')) {
+ /*
+ * an illegal scheme character means that we
+ * were just given a relative path
+ */
+ path_start = given;
+ state = PATH;
+ break;
+ }
+ break;
+
+ case AUTHORITY_START:
+ authority_start = c;
+ state = AUTHORITY;
+
+ /* fall through */
+
+ case AUTHORITY:
+ if (*c != '/')
+ break;
+
+ /*
+ * authority is sufficiently complex that we parse
+ * it separately
+ */
+ if ((error = url_parse_authority(
+ &user_start, &user_len,
+ &password_start,&password_len,
+ &host_start, &host_len,
+ &port_start, &port_len,
+ authority_start, (c - authority_start))) < 0)
+ goto done;
+
+ /* fall through */
+
+ case PATH_START:
+ path_start = c;
+ state = PATH;
+ /* fall through */
+
+ case PATH:
+ switch (*c) {
+ case '?':
+ path_len = (c - path_start);
+ query_start = c + 1;
+ state = QUERY;
+ break;
+ case '#':
+ path_len = (c - path_start);
+ fragment_start = c + 1;
+ state = FRAGMENT;
+ break;
+ }
+ break;
+
+ case QUERY:
+ if (*c == '#') {
+ query_len = (c - query_start);
+ fragment_start = c + 1;
+ state = FRAGMENT;
+ }
+ break;
- if (colon) {
- const char *url_username = url_userinfo;
- size_t url_username_len = colon - url_userinfo;
- const char *url_password = colon + 1;
- size_t url_password_len = url_userinfo_len - (url_username_len + 1);
+ case FRAGMENT:
+ break;
- git_str_decode_percent(&username, url_username, url_username_len);
- git_str_decode_percent(&password, url_password, url_password_len);
- } else {
- git_str_decode_percent(&username, url_userinfo, url_userinfo_len);
+ default:
+ GIT_ASSERT(!"unhandled state");
}
}
- if (git_str_oom(&scheme) ||
- git_str_oom(&host) ||
- git_str_oom(&port) ||
- git_str_oom(&path) ||
- git_str_oom(&query) ||
- git_str_oom(&username) ||
- git_str_oom(&password))
- return -1;
+ switch (state) {
+ case SCHEME:
+ /*
+ * if we never saw a ':' then we were given a relative
+ * path, not a bare scheme
+ */
+ path_start = given;
+ path_len = (c - scheme_start);
+ break;
+ case AUTHORITY_START:
+ break;
+ case AUTHORITY:
+ if ((error = url_parse_authority(
+ &user_start, &user_len,
+ &password_start,&password_len,
+ &host_start, &host_len,
+ &port_start, &port_len,
+ authority_start, (c - authority_start))) < 0)
+ goto done;
+ break;
+ case PATH_START:
+ break;
+ case PATH:
+ path_len = (c - path_start);
+ break;
+ case QUERY:
+ query_len = (c - query_start);
+ break;
+ case FRAGMENT:
+ fragment_len = (c - fragment_start);
+ break;
+ default:
+ GIT_ASSERT(!"unhandled state");
+ }
+
+ if (scheme_len) {
+ if ((error = git_str_put(&scheme, scheme_start, scheme_len)) < 0)
+ goto done;
+
+ git__strntolower(scheme.ptr, scheme.size);
+ }
+
+ if (user_len &&
+ (error = git_str_decode_percent(&user, user_start, user_len)) < 0)
+ goto done;
+
+ if (password_len &&
+ (error = git_str_decode_percent(&password, password_start, password_len)) < 0)
+ goto done;
+
+ if (host_len &&
+ (error = git_str_decode_percent(&host, host_start, host_len)) < 0)
+ goto done;
+
+ if (port_len)
+ error = git_str_put(&port, port_start, port_len);
+ else if (scheme_len && (default_port = default_port_for_scheme(scheme.ptr)) != NULL)
+ error = git_str_puts(&port, default_port);
+
+ if (error < 0)
+ goto done;
+
+ if (path_len)
+ error = git_str_put(&path, path_start, path_len);
+ else if (hierarchical)
+ error = git_str_puts(&path, "/");
+
+ if (error < 0)
+ goto done;
+
+ if (query_len &&
+ (error = git_str_decode_percent(&query, query_start, query_len)) < 0)
+ goto done;
+
+ if (fragment_len &&
+ (error = git_str_decode_percent(&fragment, fragment_start, fragment_len)) < 0)
+ goto done;
url->scheme = git_str_detach(&scheme);
url->host = git_str_detach(&host);
url->port = git_str_detach(&port);
url->path = git_str_detach(&path);
url->query = git_str_detach(&query);
- url->username = git_str_detach(&username);
+ url->fragment = git_str_detach(&fragment);
+ url->username = git_str_detach(&user);
url->password = git_str_detach(&password);
error = 0;
done:
git_str_dispose(&scheme);
+ git_str_dispose(&user);
+ git_str_dispose(&password);
git_str_dispose(&host);
git_str_dispose(&port);
git_str_dispose(&path);
git_str_dispose(&query);
- git_str_dispose(&username);
- git_str_dispose(&password);
+ git_str_dispose(&fragment);
+
return error;
}
@@ -374,7 +613,7 @@ int git_net_url_parse_scp(git_net_url *url, const char *given)
break;
default:
- GIT_ASSERT("unhandled state");
+ GIT_ASSERT(!"unhandled state");
}
}
@@ -588,7 +827,7 @@ bool git_net_url_is_default_port(git_net_url *url)
{
const char *default_port;
- if ((default_port = default_port_for_scheme(url->scheme)) != NULL)
+ if (url->scheme && (default_port = default_port_for_scheme(url->scheme)) != NULL)
return (strcmp(url->port, default_port) == 0);
else
return false;
@@ -744,6 +983,7 @@ void git_net_url_dispose(git_net_url *url)
git__free(url->port); url->port = NULL;
git__free(url->path); url->path = NULL;
git__free(url->query); url->query = NULL;
+ git__free(url->fragment); url->fragment = NULL;
git__free(url->username); url->username = NULL;
git__free(url->password); url->password = NULL;
}
diff --git a/src/util/net.h b/src/util/net.h
index 88030a952..383592812 100644
--- a/src/util/net.h
+++ b/src/util/net.h
@@ -15,6 +15,7 @@ typedef struct git_net_url {
char *port;
char *path;
char *query;
+ char *fragment;
char *username;
char *password;
} git_net_url;
diff --git a/tests/util/url/parse.c b/tests/util/url/parse.c
index 5b6116e28..29b76c63b 100644
--- a/tests/util/url/parse.c
+++ b/tests/util/url/parse.c
@@ -24,6 +24,8 @@ void test_url_parse__hostname_trivial(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -36,6 +38,8 @@ void test_url_parse__hostname_root(void)
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -48,6 +52,22 @@ void test_url_parse__hostname_implied_root(void)
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__hostname_numeric(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://8888888/"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_s(conndata.host, "8888888");
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -60,6 +80,8 @@ void test_url_parse__hostname_implied_root_custom_port(void)
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
@@ -72,6 +94,8 @@ void test_url_parse__hostname_implied_root_empty_port(void)
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -85,6 +109,8 @@ void test_url_parse__hostname_encoded_password(void)
cl_assert_equal_s(conndata.path, "/");
cl_assert_equal_s(conndata.username, "user");
cl_assert_equal_s(conndata.password, "pass/is@bad");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
@@ -98,6 +124,8 @@ void test_url_parse__hostname_user(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "user");
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -112,6 +140,8 @@ void test_url_parse__hostname_user_pass(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "user");
cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -126,6 +156,8 @@ void test_url_parse__hostname_port(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
@@ -138,6 +170,8 @@ void test_url_parse__hostname_empty_port(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_p(conndata.username, NULL);
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
}
@@ -152,6 +186,8 @@ void test_url_parse__hostname_user_port(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "user");
cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
@@ -166,6 +202,72 @@ void test_url_parse__hostname_user_pass_port(void)
cl_assert_equal_s(conndata.path, "/resource");
cl_assert_equal_s(conndata.username, "user");
cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_query(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource#fragment"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__hostname_user_pass_port_query_fragment(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf#fragment"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf");
+ cl_assert_equal_s(conndata.fragment, "fragment");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__fragment_with_question_mark(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(git_net_url_parse(&conndata,
+ "https://user:pass@example.com:9191/resource#fragment_with?question_mark"));
+ cl_assert_equal_s(conndata.scheme, "https");
+ cl_assert_equal_s(conndata.host, "example.com");
+ cl_assert_equal_s(conndata.port, "9191");
+ cl_assert_equal_s(conndata.path, "/resource");
+ cl_assert_equal_s(conndata.username, "user");
+ cl_assert_equal_s(conndata.password, "pass");
+ cl_assert_equal_p(conndata.query, NULL);
+ cl_assert_equal_s(conndata.fragment, "fragment_with?question_mark");
cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
@@ -526,6 +628,7 @@ void test_url_parse__ipv6_invalid_addresses(void)
"https://user@[fe80::dcad:beff:fe00:0001:9191/resource"));
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
"https://user:pass@[fe80::dcad:beff:fe00:0001:9191/resource"));
+
/* Both brackets missing */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
"http://fe80::dcad:beff:fe00:0001/resource"));
@@ -554,6 +657,120 @@ void test_url_parse__ipv6_invalid_addresses(void)
/* Invalid character inside address */
cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata, "http://[fe8o::dcad:beff:fe00:0001]/resource"));
+
+ /* Characters before/after braces */
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://fe80::[dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://cafe[fe80::dcad:beff:fe00:0001]/resource"));
+ cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse(&conndata,
+ "http://[fe80::dcad:beff:fe00:0001]cafe/resource"));
+}
+
+/* Oddities */
+
+void test_url_parse__invalid_scheme_is_relative(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "foo!bar://host:42/path/to/project?query_string=yes"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "foo!bar://host:42/path/to/project");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_s(conndata.query, "query_string=yes");
+ cl_assert_equal_p(conndata.fragment, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__scheme_case_is_normalized(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "GIT+SSH://host:42/path/to/project"));
+ cl_assert_equal_s(conndata.scheme, "git+ssh");
+}
+
+void test_url_parse__nonhierarchical_scheme(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "mailto:foobar@example.com"));
+ cl_assert_equal_s(conndata.scheme, "mailto");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "foobar@example.com");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__no_scheme_relative_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "path"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "path");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__no_scheme_absolute_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "/path"));
+ cl_assert_equal_p(conndata.scheme, NULL);
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, "/path");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__empty_path(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "mailto:"));
+ cl_assert_equal_s(conndata.scheme, "mailto");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_p(conndata.port, NULL);
+ cl_assert_equal_s(conndata.path, NULL);
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__empty_path_with_empty_authority(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "http://"));
+ cl_assert_equal_s(conndata.scheme, "http");
+ cl_assert_equal_p(conndata.host, NULL);
+ cl_assert_equal_s(conndata.port, "80");
+ cl_assert_equal_s(conndata.path, "/");
+ cl_assert_equal_p(conndata.username, NULL);
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1);
+}
+
+void test_url_parse__ssh_from_terrible_google_rfc_violating_products(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "ssh://my.email.address@gmail.com@source.developers.google.com:2022/p/my-project/r/my-repository"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "source.developers.google.com");
+ cl_assert_equal_s(conndata.port, "2022");
+ cl_assert_equal_s(conndata.path, "/p/my-project/r/my-repository");
+ cl_assert_equal_s(conndata.username, "my.email.address@gmail.com");
+ cl_assert_equal_p(conndata.password, NULL);
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
+}
+
+void test_url_parse__ssh_with_password_from_terrible_google_rfc_violating_products(void)
+{
+ cl_git_pass(git_net_url_parse(&conndata, "ssh://my.email.address@gmail.com:seekret@source.developers.google.com:2022/p/my-project/r/my-repository"));
+ cl_assert_equal_s(conndata.scheme, "ssh");
+ cl_assert_equal_s(conndata.host, "source.developers.google.com");
+ cl_assert_equal_s(conndata.port, "2022");
+ cl_assert_equal_s(conndata.path, "/p/my-project/r/my-repository");
+ cl_assert_equal_s(conndata.username, "my.email.address@gmail.com");
+ cl_assert_equal_s(conndata.password, "seekret");
+ cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0);
}
void test_url_parse__spaces_in_the_name(void)