Compare commits

..

1 Commits

Author SHA1 Message Date
Jeroen van Erp
dbe00fabc6 Working on #88 2016-04-11 16:16:08 +02:00
10 changed files with 149 additions and 341 deletions

View File

@@ -1,12 +1,10 @@
= sshj - SSHv2 library for Java
Jeroen van Erp
:sshj_groupid: com.hierynomus
:sshj_version: 0.16.0
:sshj_version: 0.15.0
:source-highlighter: pygments
image:https://travis-ci.org/hierynomus/sshj.svg?branch=master[link="https://travis-ci.org/hierynomus/sshj"]
image:https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj/badge.svg["Maven Central",link="https://maven-badges.herokuapp.com/maven-central/com.hierynomus/sshj"]
image:https://javadoc-emblem.rhcloud.com/doc/com.hierynomus/sshj/badge.svg["Javadoc",link="http://www.javadoc.io/doc/com.hierynomus/sshj"]
image::https://travis-ci.org/hierynomus/sshj.svg?branch=master[]
To get started, have a look at one of the examples. Hopefully you will find the API pleasant to work with :)
@@ -98,12 +96,7 @@ Google Group: http://groups.google.com/group/sshj-users
Fork away!
== Release history
SSHJ 0.17.0 (2016-07-05)::
* *Introduced breaking change in SFTP copy behaviour*: Previously an SFTP copy operation would behave differently if both source and target were folders with different names.
In this case instead of copying the contents of the source into the target directory, the directory itself was copied as a sub directory of the target directory.
This behaviour has been removed in favour of the default behaviour which is to copy the contents of the source into the target. Bringing the behaviour in line with how SCP works.
* Fixed https://github.com/hierynomus/sshj/issues/252[#252] (via: https://github.com/hierynomus/sshj/pulls/253[#253]): Same name subdirs are no longer merged by accident
SSHJ 0.16.0 (2016-04-11)::
SSHJ 0.16.0 (2016-??-??)::
* Fixed https://github.com/hierynomus/sshj/issues/239[#239]: Remote port forwards did not work if you used the empty string as address, or a catch-all address.
* Fixed https://github.com/hierynomus/sshj/issues/242[#242]: Added OSGI headers to sources jar manifest
* Fixed https://github.com/hierynomus/sshj/issues/236[#236]: Remote Port forwarding with dynamic port allocation fails with BufferUnderflowException

View File

@@ -1,11 +1,10 @@
plugins {
id "java"
id "groovy"
id "maven"
id "idea"
id "signing"
id "osgi"
id "org.ajoberstar.release-opinion" version "1.4.2"
id "org.ajoberstar.release-opinion" version "1.4.0-rc.1"
id "com.github.hierynomus.license" version "0.12.1"
}
@@ -73,7 +72,6 @@ dependencies {
compile "net.vrallev.ecc:ecc-25519-java:1.0.1"
testCompile "junit:junit:4.11"
testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
testCompile "org.mockito:mockito-core:1.9.5"
testCompile "org.apache.sshd:sshd-core:1.1.0"
testRuntime "ch.qos.logback:logback-classic:1.1.2"
@@ -105,16 +103,7 @@ task javadocJar(type: Jar) {
task sourcesJar(type: Jar) {
classifier = 'sources'
from sourceSets.main.allSource
manifest {
attributes(
// Add the needed OSGI attributes
"Bundle-ManifestVersion": "2",
"Bundle-Name": "${project.jar.manifest.name} Source",
"Bundle-Version": project.jar.manifest.version,
"Eclipse-SourceBundle": "${project.jar.manifest.symbolicName};version=\"${project.jar.manifest.version}\";roots:=\".\"",
"Bundle-SymbolicName": "${project.jar.manifest.symbolicName}.source"
)
}
manifest = project.tasks.jar.manifest
}
artifacts {
@@ -206,5 +195,4 @@ uploadArchives {
}
}
tasks.compileGroovy.onlyIf { false }
tasks.release.dependsOn 'build', 'uploadArchives'

View File

@@ -84,7 +84,7 @@ public class DefaultConfig
private final Logger log = LoggerFactory.getLogger(getClass());
private static final String VERSION = "SSHJ_0_16_0";
private static final String VERSION = "SSHJ_0_14_0";
public DefaultConfig() {
setVersion(VERSION);

View File

@@ -18,8 +18,15 @@ package net.schmizz.sshj.sftp;
public class PathComponents {
static String adjustForParent(String parent, String path, String pathSep) {
return (path.startsWith(pathSep)) ? path // Absolute path, nothing to adjust
: (parent + (parent.endsWith(pathSep) ? "" : pathSep) + path); // Relative path
if (path.startsWith(pathSep)) { // Absolute path
return path;
// } else if (parent.isEmpty()) { // Relative path
// return path;
} else if (parent.endsWith(pathSep)) {
return parent + path;
} else {
return parent + pathSep + path;
}
}
static String trimTrailingSeparator(String somePath, String pathSep) {

View File

@@ -29,7 +29,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.EnumSet;
import java.util.List;
public class SFTPFileTransfer
extends AbstractFileTransfer
@@ -68,7 +67,7 @@ public class SFTPFileTransfer
@Override
public void upload(LocalSourceFile localFile, String remotePath)
throws IOException {
new Uploader(localFile, remotePath).upload(getTransferListener());
new Uploader().upload(getTransferListener(), localFile, remotePath);
}
@Override
@@ -174,31 +173,6 @@ public class SFTPFileTransfer
private class Uploader {
private final LocalSourceFile source;
private final String remote;
private Uploader(final LocalSourceFile source, final String remote) {
this.source = source;
this.remote = remote;
}
private void upload(final TransferListener listener) throws IOException {
if (source.isDirectory()) {
makeDirIfNotExists(remote); // Ensure that the directory exists
uploadDir(listener.directory(source.getName()), source, remote);
setAttributes(source, remote);
} else if (source.isFile() && isDirectory(remote)) {
String adjustedRemote = engine.getPathHelper().adjustForParent(this.remote, source.getName());
uploadFile(listener.file(source.getName(), source.getLength()), source, adjustedRemote);
setAttributes(source, adjustedRemote);
} else if (source.isFile()) {
uploadFile(listener.file(source.getName(), source.getLength()), source, remote);
setAttributes(source, remote);
} else {
throw new IOException(source + " is not a file or directory");
}
}
private void upload(final TransferListener listener,
final LocalSourceFile local,
final String remote)
@@ -208,26 +182,20 @@ public class SFTPFileTransfer
adjustedPath = uploadDir(listener.directory(local.getName()), local, remote);
} else if (local.isFile()) {
adjustedPath = uploadFile(listener.file(local.getName(), local.getLength()), local, remote);
} else {
} else
throw new IOException(local + " is not a file or directory");
}
setAttributes(local, adjustedPath);
}
private void setAttributes(LocalSourceFile local, String remotePath) throws IOException {
if (getPreserveAttributes()) {
engine.setAttributes(remotePath, getAttributes(local));
}
if (getPreserveAttributes())
engine.setAttributes(adjustedPath, getAttributes(local));
}
private String uploadDir(final TransferListener listener,
final LocalSourceFile local,
final String remote)
throws IOException {
makeDirIfNotExists(remote);
final String adjusted = prepareDir(local, remote);
for (LocalSourceFile f : local.getChildren(getUploadFilter()))
upload(listener, f, engine.getPathHelper().adjustForParent(remote, f.getName()));
return remote;
upload(listener, f, adjusted);
return adjusted;
}
private String uploadFile(final StreamCopier.Listener listener,
@@ -235,50 +203,52 @@ public class SFTPFileTransfer
final String remote)
throws IOException {
final String adjusted = prepareFile(local, remote);
try (RemoteFile rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE, OpenMode.CREAT, OpenMode.TRUNC))) {
try (InputStream fis = local.getInputStream();
RemoteFile.RemoteFileOutputStream rfos = rf.new RemoteFileOutputStream(0, 16)) {
final RemoteFile rf = engine.open(adjusted, EnumSet.of(OpenMode.WRITE,
OpenMode.CREAT,
OpenMode.TRUNC));
try {
final InputStream fis = local.getInputStream();
final RemoteFile.RemoteFileOutputStream rfos = rf.new RemoteFileOutputStream(0, 16);
try {
new StreamCopier(fis, rfos)
.bufSize(engine.getSubsystem().getRemoteMaxPacketSize() - rf.getOutgoingPacketOverhead())
.keepFlushing(false)
.listener(listener)
.copy();
} finally {
fis.close();
rfos.close();
}
} finally {
rf.close();
}
return adjusted;
}
private boolean makeDirIfNotExists(final String remote) throws IOException {
private String prepareDir(final LocalSourceFile local, final String remote)
throws IOException {
final FileAttributes attrs;
try {
FileAttributes attrs = engine.stat(remote);
if (attrs.getMode().getType() != FileMode.Type.DIRECTORY) {
throw new IOException(remote + " exists and should be a directory, but was a " + attrs.getMode().getType());
}
// Was not created, but existed.
return false;
attrs = engine.stat(remote);
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("makeDir: {} does not exist, creating", remote);
log.debug("probeDir: {} does not exist, creating", remote);
engine.makeDir(remote);
return true;
} else {
return remote;
} else
throw e;
}
}
}
private boolean isDirectory(final String remote) throws IOException {
try {
FileAttributes attrs = engine.stat(remote);
return attrs.getMode().getType() == FileMode.Type.DIRECTORY;
} catch (SFTPException e) {
if (e.getStatusCode() == StatusCode.NO_SUCH_FILE) {
log.debug("isDir: {} does not exist", remote);
return false;
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY)
if (engine.getPathHelper().getComponents(remote).getName().equals(local.getName())) {
log.debug("probeDir: {} already exists", remote);
return remote;
} else {
throw e;
log.debug("probeDir: {} already exists, path adjusted for {}", remote, local.getName());
return prepareDir(local, engine.getPathHelper().adjustForParent(remote, local.getName()));
}
}
else
throw new IOException(attrs.getMode().getType() + " file already exists at " + remote);
}
private String prepareFile(final LocalSourceFile local, final String remote)
@@ -294,7 +264,8 @@ public class SFTPFileTransfer
throw e;
}
if (attrs.getMode().getType() == FileMode.Type.DIRECTORY) {
throw new IOException("Trying to upload file " + local.getName() + " to path " + remote + " but that is a directory");
log.debug("probeFile: {} was directory, path adjusted for {}", remote, local.getName());
return engine.getPathHelper().adjustForParent(remote, local.getName());
} else {
log.debug("probeFile: {} is a {} file that will be replaced", remote, attrs.getMode().getType());
return remote;
@@ -310,4 +281,5 @@ public class SFTPFileTransfer
}
}
}

View File

@@ -25,8 +25,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/** Support for downloading files over a connected link using SCP. */
public class SCPDownloadClient extends AbstractSCPClient {
/** Support for uploading files over a connected link using SCP. */
public final class SCPDownloadClient extends AbstractSCPClient {
private boolean recursiveMode = true;

View File

@@ -25,7 +25,7 @@ import java.io.IOException;
import java.io.InputStream;
/** Support for uploading files over a connected link using SCP. */
public class SCPUploadClient extends AbstractSCPClient {
public final class SCPUploadClient extends AbstractSCPClient {
private LocalFileFilter uploadFilter;

View File

@@ -1,244 +0,0 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed 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 com.hierynomus.sshj.sftp
import com.hierynomus.sshj.test.SshFixture
import com.hierynomus.sshj.test.util.FileUtil
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.sftp.SFTPClient
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import spock.lang.Unroll
import static org.codehaus.groovy.runtime.IOGroovyMethods.withCloseable
class SFTPClientTest extends Specification {
@Rule
public SshFixture fixture = new SshFixture()
@Rule
public TemporaryFolder temp = new TemporaryFolder()
@Unroll
def "should copy #sourceType->#targetType if #targetExists with #named name"() {
given:
File src = source
File dest = target
when:
doUpload(src, dest)
then:
exists.each { f ->
if (dest.isDirectory()) {
assert new File(dest, f).exists()
} else {
assert dest.exists()
}
}
// Dest is also counted by recursiveCount if it is a dir
exists.size() + (dest.isDirectory() ? 1 : 0) == recursiveCount(dest)
cleanup:
// Delete the temp directories
recursiveDelete(source.getParentFile())
recursiveDelete(target.getParentFile())
where:
source | target || exists
sourceTree() | existingTargetDir("dest") || ["toto.txt", "tata.txt", "tutu", "tutu/tutu.txt"]
sourceTree() | newTargetDir("dest") || ["toto.txt", "tata.txt", "tutu", "tutu/tutu.txt"]
sourceTree() | existingTargetDir("toto") || ["toto.txt", "tata.txt", "tutu", "tutu/tutu.txt"]
sourceTree() | newTargetDir("toto") || ["toto.txt", "tata.txt", "tutu", "tutu/tutu.txt"]
sourceFile() | existingTargetDir("dest") || ["toto.txt"]
sourceFile() | newTargetFile("toto.txt") || ["toto.txt"]
sourceFile() | newTargetFile("diff.txt") || ["diff.txt"]
sourceType = source.isDirectory() ? "dir" : "file"
targetType = target.isDirectory() ? "dir" : sourceType
targetExists = target.exists() ? "exists" : "not exists"
named = (target.name == source.name) ? "same" : "different"
}
def "should not throw exception on close before disconnect"() {
given:
File file = temp.newFile("source.txt")
FileUtil.writeToFile(file, "This is the source")
when:
doUpload(file, temp.newFile("dest.txt"))
then:
noExceptionThrown()
}
//
// def "should copy dir->dir if exists and same name"() {
// given:
// File srcDir = temp.newFolder("toto")
// File destDir = temp.newFolder("dest", "toto")
// FileUtil.writeToFile(new File(srcDir, "toto.txt"), "Toto file")
//
// when:
// doUpload(srcDir, destDir)
//
// then:
// destDir.exists()
// !new File(destDir, "toto").exists()
// new File(destDir, "toto.txt").exists()
// }
//
// def "should copy dir->dir if exists and different name"() {
// given:
// File srcDir = temp.newFolder("toto")
// File destDir = temp.newFolder("dest")
// FileUtil.writeToFile(new File(srcDir, "toto.txt"), "Toto file")
//
// when:
// doUpload(srcDir, destDir)
//
// then:
// destDir.exists()
// new File(destDir, "toto.txt").exists()
// }
//
// def "should copy dir->dir if not exists and same name"() {
// given:
// File dd = temp.newFolder("dest")
// File destDir = new File(dd, "toto")
//
// when:
// doUpload(srcDir, destDir)
//
// then:
// destDir.exists()
// new File(destDir, "toto.txt").exists()
// }
//
// def "should copy dir->dir if not exists and different name"() {
// given:
// File srcDir = temp.newFolder("toto")
// File destDir = new File(temp.getRoot(), "dest")
// FileUtil.writeToFile(new File(srcDir, "toto.txt"), "Toto file")
//
// when:
// doUpload(srcDir, destDir)
//
// then:
// destDir.exists()
// new File(destDir, "toto.txt").exists()
// }
def "should not merge same name subdirs (GH #252)"() {
given:
File toto = temp.newFolder("toto")
File tutu = mkdir(toto, "tutu")
File toto2 = mkdir(toto, "toto")
File dest = temp.newFolder("dest")
FileUtil.writeToFile(new File(toto, "toto.txt"), "Toto file")
FileUtil.writeToFile(new File(tutu, "tototutu.txt"), "Toto/Tutu file")
FileUtil.writeToFile(new File(toto2, "totototo.txt"), "Toto/Toto file")
when:
doUpload(toto, dest)
then:
new File(dest, "toto").exists()
new File(dest, "toto.txt").exists()
new File(dest, "tutu").exists()
new File(dest, "tutu/tototutu.txt").exists()
new File(dest, "toto").exists()
new File(dest, "toto/totototo.txt").exists()
!new File(dest, "totototo.txt").exists()
}
private void doUpload(File src, File dest) throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient()
sshClient.authPassword("test", "test")
try {
withCloseable(sshClient.newSFTPClient()) { SFTPClient sftpClient ->
sftpClient.put(src.getPath(), dest.getPath())
}
} finally {
sshClient.disconnect()
}
}
private File mkdir(File parent, String name) {
File file = new File(parent, name)
file.mkdirs()
return file
}
private def sourceTree() {
def tempDir = File.createTempDir()
File srcDir = mkdir(tempDir, "toto")
FileUtil.writeToFile(new File(srcDir, "toto.txt"), "Toto file")
FileUtil.writeToFile(new File(srcDir, "tata.txt"), "Tata file")
File tutuDir = mkdir(srcDir, "tutu")
FileUtil.writeToFile(new File(tutuDir, "tutu.txt"), "Tutu file")
return srcDir
}
private def sourceFile() {
def tempDir = File.createTempDir()
def totoFile = new File(tempDir, "toto.txt")
FileUtil.writeToFile(totoFile, "Bare toto file")
return totoFile
}
private def existingTargetDir(String name) {
def tempDir = File.createTempDir("sftp", "tmp")
tempDir.deleteOnExit()
return mkdir(tempDir, name)
}
private def newTargetFile(String name) {
def tempDir = File.createTempDir("sftp", "tmp")
tempDir.deleteOnExit()
return new File(tempDir, name)
}
private def newTargetDir(String name) {
def tempDir = File.createTempDir("sftp", "tmp")
tempDir.deleteOnExit()
return new File(tempDir, name)
}
private int recursiveCount(File file) {
if (file.isFile()) {
return 1
}
File[] files = file.listFiles();
if (files != null) {
return 1 + (files.collect({ f -> recursiveCount(f) }).sum() as int)
} else {
return 1
}
}
private void recursiveDelete(File file) {
File[] files = file.listFiles();
if (files != null) {
for (File each : files) {
recursiveDelete(each);
}
}
file.delete();
}
}

View File

@@ -17,23 +17,37 @@ package com.hierynomus.sshj;
import net.schmizz.sshj.DefaultConfig;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.connection.channel.forwarded.RemotePortForwarder;
import net.schmizz.sshj.connection.channel.forwarded.SocketForwardingConnectListener;
import net.schmizz.sshj.transport.verification.OpenSSHKnownHosts;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import org.junit.Ignore;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
public class IntegrationTest {
@Test @Ignore // Should only be enabled for testing against VM
public void shouldConnect() throws IOException {
public void shouldConnect() throws IOException, InterruptedException {
SSHClient sshClient = new SSHClient(new DefaultConfig());
sshClient.addHostKeyVerifier(new OpenSSHKnownHosts(new File("/Users/ajvanerp/.ssh/known_hosts")));
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.connect("172.16.37.129");
sshClient.authPassword("jeroen", "jeroen");
assertThat("Is connected", sshClient.isAuthenticated());
sshClient.getRemotePortForwarder().bind(
// where the server should listen
new RemotePortForwarder.Forward("0.0.0.0", 8080),
// what we do with incoming connections that are forwarded to us
new SocketForwardingConnectListener(new InetSocketAddress("localhost", 8000)));
new CountDownLatch(1).await();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (C)2009 - SSHJ Contributors
*
* Licensed 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 com.hierynomus.sshj.sftp;
import com.hierynomus.sshj.test.SshFixture;
import com.hierynomus.sshj.test.util.FileUtil;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.FileAttributes;
import net.schmizz.sshj.sftp.SFTPClient;
import org.hamcrest.CoreMatchers;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.theories.DataPoint;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
public class SFTPClientTest {
@Rule
public SshFixture fixture = new SshFixture();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@Test
public void shouldNotThrowExceptionOnCloseBeforeDisconnect() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("test", "test");
SFTPClient sftpClient = sshClient.newSFTPClient();
File file = temp.newFile("source.txt");
FileUtil.writeToFile(file, "This is the source");
try {
try {
sftpClient.put(file.getPath(), temp.newFile("dest.txt").getPath());
} finally {
sftpClient.close();
}
} finally {
sshClient.disconnect();
}
}
@Test
@DataPoint
public void shouldNotCreateAbsoluteDirectoryWhenPathIsRelative() throws IOException {
SSHClient sshClient = fixture.setupConnectedDefaultClient();
sshClient.authPassword("test", "test");
SFTPClient sftpClient = sshClient.newSFTPClient();
String foo = sftpClient.canonicalize("foo");
String fooAbs = sftpClient.canonicalize("/foo");
assertThat(sftpClient.statExistence("foo"), nullValue());
sftpClient.mkdirs("foo");
assertThat(sftpClient.statExistence("foo"), notNullValue());
sftpClient.rmdir("foo");
assertThat(sftpClient.statExistence("foo"), nullValue());
sftpClient.close();
sshClient.disconnect();
}
}