blob: a501a68f98f8b48a176b918d26cebf209359f34f [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.sshd.scp.common.helpers;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StreamCorruptedException;
import java.nio.file.AccessDeniedException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributeView;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFilePermission;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.apache.sshd.common.session.Session;
import org.apache.sshd.common.util.io.IoUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.scp.common.ScpFileOpener;
import org.apache.sshd.scp.common.ScpTargetStreamResolver;
/**
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class LocalFileScpTargetStreamResolver extends AbstractLoggingBean implements ScpTargetStreamResolver {
protected final Path path;
protected final ScpFileOpener opener;
protected final Boolean status;
private Path file;
public LocalFileScpTargetStreamResolver(Path path, ScpFileOpener opener) throws IOException {
LinkOption[] linkOptions = IoUtils.getLinkOptions(true);
this.status = IoUtils.checkFileExists(path, linkOptions);
if (status == null) {
throw new AccessDeniedException("Receive target file path existence status cannot be determined: " + path);
}
this.path = path;
this.opener = (opener == null) ? DefaultScpFileOpener.INSTANCE : opener;
}
@Override
public OutputStream resolveTargetStream(
Session session, String name, long length, Set<PosixFilePermission> perms, OpenOption... options)
throws IOException {
if (file != null) {
throw new StreamCorruptedException("resolveTargetStream(" + name + ")[" + perms + "] already resolved: " + file);
}
LinkOption[] linkOptions = IoUtils.getLinkOptions(true);
if (status && Files.isDirectory(path, linkOptions)) {
String localName = name.replace('/', File.separatorChar); // in case we are running on Windows
file = path.resolve(localName);
} else if (status && Files.isRegularFile(path, linkOptions)) {
file = path;
} else if (!status) {
Path parent = path.getParent();
Boolean parentStatus = IoUtils.checkFileExists(parent, linkOptions);
if (parentStatus == null) {
throw new AccessDeniedException(
"Receive file parent (" + parent + ") existence status cannot be determined for " + path);
}
if (parentStatus && Files.isDirectory(parent, linkOptions)) {
file = path;
}
}
if (file == null) {
throw new IOException("Can not write to " + path);
}
Boolean fileStatus = IoUtils.checkFileExists(file, linkOptions);
if (fileStatus == null) {
throw new AccessDeniedException("Receive file existence status cannot be determined: " + file);
}
if (fileStatus) {
if (Files.isDirectory(file, linkOptions)) {
throw new IOException("File is a directory: " + file);
}
if (!Files.isWritable(file)) {
throw new IOException("Can not write to file: " + file);
}
}
if (log.isTraceEnabled()) {
log.trace("resolveTargetStream({}): {}", name, file);
}
return opener.openWrite(session, file, length, perms, options);
}
@Override
public void closeTargetStream(
Session session, String name, long length, Set<PosixFilePermission> perms, OutputStream stream)
throws IOException {
if (log.isTraceEnabled()) {
log.trace("closeTargetStream({}): {}", name, file);
}
opener.closeWrite(session, file, length, perms, stream);
}
@Override
public Path getEventListenerFilePath() {
if (file == null) {
return path;
} else {
return file;
}
}
@Override
public void postProcessReceivedData(
String name, boolean preserve, Set<PosixFilePermission> perms, ScpTimestampCommandDetails time)
throws IOException {
if (file == null) {
throw new StreamCorruptedException(
"postProcessReceivedData(" + name + ")[" + perms + "] No currently resolved data");
}
if (preserve) {
updateFileProperties(name, file, perms, time);
}
}
protected void updateFileProperties(
String name, Path path, Set<PosixFilePermission> perms, ScpTimestampCommandDetails time)
throws IOException {
boolean traceEnabled = log.isTraceEnabled();
if (traceEnabled) {
log.trace("updateFileProperties({})[{}] permissions: {}", name, path, perms);
}
IoUtils.setPermissions(path, perms);
if (time != null) {
BasicFileAttributeView view = Files.getFileAttributeView(path, BasicFileAttributeView.class);
FileTime lastModified = FileTime.from(time.getLastModifiedTime(), TimeUnit.MILLISECONDS);
FileTime lastAccess = FileTime.from(time.getLastAccessTime(), TimeUnit.MILLISECONDS);
if (traceEnabled) {
log.trace("updateFileProperties({})[{}] last-modified={}, last-access={}", name, path, lastModified,
lastAccess);
}
view.setTimes(lastModified, lastAccess, null);
}
}
@Override
public String toString() {
return String.valueOf(getEventListenerFilePath());
}
}