@@ -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+
195384int git_net_url_joinpath (
196385 git_net_url * out ,
197386 git_net_url * one ,
0 commit comments