summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2019-12-06 15:39:08 +1100
committerEdward Thomson <ethomson@edwardthomson.com>2020-01-24 09:54:29 -0600
commite995f74e8822456f97a2000da5a8d6803b88f643 (patch)
tree6ae012d4b0ad407e61d0da9388551791c64ae317
parent471daeea559ada7ac215806d55f2f686e7389608 (diff)
downloadlibgit2-e995f74e8822456f97a2000da5a8d6803b88f643.tar.gz
net: introduce git_net_url_joinpath
Provide a mechanism to add a path and query string to an existing url so that we can easily append `/info/refs?...` type url segments to a url given to us by a user.
-rw-r--r--src/net.c70
-rw-r--r--src/net.h6
-rw-r--r--tests/network/joinpath.c194
3 files changed, 270 insertions, 0 deletions
diff --git a/src/net.c b/src/net.c
index bb7664366..c4a351ac6 100644
--- a/src/net.c
+++ b/src/net.c
@@ -153,6 +153,76 @@ done:
return error;
}
+int git_net_url_joinpath(
+ git_net_url *out,
+ git_net_url *one,
+ const char *two)
+{
+ git_buf path = GIT_BUF_INIT;
+ const char *query;
+ size_t one_len, two_len;
+
+ git_net_url_dispose(out);
+
+ if ((query = strchr(two, '?')) != NULL) {
+ two_len = query - two;
+
+ if (*(++query) != '\0') {
+ out->query = git__strdup(query);
+ GIT_ERROR_CHECK_ALLOC(out->query);
+ }
+ } else {
+ two_len = strlen(two);
+ }
+
+ /* Strip all trailing `/`s from the first path */
+ one_len = one->path ? strlen(one->path) : 0;
+ while (one_len && one->path[one_len - 1] == '/')
+ one_len--;
+
+ /* Strip all leading `/`s from the second path */
+ while (*two == '/') {
+ two++;
+ two_len--;
+ }
+
+ git_buf_put(&path, one->path, one_len);
+ git_buf_putc(&path, '/');
+ git_buf_put(&path, two, two_len);
+
+ if (git_buf_oom(&path))
+ return -1;
+
+ out->path = git_buf_detach(&path);
+
+ if (one->scheme) {
+ out->scheme = git__strdup(one->scheme);
+ GIT_ERROR_CHECK_ALLOC(out->scheme);
+ }
+
+ if (one->host) {
+ out->host = git__strdup(one->host);
+ GIT_ERROR_CHECK_ALLOC(out->host);
+ }
+
+ if (one->port) {
+ out->port = git__strdup(one->port);
+ GIT_ERROR_CHECK_ALLOC(out->port);
+ }
+
+ if (one->username) {
+ out->username = git__strdup(one->username);
+ GIT_ERROR_CHECK_ALLOC(out->username);
+ }
+
+ if (one->password) {
+ out->password = git__strdup(one->password);
+ GIT_ERROR_CHECK_ALLOC(out->password);
+ }
+
+ return 0;
+}
+
/*
* Some servers strip the query parameters from the Location header
* when sending a redirect. Others leave it in place.
diff --git a/src/net.h b/src/net.h
index 31fff9772..089d9ae3a 100644
--- a/src/net.h
+++ b/src/net.h
@@ -24,6 +24,12 @@ typedef struct git_net_url {
/** Parses a string containing a URL into a structure. */
extern int git_net_url_parse(git_net_url *url, const char *str);
+/** Appends a path and/or query string to the given URL */
+extern int git_net_url_joinpath(
+ git_net_url *out,
+ git_net_url *in,
+ const char *path);
+
/** Ensures that a URL is minimally valid (contains a host, port and path) */
extern bool git_net_url_valid(git_net_url *url);
diff --git a/tests/network/joinpath.c b/tests/network/joinpath.c
new file mode 100644
index 000000000..da8393b91
--- /dev/null
+++ b/tests/network/joinpath.c
@@ -0,0 +1,194 @@
+#include "clar_libgit2.h"
+#include "net.h"
+#include "netops.h"
+
+static git_net_url source, target;
+
+void test_network_joinpath__initialize(void)
+{
+ memset(&source, 0, sizeof(source));
+ memset(&target, 0, sizeof(target));
+}
+
+void test_network_joinpath__cleanup(void)
+{
+ git_net_url_dispose(&source);
+ git_net_url_dispose(&target);
+}
+
+void test_network_joinpath__target_paths_and_queries(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_s(target.query, "foo");
+ git_net_url_dispose(&target);
+}
+
+void test_network_joinpath__source_query_removed(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
+ cl_assert_equal_s(target.path, "/a/b/c/d");
+ cl_assert_equal_s(target.query, "foo");
+ git_net_url_dispose(&target);
+}
+
+void test_network_joinpath__source_lacks_path(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+void test_network_joinpath__source_is_slash(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+
+void test_network_joinpath__source_has_query(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com?query"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, ""));
+ cl_assert_equal_s(target.path, "/");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
+ cl_assert_equal_s(target.path, "/asdf");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
+ cl_assert_equal_s(target.path, "/foo/bar");
+ cl_assert_equal_s(target.query, "hello");
+ git_net_url_dispose(&target);
+}
+
+
+void test_network_joinpath__empty_query_ignored(void)
+{
+ cl_git_pass(git_net_url_parse(&source, "http://example.com/foo"));
+
+ cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?"));
+ cl_assert_equal_s(target.path, "/foo/bar/baz");
+ cl_assert_equal_p(target.query, NULL);
+ git_net_url_dispose(&target);
+}