keepass2android/src/java/JavaFileStorage/app/src/main/java/keepass2android/javafilestorage/SftpStorage.java

432 lines
10 KiB
Java

package keepass2android.javafilestorage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import com.jcraft.jsch.UserInfo;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class SftpStorage extends JavaFileStorageBase {
public static final int DEFAULT_SFTP_PORT = 22;
JSch jsch;
class ConnectionInfo
{
String host;
String username;
String password;
String localPath;
int port;
}
public SftpStorage() {
}
private static final String SFTP_PROTOCOL_ID = "sftp";
@Override
public boolean checkForFileChangeFast(String path,
String previousFileVersion) throws Exception {
String currentVersion = getCurrentFileVersionFast(path);
if (currentVersion == null)
return false;
return currentVersion.equals(previousFileVersion) == false;
}
@Override
public String getCurrentFileVersionFast(String path) {
return null; // no simple way to get the version "fast"
}
@Override
public InputStream openFileForRead(String path) throws Exception {
ChannelSftp c = init(path);
try {
byte[] buff = new byte[8000];
int bytesRead = 0;
InputStream in = c.get(extractSessionPath(path));
ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((bytesRead = in.read(buff)) != -1) {
bao.write(buff, 0, bytesRead);
}
byte[] data = bao.toByteArray();
ByteArrayInputStream bin = new ByteArrayInputStream(data);
c.getSession().disconnect();
return bin;
} catch (Exception e) {
tryDisconnect(c);
throw convertException(e);
}
}
private void tryDisconnect(ChannelSftp c) {
try {
c.getSession().disconnect();
} catch (JSchException je) {
}
}
@Override
public void uploadFile(String path, byte[] data, boolean writeTransactional)
throws Exception {
ChannelSftp c = init(path);
try {
InputStream in = new ByteArrayInputStream(data);
String targetPath = extractSessionPath(path);
if (writeTransactional)
{
//upload to temporary location:
String tmpPath = targetPath+".tmp";
c.put(in, tmpPath);
//remove previous file:
try
{
c.rm(targetPath);
}
catch (SftpException e)
{
//ignore. Can happen if file didn't exist before
}
//rename tmp to target path:
c.rename(tmpPath, targetPath);
}
else
{
c.put(in, targetPath);
}
tryDisconnect(c);
} catch (Exception e) {
tryDisconnect(c);
throw e;
}
}
@Override
public String createFolder(String parentPath, String newDirName)
throws Exception {
try {
ChannelSftp c = init(parentPath);
String newPath = concatPaths(parentPath, newDirName);
c.mkdir(extractSessionPath(newPath));
tryDisconnect(c);
return newPath;
} catch (Exception e) {
throw convertException(e);
}
}
private String extractSessionPath(String newPath) {
String withoutProtocol = newPath
.substring(getProtocolPrefix().length());
return withoutProtocol.substring(withoutProtocol.indexOf("/"));
}
private String extractUserPwdHost(String path) {
String withoutProtocol = path
.substring(getProtocolPrefix().length());
return withoutProtocol.substring(0,withoutProtocol.indexOf("/"));
}
private String concatPaths(String parentPath, String newDirName) {
String res = parentPath;
if (!res.endsWith("/"))
res += "/";
res += newDirName;
return res;
}
@Override
public String createFilePath(String parentPath, String newFileName)
throws Exception {
if (parentPath.endsWith("/") == false)
parentPath += "/";
return parentPath + newFileName;
}
@Override
public List<FileEntry> listFiles(String parentPath) throws Exception {
ChannelSftp c = init(parentPath);
return listFiles(parentPath, c);
}
private void setFromAttrs(FileEntry fileEntry, SftpATTRS attrs) {
fileEntry.isDirectory = attrs.isDir();
fileEntry.canRead = true; // currently not inferred from the
// permissions.
fileEntry.canWrite = true; // currently not inferred from the
// permissions.
fileEntry.lastModifiedTime = ((long) attrs.getMTime()) * 1000;
if (fileEntry.isDirectory)
fileEntry.sizeInBytes = 0;
else
fileEntry.sizeInBytes = attrs.getSize();
}
private Exception convertException(Exception e) {
if (SftpException.class.isAssignableFrom(e.getClass()) )
{
SftpException sftpEx = (SftpException)e;
if (sftpEx.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
return new FileNotFoundException(sftpEx.getMessage());
}
return e;
}
@Override
public FileEntry getFileEntry(String filename) throws Exception {
ChannelSftp c = init(filename);
try {
FileEntry fileEntry = new FileEntry();
String sessionPath = extractSessionPath(filename);
SftpATTRS attr = c.stat(sessionPath);
setFromAttrs(fileEntry, attr);
fileEntry.path = filename;
fileEntry.displayName = getFilename(sessionPath);
tryDisconnect(c);
return fileEntry;
} catch (Exception e) {
logDebug("Exception in getFileEntry! " + e);
tryDisconnect(c);
throw convertException(e);
}
}
@Override
public void delete(String path) throws Exception {
ChannelSftp c = init(path);
delete(path, c);
}
private void delete(String path, ChannelSftp c) throws Exception {
String sessionLocalPath = extractSessionPath(path);
try {
if (c.stat(sessionLocalPath).isDir())
{
List<FileEntry> contents = listFiles(path, c);
for (FileEntry fe: contents)
{
delete(fe.path, c);
}
c.rmdir(sessionLocalPath);
}
else
{
c.rm(sessionLocalPath);
}
} catch (Exception e) {
tryDisconnect(c);
throw convertException(e);
}
}
private List<FileEntry> listFiles(String path, ChannelSftp c) throws Exception {
try {
List<FileEntry> res = new ArrayList<FileEntry>();
@SuppressWarnings("rawtypes")
java.util.Vector vv = c.ls(extractSessionPath(path));
if (vv != null) {
for (int ii = 0; ii < vv.size(); ii++) {
Object obj = vv.elementAt(ii);
if (obj instanceof com.jcraft.jsch.ChannelSftp.LsEntry) {
LsEntry lsEntry = (com.jcraft.jsch.ChannelSftp.LsEntry) obj;
if ((lsEntry.getFilename().equals("."))
||(lsEntry.getFilename().equals(".."))
)
continue;
FileEntry fileEntry = new FileEntry();
fileEntry.displayName = lsEntry.getFilename();
fileEntry.path = createFilePath(path, fileEntry.displayName);
SftpATTRS attrs = lsEntry.getAttrs();
setFromAttrs(fileEntry, attrs);
res.add(fileEntry);
}
}
}
return res;
} catch (Exception e) {
tryDisconnect(c);
throw convertException(e);
}
}
@Override
public void startSelectFile(
JavaFileStorage.FileStorageSetupInitiatorActivity activity,
boolean isForSave, int requestCode) {
activity.performManualFileSelect(isForSave, requestCode, getProtocolId());
}
@Override
protected String decode(String encodedString)
throws UnsupportedEncodingException {
return java.net.URLDecoder.decode(encodedString, UTF_8);
}
@Override
protected String encode(final String unencoded)
throws UnsupportedEncodingException {
return java.net.URLEncoder.encode(unencoded, UTF_8);
}
ChannelSftp init(String filename) throws JSchException, UnsupportedEncodingException {
jsch = new JSch();
ConnectionInfo ci = splitStringToConnectionInfo(filename);
Session session = jsch.getSession(ci.username, ci.host, ci.port);
UserInfo ui = new SftpUserInfo(ci.password);
session.setUserInfo(ui);
session.connect();
Channel channel = session.openChannel("sftp");
channel.connect();
ChannelSftp c = (ChannelSftp) channel;
logDebug("success: init Sftp");
return c;
}
private ConnectionInfo splitStringToConnectionInfo(String filename)
throws UnsupportedEncodingException {
ConnectionInfo ci = new ConnectionInfo();
ci.host = extractUserPwdHost(filename);
String userPwd = ci.host.substring(0, ci.host.indexOf('@'));
ci.username = decode(userPwd.substring(0, userPwd.indexOf(":")));
ci.password = decode(userPwd.substring(userPwd.indexOf(":")+1));
ci.host = ci.host.substring(ci.host.indexOf('@') + 1);
ci.port = DEFAULT_SFTP_PORT;
int portSeparatorIndex = ci.host.indexOf(":");
if (portSeparatorIndex >= 0)
{
ci.port = Integer.parseInt(ci.host.substring(portSeparatorIndex+1));
ci.host = ci.host.substring(0, portSeparatorIndex);
}
return ci;
}
@Override
public void prepareFileUsage(JavaFileStorage.FileStorageSetupInitiatorActivity activity, String path, int requestCode, boolean alwaysReturnSuccess) {
Intent intent = new Intent();
intent.putExtra(EXTRA_PATH, path);
activity.onImmediateResult(requestCode, RESULT_FILEUSAGE_PREPARED, intent);
}
@Override
public String getProtocolId() {
return SFTP_PROTOCOL_ID;
}
@Override
public void onResume(JavaFileStorage.FileStorageSetupActivity setupAct) {
}
@Override
public boolean requiresSetup(String path) {
return false;
}
@Override
public void onCreate(FileStorageSetupActivity activity,
Bundle savedInstanceState) {
}
@Override
public String getDisplayName(String path) {
try
{
ConnectionInfo ci = splitStringToConnectionInfo(path);
return getProtocolPrefix()+ci.username+"@"+ci.host+extractSessionPath(path);
}
catch (Exception e)
{
return extractSessionPath(path);
}
}
@Override
public String getFilename(String path) throws Exception {
if (path.endsWith("/"))
path = path.substring(0, path.length()-1);
int lastIndex = path.lastIndexOf("/");
if (lastIndex >= 0)
return path.substring(lastIndex + 1);
else
return path;
}
@Override
public void onStart(FileStorageSetupActivity activity) {
}
@Override
public void onActivityResult(FileStorageSetupActivity activity,
int requestCode, int resultCode, Intent data) {
}
public String buildFullPath( String host, int port, String localPath, String username, String password) throws UnsupportedEncodingException
{
if (port != DEFAULT_SFTP_PORT)
host += ":"+String.valueOf(port);
return getProtocolPrefix()+encode(username)+":"+encode(password)+"@"+host+localPath;
}
@Override
public void prepareFileUsage(Context appContext, String path) {
//nothing to do
}
}