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