Skip to content

Commit e995f74

Browse files
committed
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.
1 parent 471daee commit e995f74

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

src/net.c

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,76 @@ int git_net_url_parse(git_net_url *url, const char *given)
153153
return error;
154154
}
155155

156+
int git_net_url_joinpath(
157+
git_net_url *out,
158+
git_net_url *one,
159+
const char *two)
160+
{
161+
git_buf path = GIT_BUF_INIT;
162+
const char *query;
163+
size_t one_len, two_len;
164+
165+
git_net_url_dispose(out);
166+
167+
if ((query = strchr(two, '?')) != NULL) {
168+
two_len = query - two;
169+
170+
if (*(++query) != '\0') {
171+
out->query = git__strdup(query);
172+
GIT_ERROR_CHECK_ALLOC(out->query);
173+
}
174+
} else {
175+
two_len = strlen(two);
176+
}
177+
178+
/* Strip all trailing `/`s from the first path */
179+
one_len = one->path ? strlen(one->path) : 0;
180+
while (one_len && one->path[one_len - 1] == '/')
181+
one_len--;
182+
183+
/* Strip all leading `/`s from the second path */
184+
while (*two == '/') {
185+
two++;
186+
two_len--;
187+
}
188+
189+
git_buf_put(&path, one->path, one_len);
190+
git_buf_putc(&path, '/');
191+
git_buf_put(&path, two, two_len);
192+
193+
if (git_buf_oom(&path))
194+
return -1;
195+
196+
out->path = git_buf_detach(&path);
197+
198+
if (one->scheme) {
199+
out->scheme = git__strdup(one->scheme);
200+
GIT_ERROR_CHECK_ALLOC(out->scheme);
201+
}
202+
203+
if (one->host) {
204+
out->host = git__strdup(one->host);
205+
GIT_ERROR_CHECK_ALLOC(out->host);
206+
}
207+
208+
if (one->port) {
209+
out->port = git__strdup(one->port);
210+
GIT_ERROR_CHECK_ALLOC(out->port);
211+
}
212+
213+
if (one->username) {
214+
out->username = git__strdup(one->username);
215+
GIT_ERROR_CHECK_ALLOC(out->username);
216+
}
217+
218+
if (one->password) {
219+
out->password = git__strdup(one->password);
220+
GIT_ERROR_CHECK_ALLOC(out->password);
221+
}
222+
223+
return 0;
224+
}
225+
156226
/*
157227
* Some servers strip the query parameters from the Location header
158228
* when sending a redirect. Others leave it in place.

src/net.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ typedef struct git_net_url {
2424
/** Parses a string containing a URL into a structure. */
2525
extern int git_net_url_parse(git_net_url *url, const char *str);
2626

27+
/** Appends a path and/or query string to the given URL */
28+
extern int git_net_url_joinpath(
29+
git_net_url *out,
30+
git_net_url *in,
31+
const char *path);
32+
2733
/** Ensures that a URL is minimally valid (contains a host, port and path) */
2834
extern bool git_net_url_valid(git_net_url *url);
2935

tests/network/joinpath.c

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#include "clar_libgit2.h"
2+
#include "net.h"
3+
#include "netops.h"
4+
5+
static git_net_url source, target;
6+
7+
void test_network_joinpath__initialize(void)
8+
{
9+
memset(&source, 0, sizeof(source));
10+
memset(&target, 0, sizeof(target));
11+
}
12+
13+
void test_network_joinpath__cleanup(void)
14+
{
15+
git_net_url_dispose(&source);
16+
git_net_url_dispose(&target);
17+
}
18+
19+
void test_network_joinpath__target_paths_and_queries(void)
20+
{
21+
cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b"));
22+
23+
cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
24+
cl_assert_equal_s(target.path, "/a/b/c/d");
25+
cl_assert_equal_p(target.query, NULL);
26+
git_net_url_dispose(&target);
27+
28+
cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
29+
cl_assert_equal_s(target.path, "/a/b/c/d");
30+
cl_assert_equal_s(target.query, "foo");
31+
git_net_url_dispose(&target);
32+
}
33+
34+
void test_network_joinpath__source_query_removed(void)
35+
{
36+
cl_git_pass(git_net_url_parse(&source, "http://example.com/a/b?query&one&two"));
37+
38+
cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d"));
39+
cl_assert_equal_s(target.path, "/a/b/c/d");
40+
cl_assert_equal_p(target.query, NULL);
41+
git_net_url_dispose(&target);
42+
43+
cl_git_pass(git_net_url_joinpath(&target, &source, "/c/d?foo"));
44+
cl_assert_equal_s(target.path, "/a/b/c/d");
45+
cl_assert_equal_s(target.query, "foo");
46+
git_net_url_dispose(&target);
47+
}
48+
49+
void test_network_joinpath__source_lacks_path(void)
50+
{
51+
cl_git_pass(git_net_url_parse(&source, "http://example.com"));
52+
53+
cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
54+
cl_assert_equal_s(target.path, "/");
55+
cl_assert_equal_p(target.query, NULL);
56+
git_net_url_dispose(&target);
57+
58+
cl_git_pass(git_net_url_joinpath(&target, &source, ""));
59+
cl_assert_equal_s(target.path, "/");
60+
cl_assert_equal_p(target.query, NULL);
61+
git_net_url_dispose(&target);
62+
63+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
64+
cl_assert_equal_s(target.path, "/asdf");
65+
cl_assert_equal_p(target.query, NULL);
66+
git_net_url_dispose(&target);
67+
68+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
69+
cl_assert_equal_s(target.path, "/asdf");
70+
cl_assert_equal_p(target.query, NULL);
71+
git_net_url_dispose(&target);
72+
73+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
74+
cl_assert_equal_s(target.path, "/foo/bar");
75+
cl_assert_equal_p(target.query, NULL);
76+
git_net_url_dispose(&target);
77+
78+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
79+
cl_assert_equal_s(target.path, "/asdf");
80+
cl_assert_equal_s(target.query, "hello");
81+
git_net_url_dispose(&target);
82+
83+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
84+
cl_assert_equal_s(target.path, "/asdf");
85+
cl_assert_equal_s(target.query, "hello");
86+
git_net_url_dispose(&target);
87+
88+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
89+
cl_assert_equal_s(target.path, "/foo/bar");
90+
cl_assert_equal_s(target.query, "hello");
91+
git_net_url_dispose(&target);
92+
}
93+
94+
void test_network_joinpath__source_is_slash(void)
95+
{
96+
cl_git_pass(git_net_url_parse(&source, "http://example.com/"));
97+
98+
cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
99+
cl_assert_equal_s(target.path, "/");
100+
cl_assert_equal_p(target.query, NULL);
101+
git_net_url_dispose(&target);
102+
103+
cl_git_pass(git_net_url_joinpath(&target, &source, ""));
104+
cl_assert_equal_s(target.path, "/");
105+
cl_assert_equal_p(target.query, NULL);
106+
git_net_url_dispose(&target);
107+
108+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
109+
cl_assert_equal_s(target.path, "/asdf");
110+
cl_assert_equal_p(target.query, NULL);
111+
git_net_url_dispose(&target);
112+
113+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
114+
cl_assert_equal_s(target.path, "/asdf");
115+
cl_assert_equal_p(target.query, NULL);
116+
git_net_url_dispose(&target);
117+
118+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
119+
cl_assert_equal_s(target.path, "/foo/bar");
120+
cl_assert_equal_p(target.query, NULL);
121+
git_net_url_dispose(&target);
122+
123+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
124+
cl_assert_equal_s(target.path, "/asdf");
125+
cl_assert_equal_s(target.query, "hello");
126+
git_net_url_dispose(&target);
127+
128+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
129+
cl_assert_equal_s(target.path, "/asdf");
130+
cl_assert_equal_s(target.query, "hello");
131+
git_net_url_dispose(&target);
132+
133+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
134+
cl_assert_equal_s(target.path, "/foo/bar");
135+
cl_assert_equal_s(target.query, "hello");
136+
git_net_url_dispose(&target);
137+
}
138+
139+
140+
void test_network_joinpath__source_has_query(void)
141+
{
142+
cl_git_pass(git_net_url_parse(&source, "http://example.com?query"));
143+
144+
cl_git_pass(git_net_url_joinpath(&target, &source, "/"));
145+
cl_assert_equal_s(target.path, "/");
146+
cl_assert_equal_p(target.query, NULL);
147+
git_net_url_dispose(&target);
148+
149+
cl_git_pass(git_net_url_joinpath(&target, &source, ""));
150+
cl_assert_equal_s(target.path, "/");
151+
cl_assert_equal_p(target.query, NULL);
152+
git_net_url_dispose(&target);
153+
154+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf"));
155+
cl_assert_equal_s(target.path, "/asdf");
156+
cl_assert_equal_p(target.query, NULL);
157+
git_net_url_dispose(&target);
158+
159+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf"));
160+
cl_assert_equal_s(target.path, "/asdf");
161+
cl_assert_equal_p(target.query, NULL);
162+
git_net_url_dispose(&target);
163+
164+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar"));
165+
cl_assert_equal_s(target.path, "/foo/bar");
166+
cl_assert_equal_p(target.query, NULL);
167+
git_net_url_dispose(&target);
168+
169+
cl_git_pass(git_net_url_joinpath(&target, &source, "asdf?hello"));
170+
cl_assert_equal_s(target.path, "/asdf");
171+
cl_assert_equal_s(target.query, "hello");
172+
git_net_url_dispose(&target);
173+
174+
cl_git_pass(git_net_url_joinpath(&target, &source, "/asdf?hello"));
175+
cl_assert_equal_s(target.path, "/asdf");
176+
cl_assert_equal_s(target.query, "hello");
177+
git_net_url_dispose(&target);
178+
179+
cl_git_pass(git_net_url_joinpath(&target, &source, "/foo/bar?hello"));
180+
cl_assert_equal_s(target.path, "/foo/bar");
181+
cl_assert_equal_s(target.query, "hello");
182+
git_net_url_dispose(&target);
183+
}
184+
185+
186+
void test_network_joinpath__empty_query_ignored(void)
187+
{
188+
cl_git_pass(git_net_url_parse(&source, "http://example.com/foo"));
189+
190+
cl_git_pass(git_net_url_joinpath(&target, &source, "/bar/baz?"));
191+
cl_assert_equal_s(target.path, "/foo/bar/baz");
192+
cl_assert_equal_p(target.query, NULL);
193+
git_net_url_dispose(&target);
194+
}

0 commit comments

Comments
 (0)