Squashed commit of not-yet-approved PR #2038

https://github.com/PhilippC/keepass2android/pull/2038
User-defined SFTP connection timeout

The addition of SFTP query parameter options are needed
to support custom private key functionality.

Squashed commits from hyproman:sftp-conn-timeout:

commit 9c6b96e8198f1b912acdc1248af775f8fed58e1c
commit ebe59d9bc337a46bf0646677eb38b13ddde21f14
commit 69eb0bfd1a7010a2e442c36d10a16d1710c958de
commit 9394947c12bedb8667b7b94d0b1457f9e0451e18
This commit is contained in:
Rick Brown
2023-02-15 19:08:14 -05:00
parent 76107b1207
commit 15b3b76b27
13 changed files with 353 additions and 102 deletions

View File

@@ -97,7 +97,28 @@ public class FileEntry {
return false; return false;
return true; return true;
} }
@Override
public String toString() {
StringBuilder s = new StringBuilder("JavaFileStorage.FileEntry{").append(displayName).append("|")
.append("path=").append(path).append(",sz=").append(sizeInBytes)
.append(",").append(isDirectory ? "dir" : "file")
.append(",lastMod=").append(lastModifiedTime);
StringBuilder perms = new StringBuilder();
if (canRead)
perms.append("r");
if (canWrite)
perms.append("w");
if (perms.length() > 0) {
s.append(",").append(perms);
}
if (userData != null && userData.length() > 0)
s.append(",userData=").append(userData);
return s.append("}").toString();
}
} }

View File

@@ -8,7 +8,12 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import com.jcraft.jsch.Channel; import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp; import com.jcraft.jsch.ChannelSftp;
@@ -28,6 +33,9 @@ import android.os.Bundle;
public class SftpStorage extends JavaFileStorageBase { public class SftpStorage extends JavaFileStorageBase {
public static final int DEFAULT_SFTP_PORT = 22; public static final int DEFAULT_SFTP_PORT = 22;
public static final int UNSET_SFTP_CONNECT_TIMEOUT = -1;
private static final String SFTP_CONNECT_TIMEOUT_OPTION_NAME = "connectTimeout";
JSch jsch; JSch jsch;
public class ConnectionInfo public class ConnectionInfo
@@ -37,7 +45,33 @@ public class SftpStorage extends JavaFileStorageBase {
public String password; public String password;
public String localPath; public String localPath;
public int port; public int port;
public int connectTimeoutSec = UNSET_SFTP_CONNECT_TIMEOUT;
public String toString() {
return "ConnectionInfo{host=" + host + ",port=" + port + ",user=" + username +
",pwd=<hidden>,path=" + localPath + ",connectTimeout=" + connectTimeoutSec +
"}";
}
boolean hasOptions() {
return connectTimeoutSec != UNSET_SFTP_CONNECT_TIMEOUT;
}
} }
private static Map<String, String> buildOptionMap(ConnectionInfo ci) {
return buildOptionMap(ci.connectTimeoutSec);
}
private static Map<String, String> buildOptionMap(int connectTimeoutSec) {
Map<String, String> opts = new HashMap<>();
if (connectTimeoutSec != UNSET_SFTP_CONNECT_TIMEOUT) {
opts.put(SFTP_CONNECT_TIMEOUT_OPTION_NAME, String.valueOf(connectTimeoutSec));
}
return opts;
}
Context _appContext; Context _appContext;
public SftpStorage(Context appContext) { public SftpStorage(Context appContext) {
@@ -64,15 +98,15 @@ public class SftpStorage extends JavaFileStorageBase {
@Override @Override
public InputStream openFileForRead(String path) throws Exception { public InputStream openFileForRead(String path) throws Exception {
ConnectionInfo cInfo = splitStringToConnectionInfo(path);
ChannelSftp c = init(path); ChannelSftp c = init(cInfo);
try { try {
byte[] buff = new byte[8000]; byte[] buff = new byte[8000];
int bytesRead = 0; int bytesRead = 0;
InputStream in = c.get(extractSessionPath(path)); InputStream in = c.get(cInfo.localPath);
ByteArrayOutputStream bao = new ByteArrayOutputStream(); ByteArrayOutputStream bao = new ByteArrayOutputStream();
while ((bytesRead = in.read(buff)) != -1) { while ((bytesRead = in.read(buff)) != -1) {
@@ -104,14 +138,15 @@ public class SftpStorage extends JavaFileStorageBase {
public void uploadFile(String path, byte[] data, boolean writeTransactional) public void uploadFile(String path, byte[] data, boolean writeTransactional)
throws Exception { throws Exception {
ChannelSftp c = init(path); ConnectionInfo cInfo = splitStringToConnectionInfo(path);
ChannelSftp c = init(cInfo);
try { try {
InputStream in = new ByteArrayInputStream(data); InputStream in = new ByteArrayInputStream(data);
String targetPath = extractSessionPath(path); String targetPath = cInfo.localPath;
if (writeTransactional) if (writeTransactional)
{ {
//upload to temporary location: //upload to temporary location:
String tmpPath = targetPath+".tmp"; String tmpPath = targetPath + ".tmp";
c.put(in, tmpPath); c.put(in, tmpPath);
//remove previous file: //remove previous file:
try try
@@ -127,9 +162,9 @@ public class SftpStorage extends JavaFileStorageBase {
} }
else else
{ {
c.put(in, targetPath); c.put(in, targetPath);
} }
tryDisconnect(c); tryDisconnect(c);
} catch (Exception e) { } catch (Exception e) {
tryDisconnect(c); tryDisconnect(c);
@@ -141,53 +176,95 @@ public class SftpStorage extends JavaFileStorageBase {
@Override @Override
public String createFolder(String parentPath, String newDirName) public String createFolder(String parentPath, String newDirName)
throws Exception { throws Exception {
ConnectionInfo cInfo = splitStringToConnectionInfo(parentPath);
try { try {
ChannelSftp c = init(parentPath); ChannelSftp c = init(cInfo);
String newPath = concatPaths(parentPath, newDirName); String newPath = concatPaths(cInfo.localPath, newDirName);
c.mkdir(extractSessionPath(newPath)); c.mkdir(newPath);
tryDisconnect(c); tryDisconnect(c);
return newPath;
return buildFullPath(cInfo.host, cInfo.port, newPath,
cInfo.username, cInfo.password, cInfo.connectTimeoutSec);
} catch (Exception e) { } catch (Exception e) {
throw convertException(e); throw convertException(e);
} }
} }
private String extractUserPwdHostPort(String path) {
String withoutProtocol = path
.substring(getProtocolPrefix().length());
return withoutProtocol.substring(0, withoutProtocol.indexOf("/"));
}
private String extractSessionPath(String newPath) { private String extractSessionPath(String newPath) {
String withoutProtocol = newPath String withoutProtocol = newPath
.substring(getProtocolPrefix().length()); .substring(getProtocolPrefix().length());
return withoutProtocol.substring(withoutProtocol.indexOf("/")); int pathStartIdx = withoutProtocol.indexOf("/");
int pathEndIdx = withoutProtocol.indexOf("?");
if (pathEndIdx < 0) {
pathEndIdx = withoutProtocol.length();
}
return withoutProtocol.substring(pathStartIdx, pathEndIdx);
} }
private String extractUserPwdHost(String path) { private Map<String, String> extractOptionsMap(String path) throws UnsupportedEncodingException {
String withoutProtocol = path String withoutProtocol = path
.substring(getProtocolPrefix().length()); .substring(getProtocolPrefix().length());
return withoutProtocol.substring(0,withoutProtocol.indexOf("/"));
Map<String, String> options = new HashMap<>();
int extraOptsIdx = withoutProtocol.indexOf("?");
if (extraOptsIdx > 0 && extraOptsIdx + 1 < withoutProtocol.length()) {
String optsString = withoutProtocol.substring(extraOptsIdx + 1);
String[] parts = optsString.split("&");
for (String p : parts) {
int sepIdx = p.indexOf('=');
if (sepIdx > 0) {
String key = decode(p.substring(0, sepIdx));
String value = decode(p.substring(sepIdx + 1));
options.put(key, value);
} else {
options.put(decode(p), "true");
}
}
}
return options;
} }
private String concatPaths(String parentPath, String newDirName) { private String concatPaths(String parentPath, String newDirName) {
String res = parentPath; StringBuilder fp = new StringBuilder(parentPath);
if (!res.endsWith("/")) if (!parentPath.endsWith("/"))
res += "/"; fp.append("/");
res += newDirName; return fp.append(newDirName).toString();
return res;
} }
@Override @Override
public String createFilePath(String parentPath, String newFileName) public String createFilePath(final String parentUri, String newFileName)
throws Exception { throws Exception {
if (parentPath.endsWith("/") == false)
parentPath += "/"; String parentPath = parentUri;
return parentPath + newFileName; String params = null;
int paramsIdx = parentUri.lastIndexOf("?");
if (paramsIdx > 0) {
params = parentUri.substring(paramsIdx);
parentPath = parentPath.substring(0, paramsIdx);
}
String newPath = concatPaths(parentPath, newFileName);
if (params != null) {
newPath += params;
}
return newPath;
} }
@Override @Override
public List<FileEntry> listFiles(String parentPath) throws Exception { public List<FileEntry> listFiles(String parentPath) throws Exception {
ConnectionInfo cInfo = splitStringToConnectionInfo(parentPath);
ChannelSftp c = init(cInfo);
ChannelSftp c = init(parentPath);
return listFiles(parentPath, c); return listFiles(parentPath, c);
} }
private void setFromAttrs(FileEntry fileEntry, SftpATTRS attrs) { private void setFromAttrs(FileEntry fileEntry, SftpATTRS attrs) {
@@ -211,23 +288,27 @@ public class SftpStorage extends JavaFileStorageBase {
if (sftpEx.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) if (sftpEx.id == ChannelSftp.SSH_FX_NO_SUCH_FILE)
return new FileNotFoundException(sftpEx.getMessage()); return new FileNotFoundException(sftpEx.getMessage());
} }
return e; return e;
} }
@Override @Override
public FileEntry getFileEntry(String filename) throws Exception { public FileEntry getFileEntry(String filename) throws Exception {
ConnectionInfo cInfo = splitStringToConnectionInfo(filename);
ChannelSftp c = init(filename); ChannelSftp c = init(cInfo);
try { try {
FileEntry fileEntry = new FileEntry(); FileEntry fileEntry = new FileEntry();
String sessionPath = extractSessionPath(filename); SftpATTRS attr = c.stat(cInfo.localPath);
SftpATTRS attr = c.stat(sessionPath);
setFromAttrs(fileEntry, attr); setFromAttrs(fileEntry, attr);
// Full URI
fileEntry.path = filename; fileEntry.path = filename;
fileEntry.displayName = getFilename(sessionPath);
fileEntry.displayName = getFilename(cInfo.localPath);
tryDisconnect(c); tryDisconnect(c);
return fileEntry; return fileEntry;
} catch (Exception e) { } catch (Exception e) {
logDebug("Exception in getFileEntry! " + e); logDebug("Exception in getFileEntry! " + e);
@@ -238,8 +319,9 @@ public class SftpStorage extends JavaFileStorageBase {
@Override @Override
public void delete(String path) throws Exception { public void delete(String path) throws Exception {
ConnectionInfo cInfo = splitStringToConnectionInfo(path);
ChannelSftp c = init(cInfo);
ChannelSftp c = init(path);
delete(path, c); delete(path, c);
} }
@@ -263,10 +345,11 @@ public class SftpStorage extends JavaFileStorageBase {
tryDisconnect(c); tryDisconnect(c);
throw convertException(e); throw convertException(e);
} }
} }
private List<FileEntry> listFiles(String path, ChannelSftp c) throws Exception { private List<FileEntry> listFiles(String path, ChannelSftp c) throws Exception {
try { try {
List<FileEntry> res = new ArrayList<FileEntry>(); List<FileEntry> res = new ArrayList<FileEntry>();
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@@ -282,7 +365,7 @@ public class SftpStorage extends JavaFileStorageBase {
||(lsEntry.getFilename().equals("..")) ||(lsEntry.getFilename().equals(".."))
) )
continue; continue;
FileEntry fileEntry = new FileEntry(); FileEntry fileEntry = new FileEntry();
fileEntry.displayName = lsEntry.getFilename(); fileEntry.displayName = lsEntry.getFilename();
fileEntry.path = createFilePath(path, fileEntry.displayName); fileEntry.path = createFilePath(path, fileEntry.displayName);
@@ -312,16 +395,15 @@ public class SftpStorage extends JavaFileStorageBase {
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
return java.net.URLDecoder.decode(encodedString, UTF_8); return java.net.URLDecoder.decode(encodedString, UTF_8);
} }
@Override @Override
protected String encode(final String unencoded) protected String encode(final String unencoded)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
return java.net.URLEncoder.encode(unencoded, UTF_8); return java.net.URLEncoder.encode(unencoded, UTF_8);
} }
ChannelSftp init(String filename) throws JSchException, UnsupportedEncodingException { ChannelSftp init(ConnectionInfo cInfo) throws JSchException, UnsupportedEncodingException {
jsch = new JSch(); jsch = new JSch();
ConnectionInfo ci = splitStringToConnectionInfo(filename);
String base_dir = getBaseDir(); String base_dir = getBaseDir();
jsch.setKnownHosts(base_dir + "/known_hosts"); jsch.setKnownHosts(base_dir + "/known_hosts");
@@ -340,13 +422,13 @@ public class SftpStorage extends JavaFileStorageBase {
} }
Session session = jsch.getSession(ci.username, ci.host, ci.port); Session session = jsch.getSession(cInfo.username, cInfo.host, cInfo.port);
UserInfo ui = new SftpUserInfo(ci.password,_appContext); UserInfo ui = new SftpUserInfo(cInfo.password, _appContext);
session.setUserInfo(ui); session.setUserInfo(ui);
session.setConfig("PreferredAuthentications", "publickey,password"); session.setConfig("PreferredAuthentications", "publickey,password");
session.connect(); sessionConnect(session, cInfo);
Channel channel = session.openChannel("sftp"); Channel channel = session.openChannel("sftp");
channel.connect(); channel.connect();
@@ -357,6 +439,14 @@ public class SftpStorage extends JavaFileStorageBase {
} }
private void sessionConnect(Session session, ConnectionInfo ci) throws JSchException {
if (ci.connectTimeoutSec != UNSET_SFTP_CONNECT_TIMEOUT) {
session.connect(ci.connectTimeoutSec * 1000);
} else {
session.connect();
}
}
private String getBaseDir() { private String getBaseDir() {
return _appContext.getFilesDir().getAbsolutePath(); return _appContext.getFilesDir().getAbsolutePath();
} }
@@ -389,7 +479,7 @@ public class SftpStorage extends JavaFileStorageBase {
public ConnectionInfo splitStringToConnectionInfo(String filename) public ConnectionInfo splitStringToConnectionInfo(String filename)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
ConnectionInfo ci = new ConnectionInfo(); ConnectionInfo ci = new ConnectionInfo();
ci.host = extractUserPwdHost(filename); ci.host = extractUserPwdHostPort(filename);
String userPwd = ci.host.substring(0, ci.host.indexOf('@')); String userPwd = ci.host.substring(0, ci.host.indexOf('@'));
ci.username = decode(userPwd.substring(0, userPwd.indexOf(":"))); ci.username = decode(userPwd.substring(0, userPwd.indexOf(":")));
ci.password = decode(userPwd.substring(userPwd.indexOf(":")+1)); ci.password = decode(userPwd.substring(userPwd.indexOf(":")+1));
@@ -398,10 +488,21 @@ public class SftpStorage extends JavaFileStorageBase {
int portSeparatorIndex = ci.host.indexOf(":"); int portSeparatorIndex = ci.host.indexOf(":");
if (portSeparatorIndex >= 0) if (portSeparatorIndex >= 0)
{ {
ci.port = Integer.parseInt(ci.host.substring(portSeparatorIndex+1)); ci.port = Integer.parseInt(ci.host.substring(portSeparatorIndex + 1));
ci.host = ci.host.substring(0, portSeparatorIndex); ci.host = ci.host.substring(0, portSeparatorIndex);
} }
ci.localPath = extractSessionPath(filename); ci.localPath = extractSessionPath(filename);
Map<String, String> options = extractOptionsMap(filename);
if (options.containsKey(SFTP_CONNECT_TIMEOUT_OPTION_NAME)) {
String optVal = options.get(SFTP_CONNECT_TIMEOUT_OPTION_NAME);
try {
ci.connectTimeoutSec = Integer.parseInt(optVal);
} catch (NumberFormatException nan) {
logDebug(SFTP_CONNECT_TIMEOUT_OPTION_NAME + " option not a number: " + optVal);
}
}
return ci; return ci;
} }
@@ -438,12 +539,20 @@ public class SftpStorage extends JavaFileStorageBase {
try try
{ {
ConnectionInfo ci = splitStringToConnectionInfo(path); ConnectionInfo ci = splitStringToConnectionInfo(path);
return getProtocolPrefix()+ci.username+"@"+ci.host+ci.localPath; StringBuilder dName = new StringBuilder(getProtocolPrefix())
.append(ci.username)
.append("@")
.append(ci.host)
.append(ci.localPath);
if (ci.hasOptions()) {
appendOptions(dName, buildOptionMap(ci));
}
return dName.toString();
} }
catch (Exception e) catch (Exception e)
{ {
return extractSessionPath(path); return extractSessionPath(path);
} }
} }
@Override @Override
@@ -465,22 +574,65 @@ public class SftpStorage extends JavaFileStorageBase {
@Override @Override
public void onActivityResult(FileStorageSetupActivity activity, public void onActivityResult(FileStorageSetupActivity activity,
int requestCode, int resultCode, Intent data) { int requestCode, int resultCode, Intent data) {
} }
public String buildFullPath( String host, int port, String localPath, String username, String password) throws UnsupportedEncodingException public String buildFullPath(String host, int port, String localPath,
{ String username, String password,
if (port != DEFAULT_SFTP_PORT) int connectTimeoutSec)
host += ":"+String.valueOf(port); throws UnsupportedEncodingException {
return getProtocolPrefix()+encode(username)+":"+encode(password)+"@"+host+localPath; StringBuilder uri = new StringBuilder(getProtocolPrefix())
.append(encode(username)).append(":").append(encode(password))
.append("@").append(host);
if (port != DEFAULT_SFTP_PORT) {
uri.append(":").append(port);
}
if (localPath != null && localPath.startsWith("/")) {
uri.append(localPath);
}
Map<String, String> opts = buildOptionMap(connectTimeoutSec);
appendOptions(uri, opts);
return uri.toString();
}
private void appendOptions(StringBuilder uri, Map<String, String> opts)
throws UnsupportedEncodingException {
boolean first = true;
// Sort for stability/consistency
Set<Map.Entry<String, String>> sortedEntries = new TreeSet<>(new EntryComparator<>());
sortedEntries.addAll(opts.entrySet());
for (Map.Entry<String, String> me : sortedEntries) {
if (first) {
uri.append("?");
first = false;
} else {
uri.append("&");
}
uri.append(encode(me.getKey())).append("=").append(encode(me.getValue()));
}
} }
@Override @Override
public void prepareFileUsage(Context appContext, String path) { public void prepareFileUsage(Context appContext, String path) {
//nothing to do //nothing to do
}
/**
* A comparator that compares Map.Entry objects by their keys, via natural ordering.
*
* @param <T> the Map.Entry key type, that must implement Comparable.
*/
private static class EntryComparator<T extends Comparable<T>> implements Comparator<Map.Entry<T, ?>> {
@Override
public int compare(Map.Entry<T, ?> o1, Map.Entry<T, ?> o2) {
return o1.getKey().compareTo(o2.getKey());
}
} }
} }

View File

@@ -538,12 +538,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
} }
static JavaFileStorage createStorageToTest(Context ctx, Context appContext, boolean simulateRestart) { static JavaFileStorage createStorageToTest(Context ctx, Context appContext, boolean simulateRestart) {
//storageToTest = new SftpStorage(ctx.getApplicationContext()); storageToTest = new SftpStorage(ctx.getApplicationContext());
//storageToTest = new PCloudFileStorage(ctx, "yCeH59Ffgtm"); //storageToTest = new PCloudFileStorage(ctx, "yCeH59Ffgtm");
//storageToTest = new SkyDriveFileStorage("000000004010C234", appContext); //storageToTest = new SkyDriveFileStorage("000000004010C234", appContext);
storageToTest = new GoogleDriveAppDataFileStorage(); //storageToTest = new GoogleDriveAppDataFileStorage();
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() { /*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
@Override @Override
public boolean onValidationError(String error) { public boolean onValidationError(String error) {
@@ -743,7 +743,19 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
int port = Integer.parseInt(etPort.getText().toString()); int port = Integer.parseInt(etPort.getText().toString());
EditText etInitDir = ((EditText)view.findViewById(R.id.sftp_initial_dir)); EditText etInitDir = ((EditText)view.findViewById(R.id.sftp_initial_dir));
String initialDir = etInitDir.getText().toString(); String initialDir = etInitDir.getText().toString();
onReceivePathForFileSelect(requestCode, sftpStorage.buildFullPath( host, port, initialDir, user, pwd)); EditText etConnectTimeout = ((EditText)view.findViewById(R.id.sftp_connect_timeout));
int connectTimeout = SftpStorage.UNSET_SFTP_CONNECT_TIMEOUT;
String ctStr = etConnectTimeout.getText().toString();
if (!ctStr.isEmpty()) {
try {
int ct = Integer.parseInt(ctStr);
if (connectTimeout != ct) {
connectTimeout = ct;
}
} catch (NumberFormatException parseEx) {
}
}
onReceivePathForFileSelect(requestCode, sftpStorage.buildFullPath( host, port, initialDir, user, pwd, connectTimeout));
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();

View File

@@ -13,6 +13,7 @@
android:id="@+id/sftp_host" android:id="@+id/sftp_host"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true" android:singleLine="true"
android:inputType="textNoSuggestions" android:inputType="textNoSuggestions"
android:text="" android:text=""
@@ -26,9 +27,10 @@
android:id="@+id/sftp_port" android:id="@+id/sftp_port"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="4"
android:singleLine="true" android:singleLine="true"
android:inputType="number" android:inputType="number"
android:text="22" android:text="22"
android:hint="@string/hint_sftp_port" /> android:hint="@string/hint_sftp_port" />
</LinearLayout> </LinearLayout>
<EditText <EditText
@@ -63,6 +65,14 @@
android:singleLine="true" android:singleLine="true"
android:text="/home/philipp" android:text="/home/philipp"
/> />
<EditText
android:id="@+id/sftp_connect_timeout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number"
android:text=""
android:hint="@string/hint_sftp_connect_timeout" />
<Button android:id="@+id/send_public_key" <Button android:id="@+id/send_public_key"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -333,6 +333,7 @@
<string name="hint_sftp_host">host</string> <string name="hint_sftp_host">host</string>
<string name="hint_sftp_port">port</string> <string name="hint_sftp_port">port</string>
<string name="hint_sftp_connect_timeout">connection timeout seconds</string>
<string name="select_storage_type">Select the storage type:</string> <string name="select_storage_type">Select the storage type:</string>

View File

@@ -221,7 +221,6 @@ public class FileChooserActivity extends FragmentActivity {
public static final String EXTRA_RESULT_FILE_EXISTS = CLASSNAME + ".result_file_exists"; public static final String EXTRA_RESULT_FILE_EXISTS = CLASSNAME + ".result_file_exists";
/* /*
* CONTROLS * CONTROLS

View File

@@ -269,7 +269,6 @@ public class FragmentFiles extends Fragment implements
FileChooserActivity.EXTRA_MAX_FILE_COUNT, 1000); FileChooserActivity.EXTRA_MAX_FILE_COUNT, 1000);
mFileAdapter = new BaseFileAdapter(getActivity(), mFilterMode, mFileAdapter = new BaseFileAdapter(getActivity(), mFilterMode,
mIsMultiSelection); mIsMultiSelection);
/* /*
* History. * History.
@@ -2268,12 +2267,15 @@ public class FragmentFiles extends Fragment implements
} }
if (mIsSaveDialog) { if (mIsSaveDialog) {
mTextSaveas.setText(BaseFileProviderUtils.getFileName(cursor)); String fileName = BaseFileProviderUtils.getFileName(cursor);
Uri uri = BaseFileProviderUtils.getUri(cursor);
mTextSaveas.setText(fileName);
/* /*
* Always set tag after setting text, or tag will be reset to * Always set tag after setting text, or tag will be reset to
* null. * null.
*/ */
mTextSaveas.setTag(BaseFileProviderUtils.getUri(cursor)); mTextSaveas.setTag(uri);
} }
if (mDoubleTapToChooseFiles) { if (mDoubleTapToChooseFiles) {
@@ -2286,10 +2288,12 @@ public class FragmentFiles extends Fragment implements
if (mIsMultiSelection) if (mIsMultiSelection)
return; return;
if (mIsSaveDialog) if (mIsSaveDialog) {
checkSaveasFilenameAndFinish(); checkSaveasFilenameAndFinish();
else }
else {
finish(BaseFileProviderUtils.getUri(cursor)); finish(BaseFileProviderUtils.getUri(cursor));
}
}// single tap to choose files }// single tap to choose files
}// onItemClick() }// onItemClick()
};// mViewFilesOnItemClickListener };// mViewFilesOnItemClickListener

View File

@@ -15,4 +15,24 @@ public class FileEntry {
isDirectory = false; isDirectory = false;
canRead = canWrite = true; canRead = canWrite = true;
} }
@Override
public String toString() {
StringBuilder s = new StringBuilder("kp2afilechooser.FileEntry{")
.append(displayName).append("|")
.append("path=").append(path).append(",sz=").append(sizeInBytes)
.append(",").append(isDirectory ? "dir" : "file")
.append(",lastMod=").append(lastModifiedTime);
StringBuilder perms = new StringBuilder();
if (canRead)
perms.append("r");
if (canWrite)
perms.append("w");
if (perms.length() > 0) {
s.append(",").append(perms);
}
return s.append("}").toString();
}
} }

View File

@@ -20,6 +20,7 @@ public class Kp2aFileChooserBridge {
.buildUpon() .buildUpon()
.appendPath(defaultPath) .appendPath(defaultPath)
.build()); .build());
return intent; return intent;
} }
} }

View File

@@ -306,10 +306,9 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
String parentPath = getParentPath(path); String parentPath = getParentPath(path);
if (parentPath == null) if (parentPath == null) {
{
if (Utils.doLog()) if (Utils.doLog())
Log.d(CLASSNAME, "parent file is null"); Log.d(CLASSNAME, "parent file is null");
return null; return null;
} }
FileEntry e; FileEntry e;
@@ -501,10 +500,10 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
RowBuilder newRow = matrixCursor.newRow(); RowBuilder newRow = matrixCursor.newRow();
newRow.add(id);// _ID newRow.add(id);// _ID
newRow.add(BaseFile newRow.add(BaseFile
.genContentIdUriBase( .genContentIdUriBase(
getAuthority()) getAuthority())
.buildUpon().appendPath(f.path) .buildUpon().appendPath(f.path)
.build().toString()); .build().toString());
newRow.add(f.path); newRow.add(f.path);
if (f.displayName == null) if (f.displayName == null)
Log.w("KP2AJ", "displayName is null for " + f.path); Log.w("KP2AJ", "displayName is null for " + f.path);
@@ -549,7 +548,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
//puts the file entry in the cache for later reuse with retrieveFileInfo //puts the file entry in the cache for later reuse with retrieveFileInfo
private void updateFileEntryCache(FileEntry f) { private void updateFileEntryCache(FileEntry f) {
if (f != null) if (f != null)
fileEntryMap.put(f.path, f); fileEntryMap.put(f.path, f);
} }
//removes the file entry from the cache (if cached). Should be called whenever the file changes //removes the file entry from the cache (if cached). Should be called whenever the file changes
private void removeFromCache(String filename, boolean recursive) { private void removeFromCache(String filename, boolean recursive) {
@@ -584,7 +583,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
//returns the file entry from the cache if present or queries the concrete provider method to return the file info //returns the file entry from the cache if present or queries the concrete provider method to return the file info
private FileEntry getFileEntryCached(String filename) { private FileEntry getFileEntryCached(String filename) {
//check if enry is cached: //check if entry is cached:
FileEntry cachedEntry = fileEntryMap.get(filename); FileEntry cachedEntry = fileEntryMap.get(filename);
if (cachedEntry != null) if (cachedEntry != null)
{ {
@@ -728,7 +727,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
if (targetParent != null && targetParent.startsWith(source)) if (targetParent != null && targetParent.startsWith(source))
{ {
if (Utils.doLog()) if (Utils.doLog())
Log.d("KP2A_FC_P", source+" is parent of "+target); Log.d("KP2A_FC_P", source + " is parent of " + target);
return BaseFileProviderUtils.newClosedCursor(); return BaseFileProviderUtils.newClosedCursor();
} }
if (Utils.doLog()) if (Utils.doLog())
@@ -768,28 +767,37 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
private String getParentPath(String path) private String getParentPath(String path)
{ {
path = removeTrailingSlash(path); String params = null;
if (path.indexOf("://") == -1) int paramsIdx = path.lastIndexOf("?");
{ if (paramsIdx > 0) {
Log.d("KP2A_FC_P", "invalid path: " + path); params = path.substring(paramsIdx);
return null; path = path.substring(0, paramsIdx);
} }
String pathWithoutProtocol = path.substring(path.indexOf("://")+3);
int lastSlashPos = path.lastIndexOf("/"); path = removeTrailingSlash(path);
if (pathWithoutProtocol.indexOf("/") == -1) if (path.indexOf("://") == -1)
{ {
Log.d("KP2A_FC_P", "parent of " + path +" is null"); Log.d("KP2A_FC_P", "invalid path: " + path);
return null; return null;
} }
else String pathWithoutProtocol = path.substring(path.indexOf("://") + 3);
{ int lastSlashPos = path.lastIndexOf("/");
String parent = path.substring(0, lastSlashPos)+"/"; if (pathWithoutProtocol.indexOf("/") == -1)
Log.d("KP2A_FC_P", "parent of " + path +" is "+parent); {
return parent; Log.d("KP2A_FC_P", "parent of " + path + " is null");
} return null;
}
else
{
String parent = path.substring(0, lastSlashPos) + "/";
if (params != null) {
parent += params;
}
Log.d("KP2A_FC_P", "parent of " + path +" is " + parent);
return parent;
}
} }
protected abstract FileEntry getFileEntry(String path, StringBuilder errorMessageBuilder) throws Exception; protected abstract FileEntry getFileEntry(String path, StringBuilder errorMessageBuilder) throws Exception;

View File

@@ -90,13 +90,17 @@ namespace keepass2android
if (!defaultPath.EndsWith(_schemeSeparator)) if (!defaultPath.EndsWith(_schemeSeparator))
{ {
var fileStorage = new Keepass2android.Javafilestorage.SftpStorage(activity.ApplicationContext); var fileStorage = new SftpStorage(activity.ApplicationContext);
SftpStorage.ConnectionInfo ci = fileStorage.SplitStringToConnectionInfo(defaultPath); SftpStorage.ConnectionInfo ci = fileStorage.SplitStringToConnectionInfo(defaultPath);
dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text = ci.Host; dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text = ci.Host;
dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text = ci.Port.ToString(); dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text = ci.Port.ToString();
dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text = ci.Username; dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text = ci.Username;
dlgContents.FindViewById<EditText>(Resource.Id.sftp_password).Text = ci.Password; dlgContents.FindViewById<EditText>(Resource.Id.sftp_password).Text = ci.Password;
dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text = ci.LocalPath; dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text = ci.LocalPath;
if (ci.ConnectTimeoutSec != SftpStorage.UnsetSftpConnectTimeout)
{
dlgContents.FindViewById<EditText>(Resource.Id.sftp_connect_timeout).Text = ci.ConnectTimeoutSec.ToString();
}
if (string.IsNullOrEmpty(ci.Password)) if (string.IsNullOrEmpty(ci.Password))
{ {
spinner.SetSelection(1); spinner.SetSelection(1);
@@ -109,7 +113,7 @@ namespace keepass2android
{ {
string host = dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text; string host = dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).Text;
string portText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text; string portText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_port).Text;
int port = Keepass2android.Javafilestorage.SftpStorage.DefaultSftpPort; int port = SftpStorage.DefaultSftpPort;
if (!string.IsNullOrEmpty(portText)) if (!string.IsNullOrEmpty(portText))
int.TryParse(portText, out port); int.TryParse(portText, out port);
string user = dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text; string user = dlgContents.FindViewById<EditText>(Resource.Id.sftp_user).Text;
@@ -117,8 +121,14 @@ namespace keepass2android
string initialPath = dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text; string initialPath = dlgContents.FindViewById<EditText>(Resource.Id.sftp_initial_dir).Text;
if (string.IsNullOrEmpty(initialPath)) if (string.IsNullOrEmpty(initialPath))
initialPath = "/"; initialPath = "/";
string sftpPath = new Keepass2android.Javafilestorage.SftpStorage(activity.ApplicationContext).BuildFullPath(host, port, initialPath, user, string connectTimeoutText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_connect_timeout).Text;
password); int connectTimeout = SftpStorage.UnsetSftpConnectTimeout;
if (!string.IsNullOrEmpty(connectTimeoutText)) {
int.TryParse(connectTimeoutText, out connectTimeout);
}
string sftpPath = new SftpStorage(activity.ApplicationContext)
.BuildFullPath(host, port, initialPath, user, password, connectTimeout);
onStartBrowse(sftpPath); onStartBrowse(sftpPath);
}); });
EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel()); EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());

View File

@@ -88,6 +88,18 @@
android:singleLine="true" android:singleLine="true"
android:text="/" android:text="/"
/> />
<TextView android:id="@+id/connect_timeout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dip"
android:layout_marginTop="4dip"
android:text="@string/connect_timeout" />
<EditText
android:id="@+id/sftp_connect_timeout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="number" />
</LinearLayout> </LinearLayout>

View File

@@ -586,6 +586,7 @@
<string name="hint_sftp_host">host (ex: 192.168.0.1)</string> <string name="hint_sftp_host">host (ex: 192.168.0.1)</string>
<string name="hint_sftp_port">port</string> <string name="hint_sftp_port">port</string>
<string name="initial_directory">Initial directory (optional):</string> <string name="initial_directory">Initial directory (optional):</string>
<string name="connect_timeout">Connection timeout seconds (optional):"</string>
<string name="enter_sftp_login_title">Enter SFTP login data:</string> <string name="enter_sftp_login_title">Enter SFTP login data:</string>
<string name="sftp_auth_mode">Authentication mode</string> <string name="sftp_auth_mode">Authentication mode</string>
<string name="send_public_key">Send public key...</string> <string name="send_public_key">Send public key...</string>