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         final 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         // skip over protocol
175         host = host.substring( host.indexOf( ":" ) + 1 ).trim();
176         if ( host.startsWith( "//" ) )
177         {
178             host = host.substring( 2 );
179         }
180 
181         int pos = host.indexOf( "/" );
182 
183         if ( pos > 0 )
184         {
185             host = host.substring( 0, pos );
186         }
187 
188         pos = host.indexOf( '@' );
189 
190         pos = ( pos > 0 ) ? endOfHostPosition( host, pos ) : endOfHostPosition( host, 0 );
191 
192         if ( pos > 0 )
193         {
194             host = host.substring( 0, pos );
195         }
196         return host;
197     }
198 
199     private static int endOfHostPosition( String host, int pos )
200     {
201         // if this is IPv6 then it will be in IPv6 Literal Addresses in URL's format
202         // see: http://www.ietf.org/rfc/rfc2732.txt
203         int endOfIPv6Pos = host.indexOf( ']', pos );
204         return ( endOfIPv6Pos > 0 ) ? endOfIPv6Pos + 1 : host.indexOf( ":", pos );
205     }
206 
207     /**
208      * /**
209      * Return the protocol name.
210      * <br/>
211      * E.g: for input
212      * <code>http://www.codehause.org</code> this method will return <code>http</code>
213      *
214      * @param url the url
215      * @return the host name
216      */
217     public static String protocol( final String url )
218     {
219         final int pos = url.indexOf( ":" );
220 
221         if ( pos == -1 )
222         {
223             return "";
224         }
225         return url.substring( 0, pos ).trim();
226     }
227 
228     /**
229      * @param url
230      * @return the port or {@link WagonConstants#UNKNOWN_PORT} if not existent
231      */
232     public static int port( String url )
233     {
234 
235         final String protocol = PathUtils.protocol( url );
236 
237         if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
238         {
239             return WagonConstants.UNKNOWN_PORT;
240         }
241 
242         final String authorization = PathUtils.authorization( url );
243 
244         if ( authorization == null )
245         {
246             return WagonConstants.UNKNOWN_PORT;
247         }
248 
249         if ( protocol.equalsIgnoreCase( "scm" ) )
250         {
251             // skip over type
252             url = url.substring( url.indexOf( ":", 4 ) + 1 ).trim();
253         }
254 
255         if ( url.regionMatches( true, 0, "file:", 0, 5 ) || url.regionMatches( true, 0, "local:", 0, 6 ) )
256         {
257             return WagonConstants.UNKNOWN_PORT;
258         }
259 
260         // skip over protocol
261         url = url.substring( url.indexOf( ":" ) + 1 ).trim();
262         if ( url.startsWith( "//" ) )
263         {
264             url = url.substring( 2 );
265         }
266 
267         int start = authorization.length();
268 
269         if ( url.length() > start && url.charAt( start ) == ':' )
270         {
271             int end = url.indexOf( '/', start );
272 
273             if ( end == start + 1 )
274             {
275                 // it is :/
276                 return WagonConstants.UNKNOWN_PORT;
277             }
278 
279             if ( end == -1 )
280             {
281                 end = url.length();
282             }
283 
284             return Integer.parseInt( url.substring( start + 1, end ) );
285         }
286         else
287         {
288             return WagonConstants.UNKNOWN_PORT;
289         }
290 
291     }
292 
293     /**
294      * Derive the path portion of the given URL.
295      * 
296      * @param url the repository URL
297      * @return the basedir of the repository
298      * @todo need to URL decode for spaces?
299      */
300     public static String basedir( String url )
301     {
302         String protocol = PathUtils.protocol( url );
303 
304         String retValue = null;
305 
306         if ( protocol.equalsIgnoreCase( "scm" ) )
307         {
308             // skip over SCM bits
309             if ( url.regionMatches( true, 0, "scm:svn:", 0, 8 ) )
310             {
311                 url = url.substring( url.indexOf( ":", 4 ) + 1 );
312                 protocol = PathUtils.protocol( url );
313             }
314         }
315 
316         if ( protocol.equalsIgnoreCase( "file" ) )
317         {
318             retValue = url.substring( protocol.length() + 1 );
319             retValue = decode( retValue );
320             // special case: if omitted // on protocol, keep path as is
321             if ( retValue.startsWith( "//" ) )
322             {
323                 retValue = retValue.substring( 2 );
324 
325                 if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
326                 {
327                     // special case: if there is a windows drive letter, then keep the original return value
328                     retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
329                 }
330                 else
331                 {
332                     // Now we expect the host
333                     int index = retValue.indexOf( "/" );
334                     if ( index >= 0 )
335                     {
336                         retValue = retValue.substring( index + 1 );
337                     }
338 
339                     // special case: if there is a windows drive letter, then keep the original return value
340                     if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
341                     {
342                         retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
343                     }
344                     else if ( index >= 0 )
345                     {
346                         // leading / was previously stripped
347                         retValue = "/" + retValue;
348                     }
349                 }
350             }
351 
352             // special case: if there is a windows drive letter using |, switch to :
353             if ( retValue.length() >= 2 && retValue.charAt( 1 ) == '|' )
354             {
355                 retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
356             }
357         }
358         else
359         {
360             final String authorization = PathUtils.authorization( url );
361 
362             final int port = PathUtils.port( url );
363 
364             int pos = 0;
365 
366             if ( protocol.equalsIgnoreCase( "scm" ) )
367             {
368                 pos = url.indexOf( ":", 4 ) + 1;
369                 pos = url.indexOf( ":", pos ) + 1;
370             }
371             else
372             {
373                 int index = url.indexOf( "://" );
374                 if ( index != -1 )
375                 {
376                     pos = index + 3;
377                 }
378             }
379 
380             pos += authorization.length();
381 
382             if ( port != WagonConstants.UNKNOWN_PORT )
383             {
384                 pos = pos + Integer.toString( port ).length() + 1;
385             }
386 
387             if ( url.length() > pos )
388             {
389                 retValue = url.substring( pos );
390                 if ( retValue.startsWith( ":" ) )
391                 {
392                     // this is for :/ after the host
393                     retValue = retValue.substring( 1 );
394                 }
395 
396                 // one module may be allowed in the path in CVS
397                 retValue = retValue.replace( ':', '/' );
398             }
399         }
400 
401         if ( retValue == null )
402         {
403             retValue = "/";
404         }
405         return retValue.trim();
406     }
407 
408     /**
409      * Decodes the specified (portion of a) URL. <strong>Note:</strong> This decoder assumes that ISO-8859-1 is used to
410      * convert URL-encoded octets to characters.
411      * 
412      * @param url The URL to decode, may be <code>null</code>.
413      * @return The decoded URL or <code>null</code> if the input was <code>null</code>.
414      */
415     private static String decode( String url )
416     {
417         String decoded = url;
418         if ( url != null )
419         {
420             int pos = -1;
421             while ( ( pos = decoded.indexOf( '%', pos + 1 ) ) >= 0 )
422             {
423                 if ( pos + 2 < decoded.length() )
424                 {
425                     String hexStr = decoded.substring( pos + 1, pos + 3 );
426                     char ch = (char) Integer.parseInt( hexStr, 16 );
427                     decoded = decoded.substring( 0, pos ) + ch + decoded.substring( pos + 3 );
428                 }
429             }
430         }
431         return decoded;
432     }
433 
434     public static String user( String url )
435     {
436         String host = authorization( url );
437         int index = host.indexOf( '@' );
438         if ( index > 0 )
439         {
440             String userInfo = host.substring( 0, index );
441             index = userInfo.indexOf( ':' );
442             if ( index > 0 )
443             {
444                 return userInfo.substring( 0, index );
445             }
446             else if ( index < 0 )
447             {
448                 return userInfo;
449             }
450         }
451         return null;
452     }
453 
454     public static String password( String url )
455     {
456         String host = authorization( url );
457         int index = host.indexOf( '@' );
458         if ( index > 0 )
459         {
460             String userInfo = host.substring( 0, index );
461             index = userInfo.indexOf( ':' );
462             if ( index >= 0 )
463             {
464                 return userInfo.substring( index + 1 );
465             }
466         }
467         return null;
468     }
469 
470     // TODO: move to plexus-utils or use something appropriate from there
471     public static String toRelative( File basedir, String absolutePath )
472     {
473         String relative;
474 
475         absolutePath = absolutePath.replace( '\\', '/' );
476         String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
477 
478         if ( absolutePath.startsWith( basedirPath ) )
479         {
480             relative = absolutePath.substring( basedirPath.length() );
481             if ( relative.startsWith( "/" ) )
482             {
483                 relative = relative.substring( 1 );
484             }
485             if ( relative.length() <= 0 )
486             {
487                 relative = ".";
488             }
489         }
490         else
491         {
492             relative = absolutePath;
493         }
494 
495         return relative;
496     }
497 }