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}