001package org.apache.maven.scm.provider.cvslib.cvsjava.util; 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 org.apache.maven.scm.log.ScmLogger; 023import org.codehaus.plexus.util.StringUtils; 024import org.codehaus.plexus.util.cli.CommandLineUtils; 025import org.netbeans.lib.cvsclient.CVSRoot; 026import org.netbeans.lib.cvsclient.Client; 027import org.netbeans.lib.cvsclient.admin.StandardAdminHandler; 028import org.netbeans.lib.cvsclient.command.Command; 029import org.netbeans.lib.cvsclient.command.CommandAbortedException; 030import org.netbeans.lib.cvsclient.command.CommandException; 031import org.netbeans.lib.cvsclient.command.GlobalOptions; 032import org.netbeans.lib.cvsclient.commandLine.CommandFactory; 033import org.netbeans.lib.cvsclient.commandLine.GetOpt; 034import org.netbeans.lib.cvsclient.connection.AbstractConnection; 035import org.netbeans.lib.cvsclient.connection.AuthenticationException; 036import org.netbeans.lib.cvsclient.connection.Connection; 037import org.netbeans.lib.cvsclient.connection.ConnectionFactory; 038import org.netbeans.lib.cvsclient.connection.PServerConnection; 039import org.netbeans.lib.cvsclient.connection.StandardScrambler; 040import org.netbeans.lib.cvsclient.event.CVSListener; 041 042import java.io.BufferedReader; 043import java.io.File; 044import java.io.FileReader; 045import java.io.IOException; 046 047/** 048 * A Cvs connection that simulates a command line interface. 049 * 050 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 051 * 052 */ 053public class CvsConnection 054{ 055 056 /** 057 * The path to the repository on the server 058 */ 059 @SuppressWarnings( "unused" ) 060 private String repository; 061 062 /** 063 * The local path to use to perform operations (the top level) 064 */ 065 private String localPath; 066 067 /** 068 * The connection to the server 069 */ 070 private Connection connection; 071 072 /** 073 * The client that manages interactions with the server 074 */ 075 private Client client; 076 077 /** 078 * The global options being used. GlobalOptions are only global for a 079 * particular command. 080 */ 081 private GlobalOptions globalOptions; 082 083 private CvsConnection() 084 { 085 } 086 087 /** 088 * Execute a configured CVS command 089 * 090 * @param command the command to execute 091 * @throws CommandException if there is an error running the command 092 */ 093 public boolean executeCommand( Command command ) 094 throws CommandException, AuthenticationException 095 { 096 return client.executeCommand( command, globalOptions ); 097 } 098 099 public void setRepository( String repository ) 100 { 101 this.repository = repository; 102 } 103 104 public void setLocalPath( String localPath ) 105 { 106 this.localPath = localPath; 107 } 108 109 public void setGlobalOptions( GlobalOptions globalOptions ) 110 { 111 this.globalOptions = globalOptions; 112 } 113 114 /** 115 * Creates the connection and the client and connects. 116 */ 117 private void connect( CVSRoot root, String password ) 118 throws AuthenticationException, CommandAbortedException 119 { 120 if ( CVSRoot.METHOD_EXT.equals( root.getMethod() ) ) 121 { 122 String cvsRsh = System.getProperty( "maven.scm.cvs.java.cvs_rsh" ); 123 if ( cvsRsh == null ) 124 { 125 try 126 { 127 cvsRsh = CommandLineUtils.getSystemEnvVars().getProperty( "CVS_RSH" ); 128 } 129 catch ( IOException e ) 130 { 131 // we assume searching env var can't fail 132 } 133 } 134 135 if ( cvsRsh != null ) 136 { 137 if ( cvsRsh.indexOf( ' ' ) < 0 ) 138 { 139 //cvs_rsh should be 'rsh' or 'ssh' 140 //Complete the command to use 141 String username = root.getUserName(); 142 if ( username == null ) 143 { 144 username = System.getProperty( "user.name" ); 145 } 146 147 cvsRsh += " " + username + "@" + root.getHostName() + " cvs server"; 148 } 149 150 AbstractConnection conn = new org.netbeans.lib.cvsclient.connection.ExtConnection( cvsRsh ); 151 conn.setRepository( root.getRepository() ); 152 connection = conn; 153 } 154 else 155 { 156 connection = new ExtConnection( root ); 157 } 158 } 159 else 160 { 161 connection = ConnectionFactory.getConnection( root ); 162 if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) ) 163 { 164 ( (PServerConnection) connection ).setEncodedPassword( password ); 165 } 166 } 167 connection.open(); 168 169 client = new Client( connection, new StandardAdminHandler() ); 170 client.setLocalPath( localPath ); 171 } 172 173 private void disconnect() 174 { 175 if ( connection != null && connection.isOpen() ) 176 { 177 try 178 { 179 connection.close(); 180 } 181 catch ( IOException e ) 182 { 183 //ignore 184 } 185 } 186 } 187 188 private void addListener( CVSListener listener ) 189 { 190 if ( client != null ) 191 { 192 // add a listener to the client 193 client.getEventManager().addCVSListener( listener ); 194 } 195 } 196 197 /** 198 * Obtain the CVS root, either from the -D option cvs.root or from the CVS 199 * directory 200 * 201 * @return the CVSRoot string 202 */ 203 private static String getCVSRoot( String workingDir ) 204 { 205 String root = null; 206 BufferedReader r = null; 207 if ( workingDir == null ) 208 { 209 workingDir = System.getProperty( "user.dir" ); 210 } 211 try 212 { 213 File f = new File( workingDir ); 214 File rootFile = new File( f, "CVS/Root" ); 215 if ( rootFile.exists() ) 216 { 217 r = new BufferedReader( new FileReader( rootFile ) ); 218 root = r.readLine(); 219 } 220 } 221 catch ( IOException e ) 222 { 223 // ignore 224 } 225 finally 226 { 227 try 228 { 229 if ( r != null ) 230 { 231 r.close(); 232 } 233 } 234 catch ( IOException e ) 235 { 236 System.err.println( "Warning: could not close CVS/Root file!" ); 237 } 238 } 239 if ( root == null ) 240 { 241 root = System.getProperty( "cvs.root" ); 242 } 243 return root; 244 } 245 246 /** 247 * Process global options passed into the application 248 * 249 * @param args the argument list, complete 250 * @param globalOptions the global options structure that will be passed to 251 * the command 252 */ 253 private static int processGlobalOptions( String[] args, GlobalOptions globalOptions ) 254 { 255 final String getOptString = globalOptions.getOptString(); 256 GetOpt go = new GetOpt( args, getOptString ); 257 int ch; 258 while ( ( ch = go.getopt() ) != GetOpt.optEOF ) 259 { 260 //System.out.println("Global option '"+((char) ch)+"', 261 // '"+go.optArgGet()+"'"); 262 String arg = go.optArgGet(); 263 boolean success = globalOptions.setCVSCommand( (char) ch, arg ); 264 if ( !success ) 265 { 266 throw new IllegalArgumentException( "Failed to set CVS Command: -" + ch + " = " + arg ); 267 } 268 } 269 270 return go.optIndexGet(); 271 } 272 273 /** 274 * Lookup the password in the .cvspass file. This file is looked for in the 275 * user.home directory if the option cvs.passfile is not set 276 * 277 * @param cvsRoot the CVS root for which the password is being searched 278 * @return the password, scrambled 279 */ 280 private static String lookupPassword( String cvsRoot, ScmLogger logger ) 281 { 282 File passFile = new File( System.getProperty( "cygwin.user.home", System.getProperty( "user.home" ) ) + File 283 .separatorChar + ".cvspass" ); 284 285 BufferedReader reader = null; 286 String password = null; 287 288 try 289 { 290 reader = new BufferedReader( new FileReader( passFile ) ); 291 password = processCvspass( cvsRoot, reader ); 292 } 293 catch ( IOException e ) 294 { 295 if ( logger.isWarnEnabled() ) 296 { 297 logger.warn( "Could not read password for '" + cvsRoot + "' from '" + passFile + "'", e ); 298 } 299 return null; 300 } 301 finally 302 { 303 if ( reader != null ) 304 { 305 try 306 { 307 reader.close(); 308 } 309 catch ( IOException e ) 310 { 311 if ( logger.isErrorEnabled() ) 312 { 313 logger.error( "Warning: could not close password file." ); 314 } 315 } 316 } 317 } 318 if ( password == null ) 319 { 320 if ( logger.isErrorEnabled() ) 321 { 322 logger.error( "Didn't find password for CVSROOT '" + cvsRoot + "'." ); 323 } 324 } 325 return password; 326 } 327 328 /** 329 * Read in a list of return delimited lines from .cvspass and retreive 330 * the password. Return null if the cvsRoot can't be found. 331 * 332 * @param cvsRoot the CVS root for which the password is being searched 333 * @param reader A buffered reader of lines of cvspass information 334 * @return The password, or null if it can't be found. 335 * @throws IOException 336 */ 337 static String processCvspass( String cvsRoot, BufferedReader reader ) 338 throws IOException 339 { 340 String line; 341 String password = null; 342 while ( ( line = reader.readLine() ) != null ) 343 { 344 if ( line.startsWith( "/" ) ) 345 { 346 String[] cvspass = StringUtils.split( line, " " ); 347 String cvspassRoot = cvspass[1]; 348 if ( compareCvsRoot( cvsRoot, cvspassRoot ) ) 349 { 350 int index = line.indexOf( cvspassRoot ) + cvspassRoot.length() + 1; 351 password = line.substring( index ); 352 break; 353 } 354 } 355 else if ( line.startsWith( cvsRoot ) ) 356 { 357 password = line.substring( cvsRoot.length() + 1 ); 358 break; 359 } 360 } 361 return password; 362 } 363 364 static boolean compareCvsRoot( String cvsRoot, String target ) 365 { 366 String s1 = completeCvsRootPort( cvsRoot ); 367 String s2 = completeCvsRootPort( target ); 368 return s1 != null && s1.equals( s2 ); 369 370 } 371 372 private static String completeCvsRootPort( String cvsRoot ) 373 { 374 String result = cvsRoot; 375 int idx = cvsRoot.indexOf( ':' ); 376 for ( int i = 0; i < 2 && idx != -1; i++ ) 377 { 378 idx = cvsRoot.indexOf( ':', idx + 1 ); 379 } 380 if ( idx != -1 && cvsRoot.charAt( idx + 1 ) == '/' ) 381 { 382 StringBuilder sb = new StringBuilder(); 383 sb.append( cvsRoot.substring( 0, idx + 1 ) ); 384 sb.append( "2401" ); 385 sb.append( cvsRoot.substring( idx + 1 ) ); 386 result = sb.toString(); 387 } 388 return result; 389 390 } 391 392 /** 393 * Process the CVS command passed in args[] array with all necessary 394 * options. The only difference from main() method is, that this method 395 * does not exit the JVM and provides command output. 396 * 397 * @param args The command with options 398 */ 399 public static boolean processCommand( String[] args, String localPath, CVSListener listener, ScmLogger logger ) 400 throws Exception 401 { 402 // Set up the CVSRoot. Note that it might still be null after this 403 // call if the user has decided to set it with the -d command line 404 // global option 405 GlobalOptions globalOptions = new GlobalOptions(); 406 globalOptions.setCVSRoot( getCVSRoot( localPath ) ); 407 408 // Set up any global options specified. These occur before the 409 // name of the command to run 410 int commandIndex; 411 412 try 413 { 414 commandIndex = processGlobalOptions( args, globalOptions ); 415 } 416 catch ( IllegalArgumentException e ) 417 { 418 if ( logger.isErrorEnabled() ) 419 { 420 logger.error( "Invalid argument: " + e ); 421 } 422 return false; 423 } 424 425 // if we don't have a CVS root by now, the user has messed up 426 if ( globalOptions.getCVSRoot() == null ) 427 { 428 if ( logger.isErrorEnabled() ) 429 { 430 logger.error( "No CVS root is set. Check your <repository> information in the POM." ); 431 } 432 return false; 433 } 434 435 // parse the CVS root into its constituent parts 436 CVSRoot root; 437 final String cvsRoot = globalOptions.getCVSRoot(); 438 try 439 { 440 root = CVSRoot.parse( cvsRoot ); 441 } 442 catch ( IllegalArgumentException e ) 443 { 444 if ( logger.isErrorEnabled() ) 445 { 446 logger.error( "Incorrect format for CVSRoot: " + cvsRoot + "\nThe correct format is: " 447 + "[:method:][[user][:password]@][hostname:[port]]/path/to/repository" 448 + "\nwhere \"method\" is pserver." ); 449 } 450 return false; 451 } 452 453 final String command = args[commandIndex]; 454 455 // this is not login, but a 'real' cvs command, so construct it, 456 // set the options, and then connect to the server and execute it 457 458 Command c; 459 try 460 { 461 c = CommandFactory.getDefault().createCommand( command, args, ++commandIndex, globalOptions, localPath ); 462 } 463 catch ( IllegalArgumentException e ) 464 { 465 if ( logger.isErrorEnabled() ) 466 { 467 logger.error( "Illegal argument: " + e.getMessage() ); 468 } 469 return false; 470 } 471 472 String password = null; 473 474 if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) ) 475 { 476 password = root.getPassword(); 477 if ( password != null ) 478 { 479 password = StandardScrambler.getInstance().scramble( password ); 480 } 481 else 482 { 483 password = lookupPassword( cvsRoot, logger ); 484 if ( password == null ) 485 { 486 password = StandardScrambler.getInstance().scramble( "" ); 487 // an empty password 488 } 489 } 490 } 491 CvsConnection cvsCommand = new CvsConnection(); 492 cvsCommand.setGlobalOptions( globalOptions ); 493 cvsCommand.setRepository( root.getRepository() ); 494 // the local path is just the path where we executed the 495 // command. This is the case for command-line CVS but not 496 // usually for GUI front-ends 497 cvsCommand.setLocalPath( localPath ); 498 499 cvsCommand.connect( root, password ); 500 cvsCommand.addListener( listener ); 501 if ( logger.isDebugEnabled() ) 502 { 503 logger.debug( "Executing CVS command: " + c.getCVSCommand() ); 504 } 505 boolean result = cvsCommand.executeCommand( c ); 506 cvsCommand.disconnect(); 507 return result; 508 } 509}