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+
2341static 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+
195404int git_net_url_joinpath (
196405 git_net_url * out ,
197406 git_net_url * one ,
0 commit comments