View Javadoc
1   package org.apache.maven.scm.provider.cvslib.cvsjava.util;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   * http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.maven.scm.log.ScmLogger;
23  import org.codehaus.plexus.util.StringUtils;
24  import org.codehaus.plexus.util.cli.CommandLineUtils;
25  import org.netbeans.lib.cvsclient.CVSRoot;
26  import org.netbeans.lib.cvsclient.Client;
27  import org.netbeans.lib.cvsclient.admin.StandardAdminHandler;
28  import org.netbeans.lib.cvsclient.command.Command;
29  import org.netbeans.lib.cvsclient.command.CommandAbortedException;
30  import org.netbeans.lib.cvsclient.command.CommandException;
31  import org.netbeans.lib.cvsclient.command.GlobalOptions;
32  import org.netbeans.lib.cvsclient.commandLine.CommandFactory;
33  import org.netbeans.lib.cvsclient.commandLine.GetOpt;
34  import org.netbeans.lib.cvsclient.connection.AbstractConnection;
35  import org.netbeans.lib.cvsclient.connection.AuthenticationException;
36  import org.netbeans.lib.cvsclient.connection.Connection;
37  import org.netbeans.lib.cvsclient.connection.ConnectionFactory;
38  import org.netbeans.lib.cvsclient.connection.PServerConnection;
39  import org.netbeans.lib.cvsclient.connection.StandardScrambler;
40  import org.netbeans.lib.cvsclient.event.CVSListener;
41  
42  import java.io.BufferedReader;
43  import java.io.File;
44  import java.io.FileReader;
45  import java.io.IOException;
46  
47  /**
48   * A Cvs connection that simulates a command line interface.
49   *
50   * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
51   *
52   */
53  public class CvsConnection
54  {
55  
56      /**
57       * The path to the repository on the server
58       */
59      @SuppressWarnings( "unused" )
60      private String repository;
61  
62      /**
63       * The local path to use to perform operations (the top level)
64       */
65      private String localPath;
66  
67      /**
68       * The connection to the server
69       */
70      private Connection connection;
71  
72      /**
73       * The client that manages interactions with the server
74       */
75      private Client client;
76  
77      /**
78       * The global options being used. GlobalOptions are only global for a
79       * particular command.
80       */
81      private GlobalOptions globalOptions;
82  
83      private CvsConnection()
84      {
85      }
86  
87      /**
88       * Execute a configured CVS command
89       *
90       * @param command the command to execute
91       * @throws CommandException if there is an error running the command
92       */
93      public boolean executeCommand( Command command )
94          throws CommandException, AuthenticationException
95      {
96          return client.executeCommand( command, globalOptions );
97      }
98  
99      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         GlobalOptions globalOptions = new GlobalOptions();
403 
404         // Set up any global options specified. These occur before the
405         // name of the command to run
406         int commandIndex;
407 
408         try
409         {
410             commandIndex = processGlobalOptions( args, globalOptions );
411         }
412         catch ( IllegalArgumentException e )
413         {
414             if ( logger.isErrorEnabled() )
415             {
416                 logger.error( "Invalid argument: " + e );
417             }
418             return false;
419         }
420 
421         // CVSRoot might still be null if the user has NOT decided to set
422         // it with the -d command line global option
423         if ( globalOptions.getCVSRoot() == null )
424         {
425             String cvsRoot = getCVSRoot( localPath );
426             if ( cvsRoot == null )
427             {
428                 // if we don't have a CVS root by now, the user has messed up
429                 if ( logger.isErrorEnabled() )
430                 {
431                     logger.error( "No CVS root is set. Check your <repository> information in the POM." );
432                 }
433                 return false;
434             }
435             globalOptions.setCVSRoot( cvsRoot );
436         }
437 
438         // parse the CVS root into its constituent parts
439         CVSRoot root;
440         final String cvsRoot = globalOptions.getCVSRoot();
441         try
442         {
443             root = CVSRoot.parse( cvsRoot );
444         }
445         catch ( IllegalArgumentException e )
446         {
447             if ( logger.isErrorEnabled() )
448             {
449                 logger.error( "Incorrect format for CVSRoot: " + cvsRoot + "\nThe correct format is: "
450                     + "[:method:][[user][:password]@][hostname:[port]]/path/to/repository"
451                     + "\nwhere \"method\" is pserver." );
452             }
453             return false;
454         }
455 
456         final String command = args[commandIndex];
457 
458         // this is not login, but a 'real' cvs command, so construct it,
459         // set the options, and then connect to the server and execute it
460 
461         Command c;
462         try
463         {
464             c = CommandFactory.getDefault().createCommand( command, args, ++commandIndex, globalOptions, localPath );
465         }
466         catch ( IllegalArgumentException e )
467         {
468             if ( logger.isErrorEnabled() )
469             {
470                 logger.error( "Illegal argument: " + e.getMessage() );
471             }
472             return false;
473         }
474 
475         String password = null;
476 
477         if ( CVSRoot.METHOD_PSERVER.equals( root.getMethod() ) )
478         {
479             password = root.getPassword();
480             if ( password != null )
481             {
482                 password = StandardScrambler.getInstance().scramble( password );
483             }
484             else
485             {
486                 password = lookupPassword( cvsRoot, logger );
487                 if ( password == null )
488                 {
489                     password = StandardScrambler.getInstance().scramble( "" );
490                     // an empty password
491                 }
492             }
493         }
494         CvsConnection cvsCommand = new CvsConnection();
495         cvsCommand.setGlobalOptions( globalOptions );
496         cvsCommand.setRepository( root.getRepository() );
497         // the local path is just the path where we executed the
498         // command. This is the case for command-line CVS but not
499         // usually for GUI front-ends
500         cvsCommand.setLocalPath( localPath );
501 
502         cvsCommand.connect( root, password );
503         cvsCommand.addListener( listener );
504         if ( logger.isDebugEnabled() )
505         {
506             logger.debug( "Executing CVS command: " + c.getCVSCommand() );
507         }
508         boolean result = cvsCommand.executeCommand( c );
509         cvsCommand.disconnect();
510         return result;
511     }
512 }