Skip to content

Commit d298059

Browse files
authored
Merge pull request libgit2#6167 from libgit2/ethomson/scp_urls_with_ports
Support scp style paths with ports
2 parents d50b346 + 27307ed commit d298059

File tree

12 files changed

+645
-158
lines changed

12 files changed

+645
-158
lines changed

ci/test.sh

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,18 +300,28 @@ if [ -z "$SKIP_NEGOTIATE_TESTS" -a -n "$GITTEST_NEGOTIATE_PASSWORD" ]; then
300300
fi
301301

302302
if [ -z "$SKIP_SSH_TESTS" ]; then
303-
echo ""
304-
echo "Running ssh tests"
305-
echo ""
306-
307-
export GITTEST_REMOTE_URL="ssh://localhost:2222/$SSHD_DIR/test.git"
308303
export GITTEST_REMOTE_USER=$USER
309304
export GITTEST_REMOTE_SSH_KEY="${HOME}/.ssh/id_rsa"
310305
export GITTEST_REMOTE_SSH_PUBKEY="${HOME}/.ssh/id_rsa.pub"
311306
export GITTEST_REMOTE_SSH_PASSPHRASE=""
312307
export GITTEST_REMOTE_SSH_FINGERPRINT="${SSH_FINGERPRINT}"
308+
309+
echo ""
310+
echo "Running ssh tests"
311+
echo ""
312+
313+
export GITTEST_REMOTE_URL="ssh://localhost:2222/$SSHD_DIR/test.git"
313314
run_test ssh
314315
unset GITTEST_REMOTE_URL
316+
317+
echo ""
318+
echo "Running ssh tests (scp-style paths)"
319+
echo ""
320+
321+
export GITTEST_REMOTE_URL="[localhost:2222]:$SSHD_DIR/test.git"
322+
run_test ssh
323+
unset GITTEST_REMOTE_URL
324+
315325
unset GITTEST_REMOTE_USER
316326
unset GITTEST_REMOTE_SSH_KEY
317327
unset GITTEST_REMOTE_SSH_PUBKEY

src/common.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,16 @@
121121
/**
122122
* Check a pointer allocation result, returning -1 if it failed.
123123
*/
124-
#define GIT_ERROR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
124+
#define GIT_ERROR_CHECK_ALLOC(ptr) do { \
125+
if ((ptr) == NULL) { return -1; } \
126+
} while(0)
125127

126128
/**
127129
* Check a string buffer allocation result, returning -1 if it failed.
128130
*/
129-
#define GIT_ERROR_CHECK_ALLOC_STR(buf) if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; }
131+
#define GIT_ERROR_CHECK_ALLOC_STR(buf) do { \
132+
if ((void *)(buf) == NULL || git_str_oom(buf)) { return -1; } \
133+
} while(0)
130134

131135
/**
132136
* Check a return value and propagate result if non-zero.

src/net.c

Lines changed: 210 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,24 @@
2020
#define DEFAULT_PORT_GIT "9418"
2121
#define DEFAULT_PORT_SSH "22"
2222

23+
bool git_net_str_is_url(const char *str)
24+
{
25+
const char *c;
26+
27+
for (c = str; *c; c++) {
28+
if (*c == ':' && *(c+1) == '/' && *(c+2) == '/')
29+
return true;
30+
31+
if ((*c < 'a' || *c > 'z') &&
32+
(*c < 'A' || *c > 'Z') &&
33+
(*c < '0' || *c > '9') &&
34+
(*c != '+' && *c != '-' && *c != '.'))
35+
break;
36+
}
37+
38+
return false;
39+
}
40+
2341
static const char *default_port_for_scheme(const char *scheme)
2442
{
2543
if (strcmp(scheme, "http") == 0)
@@ -28,7 +46,9 @@ static const char *default_port_for_scheme(const char *scheme)
2846
return DEFAULT_PORT_HTTPS;
2947
else if (strcmp(scheme, "git") == 0)
3048
return DEFAULT_PORT_GIT;
31-
else if (strcmp(scheme, "ssh") == 0)
49+
else if (strcmp(scheme, "ssh") == 0 ||
50+
strcmp(scheme, "ssh+git") == 0 ||
51+
strcmp(scheme, "git+ssh") == 0)
3252
return DEFAULT_PORT_SSH;
3353

3454
return NULL;
@@ -192,6 +212,195 @@ int git_net_url_parse(git_net_url *url, const char *given)
192212
return error;
193213
}
194214

215+
static int scp_invalid(const char *message)
216+
{
217+
git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message);
218+
return GIT_EINVALIDSPEC;
219+
}
220+
221+
static bool is_ipv6(const char *str)
222+
{
223+
const char *c;
224+
size_t colons = 0;
225+
226+
if (*str++ != '[')
227+
return false;
228+
229+
for (c = str; *c; c++) {
230+
if (*c == ':')
231+
colons++;
232+
233+
if (*c == ']')
234+
return (colons > 1);
235+
236+
if (*c != ':' &&
237+
(*c < '0' || *c > '9') &&
238+
(*c < 'a' || *c > 'f') &&
239+
(*c < 'A' || *c > 'F'))
240+
return false;
241+
}
242+
243+
return false;
244+
}
245+
246+
static bool has_at(const char *str)
247+
{
248+
const char *c;
249+
250+
for (c = str; *c; c++) {
251+
if (*c == '@')
252+
return true;
253+
254+
if (*c == ':')
255+
break;
256+
}
257+
258+
return false;
259+
}
260+
261+
int git_net_url_parse_scp(git_net_url *url, const char *given)
262+
{
263+
const char *default_port = default_port_for_scheme("ssh");
264+
const char *c, *user, *host, *port, *path = NULL;
265+
size_t user_len = 0, host_len = 0, port_len = 0;
266+
unsigned short bracket = 0;
267+
268+
enum {
269+
NONE,
270+
USER,
271+
HOST_START, HOST, HOST_END,
272+
IPV6, IPV6_END,
273+
PORT_START, PORT, PORT_END,
274+
PATH_START
275+
} state = NONE;
276+
277+
memset(url, 0, sizeof(git_net_url));
278+
279+
for (c = given; *c && !path; c++) {
280+
switch (state) {
281+
case NONE:
282+
switch (*c) {
283+
case '@':
284+
return scp_invalid("unexpected '@'");
285+
case ':':
286+
return scp_invalid("unexpected ':'");
287+
case '[':
288+
if (is_ipv6(c)) {
289+
state = IPV6;
290+
host = c;
291+
} else if (bracket++ > 1) {
292+
return scp_invalid("unexpected '['");
293+
}
294+
break;
295+
default:
296+
if (has_at(c)) {
297+
state = USER;
298+
user = c;
299+
} else {
300+
state = HOST;
301+
host = c;
302+
}
303+
break;
304+
}
305+
break;
306+
307+
case USER:
308+
if (*c == '@') {
309+
user_len = (c - user);
310+
state = HOST_START;
311+
}
312+
break;
313+
314+
case HOST_START:
315+
state = (*c == '[') ? IPV6 : HOST;
316+
host = c;
317+
break;
318+
319+
case HOST:
320+
if (*c == ':') {
321+
host_len = (c - host);
322+
state = bracket ? PORT_START : PATH_START;
323+
} else if (*c == ']') {
324+
if (bracket-- == 0)
325+
return scp_invalid("unexpected ']'");
326+
327+
host_len = (c - host);
328+
state = HOST_END;
329+
}
330+
break;
331+
332+
case HOST_END:
333+
if (*c != ':')
334+
return scp_invalid("unexpected character after hostname");
335+
state = PATH_START;
336+
break;
337+
338+
case IPV6:
339+
if (*c == ']')
340+
state = IPV6_END;
341+
break;
342+
343+
case IPV6_END:
344+
if (*c != ':')
345+
return scp_invalid("unexpected character after ipv6 address");
346+
347+
host_len = (c - host);
348+
state = bracket ? PORT_START : PATH_START;
349+
break;
350+
351+
case PORT_START:
352+
port = c;
353+
state = PORT;
354+
break;
355+
356+
case PORT:
357+
if (*c == ']') {
358+
if (bracket-- == 0)
359+
return scp_invalid("unexpected ']'");
360+
361+
port_len = c - port;
362+
state = PORT_END;
363+
}
364+
break;
365+
366+
case PORT_END:
367+
if (*c != ':')
368+
return scp_invalid("unexpected character after ipv6 address");
369+
370+
state = PATH_START;
371+
break;
372+
373+
case PATH_START:
374+
path = c;
375+
break;
376+
377+
default:
378+
GIT_ASSERT("unhandled state");
379+
}
380+
}
381+
382+
if (!path)
383+
return scp_invalid("path is required");
384+
385+
GIT_ERROR_CHECK_ALLOC(url->scheme = git__strdup("ssh"));
386+
387+
if (user_len)
388+
GIT_ERROR_CHECK_ALLOC(url->username = git__strndup(user, user_len));
389+
390+
GIT_ASSERT(host_len);
391+
GIT_ERROR_CHECK_ALLOC(url->host = git__strndup(host, host_len));
392+
393+
if (port_len)
394+
GIT_ERROR_CHECK_ALLOC(url->port = git__strndup(port, port_len));
395+
else
396+
GIT_ERROR_CHECK_ALLOC(url->port = git__strdup(default_port));
397+
398+
GIT_ASSERT(path);
399+
GIT_ERROR_CHECK_ALLOC(url->path = git__strdup(path));
400+
401+
return 0;
402+
}
403+
195404
int git_net_url_joinpath(
196405
git_net_url *out,
197406
git_net_url *one,

src/net.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,18 @@ typedef struct git_net_url {
2121

2222
#define GIT_NET_URL_INIT { NULL }
2323

24+
/** Is a given string a url? */
25+
extern bool git_net_str_is_url(const char *str);
26+
2427
/** Duplicate a URL */
2528
extern int git_net_url_dup(git_net_url *out, git_net_url *in);
2629

27-
/** Parses a string containing a URL into a structure. */
30+
/** Parses a string containing a URL into a structure. */
2831
extern int git_net_url_parse(git_net_url *url, const char *str);
2932

33+
/** Parses a string containing an SCP style path into a URL structure. */
34+
extern int git_net_url_parse_scp(git_net_url *url, const char *str);
35+
3036
/** Appends a path and/or query string to the given URL */
3137
extern int git_net_url_joinpath(
3238
git_net_url *out,

0 commit comments

Comments
 (0)