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 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.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());
}
}
}

View File

@@ -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();

View File

@@ -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"

View File

@@ -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>

View File

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

View File

@@ -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

View File

@@ -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();
}
}

View File

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

View File

@@ -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;

View File

@@ -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());

View File

@@ -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>

View File

@@ -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>