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