001package org.apache.maven.wagon;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.util.StringTokenizer;
024
025/**
026 * Various path (URL) manipulation routines
027 *
028 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a>
029 *
030 */
031public final class PathUtils
032{
033    private PathUtils()
034    {
035    }
036    
037    /**
038     * Returns the directory path portion of a file specification string.
039     * Matches the equally named unix command.
040     *
041     * @return The directory portion excluding the ending file separator.
042     */
043    public static String dirname( final String path )
044    {
045        final int i = path.lastIndexOf( "/" );
046
047        return ( ( i >= 0 ) ? path.substring( 0, i ) : "" );
048    }
049
050    /**
051     * Returns the filename portion of a file specification string.
052     *
053     * @return The filename string with extension.
054     */
055    public static String filename( final String path )
056    {
057        final int i = path.lastIndexOf( "/" );
058        return ( ( i >= 0 ) ? path.substring( i + 1 ) : path );
059    }
060
061    public static String[] dirnames( final String path )
062    {
063        final String dirname = PathUtils.dirname( path );
064        return split( dirname, "/", -1 );
065
066    }
067
068    private static String[] split( final String str, final String separator, final int max )
069    {
070        final StringTokenizer tok;
071
072        if ( separator == null )
073        {
074            // Null separator means we're using StringTokenizer's default
075            // delimiter, which comprises all whitespace characters.
076            tok = new StringTokenizer( str );
077        }
078        else
079        {
080            tok = new StringTokenizer( str, separator );
081        }
082
083        int listSize = tok.countTokens();
084
085        if ( max > 0 && listSize > max )
086        {
087            listSize = max;
088        }
089
090        final String[] list = new String[listSize];
091
092        int i = 0;
093
094        int lastTokenBegin;
095        int lastTokenEnd = 0;
096
097        while ( tok.hasMoreTokens() )
098        {
099            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        String authorization = authorization( url );
137        int index = authorization.indexOf( '@' );
138        if ( index >= 0 )
139        {
140            return authorization.substring( index + 1 );
141        }
142        else
143        {
144            return authorization;
145        }
146    }
147
148    /**
149     * This was changed from private to package local so that it can be unit tested.
150     */
151    static String authorization( final String url )
152    {
153        if ( url == null )
154        {
155            return "localhost";
156        }
157
158        final String protocol = PathUtils.protocol( url );
159
160        if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
161        {
162            return "localhost";
163        }
164
165        String host = url;
166        if ( protocol.equalsIgnoreCase( "scm" ) )
167        {
168            // skip over type
169            host = host.substring( host.indexOf( ":", 4 ) + 1 ).trim();
170        }
171
172        // skip over protocol
173        host = host.substring( host.indexOf( ":" ) + 1 ).trim();
174        if ( host.startsWith( "//" ) )
175        {
176            host = host.substring( 2 );
177        }
178
179        int pos = host.indexOf( "/" );
180
181        if ( pos > 0 )
182        {
183            host = host.substring( 0, pos );
184        }
185
186        pos = host.indexOf( '@' );
187
188        if ( pos > 0 )
189        {
190            pos = host.indexOf( ':', pos );
191        }
192        else
193        {
194            pos = host.indexOf( ":" );
195        }
196
197        if ( pos > 0 )
198        {
199            host = host.substring( 0, pos );
200        }
201        return host;
202    }
203
204    /**
205     * /**
206     * Return the protocol name.
207     * <br/>
208     * E.g: for input
209     * <code>http://www.codehause.org</code> this method will return <code>http</code>
210     *
211     * @param url the url
212     * @return the host name
213     */
214    public static String protocol( final String url )
215    {
216        final int pos = url.indexOf( ":" );
217
218        if ( pos == -1 )
219        {
220            return "";
221        }
222        return url.substring( 0, pos ).trim();
223    }
224
225    /**
226     * @param url
227     * @return the port or {@link WagonConstants#UNKNOWN_PORT} if not existent
228     */
229    public static int port( String url )
230    {
231
232        final String protocol = PathUtils.protocol( url );
233
234        if ( protocol == null || protocol.equalsIgnoreCase( "file" ) )
235        {
236            return WagonConstants.UNKNOWN_PORT;
237        }
238
239        final String authorization = PathUtils.authorization( url );
240
241        if ( authorization == null )
242        {
243            return WagonConstants.UNKNOWN_PORT;
244        }
245
246        if ( protocol.equalsIgnoreCase( "scm" ) )
247        {
248            // skip over type
249            url = url.substring( url.indexOf( ":", 4 ) + 1 ).trim();
250        }
251
252        if ( url.regionMatches( true, 0, "file:", 0, 5 ) || url.regionMatches( true, 0, "local:", 0, 6 ) )
253        {
254            return WagonConstants.UNKNOWN_PORT;
255        }
256
257        // skip over protocol
258        url = url.substring( url.indexOf( ":" ) + 1 ).trim();
259        if ( url.startsWith( "//" ) )
260        {
261            url = url.substring( 2 );
262        }
263
264        int start = authorization.length();
265
266        if ( url.length() > start && url.charAt( start ) == ':' )
267        {
268            int end = url.indexOf( '/', start );
269
270            if ( end == start + 1 )
271            {
272                // it is :/
273                return WagonConstants.UNKNOWN_PORT;
274            }
275
276            if ( end == -1 )
277            {
278                end = url.length();
279            }
280
281            return Integer.parseInt( url.substring( start + 1, end ) );
282        }
283        else
284        {
285            return WagonConstants.UNKNOWN_PORT;
286        }
287
288    }
289
290    /**
291     * Derive the path portion of the given URL.
292     * 
293     * @param url the repository URL
294     * @return the basedir of the repository
295     * @todo need to URL decode for spaces?
296     */
297    public static String basedir( String url )
298    {
299        String protocol = PathUtils.protocol( url );
300
301        String retValue = null;
302
303        if ( protocol.equalsIgnoreCase( "scm" ) )
304        {
305            // skip over SCM bits
306            if ( url.regionMatches( true, 0, "scm:svn:", 0, 8 ) )
307            {
308                url = url.substring( url.indexOf( ":", 4 ) + 1 );
309                protocol = PathUtils.protocol( url );
310            }
311        }
312
313        if ( protocol.equalsIgnoreCase( "file" ) )
314        {
315            retValue = url.substring( protocol.length() + 1 );
316            retValue = decode( retValue );
317            // special case: if omitted // on protocol, keep path as is
318            if ( retValue.startsWith( "//" ) )
319            {
320                retValue = retValue.substring( 2 );
321
322                if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
323                {
324                    // special case: if there is a windows drive letter, then keep the original return value
325                    retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
326                }
327                else
328                {
329                    // Now we expect the host
330                    int index = retValue.indexOf( "/" );
331                    if ( index >= 0 )
332                    {
333                        retValue = retValue.substring( index + 1 );
334                    }
335
336                    // special case: if there is a windows drive letter, then keep the original return value
337                    if ( retValue.length() >= 2 && ( retValue.charAt( 1 ) == '|' || retValue.charAt( 1 ) == ':' ) )
338                    {
339                        retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
340                    }
341                    else if ( index >= 0 )
342                    {
343                        // leading / was previously stripped
344                        retValue = "/" + retValue;
345                    }
346                }
347            }
348
349            // special case: if there is a windows drive letter using |, switch to :
350            if ( retValue.length() >= 2 && retValue.charAt( 1 ) == '|' )
351            {
352                retValue = retValue.charAt( 0 ) + ":" + retValue.substring( 2 );
353            }
354        }
355        else
356        {
357            final String authorization = PathUtils.authorization( url );
358
359            final int port = PathUtils.port( url );
360
361            int pos = 0;
362
363            if ( protocol.equalsIgnoreCase( "scm" ) )
364            {
365                pos = url.indexOf( ":", 4 ) + 1;
366                pos = url.indexOf( ":", pos ) + 1;
367            }
368            else
369            {
370                int index = url.indexOf( "://" );
371                if ( index != -1 )
372                {
373                    pos = index + 3;
374                }
375            }
376
377            pos += authorization.length();
378
379            if ( port != WagonConstants.UNKNOWN_PORT )
380            {
381                pos = pos + Integer.toString( port ).length() + 1;
382            }
383
384            if ( url.length() > pos )
385            {
386                retValue = url.substring( pos );
387                if ( retValue.startsWith( ":" ) )
388                {
389                    // this is for :/ after the host
390                    retValue = retValue.substring( 1 );
391                }
392
393                // one module may be allowed in the path in CVS
394                retValue = retValue.replace( ':', '/' );
395            }
396        }
397
398        if ( retValue == null )
399        {
400            retValue = "/";
401        }
402        return retValue.trim();
403    }
404
405    /**
406     * Decodes the specified (portion of a) URL. <strong>Note:</strong> This decoder assumes that ISO-8859-1 is used to
407     * convert URL-encoded octets to characters.
408     * 
409     * @param url The URL to decode, may be <code>null</code>.
410     * @return The decoded URL or <code>null</code> if the input was <code>null</code>.
411     */
412    private static String decode( String url )
413    {
414        String decoded = url;
415        if ( url != null )
416        {
417            int pos = -1;
418            while ( ( pos = decoded.indexOf( '%', pos + 1 ) ) >= 0 )
419            {
420                if ( pos + 2 < decoded.length() )
421                {
422                    String hexStr = decoded.substring( pos + 1, pos + 3 );
423                    char ch = (char) Integer.parseInt( hexStr, 16 );
424                    decoded = decoded.substring( 0, pos ) + ch + decoded.substring( pos + 3 );
425                }
426            }
427        }
428        return decoded;
429    }
430
431    public static String user( String url )
432    {
433        String host = authorization( url );
434        int index = host.indexOf( '@' );
435        if ( index > 0 )
436        {
437            String userInfo = host.substring( 0, index );
438            index = userInfo.indexOf( ':' );
439            if ( index > 0 )
440            {
441                return userInfo.substring( 0, index );
442            }
443            else if ( index < 0 )
444            {
445                return userInfo;
446            }
447        }
448        return null;
449    }
450
451    public static String password( String url )
452    {
453        String host = authorization( url );
454        int index = host.indexOf( '@' );
455        if ( index > 0 )
456        {
457            String userInfo = host.substring( 0, index );
458            index = userInfo.indexOf( ':' );
459            if ( index >= 0 )
460            {
461                return userInfo.substring( index + 1 );
462            }
463        }
464        return null;
465    }
466
467    // TODO: move to plexus-utils or use something appropriate from there
468    public static String toRelative( File basedir, String absolutePath )
469    {
470        String relative;
471
472        absolutePath = absolutePath.replace( '\\', '/' );
473        String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
474
475        if ( absolutePath.startsWith( basedirPath ) )
476        {
477            relative = absolutePath.substring( basedirPath.length() );
478            if ( relative.startsWith( "/" ) )
479            {
480                relative = relative.substring( 1 );
481            }
482            if ( relative.length() <= 0 )
483            {
484                relative = ".";
485            }
486        }
487        else
488        {
489            relative = absolutePath;
490        }
491
492        return relative;
493    }
494}