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}