Skip to content

Commit 5585e35

Browse files
authored
Merge pull request libgit2#4563 from libgit2/ethomson/ssh-unescape
Refactor `gitno_extract_url_parts`
2 parents 3198577 + 9108959 commit 5585e35

File tree

6 files changed

+163
-64
lines changed

6 files changed

+163
-64
lines changed

src/buffer.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ int git_buf_put(git_buf *buf, const char *data, size_t len)
212212
size_t new_size;
213213

214214
assert(data);
215-
215+
216216
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, len);
217217
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
218218
ENSURE_SIZE(buf, new_size);
@@ -455,6 +455,36 @@ int git_buf_decode_base85(
455455
return -1;
456456
}
457457

458+
#define HEX_DECODE(c) ((c | 32) % 39 - 9)
459+
460+
int git_buf_decode_percent(
461+
git_buf *buf,
462+
const char *str,
463+
size_t str_len)
464+
{
465+
size_t str_pos, new_size;
466+
467+
GITERR_CHECK_ALLOC_ADD(&new_size, buf->size, str_len);
468+
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
469+
ENSURE_SIZE(buf, new_size);
470+
471+
for (str_pos = 0; str_pos < str_len; buf->size++, str_pos++) {
472+
if (str[str_pos] == '%' &&
473+
str_len > str_pos + 2 &&
474+
isxdigit(str[str_pos + 1]) &&
475+
isxdigit(str[str_pos + 2])) {
476+
buf->ptr[buf->size] = (HEX_DECODE(str[str_pos + 1]) << 4) +
477+
HEX_DECODE(str[str_pos + 2]);
478+
str_pos += 2;
479+
} else {
480+
buf->ptr[buf->size] = str[str_pos];
481+
}
482+
}
483+
484+
buf->ptr[buf->size] = '\0';
485+
return 0;
486+
}
487+
458488
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
459489
{
460490
size_t expected_size, new_size;

src/buffer.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
190190
/* Decode the given "base85" and write the result to the buffer */
191191
int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len);
192192

193+
/* Decode the given percent-encoded string and write the result to the buffer */
194+
int git_buf_decode_percent(git_buf *buf, const char *str, size_t len);
195+
193196
/*
194197
* Insert, remove or replace a portion of the buffer.
195198
*

src/netops.c

Lines changed: 73 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -206,83 +206,96 @@ void gitno_connection_data_free_ptrs(gitno_connection_data *d)
206206
git__free(d->pass); d->pass = NULL;
207207
}
208208

209-
#define hex2c(c) ((c | 32) % 39 - 9)
210-
static char* unescape(char *str)
211-
{
212-
int x, y;
213-
int len = (int)strlen(str);
214-
215-
for (x=y=0; str[y]; ++x, ++y) {
216-
if ((str[x] = str[y]) == '%') {
217-
if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) {
218-
str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]);
219-
y += 2;
220-
}
221-
}
222-
}
223-
str[x] = '\0';
224-
return str;
225-
}
226-
227209
int gitno_extract_url_parts(
228-
char **host,
229-
char **port,
230-
char **path,
231-
char **username,
232-
char **password,
233-
const char *url,
234-
const char *default_port)
210+
char **host_out,
211+
char **port_out,
212+
char **path_out,
213+
char **username_out,
214+
char **password_out,
215+
const char *url,
216+
const char *default_port)
235217
{
236218
struct http_parser_url u = {0};
237-
const char *_host, *_port, *_path, *_userinfo;
219+
bool has_host, has_port, has_path, has_userinfo;
220+
git_buf host = GIT_BUF_INIT,
221+
port = GIT_BUF_INIT,
222+
path = GIT_BUF_INIT,
223+
username = GIT_BUF_INIT,
224+
password = GIT_BUF_INIT;
225+
int error = 0;
238226

239227
if (http_parser_parse_url(url, strlen(url), false, &u)) {
240228
giterr_set(GITERR_NET, "malformed URL '%s'", url);
241-
return GIT_EINVALIDSPEC;
229+
error = GIT_EINVALIDSPEC;
230+
goto done;
242231
}
243232

244-
_host = url+u.field_data[UF_HOST].off;
245-
_port = url+u.field_data[UF_PORT].off;
246-
_path = url+u.field_data[UF_PATH].off;
247-
_userinfo = url+u.field_data[UF_USERINFO].off;
233+
has_host = !!(u.field_set & (1 << UF_HOST));
234+
has_port = !!(u.field_set & (1 << UF_PORT));
235+
has_path = !!(u.field_set & (1 << UF_PATH));
236+
has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
248237

249-
if (u.field_set & (1 << UF_HOST)) {
250-
*host = git__substrdup(_host, u.field_data[UF_HOST].len);
251-
GITERR_CHECK_ALLOC(*host);
238+
if (has_host) {
239+
const char *url_host = url + u.field_data[UF_HOST].off;
240+
size_t url_host_len = u.field_data[UF_HOST].len;
241+
git_buf_decode_percent(&host, url_host, url_host_len);
252242
}
253243

254-
if (u.field_set & (1 << UF_PORT))
255-
*port = git__substrdup(_port, u.field_data[UF_PORT].len);
256-
else
257-
*port = git__strdup(default_port);
258-
GITERR_CHECK_ALLOC(*port);
244+
if (has_port) {
245+
const char *url_port = url + u.field_data[UF_PORT].off;
246+
size_t url_port_len = u.field_data[UF_PORT].len;
247+
git_buf_put(&port, url_port, url_port_len);
248+
} else {
249+
git_buf_puts(&port, default_port);
250+
}
259251

260-
if (path) {
261-
if (u.field_set & (1 << UF_PATH)) {
262-
*path = git__substrdup(_path, u.field_data[UF_PATH].len);
263-
GITERR_CHECK_ALLOC(*path);
264-
} else {
265-
git__free(*port);
266-
*port = NULL;
267-
git__free(*host);
268-
*host = NULL;
269-
giterr_set(GITERR_NET, "invalid url, missing path");
270-
return GIT_EINVALIDSPEC;
271-
}
252+
if (has_path && path_out) {
253+
const char *url_path = url + u.field_data[UF_PATH].off;
254+
size_t url_path_len = u.field_data[UF_PATH].len;
255+
git_buf_decode_percent(&path, url_path, url_path_len);
256+
} else if (path_out) {
257+
giterr_set(GITERR_NET, "invalid url, missing path");
258+
error = GIT_EINVALIDSPEC;
259+
goto done;
272260
}
273261

274-
if (u.field_set & (1 << UF_USERINFO)) {
275-
const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
262+
if (has_userinfo) {
263+
const char *url_userinfo = url + u.field_data[UF_USERINFO].off;
264+
size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
265+
const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
266+
276267
if (colon) {
277-
*username = unescape(git__substrdup(_userinfo, colon - _userinfo));
278-
*password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo)));
279-
GITERR_CHECK_ALLOC(*password);
268+
const char *url_username = url_userinfo;
269+
size_t url_username_len = colon - url_userinfo;
270+
const char *url_password = colon + 1;
271+
size_t url_password_len = url_userinfo_len - (url_username_len + 1);
272+
273+
git_buf_decode_percent(&username, url_username, url_username_len);
274+
git_buf_decode_percent(&password, url_password, url_password_len);
280275
} else {
281-
*username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
276+
git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
282277
}
283-
GITERR_CHECK_ALLOC(*username);
284-
285278
}
286279

287-
return 0;
280+
if (git_buf_oom(&host) ||
281+
git_buf_oom(&port) ||
282+
git_buf_oom(&path) ||
283+
git_buf_oom(&username) ||
284+
git_buf_oom(&password))
285+
return -1;
286+
287+
*host_out = git_buf_detach(&host);
288+
*port_out = git_buf_detach(&port);
289+
if (path_out)
290+
*path_out = git_buf_detach(&path);
291+
*username_out = git_buf_detach(&username);
292+
*password_out = git_buf_detach(&password);
293+
294+
done:
295+
git_buf_free(&host);
296+
git_buf_free(&port);
297+
git_buf_free(&path);
298+
git_buf_free(&username);
299+
git_buf_free(&password);
300+
return error;
288301
}

src/transports/ssh.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
6464
*/
6565
static int gen_proto(git_buf *request, const char *cmd, const char *url)
6666
{
67-
char *repo;
67+
const char *repo;
6868
int len;
6969
size_t i;
7070

@@ -92,8 +92,10 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url)
9292
len = strlen(cmd) + 1 /* Space */ + 1 /* Quote */ + strlen(repo) + 1 /* Quote */ + 1;
9393

9494
git_buf_grow(request, len);
95-
git_buf_printf(request, "%s '%s'", cmd, repo);
96-
git_buf_putc(request, '\0');
95+
git_buf_puts(request, cmd);
96+
git_buf_puts(request, " '");
97+
git_buf_decode_percent(request, repo, strlen(repo));
98+
git_buf_puts(request, "'");
9799

98100
if (git_buf_oom(request))
99101
return -1;

tests/buf/percent.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include "clar_libgit2.h"
2+
#include "buffer.h"
3+
4+
static void expect_decode_pass(const char *expected, const char *encoded)
5+
{
6+
git_buf in = GIT_BUF_INIT, out = GIT_BUF_INIT;
7+
8+
/*
9+
* ensure that we only read the given length of the input buffer
10+
* by putting garbage at the end. this will ensure that we do
11+
* not, eg, rely on nul-termination or walk off the end of the buf.
12+
*/
13+
cl_git_pass(git_buf_puts(&in, encoded));
14+
cl_git_pass(git_buf_PUTS(&in, "TRAILER"));
15+
16+
cl_git_pass(git_buf_decode_percent(&out, in.ptr, strlen(encoded)));
17+
18+
cl_assert_equal_s(expected, git_buf_cstr(&out));
19+
cl_assert_equal_i(strlen(expected), git_buf_len(&out));
20+
21+
git_buf_free(&in);
22+
git_buf_free(&out);
23+
}
24+
25+
void test_buf_percent__decode_succeeds(void)
26+
{
27+
expect_decode_pass("", "");
28+
expect_decode_pass(" ", "%20");
29+
expect_decode_pass("a", "a");
30+
expect_decode_pass(" a", "%20a");
31+
expect_decode_pass("a ", "a%20");
32+
expect_decode_pass("github.com", "github.com");
33+
expect_decode_pass("github.com", "githu%62.com");
34+
expect_decode_pass("github.com", "github%2ecom");
35+
expect_decode_pass("foo bar baz", "foo%20bar%20baz");
36+
expect_decode_pass("foo bar baz", "foo%20bar%20baz");
37+
expect_decode_pass("foo bar ", "foo%20bar%20");
38+
}
39+
40+
void test_buf_percent__ignores_invalid(void)
41+
{
42+
expect_decode_pass("githu%%.com", "githu%%.com");
43+
expect_decode_pass("github.co%2", "github.co%2");
44+
expect_decode_pass("github%2.com", "github%2.com");
45+
expect_decode_pass("githu%2z.com", "githu%2z.com");
46+
expect_decode_pass("github.co%9z", "github.co%9z");
47+
expect_decode_pass("github.co%2", "github.co%2");
48+
expect_decode_pass("github.co%", "github.co%");
49+
}

tests/transport/register.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ void test_transport_register__custom_transport_ssh(void)
4545
"ssh+git://somehost:somepath",
4646
"git+ssh://somehost:somepath",
4747
"git@somehost:somepath",
48+
"ssh://somehost:somepath%20with%20%spaces",
49+
"ssh://somehost:somepath with spaces"
4850
};
4951
git_transport *transport;
5052
unsigned i;

0 commit comments

Comments
 (0)