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        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}