Coverage Report - org.apache.maven.wagon.providers.ssh.jsch.ScpWagon
 
Classes in this File Line Coverage Branch Coverage Complexity
ScpWagon
0%
0/158
0%
0/54
4,538
 
 1  
 package org.apache.maven.wagon.providers.ssh.jsch;
 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 java.io.EOFException;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.io.OutputStream;
 26  
 
 27  
 import org.apache.maven.wagon.CommandExecutionException;
 28  
 import org.apache.maven.wagon.InputData;
 29  
 import org.apache.maven.wagon.OutputData;
 30  
 import org.apache.maven.wagon.ResourceDoesNotExistException;
 31  
 import org.apache.maven.wagon.TransferFailedException;
 32  
 import org.apache.maven.wagon.events.TransferEvent;
 33  
 import org.apache.maven.wagon.providers.ssh.ScpHelper;
 34  
 import org.apache.maven.wagon.repository.RepositoryPermissions;
 35  
 import org.apache.maven.wagon.resource.Resource;
 36  
 
 37  
 import com.jcraft.jsch.ChannelExec;
 38  
 import com.jcraft.jsch.JSchException;
 39  
 
 40  
 /**
 41  
  * SCP protocol wagon.
 42  
  * 
 43  
  * Note that this implementation is <i>not</i> thread-safe, and multiple channels can not be used on the session at
 44  
  * the same time.
 45  
  * 
 46  
  * See <a href="http://blogs.sun.com/janp/entry/how_the_scp_protocol_works">
 47  
  * http://blogs.sun.com/janp/entry/how_the_scp_protocol_works</a>
 48  
  * for information on how the SCP protocol works.
 49  
  *
 50  
  * @version $Id: ScpWagon.java 688222 2008-08-22 23:06:12Z hboutemy $
 51  
  * @todo [BP] add compression flag
 52  
  * 
 53  
  * @plexus.component role="org.apache.maven.wagon.Wagon" 
 54  
  *   role-hint="scp"
 55  
  *   instantiation-strategy="per-lookup"
 56  
  */
 57  0
 public class ScpWagon
 58  
     extends AbstractJschWagon
 59  
 {
 60  
     private static final char COPY_START_CHAR = 'C';
 61  
 
 62  
     private static final char ACK_SEPARATOR = ' ';
 63  
 
 64  
     private static final String END_OF_FILES_MSG = "E\n";
 65  
 
 66  
     private static final int LINE_BUFFER_SIZE = 8192;
 67  
 
 68  
     private static final byte LF = '\n';
 69  
 
 70  
     private ChannelExec channel;
 71  
 
 72  
     private InputStream channelInputStream;
 73  
 
 74  
     private OutputStream channelOutputStream;
 75  
 
 76  
     private void setFileGroup( RepositoryPermissions permissions, String basedir, Resource resource )
 77  
         throws CommandExecutionException
 78  
     {
 79  0
         if ( permissions != null && permissions.getGroup() != null )
 80  
         {
 81  0
             executeCommand( "chgrp -f " + permissions.getGroup() + " " + getPath( basedir, resource.getName() ) );
 82  
         }
 83  0
     }
 84  
 
 85  
     protected void cleanupPutTransfer( Resource resource )
 86  
     {
 87  0
         if ( channel != null )
 88  
         {
 89  0
             channel.disconnect();
 90  0
             channel = null;
 91  
         }
 92  0
     }
 93  
 
 94  
     protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
 95  
         throws TransferFailedException
 96  
     {
 97  
         try
 98  
         {
 99  0
             sendEom( output );
 100  
 
 101  0
             checkAck( channelInputStream );
 102  
 
 103  
             // This came from SCPClient in Ganymede SSH2. It is sent after all files.
 104  0
             output.write( END_OF_FILES_MSG.getBytes() );
 105  0
             output.flush();
 106  
         }
 107  0
         catch ( IOException e )
 108  
         {
 109  0
             handleIOException( resource, e );
 110  0
         }
 111  
 
 112  0
         String basedir = getRepository().getBasedir();
 113  
         try
 114  
         {
 115  0
             setFileGroup( getRepository().getPermissions(), basedir, resource );
 116  
         }
 117  0
         catch ( CommandExecutionException e )
 118  
         {
 119  0
             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
 120  
 
 121  0
             throw new TransferFailedException( e.getMessage(), e );
 122  0
         }
 123  0
     }
 124  
 
 125  
     private void checkAck( InputStream in )
 126  
         throws IOException
 127  
     {
 128  0
         int code = in.read();
 129  0
         if ( code == -1 )
 130  
         {
 131  0
             throw new IOException( "Unexpected end of data" );
 132  
         }
 133  0
         else if ( code == 1 )
 134  
         {
 135  0
             String line = readLine( in );
 136  
 
 137  0
             throw new IOException( "SCP terminated with error: '" + line + "'" );
 138  
         }
 139  0
         else if ( code == 2 )
 140  
         {
 141  0
             throw new IOException( "SCP terminated with error (code: " + code + ")" );
 142  
         }
 143  0
         else if ( code != 0 )
 144  
         {
 145  0
             throw new IOException( "SCP terminated with unknown error code" );
 146  
         }
 147  0
     }    
 148  
 
 149  
     protected void finishGetTransfer( Resource resource, InputStream input, OutputStream output )
 150  
         throws TransferFailedException
 151  
     {
 152  
         try
 153  
         {
 154  0
             checkAck( input );
 155  
     
 156  0
             sendEom( channelOutputStream );
 157  
         }
 158  0
         catch ( IOException e )
 159  
         {
 160  0
             handleGetException( resource, e );
 161  0
         }
 162  0
     }
 163  
     
 164  
     protected void cleanupGetTransfer( Resource resource )
 165  
     {
 166  0
         if ( channel != null )
 167  
         {
 168  0
             channel.disconnect();
 169  
         }
 170  0
     }
 171  
     
 172  
     protected void getTransfer( Resource resource, OutputStream output, InputStream input, boolean closeInput,
 173  
                                 int maxSize )
 174  
         throws TransferFailedException
 175  
     {
 176  0
         super.getTransfer( resource, output, input, closeInput, (int) resource.getContentLength() );
 177  0
     }
 178  
     
 179  
     protected String readLine( InputStream in )
 180  
         throws IOException
 181  
     {
 182  0
         StringBuffer sb = new StringBuffer();
 183  
 
 184  
         while ( true )
 185  
         {
 186  0
             if ( sb.length() > LINE_BUFFER_SIZE )
 187  
             {
 188  0
                 throw new IOException( "Remote server sent a too long line" );
 189  
             }
 190  
 
 191  0
             int c = in.read();
 192  
 
 193  0
             if ( c < 0 )
 194  
             {
 195  0
                 throw new IOException( "Remote connection terminated unexpectedly." );
 196  
             }
 197  
 
 198  0
             if ( c == LF )
 199  
             {
 200  0
                 break;
 201  
             }
 202  
 
 203  0
             sb.append( (char) c );
 204  0
         }
 205  0
         return sb.toString();
 206  
     }
 207  
 
 208  
     protected static void sendEom( OutputStream out )
 209  
         throws IOException
 210  
     {
 211  0
         out.write( 0 );
 212  
 
 213  0
         out.flush();
 214  0
     }
 215  
 
 216  
     public void fillInputData( InputData inputData )
 217  
         throws TransferFailedException, ResourceDoesNotExistException
 218  
     {
 219  0
         Resource resource = inputData.getResource();
 220  
         
 221  0
         String path = getPath( getRepository().getBasedir(), resource.getName() );
 222  0
         String cmd = "scp -p -f " + path;
 223  
 
 224  0
         fireTransferDebug( "Executing command: " + cmd );
 225  
 
 226  
         try
 227  
         {
 228  0
             channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
 229  
 
 230  0
             channel.setCommand( cmd );
 231  
 
 232  
             // get I/O streams for remote scp
 233  0
             channelOutputStream = channel.getOutputStream();
 234  
             
 235  0
             InputStream in = channel.getInputStream();
 236  0
             inputData.setInputStream( in );
 237  
 
 238  0
             channel.connect();
 239  
 
 240  0
             sendEom( channelOutputStream );
 241  
 
 242  0
             int exitCode = in.read();
 243  
 
 244  0
             if ( exitCode == 'T' )
 245  
             {
 246  0
                 String line = readLine( in );
 247  
                 
 248  0
                 String[] times = line.split( " " );
 249  
                 
 250  0
                 resource.setLastModified( Long.valueOf( times[0] ).longValue() * 1000 );
 251  
 
 252  0
                 sendEom( channelOutputStream );
 253  
                 
 254  0
                 exitCode = in.read();
 255  
             }
 256  
 
 257  0
             String line = readLine( in );
 258  
 
 259  0
             if ( exitCode != COPY_START_CHAR )
 260  
             {
 261  0
                 if ( exitCode == 1 && line.indexOf( "No such file or directory" ) != -1 )
 262  
                 {
 263  0
                     throw new ResourceDoesNotExistException( line );
 264  
                 }
 265  
                 else
 266  
                 {
 267  0
                     throw new IOException( "Exit code: " + exitCode + " - " + line );
 268  
                 }
 269  
             }
 270  
 
 271  0
             if ( line == null )
 272  
             {
 273  0
                 throw new EOFException( "Unexpected end of data" );
 274  
             }
 275  
 
 276  0
             String perms = line.substring( 0, 4 );
 277  0
             fireTransferDebug( "Remote file permissions: " + perms );
 278  
 
 279  0
             if ( line.charAt( 4 ) != ACK_SEPARATOR && line.charAt( 5 ) != ACK_SEPARATOR )
 280  
             {
 281  0
                 throw new IOException( "Invalid transfer header: " + line );
 282  
             }
 283  
 
 284  0
             int index = line.indexOf( ACK_SEPARATOR, 5 );
 285  0
             if ( index < 0 )
 286  
             {
 287  0
                 throw new IOException( "Invalid transfer header: " + line );
 288  
             }
 289  
 
 290  0
             int filesize = Integer.valueOf( line.substring( 5, index ) ).intValue();
 291  0
             fireTransferDebug( "Remote file size: " + filesize );
 292  
 
 293  0
             resource.setContentLength( filesize );
 294  
 
 295  0
             String filename = line.substring( index + 1 );
 296  0
             fireTransferDebug( "Remote filename: " + filename );
 297  
 
 298  0
             sendEom( channelOutputStream );
 299  
         }
 300  0
         catch ( JSchException e )
 301  
         {
 302  0
             handleGetException( resource, e );
 303  
         }
 304  0
         catch ( IOException e )
 305  
         {
 306  0
             handleGetException( resource, e );
 307  0
         }
 308  0
     }
 309  
 
 310  
     public void fillOutputData( OutputData outputData )
 311  
         throws TransferFailedException
 312  
     {
 313  0
         Resource resource = outputData.getResource();
 314  
         
 315  0
         String basedir = getRepository().getBasedir();
 316  
 
 317  0
         String path = getPath( basedir, resource.getName() );
 318  
 
 319  0
         String dir = ScpHelper.getResourceDirectory( resource.getName() );
 320  
 
 321  
         try
 322  
         {
 323  0
             sshTool.createRemoteDirectories( getPath( basedir, dir ), getRepository().getPermissions() );
 324  
         }
 325  0
         catch ( CommandExecutionException e )
 326  
         {
 327  0
             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
 328  
 
 329  0
             throw new TransferFailedException( e.getMessage(), e );
 330  0
         }
 331  
         
 332  0
         String octalMode = getOctalMode( getRepository().getPermissions() );
 333  
         
 334  
         // exec 'scp -p -t rfile' remotely
 335  0
         String command = "scp";
 336  0
         if ( octalMode != null )
 337  
         {
 338  0
             command += " -p";
 339  
         }
 340  0
         command += " -t \"" + path + "\"";
 341  
 
 342  0
         fireTransferDebug( "Executing command: " + command );
 343  
 
 344  0
         String resourceName = resource.getName();
 345  
 
 346  0
         OutputStream out = null;
 347  
         try
 348  
         {
 349  0
             channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
 350  
 
 351  0
             channel.setCommand( command );
 352  
 
 353  
             // get I/O streams for remote scp
 354  0
             out = channel.getOutputStream();
 355  0
             outputData.setOutputStream( out );
 356  
 
 357  0
             channelInputStream = channel.getInputStream();
 358  
 
 359  0
             channel.connect();
 360  
 
 361  0
             checkAck( channelInputStream );
 362  
 
 363  
             // send "C0644 filesize filename", where filename should not include '/'
 364  0
             long filesize = resource.getContentLength();
 365  
 
 366  0
             String mode = octalMode == null ? "0644" : octalMode;
 367  0
             command = "C" + mode + " " + filesize + " ";
 368  
 
 369  0
             if ( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) > 0 )
 370  
             {
 371  0
                 command += resourceName.substring( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) + 1 );
 372  
             }
 373  
             else
 374  
             {
 375  0
                 command += resourceName;
 376  
             }
 377  
 
 378  0
             command += "\n";
 379  
 
 380  0
             out.write( command.getBytes() );
 381  
 
 382  0
             out.flush();
 383  
 
 384  0
             checkAck( channelInputStream );
 385  
         }
 386  0
         catch ( JSchException e )
 387  
         {
 388  0
             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );            
 389  
             
 390  0
             String msg = "Error occurred while deploying '" + resourceName + "' to remote repository: "
 391  
                 + getRepository().getUrl() + ": " + e.getMessage();
 392  
 
 393  0
             throw new TransferFailedException( msg, e );
 394  
         }
 395  0
         catch ( IOException e )
 396  
         {
 397  0
             handleIOException( resource, e );
 398  0
         }
 399  0
     }
 400  
 
 401  
     private void handleIOException( Resource resource, IOException e )
 402  
         throws TransferFailedException
 403  
     {
 404  0
         if ( e.getMessage().indexOf( "set mode: Operation not permitted" ) >= 0 )
 405  
         {
 406  0
             fireTransferDebug( e.getMessage() );                
 407  
         }
 408  
         else
 409  
         {
 410  0
             fireTransferError( resource, e, TransferEvent.REQUEST_PUT );            
 411  
             
 412  0
             String msg = "Error occurred while deploying '" + resource.getName() + "' to remote repository: "
 413  
                 + getRepository().getUrl() + ": " + e.getMessage();
 414  
    
 415  0
             throw new TransferFailedException( msg, e );
 416  
         }
 417  0
     }
 418  
 
 419  
     public String getOctalMode( RepositoryPermissions permissions )
 420  
     {
 421  0
         String mode = null;
 422  0
         if ( permissions != null && permissions.getFileMode() != null )
 423  
         {
 424  0
             if ( permissions.getFileMode().matches( "[0-9]{3,4}" ) )
 425  
             {
 426  0
                 mode = permissions.getFileMode();
 427  
 
 428  0
                 if ( mode.length() == 3 )
 429  
                 {
 430  0
                     mode = "0" + mode;
 431  
                 }
 432  
             }
 433  
             else
 434  
             {
 435  
                 // TODO: calculate?
 436  
                 // TODO: as warning
 437  0
                 fireSessionDebug( "Not using non-octal permissions: " + permissions.getFileMode() );
 438  
             }
 439  
         }
 440  0
         return mode;
 441  
     }
 442  
 }