Skip to content

Commit e2bda60

Browse files
committed
url: introduce git_net_url_parse_scp
Provide a mechanism for parsing scp-style paths (eg `git@github.com:libgit2/libgit2` into the url form `ssh://git@github.com/libgit2/libgit2`.)
1 parent 3db53eb commit e2bda60

File tree

4 files changed

+515
-38
lines changed

4 files changed

+515
-38
lines changed

src/net.c

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,195 @@ int git_net_url_parse(git_net_url *url, const char *given)
192192
return error;
193193
}
194194

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

src/net.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ typedef struct git_net_url {
2424
/** Duplicate a URL */
2525
extern int git_net_url_dup(git_net_url *out, git_net_url *in);
2626

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

30+
/** Parses a string containing an SCP style path into a URL structure. */
31+
extern int git_net_url_parse_scp(git_net_url *url, const char *str);
32+
3033
/** Appends a path and/or query string to the given URL */
3134
extern int git_net_url_joinpath(
3235
git_net_url *out,

src/transports/ssh.c

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -258,37 +258,6 @@ static int ssh_stream_alloc(
258258
return 0;
259259
}
260260

261-
static int git_ssh_extract_url_parts(
262-
git_net_url *urldata,
263-
const char *url)
264-
{
265-
char *colon, *at;
266-
const char *start;
267-
268-
colon = strchr(url, ':');
269-
270-
271-
at = strchr(url, '@');
272-
if (at) {
273-
start = at + 1;
274-
urldata->username = git__substrdup(url, at - url);
275-
GIT_ERROR_CHECK_ALLOC(urldata->username);
276-
} else {
277-
start = url;
278-
urldata->username = NULL;
279-
}
280-
281-
if (colon == NULL || (colon < start)) {
282-
git_error_set(GIT_ERROR_NET, "malformed URL");
283-
return -1;
284-
}
285-
286-
urldata->host = git__substrdup(start, colon - start);
287-
GIT_ERROR_CHECK_ALLOC(urldata->host);
288-
289-
return 0;
290-
}
291-
292261
static int ssh_agent_auth(LIBSSH2_SESSION *session, git_credential_ssh_key *c) {
293262
int rc = LIBSSH2_ERROR_NONE;
294263

@@ -546,14 +515,9 @@ static int _git_ssh_setup_conn(
546515
goto post_extract;
547516
}
548517
}
549-
if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0)
518+
if ((error = git_net_url_parse_scp(&urldata, url)) < 0)
550519
goto done;
551520

552-
if (urldata.port == NULL)
553-
urldata.port = git__strdup(SSH_DEFAULT_PORT);
554-
555-
GIT_ERROR_CHECK_ALLOC(urldata.port);
556-
557521
post_extract:
558522
if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 ||
559523
(error = git_stream_connect(s->io)) < 0)

0 commit comments

Comments
 (0)