View Javadoc
1   package org.apache.maven.wagon;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.File;
23  import java.util.StringTokenizer;
24  
25  /**
26   * Various path (URL) manipulation routines
27   *
28   * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
29   *
30   */
31  public final class PathUtils
32  {
33      private PathUtils()
34      {
35      }
36  
37      /**
38       * Returns the directory path portion of a file specification string.
39       * Matches the equally named unix command.
40       *
41       * @return The directory portion excluding the ending file separator.
42       */
43      public static String dirname( final String path )
44      {
45          final int i = path.lastIndexOf( "/" );
46  
47          return ( ( i >= 0 ) ? path.substring( 0, i ) : "" );
48      }
49  
50      /**
51       * Returns the filename portion of a file specification string.
52       *
53       * @return The filename string with extension.
54       */
55      public static String filename( final String path )
56      {
57          final int i = path.lastIndexOf( "/" );
58          return ( ( i >= 0 ) ? path.substring( i + 1 ) : path );
59      }
60  
61      public static String[] dirnames( final String path )
62      {
63          final String dirname = PathUtils.dirname( path );
64          return split( dirname, "/", -1 );
65  
66      }
67  
68      private static String[] split( final String str, final String separator, final int max )
69      {
70          final StringTokenizer tok;
71  
72          if ( separator == null )
73          {
74              // Null separator means we're using StringTokenizer's default
75              // delimiter, which comprises all whitespace characters.
76              tok = new StringTokenizer( str );
77          }
78          else
79          {
80              tok = new StringTokenizer( str, separator );
81          }
82  
83          int listSize = tok.countTokens();
84  
85          if ( max > 0 && listSize > max )
86          {
87              listSize = max;
88          }
89  
90          final String[] list = new String[listSize];
91  
92          int i = 0;
93  
94          int lastTokenBegin;
95          int lastTokenEnd = 0;
96  
97          while ( tok.hasMoreTokens() )
98          {
99              if ( max > 0 && i == listSize - 1 )
100             {
101                 // In the situation where we hit the max yet have
102                 // tokens left over in our input, the last list
103                 // element gets all remaining text.
104                 final String endToken = tok.nextToken();
105 
106                 lastTokenBegin = str.indexOf( endToken, lastTokenEnd );
107 
108                 list[i] = str.substring( lastTokenBegin );
109 
110                 break;
111 
112             }
113             else
114             {
115                 list[i] = tok.nextToken();
116 
117                 lastTokenBegin = str.indexOf( list[i], lastTokenEnd );
118 
119                 lastTokenEnd = lastTokenBegin + list[i].length();
120             }
121 
122             i++;
123         }
124         return list;
125     }
126 
127     /**
128      * Return the host name (Removes protocol and path from the URL) E.g: for input
129      * <code>http://www.codehause.org</code> this method will return <code>www.apache.org</code>
130      *
131      * @param url the url
132      * @return the host name
133      */
134     public static String host( final String url )
135     {
136         if ( url == null || url.length() == 0 )
137         {
138             return "localhost";
139         }
140         String authorization = authorization( url );
141         int index = authorization.indexOf( '@' );
142         String host = ( index >= 0 ) ? authorization.substring( index + 1 ) : authorization;
143         // In case we have IPv6 in the host portion of url
144         // we have to remove brackets '[' and ']'
145         return ( ( host.charAt( 0 ) == '[' ) && ( host.charAt( host.length() - 1 ) == ']' ) )
146                         ? host.substring( 1, host.length() - 1 )
147                         : host;
148     }
149 
150     /**
151      * This was changed from private to package local so that it can be unit tested.
152      */
153     static String authorization( final String url )
154     {
155         if ( url == null )
156         {
157             return "localhost";
158         }
159 
160         String protocol = PathUtils.protocol( url );
161 
162         if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
163         {
164             return "localhost";
165         }
166 
167         String host = url;
168         if ( protocol.equalsIgnoreCase( "scm" ) )
169         {
170             // skip over type
171             host = host.substring( host.indexOf( ":", 4 ) + 1 ).trim();
172         }
173 
174         protocol = PathUtils.protocol( host );
175 
176         if ( protocol.equalsIgnoreCase( "file" ) )
177         {
178             return "localhost";
179         }
180 
181         // skip over protocol
182         host = host.substring( host.indexOf( ":" ) + 1 ).trim();
183         if ( host.startsWith( "//" ) )
184         {
185             host = host.substring( 2 );
186         }
187 
188         int pos = host.indexOf( "/" );
189 
190         if ( pos > 0 )
191         {
192             host = host.substring( 0, pos );
193         }
194 
195         pos = host.indexOf( '@' );
196 
197         pos = ( pos > 0 ) ? endOfHostPosition( host, pos ) : endOfHostPosition( host, 0 );
198 
199         if ( pos > 0 )
200         {
201             host = host.substring( 0, pos );
202         }
203         return host;
204     }
205 
206     private static int endOfHostPosition( String host, int pos )
207     {
208         // if this is IPv6 then it will be in IPv6 Literal Addresses in URL's format
209         // see: http://www.ietf.org/rfc/rfc2732.txt
210         int endOfIPv6Pos = host.indexOf( ']', pos );
211         return ( endOfIPv6Pos > 0 ) ? endOfIPv6Pos + 1 : host.indexOf( ":", pos );
212     }
213 
214     /**
215      * /**
216      * Return the protocol name.
217      * <br/>
218      * E.g: for input
219      * <code>http://www.codehause.org</code> this method will return <code>http</code>
220      *
221      * @param url the url
222      * @return the host name
223      */
224     public static String protocol( final String url )
225     {
226         final int pos = url.indexOf( ":" );
227 
228         if ( pos == -1 )
229         {
230             return "";
231         }
232         return url.substring( 0, pos ).trim();
233     }
234 
235     /**
236      * @param url
237      * @return the port or {@link WagonConstants#UNKNOWN_PORT} if not existent
238      */
239     public static int port( String url )
240     {
241 
242         final String protocol = PathUtils.protocol( url );
243 
244         if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
245         {
246             return WagonConstants.UNKNOWN_PORT;
247         }
248 
249         final String authorization = PathUtils.authorization( url );
250 
251         if ( authorization == null )
252         {
253             return WagonConstants.UNKNOWN_PORT;
254         }
255 
256         if ( protocol.equalsIgnoreCase( "scm" ) )
257         {
258             // skip over type
259             url = url.substring( url.indexOf( ":", 4 ) + 1 ).trim();
260         }
261 
262         if ( url.regionMatches( true, 0, "file:", 0, 5 ) || url.regionMatches( true, 0, "local:", 0, 6 ) )
263         {
264             return WagonConstants.UNKNOWN_PORT;
265         }
266 
267         // skip over protocol
268         url = url.substring( url.indexOf( ":" ) + 1 ).trim();
269         if ( url.startsWith( "//" ) )
270         {
271             url = url.substring( 2 );
272         }
273 
274         int start = authorization.length();
275 
276         if ( url.length() > start && url.charAt( start ) == ':' )
277         {
278             int end = url.indexOf( '/', start );
279 
280             if ( end == start + 1 )
281             {
282                 // it is :/
283                 return WagonConstants.UNKNOWN_PORT;
284             }
285 
286             if ( end == -1 )
287             {
288                 end = url.length();
289             }
290 
291             return Integer.parseInt( url.substring( start + 1, end ) );
292         }
293         else
294         {
295             return WagonConstants.UNKNOWN_PORT;
296         }
297 
298     }
299 
300     /**
301      * Derive the path portion of the given URL.
302      *
303      * @param url the repository URL
304      * @return the basedir of the repository
305      * @todo need to URL decode for spaces?
306      */
307     public static String basedir( String url )
308     {
309         String protocol = PathUtils.protocol( url );
310 
311         String retValue = null;
312 
313         if ( protocol.equalsIgnoreCase( "scm" ) )
314         {
315             // skip over SCM bits
316             if ( url.regionMatches( true, 0, "scm:svn:", 0, 8 ) )
317             {
318                 url = url.substring( url.indexOf( ":", 4 ) + 1 );
319                 protocol = PathUtils.protocol( url );
320             }
321         }
322 
323         if ( protocol.equalsIgnoreCase( "file" ) )
324         {
325             retValue = url.substring( protocol.length() + 1 );
326             retValue = decode( retValue );
327             // special case: if omitted // on protocol, keep path as is
328             if ( retValue.startsWith( "//" ) )
329             {
330                 retValue = retValue.substring( 2 );
331 
332                 if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
333                 {
334                     // special case: if there is a windows drive letter, then keep the original return value
335                     retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
336                 }
337                 else
338                 {
339                     // Now we expect the host
340                     int index = retValue.indexOf( "/" );
341                     if ( index >= 0 )
342                     {
343                         retValue = retValue.substring( index + 1 );
344                     }
345 
346                     // special case: if there is a windows drive letter, then keep the original return value
347                     if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
348                     {
349                         retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
350                     }
351                     else if ( index >= 0 )
352                     {
353                         // leading / was previously stripped
354                         retValue = "/" + retValue;
355                     }
356                 }
357             }
358 
359             // special case: if there is a windows drive letter using |, switch to :
360             if ( retValue.length() >= 2 && retValue.charAt( 1 ) == '|' )
361             {
362                 retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
363             }
364         }
365         else
366         {
367             final String authorization = PathUtils.authorization( url );
368 
369             final int port = PathUtils.port( url );
370 
371             int pos = 0;
372 
373             if ( protocol.equalsIgnoreCase( "scm" ) )
374             {
375                 pos = url.indexOf( ":", 4 ) + 1;
376                 pos = url.indexOf( ":", pos ) + 1;
377             }
378             else
379             {
380                 int index = url.indexOf( "://" );
381                 if ( index != -1 )
382                 {
383                     pos = index + 3;
384                 }
385             }
386 
387             pos += authorization.length();
388 
389             if ( port != WagonConstants.UNKNOWN_PORT )
390             {
391                 pos = pos + Integer.toString( port ).length() + 1;
392             }
393 
394             if ( url.length() > pos )
395             {
396                 retValue = url.substring( pos );
397                 if ( retValue.startsWith( ":" ) )
398                 {
399                     // this is for :/ after the host
400                     retValue = retValue.substring( 1 );
401                 }
402 
403                 // one module may be allowed in the path in CVS
404                 retValue = retValue.replace( ':', '/' );
405             }
406         }
407 
408         if ( retValue == null )
409         {
410             retValue = "/";
411         }
412         return retValue.trim();
413     }
414 
415     /**
416      * Decodes the specified (portion of a) URL. <strong>Note:</strong> This decoder assumes that ISO-8859-1 is used to
417      * convert URL-encoded octets to characters.
418      *
419      * @param url The URL to decode, may be <code>null</code>.
420      * @return The decoded URL or <code>null</code> if the input was <code>null</code>.
421      */
422     private static String decode( String url )
423     {
424         String decoded = url;
425         if ( url != null )
426         {
427             int pos = -1;
428             while ( ( pos = decoded.indexOf( '%', pos + 1 ) ) >= 0 )
429             {
430                 if ( pos + 2 < decoded.length() )
431                 {
432                     String hexStr = decoded.substring( pos + 1, pos + 3 );
433                     char ch = (char) Integer.parseInt( hexStr, 16 );
434                     decoded = decoded.substring( 0, pos ) + ch + decoded.substring( pos + 3 );
435                 }
436             }
437         }
438         return decoded;
439     }
440 
441     public static String user( String url )
442     {
443         String host = authorization( url );
444         int index = host.indexOf( '@' );
445         if ( index > 0 )
446         {
447             String userInfo = host.substring( 0, index );
448             index = userInfo.indexOf( ':' );
449             if ( index > 0 )
450             {
451                 return userInfo.substring( 0, index );
452             }
453             else if ( index < 0 )
454             {
455                 return userInfo;
456             }
457         }
458         return null;
459     }
460 
461     public static String password( String url )
462     {
463         String host = authorization( url );
464         int index = host.indexOf( '@' );
465         if ( index > 0 )
466         {
467             String userInfo = host.substring( 0, index );
468             index = userInfo.indexOf( ':' );
469             if ( index >= 0 )
470             {
471                 return userInfo.substring( index + 1 );
472             }
473         }
474         return null;
475     }
476 
477     // TODO: move to plexus-utils or use something appropriate from there
478     public static String toRelative( File basedir, String absolutePath )
479     {
480         String relative;
481 
482         absolutePath = absolutePath.replace( '\\', '/' );
483         String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
484 
485         if ( absolutePath.startsWith( basedirPath ) )
486         {
487             relative = absolutePath.substring( basedirPath.length() );
488             if ( relative.startsWith( "/" ) )
489             {
490                 relative = relative.substring( 1 );
491             }
492             if ( relative.length() <= 0 )
493             {
494                 relative = ".";
495             }
496         }
497         else
498         {
499             relative = absolutePath;
500         }
501 
502         return relative;
503     }
504 }