Skip to content

Commit

Permalink
allow relative paths for SFTP
Browse files Browse the repository at this point in the history
  • Loading branch information
maddingo committed Jan 1, 2024
1 parent 82ae1dc commit 558c4b0
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 37 deletions.
3 changes: 3 additions & 0 deletions integration-tests/src/test/java/FilesListTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import no.maddin.niofs.sftp.SFTPPath;
import no.maddin.niofs.testutil.BasicTestContainer;
import no.maddin.niofs.testutil.FileUtils;
import no.maddin.niofs.testutil.SftpgoContainer;
import no.maddin.niofs.webdav.WebdavPath;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
Expand All @@ -12,6 +14,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand Down
10 changes: 10 additions & 0 deletions sftp/src/main/java/no/maddin/niofs/sftp/SFTPHost.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class SFTPHost extends FileSystem {

private final AtomicBoolean open = new AtomicBoolean();

static final SFTPHost SFTP_TEST_HOST = new SFTPHost();

SFTPHost(SFTPFileSystemProvider provider, URI serverUri) {
this.serverUri = serverUri;
this.provider = provider;
Expand All @@ -34,6 +36,14 @@ public class SFTPHost extends FileSystem {
open.set(true);
}

private SFTPHost() {
this.provider = null;
this.host = null;
this.port = 0;
this.userInfo = null;
this.serverUri = null;
}

private static UserInfo userInfo(String userInfo) {
String un = null;
String pw = null;
Expand Down
89 changes: 61 additions & 28 deletions sftp/src/main/java/no/maddin/niofs/sftp/SFTPPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,23 @@ public class SFTPPath implements Path {
static final String PATH_SEP = "/";
private final String path;
private final SFTPHost host;
private final java.util.List<String> parts;
private final List<String> parts;

SFTPPath(SFTPHost sftpHost, String path) {
SFTPPath(@NotNull SFTPHost sftpHost, String path) {
this.host = sftpHost;
parts = splitParts(path);
this.path = "/" + String.join(PATH_SEP, parts);
if (path == null) {
this.path = "";
this.parts = Collections.emptyList();
} else {
this.path = path;
this.parts = splitParts(path);
}
}

private static List<String> splitParts(String path) {
if (path == null || !path.startsWith(PATH_SEP)) {
throw new IllegalArgumentException("Path must start with " + PATH_SEP);
}
private static List<String> splitParts(@NotNull String path) {
Deque<String> parts = new ArrayDeque<>();
try {
for (String p : path.substring(1).split(PATH_SEP, -1)) {
for (String p : path.split(PATH_SEP, -1)) {
if (p.isEmpty() || p.equals(".")) {
// ignore
} else if (p.equals("..")) {
Expand All @@ -48,63 +50,73 @@ private static List<String> splitParts(String path) {
return new ArrayList<>(parts);
}

private String combineParts(int startIdx, int endIdx) {
StringBuilder sb = new StringBuilder(PATH_SEP);
for (String part : parts.subList(startIdx, endIdx)) {
if (sb.length() > 0) {
private String combineParts(int endIdx) {
StringBuilder sb = new StringBuilder();
boolean first = true;
if (path.startsWith(PATH_SEP)) {
sb.append(PATH_SEP);
}
for (String part : parts.subList(0, endIdx)) {
if (!first) {
sb.append(PATH_SEP);
}
sb.append(part);
first = false;
}
return sb.toString();
}

@Override
public FileSystem getFileSystem() {
public @NotNull FileSystem getFileSystem() {
return this.host;
}

@Override
public boolean isAbsolute() {
return path.startsWith(PATH_SEP);
return path.startsWith(PATH_SEP) && host != null;
}

@Override
public Path getRoot() {
if (path == null) {
return this;
if (host == null) {
return null;
}
return new SFTPPath(this.host, null);
return new SFTPPath(this.host, PATH_SEP);
}

@Override
public Path getFileName() {
throw new UnsupportedOperationException("Not Implemented");
if (path.isEmpty()) {
return null;
}
return new SFTPPath(this.host, parts.get(parts.size() - 1));
}

@Override
public Path getParent() {

if (path == null) {
if (!path.startsWith(PATH_SEP)) {
return null;
}
return new SFTPPath(this.host, combineParts(0, getNameCount() - 1));
return new SFTPPath(this.host, combineParts(getNameCount() - 1));
}

@Override
public int getNameCount() {

return parts.size();
}

@Override
public @NotNull Path getName(int index) {
throw new UnsupportedOperationException("Not Implemented");
if (index < 0 || index >= parts.size()) {
throw new IllegalArgumentException("index");
}
return new SFTPPath(this.host, parts.get(index - 1));
}

@Override
public @NotNull Path subpath(int beginIndex, int endIndex) {
return new SFTPPath(beginIndex == 0 ? host : null, combineParts(0, endIndex));
return new SFTPPath(beginIndex == 0 ? host : null, combineParts(endIndex));
}

@Override
Expand Down Expand Up @@ -134,12 +146,9 @@ public boolean endsWith(@NotNull String other) {
return false;
}

/**
* SFTPPAths are normalized at creation time. This just returns itself.
*/
@Override
public @NotNull Path normalize() {
return this;
return new SFTPPath(this.host, combineParts(getNameCount()));
}

@Override
Expand Down Expand Up @@ -228,4 +237,28 @@ public String getPathString() {
public List<String> getParts() {
return Collections.unmodifiableList(parts);
}

@Override
@NotNull
public String toString() {
StringBuilder sb = new StringBuilder();
if (host != null && path.startsWith(PATH_SEP)) {
sb.append(host);
}
sb.append(path);
return sb.toString();
}

@Override
public boolean equals(Object obj) {
if (obj instanceof SFTPPath) {
SFTPPath other = (SFTPPath) obj;
return Objects.equals(this.host, other.host) && Objects.equals(this.path, other.path);
}
return false;
}

public int hashCode() {
return Objects.hash(this.host, this.path);
}
}
21 changes: 13 additions & 8 deletions sftp/src/test/java/no/maddin/niofs/sftp/PartsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,22 @@ static Stream<Arguments> validPathsData() {
Arguments.of("/aa/bb", Arrays.asList("aa", "bb")),
Arguments.of("/aa/../bb", Collections.singletonList("bb")),
Arguments.of("/aa/../bb", Collections.singletonList("bb")),
Arguments.of("/aa/../bb/cc/../d.txt", Arrays.asList("bb", "d.txt"))
Arguments.of("/aa/../bb/cc/../d.txt", Arrays.asList("bb", "d.txt")),
Arguments.of(".", Collections.emptyList()),
Arguments.of("./", Collections.emptyList()),
Arguments.of("", Collections.emptyList()),
Arguments.of("aa", Collections.singletonList("aa")),
Arguments.of(null, Collections.emptyList())
);
}

static Stream<Arguments> invalidPathsData() {
return Stream.of(
Arguments.of(".", instanceOf(IllegalArgumentException.class)),
Arguments.of("", instanceOf(IllegalArgumentException.class)),
Arguments.of(null, instanceOf(IllegalArgumentException.class)),
Arguments.of("./", instanceOf(IllegalArgumentException.class)),
Arguments.of("aa", instanceOf(IllegalArgumentException.class)),
// Arguments.of(".", instanceOf(IllegalArgumentException.class)),
// Arguments.of("", instanceOf(IllegalArgumentException.class)),
// Arguments.of(null, instanceOf(IllegalArgumentException.class)),
// Arguments.of("./", instanceOf(IllegalArgumentException.class)),
// Arguments.of("aa", instanceOf(IllegalArgumentException.class)),
Arguments.of("/aa/../../bb.txt", instanceOf(IllegalArgumentException.class)),
Arguments.of("../", instanceOf(IllegalArgumentException.class))
);
Expand All @@ -46,7 +51,7 @@ static Stream<Arguments> invalidPathsData() {
@ParameterizedTest
@MethodSource({"validPathsData"})
void validPaths(String input, List<String> result) {
SFTPPath path = new SFTPPath(null, input);
SFTPPath path = new SFTPPath(SFTPHost.SFTP_TEST_HOST, input);

assertThat(path, hasProperty("parts", is(equalTo(result))));
}
Expand All @@ -55,7 +60,7 @@ void validPaths(String input, List<String> result) {
@MethodSource({"invalidPathsData"})
void invalidPaths(String input, Matcher<Exception> expectedException) {
try {
SFTPPath path = new SFTPPath(null, input);
SFTPPath path = new SFTPPath(SFTPHost.SFTP_TEST_HOST, input);

fail("Call with '" + input + "' should have failed.");
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ public SftpgoContainer(String testDataResource) {
.withClasspathResourceMapping(testDataResource, "/srv/sftpgo", BindMode.READ_WRITE)
.withClasspathResourceMapping("/sftpgo-config", "/var/lib/sftpgo", BindMode.READ_WRITE)
.withExposedPorts(WEBDAV_PORT, SFTP_PORT)
.waitingFor(Wait.forHttp("/").forPort(WEBDAV_PORT).withBasicCredentials(USERNAME, PASSWORD).forStatusCode(207));
.waitingFor(Wait.forHttp("/").forPort(WEBDAV_PORT).withBasicCredentials(USERNAME, PASSWORD).forStatusCode(207))
.waitingFor(Wait.forListeningPorts(SFTP_PORT))
;
}

@SuppressWarnings("java:S112")
Expand Down

0 comments on commit 558c4b0

Please sign in to comment.