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:
@@ -97,7 +97,28 @@ public class FileEntry {
|
||||
return false;
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,12 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
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.ChannelSftp;
|
||||
@@ -28,6 +33,9 @@ import android.os.Bundle;
|
||||
public class SftpStorage extends JavaFileStorageBase {
|
||||
|
||||
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;
|
||||
|
||||
public class ConnectionInfo
|
||||
@@ -37,7 +45,33 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
public String password;
|
||||
public String localPath;
|
||||
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;
|
||||
|
||||
public SftpStorage(Context appContext) {
|
||||
@@ -64,15 +98,15 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
|
||||
@Override
|
||||
public InputStream openFileForRead(String path) throws Exception {
|
||||
|
||||
ChannelSftp c = init(path);
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(path);
|
||||
ChannelSftp c = init(cInfo);
|
||||
|
||||
try {
|
||||
byte[] buff = new byte[8000];
|
||||
|
||||
int bytesRead = 0;
|
||||
|
||||
InputStream in = c.get(extractSessionPath(path));
|
||||
InputStream in = c.get(cInfo.localPath);
|
||||
ByteArrayOutputStream bao = new ByteArrayOutputStream();
|
||||
|
||||
while ((bytesRead = in.read(buff)) != -1) {
|
||||
@@ -104,14 +138,15 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
public void uploadFile(String path, byte[] data, boolean writeTransactional)
|
||||
throws Exception {
|
||||
|
||||
ChannelSftp c = init(path);
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(path);
|
||||
ChannelSftp c = init(cInfo);
|
||||
try {
|
||||
InputStream in = new ByteArrayInputStream(data);
|
||||
String targetPath = extractSessionPath(path);
|
||||
String targetPath = cInfo.localPath;
|
||||
if (writeTransactional)
|
||||
{
|
||||
//upload to temporary location:
|
||||
String tmpPath = targetPath+".tmp";
|
||||
String tmpPath = targetPath + ".tmp";
|
||||
c.put(in, tmpPath);
|
||||
//remove previous file:
|
||||
try
|
||||
@@ -127,9 +162,9 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
}
|
||||
else
|
||||
{
|
||||
c.put(in, targetPath);
|
||||
c.put(in, targetPath);
|
||||
}
|
||||
|
||||
|
||||
tryDisconnect(c);
|
||||
} catch (Exception e) {
|
||||
tryDisconnect(c);
|
||||
@@ -141,53 +176,95 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
@Override
|
||||
public String createFolder(String parentPath, String newDirName)
|
||||
throws Exception {
|
||||
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(parentPath);
|
||||
try {
|
||||
ChannelSftp c = init(parentPath);
|
||||
String newPath = concatPaths(parentPath, newDirName);
|
||||
c.mkdir(extractSessionPath(newPath));
|
||||
ChannelSftp c = init(cInfo);
|
||||
String newPath = concatPaths(cInfo.localPath, newDirName);
|
||||
c.mkdir(newPath);
|
||||
tryDisconnect(c);
|
||||
return newPath;
|
||||
|
||||
return buildFullPath(cInfo.host, cInfo.port, newPath,
|
||||
cInfo.username, cInfo.password, cInfo.connectTimeoutSec);
|
||||
} catch (Exception 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) {
|
||||
String withoutProtocol = newPath
|
||||
.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
|
||||
.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) {
|
||||
String res = parentPath;
|
||||
if (!res.endsWith("/"))
|
||||
res += "/";
|
||||
res += newDirName;
|
||||
return res;
|
||||
StringBuilder fp = new StringBuilder(parentPath);
|
||||
if (!parentPath.endsWith("/"))
|
||||
fp.append("/");
|
||||
return fp.append(newDirName).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createFilePath(String parentPath, String newFileName)
|
||||
public String createFilePath(final String parentUri, String newFileName)
|
||||
throws Exception {
|
||||
if (parentPath.endsWith("/") == false)
|
||||
parentPath += "/";
|
||||
return parentPath + newFileName;
|
||||
|
||||
String parentPath = parentUri;
|
||||
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
|
||||
public List<FileEntry> listFiles(String parentPath) throws Exception {
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(parentPath);
|
||||
ChannelSftp c = init(cInfo);
|
||||
|
||||
ChannelSftp c = init(parentPath);
|
||||
return listFiles(parentPath, c);
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
return new FileNotFoundException(sftpEx.getMessage());
|
||||
}
|
||||
|
||||
|
||||
return e;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileEntry getFileEntry(String filename) throws Exception {
|
||||
|
||||
ChannelSftp c = init(filename);
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(filename);
|
||||
ChannelSftp c = init(cInfo);
|
||||
try {
|
||||
FileEntry fileEntry = new FileEntry();
|
||||
String sessionPath = extractSessionPath(filename);
|
||||
SftpATTRS attr = c.stat(sessionPath);
|
||||
SftpATTRS attr = c.stat(cInfo.localPath);
|
||||
setFromAttrs(fileEntry, attr);
|
||||
|
||||
// Full URI
|
||||
fileEntry.path = filename;
|
||||
fileEntry.displayName = getFilename(sessionPath);
|
||||
|
||||
fileEntry.displayName = getFilename(cInfo.localPath);
|
||||
|
||||
tryDisconnect(c);
|
||||
|
||||
return fileEntry;
|
||||
} catch (Exception e) {
|
||||
logDebug("Exception in getFileEntry! " + e);
|
||||
@@ -238,8 +319,9 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
|
||||
@Override
|
||||
public void delete(String path) throws Exception {
|
||||
ConnectionInfo cInfo = splitStringToConnectionInfo(path);
|
||||
ChannelSftp c = init(cInfo);
|
||||
|
||||
ChannelSftp c = init(path);
|
||||
delete(path, c);
|
||||
}
|
||||
|
||||
@@ -263,10 +345,11 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
tryDisconnect(c);
|
||||
throw convertException(e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private List<FileEntry> listFiles(String path, ChannelSftp c) throws Exception {
|
||||
|
||||
try {
|
||||
List<FileEntry> res = new ArrayList<FileEntry>();
|
||||
@SuppressWarnings("rawtypes")
|
||||
@@ -282,7 +365,7 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
||(lsEntry.getFilename().equals(".."))
|
||||
)
|
||||
continue;
|
||||
|
||||
|
||||
FileEntry fileEntry = new FileEntry();
|
||||
fileEntry.displayName = lsEntry.getFilename();
|
||||
fileEntry.path = createFilePath(path, fileEntry.displayName);
|
||||
@@ -312,16 +395,15 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
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 {
|
||||
|
||||
ChannelSftp init(ConnectionInfo cInfo) throws JSchException, UnsupportedEncodingException {
|
||||
jsch = new JSch();
|
||||
ConnectionInfo ci = splitStringToConnectionInfo(filename);
|
||||
|
||||
String base_dir = getBaseDir();
|
||||
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);
|
||||
UserInfo ui = new SftpUserInfo(ci.password,_appContext);
|
||||
Session session = jsch.getSession(cInfo.username, cInfo.host, cInfo.port);
|
||||
UserInfo ui = new SftpUserInfo(cInfo.password, _appContext);
|
||||
session.setUserInfo(ui);
|
||||
|
||||
session.setConfig("PreferredAuthentications", "publickey,password");
|
||||
|
||||
session.connect();
|
||||
sessionConnect(session, cInfo);
|
||||
|
||||
Channel channel = session.openChannel("sftp");
|
||||
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() {
|
||||
return _appContext.getFilesDir().getAbsolutePath();
|
||||
}
|
||||
@@ -389,7 +479,7 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
public ConnectionInfo splitStringToConnectionInfo(String filename)
|
||||
throws UnsupportedEncodingException {
|
||||
ConnectionInfo ci = new ConnectionInfo();
|
||||
ci.host = extractUserPwdHost(filename);
|
||||
ci.host = extractUserPwdHostPort(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));
|
||||
@@ -398,10 +488,21 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
int portSeparatorIndex = ci.host.indexOf(":");
|
||||
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.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;
|
||||
}
|
||||
|
||||
@@ -438,12 +539,20 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
try
|
||||
{
|
||||
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)
|
||||
{
|
||||
return extractSessionPath(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -465,22 +574,65 @@ public class SftpStorage extends JavaFileStorageBase {
|
||||
@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;
|
||||
|
||||
public String buildFullPath(String host, int port, String localPath,
|
||||
String username, String password,
|
||||
int connectTimeoutSec)
|
||||
throws UnsupportedEncodingException {
|
||||
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
|
||||
public void prepareFileUsage(Context appContext, String path) {
|
||||
//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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -538,12 +538,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
}
|
||||
|
||||
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 SkyDriveFileStorage("000000004010C234", appContext);
|
||||
|
||||
|
||||
storageToTest = new GoogleDriveAppDataFileStorage();
|
||||
//storageToTest = new GoogleDriveAppDataFileStorage();
|
||||
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
@Override
|
||||
public boolean onValidationError(String error) {
|
||||
@@ -743,7 +743,19 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
int port = Integer.parseInt(etPort.getText().toString());
|
||||
EditText etInitDir = ((EditText)view.findViewById(R.id.sftp_initial_dir));
|
||||
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) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
|
@@ -13,6 +13,7 @@
|
||||
android:id="@+id/sftp_host"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:singleLine="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:text=""
|
||||
@@ -26,9 +27,10 @@
|
||||
android:id="@+id/sftp_port"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="4"
|
||||
android:singleLine="true"
|
||||
android:inputType="number"
|
||||
android:text="22"
|
||||
android:text="22"
|
||||
android:hint="@string/hint_sftp_port" />
|
||||
</LinearLayout>
|
||||
<EditText
|
||||
@@ -63,6 +65,14 @@
|
||||
android:singleLine="true"
|
||||
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"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@@ -333,6 +333,7 @@
|
||||
|
||||
<string name="hint_sftp_host">host</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>
|
||||
|
||||
|
@@ -221,7 +221,6 @@ public class FileChooserActivity extends FragmentActivity {
|
||||
|
||||
public static final String EXTRA_RESULT_FILE_EXISTS = CLASSNAME + ".result_file_exists";
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* CONTROLS
|
||||
|
@@ -269,7 +269,6 @@ public class FragmentFiles extends Fragment implements
|
||||
FileChooserActivity.EXTRA_MAX_FILE_COUNT, 1000);
|
||||
mFileAdapter = new BaseFileAdapter(getActivity(), mFilterMode,
|
||||
mIsMultiSelection);
|
||||
|
||||
|
||||
/*
|
||||
* History.
|
||||
@@ -2268,12 +2267,15 @@ public class FragmentFiles extends Fragment implements
|
||||
}
|
||||
|
||||
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
|
||||
* null.
|
||||
*/
|
||||
mTextSaveas.setTag(BaseFileProviderUtils.getUri(cursor));
|
||||
mTextSaveas.setTag(uri);
|
||||
}
|
||||
|
||||
if (mDoubleTapToChooseFiles) {
|
||||
@@ -2286,10 +2288,12 @@ public class FragmentFiles extends Fragment implements
|
||||
if (mIsMultiSelection)
|
||||
return;
|
||||
|
||||
if (mIsSaveDialog)
|
||||
if (mIsSaveDialog) {
|
||||
checkSaveasFilenameAndFinish();
|
||||
else
|
||||
}
|
||||
else {
|
||||
finish(BaseFileProviderUtils.getUri(cursor));
|
||||
}
|
||||
}// single tap to choose files
|
||||
}// onItemClick()
|
||||
};// mViewFilesOnItemClickListener
|
||||
|
@@ -15,4 +15,24 @@ public class FileEntry {
|
||||
isDirectory = false;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ public class Kp2aFileChooserBridge {
|
||||
.buildUpon()
|
||||
.appendPath(defaultPath)
|
||||
.build());
|
||||
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
@@ -306,10 +306,9 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
String parentPath = getParentPath(path);
|
||||
|
||||
|
||||
if (parentPath == null)
|
||||
{
|
||||
if (parentPath == null) {
|
||||
if (Utils.doLog())
|
||||
Log.d(CLASSNAME, "parent file is null");
|
||||
Log.d(CLASSNAME, "parent file is null");
|
||||
return null;
|
||||
}
|
||||
FileEntry e;
|
||||
@@ -501,10 +500,10 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
RowBuilder newRow = matrixCursor.newRow();
|
||||
newRow.add(id);// _ID
|
||||
newRow.add(BaseFile
|
||||
.genContentIdUriBase(
|
||||
getAuthority())
|
||||
.buildUpon().appendPath(f.path)
|
||||
.build().toString());
|
||||
.genContentIdUriBase(
|
||||
getAuthority())
|
||||
.buildUpon().appendPath(f.path)
|
||||
.build().toString());
|
||||
newRow.add(f.path);
|
||||
if (f.displayName == null)
|
||||
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
|
||||
private void updateFileEntryCache(FileEntry f) {
|
||||
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
|
||||
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
|
||||
private FileEntry getFileEntryCached(String filename) {
|
||||
//check if enry is cached:
|
||||
//check if entry is cached:
|
||||
FileEntry cachedEntry = fileEntryMap.get(filename);
|
||||
if (cachedEntry != null)
|
||||
{
|
||||
@@ -728,7 +727,7 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
if (targetParent != null && targetParent.startsWith(source))
|
||||
{
|
||||
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();
|
||||
}
|
||||
if (Utils.doLog())
|
||||
@@ -768,28 +767,37 @@ public abstract class Kp2aFileProvider extends BaseFileProvider {
|
||||
|
||||
private String getParentPath(String path)
|
||||
{
|
||||
path = removeTrailingSlash(path);
|
||||
if (path.indexOf("://") == -1)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "invalid path: " + path);
|
||||
return null;
|
||||
}
|
||||
String pathWithoutProtocol = path.substring(path.indexOf("://")+3);
|
||||
int lastSlashPos = path.lastIndexOf("/");
|
||||
if (pathWithoutProtocol.indexOf("/") == -1)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "parent of " + path +" is null");
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
String parent = path.substring(0, lastSlashPos)+"/";
|
||||
Log.d("KP2A_FC_P", "parent of " + path +" is "+parent);
|
||||
return parent;
|
||||
}
|
||||
String params = null;
|
||||
int paramsIdx = path.lastIndexOf("?");
|
||||
if (paramsIdx > 0) {
|
||||
params = path.substring(paramsIdx);
|
||||
path = path.substring(0, paramsIdx);
|
||||
}
|
||||
|
||||
path = removeTrailingSlash(path);
|
||||
if (path.indexOf("://") == -1)
|
||||
{
|
||||
Log.d("KP2A_FC_P", "invalid path: " + path);
|
||||
return null;
|
||||
}
|
||||
String pathWithoutProtocol = path.substring(path.indexOf("://") + 3);
|
||||
int lastSlashPos = path.lastIndexOf("/");
|
||||
if (pathWithoutProtocol.indexOf("/") == -1)
|
||||
{
|
||||
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;
|
||||
|
||||
|
@@ -90,13 +90,17 @@ namespace keepass2android
|
||||
|
||||
if (!defaultPath.EndsWith(_schemeSeparator))
|
||||
{
|
||||
var fileStorage = new Keepass2android.Javafilestorage.SftpStorage(activity.ApplicationContext);
|
||||
var fileStorage = new SftpStorage(activity.ApplicationContext);
|
||||
SftpStorage.ConnectionInfo ci = fileStorage.SplitStringToConnectionInfo(defaultPath);
|
||||
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_user).Text = ci.Username;
|
||||
dlgContents.FindViewById<EditText>(Resource.Id.sftp_password).Text = ci.Password;
|
||||
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))
|
||||
{
|
||||
spinner.SetSelection(1);
|
||||
@@ -109,7 +113,7 @@ namespace keepass2android
|
||||
{
|
||||
string host = dlgContents.FindViewById<EditText>(Resource.Id.sftp_host).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))
|
||||
int.TryParse(portText, out port);
|
||||
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;
|
||||
if (string.IsNullOrEmpty(initialPath))
|
||||
initialPath = "/";
|
||||
string sftpPath = new Keepass2android.Javafilestorage.SftpStorage(activity.ApplicationContext).BuildFullPath(host, port, initialPath, user,
|
||||
password);
|
||||
string connectTimeoutText = dlgContents.FindViewById<EditText>(Resource.Id.sftp_connect_timeout).Text;
|
||||
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);
|
||||
});
|
||||
EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel());
|
||||
|
@@ -88,6 +88,18 @@
|
||||
android:singleLine="true"
|
||||
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>
|
@@ -586,6 +586,7 @@
|
||||
<string name="hint_sftp_host">host (ex: 192.168.0.1)</string>
|
||||
<string name="hint_sftp_port">port</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="sftp_auth_mode">Authentication mode</string>
|
||||
<string name="send_public_key">Send public key...</string>
|
||||
|
Reference in New Issue
Block a user