blob: 0ece66c0a9022557a054cf6223aba4201f4f9344 [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.common.file.nativefs;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.sshd.common.file.SshFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <strong>Internal class, do not use directly.</strong>
*
* This class wraps native file object.
*
* @author <a href="http://mina.apache.org">Apache MINA Project</a>
*/
public class NativeSshFile implements SshFile {
protected static final Logger LOG = LoggerFactory.getLogger(NativeSshFile.class);
// the file name with respect to the user root.
// The path separator character will be '/'.
protected String fileName;
protected File file;
protected String userName;
protected final NativeFileSystemView nativeFileSystemView;
/**
* Constructor, internal do not use directly.
* @param nativeFileSystemView
*/
protected NativeSshFile(final NativeFileSystemView nativeFileSystemView, final String fileName, final File file,
final String userName) {
this.nativeFileSystemView = nativeFileSystemView;
if (fileName == null) {
throw new IllegalArgumentException("fileName can not be null");
}
if (file == null) {
throw new IllegalArgumentException("file can not be null");
}
if (fileName.length() == 0) {
throw new IllegalArgumentException("fileName can not be empty");
}
this.fileName = fileName;
this.file = file;
this.userName = userName;
}
public File getNativeFile() {
return file;
}
/**
* Get full name.
*/
public String getAbsolutePath() {
char separator = nativeFileSystemView.getSeparator();
// strip the last '/' if necessary
String fullName = fileName;
int filelen = fullName.length();
if (fileName.indexOf(separator) != filelen - 1 && (fullName.charAt(filelen - 1) == separator)) {
fullName = fullName.substring(0, filelen - 1);
}
return fullName;
}
/**
* Get short name.
*/
public String getName() {
char separator = nativeFileSystemView.getSeparator();
// root - the short name will be '/'
if (fileName.indexOf(separator) == fileName.length() - 1) {
return fileName;
}
// strip the last '/'
String shortName = fileName;
int filelen = fileName.length();
if (shortName.charAt(filelen - 1) == separator) {
shortName = shortName.substring(0, filelen - 1);
}
// return from the last '/'
int slashIndex = shortName.lastIndexOf(separator);
if (slashIndex != -1) {
shortName = shortName.substring(slashIndex + 1);
}
return shortName;
}
/**
* Get owner name
*/
public String getOwner() {
return userName;
}
/**
* Is it a directory?
*/
public boolean isDirectory() {
return file.isDirectory();
}
/**
* Is it a file?
*/
public boolean isFile() {
return file.isFile();
}
/**
* Does this file exists?
*/
public boolean doesExist() {
return file.exists();
}
/**
* Get file size.
*/
public long getSize() {
return file.length();
}
/**
* Get last modified time.
*/
public long getLastModified() {
return file.lastModified();
}
/**
* {@inheritDoc}
*/
public boolean setLastModified(long time) {
return file.setLastModified(time);
}
/**
* Check read permission.
*/
public boolean isReadable() {
return file.canRead();
}
/**
* Check file write permission.
*/
public boolean isWritable() {
LOG.debug("Checking if file exists");
if (file.exists()) {
LOG.debug("Checking can write: " + file.canWrite());
return file.canWrite();
}
LOG.debug("Authorized");
return true;
}
/**
* File.canExecute() method is only available on JDK 1.6
*/
private static final Method CAN_EXECUTE_METHOD;
static {
Method method = null;
try {
method = File.class.getMethod("canExecute");
} catch (Throwable t) {
}
CAN_EXECUTE_METHOD = method;
}
/**
* Check file exec permission.
*/
public boolean isExecutable() {
if (CAN_EXECUTE_METHOD != null) {
try {
return (Boolean) CAN_EXECUTE_METHOD.invoke(file);
} catch (Throwable t) {
}
}
// Default directories to being executable
// as on unix systems to allow listing their contents.
return file.isDirectory();
}
/**
* Has delete permission.
*/
public boolean isRemovable() {
char separator = nativeFileSystemView.getSeparator();
// root cannot be deleted
if (fileName.indexOf(separator) == fileName.length() - 1) {
return false;
}
/* Added 12/08/2008: in the case that the permission is not explicitly denied for this file
* we will check if the parent file has write permission as most systems consider that a file can
* be deleted when their parent directory is writable.
*/
String fullName = getAbsolutePath();
// we check FTPServer's write permission for this file.
// if (user.authorize(new WriteRequest(fullName)) == null) {
// return false;
// }
// In order to maintain consistency, when possible we delete the last '/' character in the String
int indexOfSlash = fullName.lastIndexOf(separator);
String parentFullName;
if (indexOfSlash == 0) {
parentFullName = "/";
} else {
if (fullName.indexOf(separator) == indexOfSlash) {
parentFullName = fullName.substring(0, indexOfSlash + 1);
} else {
parentFullName = fullName.substring(0, indexOfSlash);
}
}
// we check if the parent FileObject is writable.
NativeSshFile parentObject = nativeFileSystemView.createNativeSshFile(parentFullName, file
.getAbsoluteFile().getParentFile(), userName);
return parentObject.isWritable();
}
public SshFile getParentFile() {
char separator = nativeFileSystemView.getSeparator();
String path = getAbsolutePath();
int indexOfSlash = path.lastIndexOf(separator);
String parentFullName;
if (indexOfSlash == 0) {
parentFullName = "/";
} else {
if (path.indexOf(separator) == indexOfSlash) {
parentFullName = path.substring(0, indexOfSlash + 1);
} else {
parentFullName = path.substring(0, indexOfSlash);
}
}
// we check if the parent FileObject is writable.
return nativeFileSystemView.createNativeSshFile(parentFullName, file
.getAbsoluteFile().getParentFile(), userName);
}
/**
* Delete file.
*/
public boolean delete() {
boolean retVal = false;
if (isRemovable()) {
retVal = file.delete();
}
return retVal;
}
/**
* Create a new file
*/
public boolean create() throws IOException {
return file.createNewFile();
}
/**
* Truncate file to length 0.
*/
public void truncate() throws IOException{
RandomAccessFile tempFile = new RandomAccessFile(file, "rw");
try {
tempFile.setLength(0);
} finally {
tempFile.close();
}
}
/**
* Move file object.
*/
public boolean move(final SshFile dest) {
boolean retVal = false;
if (dest.isWritable() && isReadable()) {
File destFile = ((NativeSshFile) dest).file;
if (destFile.exists()) {
// renameTo behaves differently on different platforms
// this check verifies that if the destination already exists,
// we fail
retVal = false;
} else {
retVal = file.renameTo(destFile);
}
}
return retVal;
}
/**
* Create directory.
*/
public boolean mkdir() {
boolean retVal = false;
if (isWritable()) {
retVal = file.mkdir();
}
return retVal;
}
/**
* List files. If not a directory or does not exist, null will be returned.
*/
public List<SshFile> listSshFiles() {
// is a directory
if (!file.isDirectory()) {
return null;
}
// directory - return all the files
File[] files = file.listFiles();
if (files == null) {
return null;
}
// make sure the files are returned in order
Arrays.sort(files, new Comparator<File>() {
public int compare(File f1, File f2) {
return f1.getName().compareTo(f2.getName());
}
});
char separator = nativeFileSystemView.getSeparator();
// get the virtual name of the base directory
String virtualFileStr = getAbsolutePath();
if (virtualFileStr.charAt(virtualFileStr.length() - 1) != separator) {
virtualFileStr += separator;
}
// now return all the files under the directory
SshFile[] virtualFiles = new SshFile[files.length];
for (int i = 0; i < files.length; ++i) {
File fileObj = files[i];
String fileName = virtualFileStr + fileObj.getName();
virtualFiles[i] = nativeFileSystemView.createNativeSshFile(fileName, fileObj, userName);
}
return Collections.unmodifiableList(Arrays.asList(virtualFiles));
}
/**
* Create output stream for writing.
*/
public OutputStream createOutputStream(final long offset)
throws IOException {
// permission check
if (!isWritable()) {
throw new IOException("No write permission : " + file.getName());
}
// move to the appropriate offset and create output stream
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
try {
raf.setLength(offset);
raf.seek(offset);
// The IBM jre needs to have both the stream and the random access file
// objects closed to actually close the file
return new FileOutputStream(raf.getFD()) {
public void close() throws IOException {
super.close();
raf.close();
}
};
} catch (IOException e) {
raf.close();
throw e;
}
}
/**
* Create input stream for reading.
*/
public InputStream createInputStream(final long offset) throws IOException {
// permission check
if (!isReadable()) {
throw new IOException("No read permission : " + file.getName());
}
// move to the appropriate offset and create input stream
final RandomAccessFile raf = new RandomAccessFile(file, "r");
try {
raf.seek(offset);
// The IBM jre needs to have both the stream and the random access file
// objects closed to actually close the file
return new FileInputStream(raf.getFD()) {
public void close() throws IOException {
super.close();
raf.close();
}
};
} catch (IOException e) {
raf.close();
throw e;
}
}
public void handleClose() {
// Noop
}
/**
* Normalize separate character. Separate character should be '/' always.
*/
public final static String normalizeSeparateChar(final String pathName) {
String normalizedPathName = pathName.replace('\\', '/');
return normalizedPathName;
}
/**
* Get the physical canonical file name. It works like
* File.getCanonicalPath().
*
* @param rootDir
* The root directory.
* @param currDir
* The current directory. It will always be with respect to the
* root directory.
* @param fileName
* The input file name.
* @return The return string will always begin with the root directory. It
* will never be null.
*/
public final static String getPhysicalName(final String rootDir,
final String currDir, final String fileName,
final boolean caseInsensitive) {
// get the starting directory
String normalizedRootDir = normalizeSeparateChar(rootDir);
if (normalizedRootDir.charAt(normalizedRootDir.length() - 1) != '/') {
normalizedRootDir += '/';
}
String normalizedFileName = normalizeSeparateChar(fileName);
String resArg;
String normalizedCurrDir = currDir;
if (normalizedFileName.charAt(0) != '/') {
if (normalizedCurrDir == null) {
normalizedCurrDir = "/";
}
if (normalizedCurrDir.length() == 0) {
normalizedCurrDir = "/";
}
normalizedCurrDir = normalizeSeparateChar(normalizedCurrDir);
if (normalizedCurrDir.charAt(0) != '/') {
normalizedCurrDir = '/' + normalizedCurrDir;
}
if (normalizedCurrDir.charAt(normalizedCurrDir.length() - 1) != '/') {
normalizedCurrDir += '/';
}
resArg = normalizedRootDir + normalizedCurrDir.substring(1);
} else {
resArg = normalizedRootDir;
}
// strip last '/'
if (resArg.charAt(resArg.length() - 1) == '/') {
resArg = resArg.substring(0, resArg.length() - 1);
}
// replace ., ~ and ..
// in this loop resArg will never end with '/'
StringTokenizer st = new StringTokenizer(normalizedFileName, "/");
while (st.hasMoreTokens()) {
String tok = st.nextToken();
// . => current directory
if (tok.equals(".")) {
continue;
}
// .. => parent directory (if not root)
if (tok.equals("..")) {
if (resArg.startsWith(normalizedRootDir)) {
int slashIndex = resArg.lastIndexOf('/');
if (slashIndex != -1) {
resArg = resArg.substring(0, slashIndex);
}
}
continue;
}
// ~ => home directory (in this case the root directory)
if (tok.equals("~")) {
resArg = normalizedRootDir.substring(0, normalizedRootDir
.length() - 1);
continue;
}
if (caseInsensitive) {
File[] matches = new File(resArg)
.listFiles(new NameEqualsFileFilter(tok, true));
if (matches != null && matches.length > 0) {
tok = matches[0].getName();
}
}
resArg = resArg + '/' + tok;
}
// add last slash if necessary
if ((resArg.length()) + 1 == normalizedRootDir.length()) {
resArg += '/';
}
// final check
if (!resArg.regionMatches(0, normalizedRootDir, 0, normalizedRootDir
.length())) {
resArg = normalizedRootDir;
}
return resArg;
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof NativeSshFile) {
File thisCanonicalFile;
File otherCanonicalFile;
try {
thisCanonicalFile = this.file.getCanonicalFile();
otherCanonicalFile = ((NativeSshFile) obj).file
.getCanonicalFile();
} catch (IOException e) {
throw new RuntimeException("Failed to get the canonical path", e);
}
return thisCanonicalFile.equals(otherCanonicalFile);
}
return false;
}
/**
* Returns the according physical file. Needed for logging, monitoring, event handling, etc.
*
* @return The according physical file.
*/
public File getPhysicalFile() {
return file;
}
@Override
public String toString() {
return fileName;
}
public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
Map<Attribute, Object> map = new HashMap<Attribute, Object>();
map.put(Attribute.Size, getSize());
map.put(Attribute.IsDirectory, isDirectory());
map.put(Attribute.IsRegularFile, isFile());
map.put(Attribute.IsSymbolicLink, false);
map.put(Attribute.LastModifiedTime, getLastModified());
map.put(Attribute.LastAccessTime, getLastModified());
map.put(Attribute.Owner, userName);
map.put(Attribute.Group, userName);
EnumSet<Permission> p = EnumSet.noneOf(Permission.class);
if (isReadable()) {
p.add(Permission.UserRead);
p.add(Permission.GroupRead);
p.add(Permission.OthersRead);
}
if (isWritable()) {
p.add(Permission.UserWrite);
p.add(Permission.GroupWrite);
p.add(Permission.OthersWrite);
}
if (isExecutable()) {
p.add(Permission.UserExecute);
p.add(Permission.GroupExecute);
p.add(Permission.OthersExecute);
}
map.put(Attribute.Permissions, p);
return map;
}
public void setAttributes(Map<Attribute, Object> attributes) throws IOException {
if (!attributes.isEmpty()) {
throw new UnsupportedOperationException();
}
}
public Object getAttribute(Attribute attribute, boolean followLinks) throws IOException {
return getAttributes(followLinks).get(attribute);
}
public void setAttribute(Attribute attribute, Object value) throws IOException {
Map<Attribute, Object> map = new HashMap<Attribute, Object>();
map.put(attribute, value);
setAttributes(map);
}
public String readSymbolicLink() throws IOException {
throw new UnsupportedOperationException();
}
public void createSymbolicLink(SshFile destination) throws IOException {
throw new UnsupportedOperationException();
}
}