blob: a9ec70643c33b138e174432943e1978e2fe3eb72 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.wagon.providers.ssh.jsch;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.PathUtils;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.events.TransferEvent;
import org.apache.maven.wagon.providers.ssh.ScpHelper;
import org.apache.maven.wagon.repository.RepositoryPermissions;
import org.apache.maven.wagon.resource.Resource;
/**
* SFTP protocol wagon.
*
* @author <a href="mailto:brett@apache.org">Brett Porter</a>
*
* @todo [BP] add compression flag
* @todo see if SftpProgressMonitor allows us to do streaming (without it, we can't do checksums as the input stream is lost)
*
* @plexus.component role="org.apache.maven.wagon.Wagon"
* role-hint="sftp"
* instantiation-strategy="per-lookup"
*/
public class SftpWagon extends AbstractJschWagon {
private static final String SFTP_CHANNEL = "sftp";
private static final int S_IFDIR = 0x4000;
private static final long MILLIS_PER_SEC = 1000L;
private ChannelSftp channel;
public void closeConnection() {
if (channel != null) {
channel.disconnect();
}
super.closeConnection();
}
public void openConnectionInternal() throws AuthenticationException {
super.openConnectionInternal();
try {
channel = (ChannelSftp) session.openChannel(SFTP_CHANNEL);
channel.connect();
} catch (JSchException e) {
throw new AuthenticationException(
"Error connecting to remote repository: " + getRepository().getUrl(), e);
}
}
private void returnToParentDirectory(Resource resource) {
try {
String dir = ScpHelper.getResourceDirectory(resource.getName());
String[] dirs = PathUtils.dirnames(dir);
for (String d : dirs) {
channel.cd("..");
}
} catch (SftpException e) {
fireTransferDebug("Error returning to parent directory: " + e.getMessage());
}
}
private void putFile(File source, Resource resource, RepositoryPermissions permissions)
throws SftpException, TransferFailedException {
resource.setContentLength(source.length());
resource.setLastModified(source.lastModified());
String filename = ScpHelper.getResourceFilename(resource.getName());
firePutStarted(resource, source);
channel.put(source.getAbsolutePath(), filename);
postProcessListeners(resource, source, TransferEvent.REQUEST_PUT);
if (permissions != null && permissions.getGroup() != null) {
setGroup(filename, permissions);
}
if (permissions != null && permissions.getFileMode() != null) {
setFileMode(filename, permissions);
}
firePutCompleted(resource, source);
}
private void setGroup(String filename, RepositoryPermissions permissions) {
try {
int group = Integer.valueOf(permissions.getGroup()).intValue();
channel.chgrp(group, filename);
} catch (NumberFormatException e) {
// TODO: warning level
fireTransferDebug("Not setting group: must be a numerical GID for SFTP");
} catch (SftpException e) {
fireTransferDebug("Not setting group: " + e.getMessage());
}
}
private void setFileMode(String filename, RepositoryPermissions permissions) {
try {
int mode = getOctalMode(permissions.getFileMode());
channel.chmod(mode, filename);
} catch (NumberFormatException e) {
// TODO: warning level
fireTransferDebug("Not setting mode: must be a numerical mode for SFTP");
} catch (SftpException e) {
fireTransferDebug("Not setting mode: " + e.getMessage());
}
}
private void mkdirs(String resourceName, int mode) throws SftpException, TransferFailedException {
String[] dirs = PathUtils.dirnames(resourceName);
for (String dir : dirs) {
mkdir(dir, mode);
channel.cd(dir);
}
}
private void mkdir(String dir, int mode) throws TransferFailedException, SftpException {
try {
SftpATTRS attrs = channel.stat(dir);
if ((attrs.getPermissions() & S_IFDIR) == 0) {
throw new TransferFailedException("Remote path is not a directory: " + dir);
}
} catch (SftpException e) {
// doesn't exist, make it and try again
channel.mkdir(dir);
if (mode != -1) {
try {
channel.chmod(mode, dir);
} catch (SftpException e1) {
// for some extrange reason we recive this exception,
// even when chmod success
}
}
}
}
private SftpATTRS changeToRepositoryDirectory(String dir, String filename)
throws ResourceDoesNotExistException, SftpException {
// This must be called first to ensure that if the file doesn't exist it throws an exception
SftpATTRS attrs;
try {
channel.cd(repository.getBasedir());
if (dir.length() > 0) {
channel.cd(dir);
}
if (filename.length() == 0) {
filename = ".";
}
attrs = channel.stat(filename);
} catch (SftpException e) {
if (e.toString().trim().endsWith("No such file")) {
throw new ResourceDoesNotExistException(e.toString(), e);
} else if (e.toString().trim().contains("Can't change directory")) {
throw new ResourceDoesNotExistException(e.toString(), e);
} else {
throw e;
}
}
return attrs;
}
public void putDirectory(File sourceDirectory, String destinationDirectory)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
final RepositoryPermissions permissions = repository.getPermissions();
try {
channel.cd("/");
String basedir = getRepository().getBasedir();
int directoryMode = getDirectoryMode(permissions);
mkdirs(basedir + "/", directoryMode);
fireTransferDebug("Recursively uploading directory " + sourceDirectory.getAbsolutePath() + " as "
+ destinationDirectory);
mkdirs(destinationDirectory, directoryMode);
ftpRecursivePut(sourceDirectory, null, ScpHelper.getResourceFilename(destinationDirectory), directoryMode);
} catch (SftpException e) {
String msg = "Error occurred while deploying '" + sourceDirectory.getAbsolutePath() + "' "
+ "to remote repository: " + getRepository().getUrl() + ": " + e.getMessage();
throw new TransferFailedException(msg, e);
}
}
private void ftpRecursivePut(File sourceFile, String prefix, String fileName, int directoryMode)
throws TransferFailedException, SftpException {
final RepositoryPermissions permissions = repository.getPermissions();
if (sourceFile.isDirectory()) {
// ScpHelper.getResourceFilename( destinationDirectory ) - could return empty string
if (!fileName.equals(".") && !fileName.equals("")) {
prefix = getFileName(prefix, fileName);
mkdir(fileName, directoryMode);
channel.cd(fileName);
}
File[] files = sourceFile.listFiles();
if (files != null && files.length > 0) {
// Directories first, then files. Let's go deep early.
for (File file : files) {
if (file.isDirectory()) {
ftpRecursivePut(file, prefix, file.getName(), directoryMode);
}
}
for (File file : files) {
if (!file.isDirectory()) {
ftpRecursivePut(file, prefix, file.getName(), directoryMode);
}
}
}
channel.cd("..");
} else {
Resource resource = ScpHelper.getResource(getFileName(prefix, fileName));
firePutInitiated(resource, sourceFile);
putFile(sourceFile, resource, permissions);
}
}
private String getFileName(String prefix, String fileName) {
if (prefix != null) {
prefix = prefix + "/" + fileName;
} else {
prefix = fileName;
}
return prefix;
}
public List<String> getFileList(String destinationDirectory)
throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {
if (destinationDirectory.length() == 0) {
destinationDirectory = ".";
}
String filename = ScpHelper.getResourceFilename(destinationDirectory);
String dir = ScpHelper.getResourceDirectory(destinationDirectory);
// we already setuped the root directory. Ignore beginning /
if (dir.length() > 0 && dir.charAt(0) == ScpHelper.PATH_SEPARATOR) {
dir = dir.substring(1);
}
try {
SftpATTRS attrs = changeToRepositoryDirectory(dir, filename);
if ((attrs.getPermissions() & S_IFDIR) == 0) {
throw new TransferFailedException("Remote path is not a directory:" + dir);
}
@SuppressWarnings("unchecked")
List<ChannelSftp.LsEntry> fileList = channel.ls(filename);
List<String> files = new ArrayList<>(fileList.size());
for (ChannelSftp.LsEntry entry : fileList) {
String name = entry.getFilename();
if (entry.getAttrs().isDir()) {
if (!name.equals(".") && !name.equals("..")) {
if (!name.endsWith("/")) {
name += "/";
}
files.add(name);
}
} else {
files.add(name);
}
}
return files;
} catch (SftpException e) {
String msg = "Error occurred while listing '" + destinationDirectory + "' " + "on remote repository: "
+ getRepository().getUrl() + ": " + e.getMessage();
throw new TransferFailedException(msg, e);
}
}
public boolean resourceExists(String resourceName) throws TransferFailedException, AuthorizationException {
String filename = ScpHelper.getResourceFilename(resourceName);
String dir = ScpHelper.getResourceDirectory(resourceName);
// we already setuped the root directory. Ignore beginning /
if (dir.length() > 0 && dir.charAt(0) == ScpHelper.PATH_SEPARATOR) {
dir = dir.substring(1);
}
try {
changeToRepositoryDirectory(dir, filename);
return true;
} catch (ResourceDoesNotExistException e) {
return false;
} catch (SftpException e) {
String msg = "Error occurred while looking for '" + resourceName + "' " + "on remote repository: "
+ getRepository().getUrl() + ": " + e.getMessage();
throw new TransferFailedException(msg, e);
}
}
protected void cleanupGetTransfer(Resource resource) {
returnToParentDirectory(resource);
}
protected void cleanupPutTransfer(Resource resource) {
returnToParentDirectory(resource);
}
protected void finishPutTransfer(Resource resource, InputStream input, OutputStream output)
throws TransferFailedException {
RepositoryPermissions permissions = getRepository().getPermissions();
String filename = ScpHelper.getResourceFilename(resource.getName());
if (permissions != null && permissions.getGroup() != null) {
setGroup(filename, permissions);
}
if (permissions != null && permissions.getFileMode() != null) {
setFileMode(filename, permissions);
}
}
public void fillInputData(InputData inputData) throws TransferFailedException, ResourceDoesNotExistException {
Resource resource = inputData.getResource();
String filename = ScpHelper.getResourceFilename(resource.getName());
String dir = ScpHelper.getResourceDirectory(resource.getName());
// we already setuped the root directory. Ignore beginning /
if (dir.length() > 0 && dir.charAt(0) == ScpHelper.PATH_SEPARATOR) {
dir = dir.substring(1);
}
try {
SftpATTRS attrs = changeToRepositoryDirectory(dir, filename);
long lastModified = attrs.getMTime() * MILLIS_PER_SEC;
resource.setContentLength(attrs.getSize());
resource.setLastModified(lastModified);
inputData.setInputStream(channel.get(filename));
} catch (SftpException e) {
handleGetException(resource, e);
}
}
public void fillOutputData(OutputData outputData) throws TransferFailedException {
int directoryMode = getDirectoryMode(getRepository().getPermissions());
Resource resource = outputData.getResource();
try {
channel.cd("/");
String basedir = getRepository().getBasedir();
mkdirs(basedir + "/", directoryMode);
mkdirs(resource.getName(), directoryMode);
String filename = ScpHelper.getResourceFilename(resource.getName());
outputData.setOutputStream(channel.put(filename));
} catch (TransferFailedException e) {
fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
throw e;
} catch (SftpException e) {
fireTransferError(resource, e, TransferEvent.REQUEST_PUT);
String msg = "Error occurred while deploying '" + resource.getName() + "' " + "to remote repository: "
+ getRepository().getUrl() + ": " + e.getMessage();
throw new TransferFailedException(msg, e);
}
}
/**
* @param permissions repository's permissions
* @return the directory mode for the repository or <code>-1</code> if it
* wasn't set
*/
public int getDirectoryMode(RepositoryPermissions permissions) {
int ret = -1;
if (permissions != null) {
ret = getOctalMode(permissions.getDirectoryMode());
}
return ret;
}
public int getOctalMode(String mode) {
int ret;
try {
ret = Integer.valueOf(mode, 8).intValue();
} catch (NumberFormatException e) {
// TODO: warning level
fireTransferDebug("the file mode must be a numerical mode for SFTP");
ret = -1;
}
return ret;
}
}