Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/main/java/net/schmizz/sshj/xfer/FilePermission.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
*/
package net.schmizz.sshj.xfer;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.nio.file.attribute.PosixFilePermission;
import java.util.EnumSet;
import java.util.Set;

public enum FilePermission {
Expand Down Expand Up @@ -72,11 +71,11 @@ public boolean isIn(int mask) {
}

public static Set<FilePermission> fromMask(int mask) {
final List<FilePermission> perms = new LinkedList<FilePermission>();
final Set<FilePermission> perms = EnumSet.noneOf(FilePermission.class);
for (FilePermission p : FilePermission.values())
if (p.isIn(mask))
perms.add(p);
return new HashSet<FilePermission>(perms);
return perms;
}

public static int toMask(Set<FilePermission> perms) {
Expand All @@ -86,4 +85,28 @@ public static int toMask(Set<FilePermission> perms) {
return mask;
}

}
public static FilePermission of(PosixFilePermission posix) {
switch (posix) {
case GROUP_EXECUTE:
return GRP_X;
case GROUP_READ:
return GRP_R;
case GROUP_WRITE:
return GRP_W;
case OTHERS_EXECUTE:
return OTH_X;
case OTHERS_READ:
return OTH_R;
case OTHERS_WRITE:
return OTH_W;
case OWNER_EXECUTE:
return USR_X;
case OWNER_READ:
return USR_R;
case OWNER_WRITE:
return USR_W;
}
throw new IllegalArgumentException(String.valueOf(posix));
}

}
220 changes: 220 additions & 0 deletions src/main/java/net/schmizz/sshj/xfer/PathFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
/*
* 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 net.schmizz.sshj.xfer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
* A file implementation using {@link Path} (NIO API).
*/
public class PathFile
implements LocalSourceFile, LocalDestFile {

private final Path path;

public PathFile(String path) {
this(Paths.get(path));
}

public PathFile(Path path) {
this.path = path;
}

public Path getPath() {
return path;
}

@Override
public String getName() {
return path.getFileName().toString();
}

@Override
public boolean isFile() {
return Files.isRegularFile(path);
}

@Override
public boolean isDirectory() {
return Files.isDirectory(path);
}

public boolean exists() {
return Files.exists(path);
}

@Override
public long getLength() {
try {
return Files.size(path);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public InputStream getInputStream()
throws IOException {
return Files.newInputStream(path);
}

@Override
public OutputStream getOutputStream()
throws IOException {
return Files.newOutputStream(path);
}

@Override
public OutputStream getOutputStream(boolean append)
throws IOException {
return Files.newOutputStream(path, StandardOpenOption.APPEND);
}

@Override
public Iterable<PathFile> getChildren(final LocalFileFilter filter)
throws IOException {
try (Stream<Path> pathStream = Files.list(path)) {
return pathStream
.map(PathFile::new)
.filter(p -> filter == null || filter.accept(p))
.collect(Collectors.toList());
}
}

@Override
public boolean providesAtimeMtime() {
return true;
}

@Override
public long getLastAccessTime()
throws IOException {
return System.currentTimeMillis() / 1000;
}

@Override
public long getLastModifiedTime()
throws IOException {
return Files.getLastModifiedTime(path).to(TimeUnit.SECONDS);
}

@Override
public int getPermissions()
throws IOException {
Set<FilePermission> permissions = Files.getPosixFilePermissions(path).stream().map(FilePermission::of).collect(Collectors.toSet());
return FilePermission.toMask(permissions);
}

@Override
public void setLastAccessedTime(long t)
throws IOException {
// ...
}

@Override
public void setLastModifiedTime(long t)
throws IOException {
Files.setLastModifiedTime(path, FileTime.from(t, TimeUnit.SECONDS));
}

@Override
public void setPermissions(int perms)
throws IOException {
Set<FilePermission> permissions = FilePermission.fromMask(perms);
Set<PosixFilePermission> posix = Arrays.stream(PosixFilePermission.values())
.filter(p -> permissions.contains(FilePermission.of(p)))
.collect(Collectors.toSet());
Files.setPosixFilePermissions(path, posix);
}

@Override
public PathFile getChild(String name) {
Path resolved = path.resolve(name).normalize();
if (!resolved.startsWith(path)) {
throw new IllegalArgumentException("Cannot traverse higher than " + path + " to get child " + name);
}
return new PathFile(resolved);
}

@Override
public PathFile getTargetFile(String filename)
throws IOException {
PathFile f = this;

if (f.isDirectory()) {
f = f.getChild(filename);
}

try {
Files.createFile(f.getPath());
} catch (FileAlreadyExistsException ignore) {
}

return f;
}

@Override
public PathFile getTargetDirectory(String dirname)
throws IOException {
PathFile f = this;

if (f.exists()) {
if (f.isDirectory()) {
if (!f.getName().equals(dirname)) {
f = f.getChild(dirname);
}
} else {
throw new IOException(f + " - already exists as a file; directory required");
}
}

Files.createDirectories(f.getPath());

return f;
}

@Override
public boolean equals(Object other) {
return (other instanceof PathFile)
&& path.equals(((PathFile) other).path);
}

@Override
public int hashCode() {
return path.hashCode();
}

@Override
public String toString() {
return path.toString();
}

}
54 changes: 54 additions & 0 deletions src/test/groovy/net/schmizz/sshj/xfer/PathSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* 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 net.schmizz.sshj.xfer

import spock.lang.Specification

class PathSpec extends Specification {

def "should get child path"() {
given:
def file = new PathFile("foo")

when:
def child = file.getChild("bar")

then:
child.getName() == "bar"
}

def "should not traverse higher than original path when getChild is called"() {
given:
def file = new PathFile("foo")

when:
file.getChild("bar/.././foo/../../")

then:
thrown(IllegalArgumentException.class)
}

def "should ignore double slash (empty path component)"() {
given:
def file = new PathFile("foo")

when:
def child = file.getChild("bar//etc/passwd")

then:
child.getPath().toString().replace('\\', '/') endsWith "foo/bar/etc/passwd"
}
}