From 53877a16a966a929a51516130c38997a85362914 Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Mon, 14 Nov 2016 12:31:16 +0100 Subject: [PATCH] implement first version of NetFtpFileStorage --- .../Io/AndroidContentStorage.cs | 4 - .../Io/BuiltInFileStorage.cs | 72 +++- .../Io/CachingFileStorage.cs | 5 - src/Kp2aBusinessLogic/Io/HttpFileStorage.cs | 287 ------------- src/Kp2aBusinessLogic/Io/IFileStorage.cs | 6 - src/Kp2aBusinessLogic/Io/JavaFileStorage.cs | 5 - src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs | 376 +++++++++++++----- .../Io/OfflineSwitchableFileStorage.cs | 5 - .../Kp2aBusinessLogic.csproj | 2 +- src/keepass2android/app/App.cs | 5 +- .../fileselect/FileDbHelper.cs | 2 - .../fileselect/FileSelectActivity.cs | 1 - src/keepass2android/packages.config | 1 + src/netftpandroid | 2 +- 14 files changed, 331 insertions(+), 442 deletions(-) delete mode 100644 src/Kp2aBusinessLogic/Io/HttpFileStorage.cs diff --git a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs index d01ae872..675a915c 100644 --- a/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs +++ b/src/Kp2aBusinessLogic/Io/AndroidContentStorage.cs @@ -256,10 +256,6 @@ namespace keepass2android.Io } - public void ResolveAccount(IOConnectionInfo ioc) - { - - } } class AndroidContentWriteTransaction : IWriteTransaction diff --git a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs index 8af5e735..a75583fb 100644 --- a/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/BuiltInFileStorage.cs @@ -20,7 +20,7 @@ using IOException = System.IO.IOException; namespace keepass2android.Io { - public class BuiltInFileStorage : IFileStorage, IPermissionRequestingFileStorage + public abstract class BuiltInFileStorage : IFileStorage, IPermissionRequestingFileStorage { private const string PermissionGrantedKey = "PermissionGranted"; @@ -56,18 +56,8 @@ namespace keepass2android.Io } - public IEnumerable SupportedProtocols - { - get - { - yield return "file"; -#if !NoNet - yield return "ftp"; - yield return "http"; - yield return "https"; -#endif - } - } + + public abstract IEnumerable SupportedProtocols { get; } public void Delete(IOConnectionInfo ioc) { @@ -398,11 +388,6 @@ namespace keepass2android.Io return false; } - public void ResolveAccount(IOConnectionInfo ioc) - { - - } - private bool IsLocalFileFlaggedReadOnly(IOConnectionInfo ioc) { try @@ -429,4 +414,55 @@ namespace keepass2android.Io fileStorageSetupActivity.State.PutBoolean(PermissionGrantedKey, grantResults[0] == Permission.Granted); } } + + class LegacyFtpStorage : BuiltInFileStorage + { + public LegacyFtpStorage(IKp2aApp app) : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { +#if !NoNet + yield return "ftp"; +#endif + } + } + } + + class LegacyWebDavStorage : BuiltInFileStorage + { + public LegacyWebDavStorage(IKp2aApp app) : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { +#if !NoNet + yield return "http"; + yield return "https"; +#endif + + } + } + } + + public class LocalFileStorage : BuiltInFileStorage + { + public LocalFileStorage(IKp2aApp app) : base(app) + { + } + + public override IEnumerable SupportedProtocols + { + get + { + yield return "file"; + } + } + } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs index b62a2ccf..31d1d0bc 100644 --- a/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/CachingFileStorage.cs @@ -563,11 +563,6 @@ namespace keepass2android.Io return _cachedStorage.IsReadOnly(ioc, reason); } - public void ResolveAccount(IOConnectionInfo ioc) - { - _cachedStorage.ResolveAccount(ioc); - } - private void StoreFilePath(IOConnectionInfo folderPath, string filename, IOConnectionInfo res) { File.WriteAllText(CachedFilePath(GetPseudoIoc(folderPath, filename)) + ".filepath", res.Path); diff --git a/src/Kp2aBusinessLogic/Io/HttpFileStorage.cs b/src/Kp2aBusinessLogic/Io/HttpFileStorage.cs deleted file mode 100644 index 9b867978..00000000 --- a/src/Kp2aBusinessLogic/Io/HttpFileStorage.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text; - -using Android.App; -using Android.Content; -using Android.OS; -using Android.Runtime; -using Android.Views; -using Android.Widget; -using KeePassLib; -using KeePassLib.Serialization; -using KeePassLib.Utility; -using ModernHttpClient; - -namespace keepass2android.Io -{ - public class HttpFileStorage: IFileStorage - { - public IEnumerable SupportedProtocols - { - get - { - yield return "http"; - yield return "https"; - } - } - - HttpClient GetHttpClient(IOConnectionInfo ioc) - { - var handler = new NativeMessageHandler(); - //var handler = new HttpClientHandler(); - - if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) - { - int backslashPos = ioc.UserName.IndexOf("\\", StringComparison.Ordinal); - if (backslashPos > 0) - { - string domain = ioc.UserName.Substring(0, backslashPos); - string user = ioc.UserName.Substring(backslashPos + 1); - handler.Credentials = new NetworkCredential(user, ioc.Password, domain); - } - else - { - handler.PreAuthenticate = true; - handler.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); - } - } - - return new HttpClient(handler); - - } - - public void Delete(IOConnectionInfo ioc) - { - GetHttpClient(ioc).DeleteAsync(ioc.Path).Result.EnsureSuccessStatusCode(); - } - - public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) - { - return false; - } - - public string GetCurrentFileVersionFast(IOConnectionInfo ioc) - { - return null; - } - - public Stream OpenFileForRead(IOConnectionInfo ioc) - { - return GetHttpClient(ioc).GetStreamAsync(ioc.Path).Result; - } - - - class UploadOnCloseMemoryStream : MemoryStream - { - IOConnectionInfo ioc; - private HttpClient client; - - - public UploadOnCloseMemoryStream(IOConnectionInfo _ioc, HttpClient _client) - { - this.ioc = _ioc; - this.client = _client; - } - - public override void Close() - { - base.Close(); - var msg = new HttpRequestMessage(HttpMethod.Put, ioc.Path); - msg.Headers.Add("Translate", "f"); - msg.Content = new StreamContent(new MemoryStream(this.ToArray())); - - client.SendAsync(msg).Result.EnsureSuccessStatusCode(); - } - - } - public class UntransactedWrite : IWriteTransaction - { - private readonly IOConnectionInfo _ioc; - private readonly HttpClient _client; - - - public UntransactedWrite(IOConnectionInfo ioc, HttpClient client) - { - _ioc = ioc; - _client = client; - } - - public void Dispose() - { - - } - - public Stream OpenFile() - { - return new UploadOnCloseMemoryStream(_ioc, _client); - } - - public void CommitWrite() - { - - } - } - - public class TransactedWrite : IWriteTransaction - { - private readonly IOConnectionInfo _ioc; - private readonly HttpFileStorage _fileStorage; - private readonly IOConnectionInfo _iocTemp; - - - - public TransactedWrite(IOConnectionInfo ioc, HttpFileStorage fileStorage) - { - _ioc = ioc; - _iocTemp = _ioc.CloneDeep(); - _iocTemp.Path += "." + new PwUuid(true).ToHexString().Substring(0, 6) + ".tmp"; - - _fileStorage = fileStorage; - } - - public void Dispose() - { - - } - - public Stream OpenFile() - { - return new UploadOnCloseMemoryStream(_ioc, _fileStorage.GetHttpClient(_ioc)); - } - - public void CommitWrite() - { - var client = _fileStorage.GetHttpClient(_ioc); - client.DeleteAsync(_ioc.Path).Result.EnsureSuccessStatusCode(); - var msg = new HttpRequestMessage(new HttpMethod("MOVE"), _iocTemp.Path); - msg.Headers.Add("Destination", _ioc.Path); - client.SendAsync(msg).Result.EnsureSuccessStatusCode(); - } - } - - public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) - { - if (useFileTransaction) - return new TransactedWrite(ioc, this); - else - return new UntransactedWrite(ioc, GetHttpClient(ioc)); - } - - public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) - { - return UrlUtil.StripExtension( - UrlUtil.GetFileName(ioc.Path)); - } - - public bool RequiresCredentials(IOConnectionInfo ioc) - { - return ioc.CredSaveMode != IOCredSaveMode.SaveCred; - } - - public void CreateDirectory(IOConnectionInfo ioc, string newDirName) - { - throw new NotImplementedException(); - } - - public IEnumerable ListContents(IOConnectionInfo ioc) - { - throw new NotImplementedException(); - } - - public FileDescription GetFileDescription(IOConnectionInfo ioc) - { - throw new NotImplementedException(); - } - - public bool RequiresSetup(IOConnectionInfo ioConnection) - { - return false; - } - - public string IocToPath(IOConnectionInfo ioc) - { - return ioc.Path; - } - - public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode, string protocolId) - { - activity.PerformManualFileSelect(isForSave, requestCode, protocolId); - } - - public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode, - bool alwaysReturnSuccess) - { - Intent intent = new Intent(); - activity.IocToIntent(intent, ioc); - activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent); - } - - public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) - { - - } - - public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) - { - - } - - public void OnResume(IFileStorageSetupActivity activity) - { - } - - public void OnStart(IFileStorageSetupActivity activity) - { - } - - public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data) - { - } - - public string GetDisplayName(IOConnectionInfo ioc) - { - return ioc.GetDisplayName(); - } - - public string CreateFilePath(string parent, string newFilename) - { - if (!parent.EndsWith("/")) - parent += "/"; - return parent + newFilename; - } - - public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) - { - return IoUtil.GetParentPath(ioc); - } - - public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) - { - IOConnectionInfo res = folderPath.CloneDeep(); - if (!res.Path.EndsWith("/")) - res.Path += "/"; - res.Path += filename; - return res; - } - - public bool IsPermanentLocation(IOConnectionInfo ioc) - { - return true; - } - - public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null) - { - return false; - } - - public void ResolveAccount(IOConnectionInfo ioc) - { - - } - } -} \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/IFileStorage.cs b/src/Kp2aBusinessLogic/Io/IFileStorage.cs index 82f8a486..79cab4a2 100644 --- a/src/Kp2aBusinessLogic/Io/IFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/IFileStorage.cs @@ -177,12 +177,6 @@ namespace keepass2android.Io /// bool IsReadOnly(IOConnectionInfo ioc, OptionalOut reason = null ); - /// - /// if the ioc.Path contains a string which refers to a stored account (like FTP account with specific settings), - /// this method resolves the path so that it is a path which can be consumed by the file storage "operational" methods (=all other methods) - /// - /// - void ResolveAccount(IOConnectionInfo ioc); } public interface IPermissionRequestingFileStorage diff --git a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs index 1934247a..fa584281 100644 --- a/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/JavaFileStorage.cs @@ -294,11 +294,6 @@ namespace keepass2android.Io return false; //TODO implement. note, however, that we MAY return false even if it's read-only } - public void ResolveAccount(IOConnectionInfo ioc) - { - - } - public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) { _jfs.OnCreate(((IJavaFileStorageFileStorageSetupActivity)activity), savedInstanceState); diff --git a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs index 9fbbc503..9dce6041 100644 --- a/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/NetFtpFileStorage.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.IO; using System.Net; using System.Net.FtpClient; +using System.Reflection; +using System.Threading; using Android.Content; using Android.OS; using Android.Preferences; @@ -14,14 +16,79 @@ namespace keepass2android.Io { public class NetFtpFileStorage: IFileStorage { - struct ConnectionSettings + class RetryConnectFtpClient : FtpClient + { + protected override FtpClient CloneConnection() + { + RetryConnectFtpClient conn = new RetryConnectFtpClient(); + + conn.m_isClone = true; + + foreach (PropertyInfo prop in GetType().GetProperties()) + { + object[] attributes = prop.GetCustomAttributes(typeof(FtpControlConnectionClone), true); + + if (attributes != null && attributes.Length > 0) + { + prop.SetValue(conn, prop.GetValue(this, null), null); + } + } + + // always accept certficate no matter what because if code execution ever + // gets here it means the certificate on the control connection object being + // cloned was already accepted. + conn.ValidateCertificate += new FtpSslValidation( + delegate(FtpClient obj, FtpSslValidationEventArgs e) + { + e.Accept = true; + }); + + return conn; + } + + private static T DoInRetryLoop(Func func) + { + double timeout = 30.0; + double timePerRequest = 1.0; + var startTime = DateTime.Now; + while (true) + { + var attemptStartTime = DateTime.Now; + try + { + return func(); + } + catch (System.Net.Sockets.SocketException e) + { + if ((e.ErrorCode != 10061) || (DateTime.Now > startTime.AddSeconds(timeout))) + { + throw; + } + double secondsSinceAttemptStart = (DateTime.Now - attemptStartTime).TotalSeconds; + if (secondsSinceAttemptStart < timePerRequest) + { + Thread.Sleep(TimeSpan.FromSeconds(timePerRequest - secondsSinceAttemptStart)); + } + } + } + } + public override void Connect() + { + DoInRetryLoop(() => + { + base.Connect(); + return true; + } + ); + } + } + + public struct ConnectionSettings { public FtpEncryptionMode EncryptionMode {get; set; } public static ConnectionSettings FromIoc(IOConnectionInfo ioc) { - if (ioc.Path.StartsWith(Kp2AAccountPathPrefix)) - throw new InvalidOperationException("cannot extract settings from account-path"); string path = ioc.Path; int schemeLength = path.IndexOf("://", StringComparison.Ordinal); path = path.Substring(schemeLength + 3); @@ -32,14 +99,23 @@ namespace keepass2android.Io }; } + + public string ToString() + { + return ((int) EncryptionMode).ToString(); + } } - private const string Kp2AAccountPathPrefix = "__kp2a_account__"; private readonly Context _context; + public MemoryStream traceStream; + public NetFtpFileStorage(Context context) { _context = context; + traceStream = new MemoryStream(); + FtpTrace.AddListener(new System.Diagnostics.TextWriterTraceListener(traceStream)); + } public IEnumerable SupportedProtocols @@ -49,16 +125,42 @@ namespace keepass2android.Io public void Delete(IOConnectionInfo ioc) { - using (FtpClient client = GetClient(ioc)) + try { - client.DeleteFile(IocPathToUri(ioc.Path).PathAndQuery); + using (FtpClient client = GetClient(ioc)) + { + string localPath = IocPathToUri(ioc.Path).PathAndQuery; + if (client.DirectoryExists(localPath)) + client.DeleteDirectory(localPath, true); + else + client.DeleteFile(localPath); + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } + + } + + public static Exception ConvertException(Exception exception) + { + if (exception is FtpCommandException) + { + var ftpEx = (FtpCommandException) exception; + + if (ftpEx.CompletionCode == "550") + throw new FileNotFoundException(exception.Message, exception); + } + + return exception; } internal FtpClient GetClient(IOConnectionInfo ioc, bool enableCloneClient = true) { - FtpClient client = new FtpClient(); + FtpClient client = new RetryConnectFtpClient(); if ((ioc.UserName.Length > 0) || (ioc.Password.Length > 0)) client.Credentials = new NetworkCredential(ioc.UserName, ioc.Password); else @@ -74,17 +176,22 @@ namespace keepass2android.Io // we don't need to be thread safe in a classic sense, but OpenRead and OpenWrite don't //perform the actual stream operation so we'd need to wrap the stream (or just enable this:) - client.EnableThreadSafeDataConnections = enableCloneClient; + client.EncryptionMode = ConnectionSettings.FromIoc(ioc).EncryptionMode; - client.Connect(); - return client; + + client.Connect(); + return client; + } + + + internal Uri IocPathToUri(string path) { - //remove addition stuff like TLS param + //remove additional stuff like TLS param int schemeLength = path.IndexOf("://", StringComparison.Ordinal); string scheme = path.Substring(0, schemeLength); path = path.Substring(schemeLength + 3); @@ -100,7 +207,9 @@ namespace keepass2android.Io string scheme = basePath.Substring(0, schemeLength); basePath = basePath.Substring(schemeLength + 3); string baseSettings = basePath.Substring(0, basePath.IndexOf("/", StringComparison.Ordinal)); - return scheme + "://" + baseSettings + "/" + uri.AbsolutePath; //TODO does this contain Query? + basePath = basePath.Substring(baseSettings.Length+1); + string baseHost = basePath.Substring(0, basePath.IndexOf("/", StringComparison.Ordinal)); + return scheme + "://" + baseSettings + "/" + baseHost + uri.AbsolutePath; //TODO does this contain Query? } @@ -116,18 +225,34 @@ namespace keepass2android.Io public Stream OpenFileForRead(IOConnectionInfo ioc) { - using (var cl = GetClient(ioc)) + try { - return cl.OpenRead(IocPathToUri(ioc.Path).PathAndQuery, FtpDataType.Binary, 0); + using (var cl = GetClient(ioc)) + { + return cl.OpenRead(IocPathToUri(ioc.Path).PathAndQuery, FtpDataType.Binary, 0); + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); } } public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) { - if (useFileTransaction) - return new UntransactedWrite(ioc, this); - else - return new TransactedWrite(ioc, this); + try + { + + + if (!useFileTransaction) + return new UntransactedWrite(ioc, this); + else + return new TransactedWrite(ioc, this); + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); + } } public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) @@ -144,73 +269,95 @@ namespace keepass2android.Io public void CreateDirectory(IOConnectionInfo ioc, string newDirName) { - using (var client = GetClient(ioc)) + try { - client.CreateDirectory(IocPathToUri(ioc.Path).PathAndQuery); + using (var client = GetClient(ioc)) + { + client.CreateDirectory(IocPathToUri(GetFilePath(ioc, newDirName).Path).PathAndQuery); + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); } } public IEnumerable ListContents(IOConnectionInfo ioc) { - using (var client = GetClient(ioc)) + try { - List files = new List(); - foreach (FtpListItem item in client.GetListing(IocPathToUri(ioc.Path).PathAndQuery, - FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks)) + using (var client = GetClient(ioc)) { - - switch (item.Type) + List files = new List(); + foreach (FtpListItem item in client.GetListing(IocPathToUri(ioc.Path).PathAndQuery, + FtpListOption.Modify | FtpListOption.Size | FtpListOption.DerefLinks)) { - case FtpFileSystemObjectType.Directory: - files.Add(new FileDescription() - { - CanRead = true, - CanWrite = true, - DisplayName = item.Name, - IsDirectory = true, - LastModified = item.Modified, - Path = IocPathFromUri(ioc, new Uri(item.FullName)) - }); - break; - case FtpFileSystemObjectType.File: - files.Add(new FileDescription() - { - CanRead = true, - CanWrite = true, - DisplayName = item.Name, - IsDirectory = false, - LastModified = item.Modified, - Path = IocPathFromUri(ioc, new Uri(item.FullName)), - SizeInBytes = item.Size - }); - break; - + + switch (item.Type) + { + case FtpFileSystemObjectType.Directory: + files.Add(new FileDescription() + { + CanRead = true, + CanWrite = true, + DisplayName = item.Name, + IsDirectory = true, + LastModified = item.Modified, + Path = IocPathFromUri(ioc, new Uri(item.FullName)) + }); + break; + case FtpFileSystemObjectType.File: + files.Add(new FileDescription() + { + CanRead = true, + CanWrite = true, + DisplayName = item.Name, + IsDirectory = false, + LastModified = item.Modified, + Path = IocPathFromUri(ioc, new Uri(item.FullName)), + SizeInBytes = item.Size + }); + break; + + } } + return files; } - return files; + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); } } public FileDescription GetFileDescription(IOConnectionInfo ioc) { - //TODO when is this called? - //is it very inefficient to connect for each description? - - using (FtpClient client = GetClient(ioc)) + try { - var uri = IocPathToUri(ioc.Path); - string path = uri.PathAndQuery; - return new FileDescription() + //TODO when is this called? + //is it very inefficient to connect for each description? + + using (FtpClient client = GetClient(ioc)) { - CanRead = true, - CanWrite = true, - Path = ioc.Path, - LastModified = client.GetModifiedTime(path), - SizeInBytes = client.GetFileSize(path), - DisplayName = UrlUtil.GetFileName(path), - IsDirectory = false - }; + + var uri = IocPathToUri(ioc.Path); + string path = uri.PathAndQuery; + return new FileDescription() + { + CanRead = true, + CanWrite = true, + Path = ioc.Path, + LastModified = client.GetModifiedTime(path), + SizeInBytes = client.GetFileSize(path), + DisplayName = UrlUtil.GetFileName(path), + IsDirectory = false + }; + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); } } @@ -298,37 +445,19 @@ namespace keepass2android.Io { return false; } - - public void ResolveAccount(IOConnectionInfo ioc) - { - string path = ioc.Path; - int schemeLength = path.IndexOf("://", StringComparison.Ordinal); - string scheme = path.Substring(0, schemeLength); - path = path.Substring(schemeLength+3); - if (path.StartsWith(Kp2AAccountPathPrefix)) - { - string accountId = path.Substring(0, path.IndexOf("/", StringComparison.Ordinal)); - path = path.Substring(accountId.Length + 1); - - var prefs = PreferenceManager.GetDefaultSharedPreferences(_context); - string host = prefs.GetString(accountId + "_Host", null); - int port = prefs.GetInt(accountId + "_Port", 0 /* auto*/); - string initialPath = prefs.GetString(accountId + "_InitPath", ""); - if (initialPath.StartsWith("/")) - initialPath = initialPath.Substring(1); - if ((!initialPath.EndsWith("/") && (initialPath != ""))) - initialPath += "/"; - int encMode = prefs.GetInt(accountId + "_EncMode", (int) FtpEncryptionMode.None); - string settings = encMode.ToString(); - ioc.Path = scheme + "://" + settings + "/" + host + (port == 0 ? "" : (":" + port)) + "/" + initialPath + path; - } - } - public Stream OpenWrite(IOConnectionInfo ioc) { - using (var client = GetClient(ioc)) + try { - return client.OpenWrite(IocPathToUri(ioc.Path).PathAndQuery); + using (var client = GetClient(ioc)) + { + return client.OpenWrite(IocPathToUri(ioc.Path).PathAndQuery); + + } + } + catch (FtpCommandException ex) + { + throw ConvertException(ex); } } } @@ -339,7 +468,7 @@ namespace keepass2android.Io private readonly NetFtpFileStorage _fileStorage; private readonly IOConnectionInfo _iocTemp; private FtpClient _client; - + private Stream _stream; public TransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage) { @@ -352,21 +481,54 @@ namespace keepass2android.Io public void Dispose() { - if (_client != null) - _client.Dispose(); - _client = null; + if (_stream != null) + _stream.Dispose(); + _stream = null; } public Stream OpenFile() { - _client = _fileStorage.GetClient(_ioc, false); - return _client.OpenWrite(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery); + try + { + + _client = _fileStorage.GetClient(_ioc, false); + _stream = _client.OpenWrite(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery); + return _stream; + } + catch (FtpCommandException ex) + { + throw NetFtpFileStorage.ConvertException(ex); + } } public void CommitWrite() { - _client.DeleteFile(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery); - _client.Rename(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery, _fileStorage.IocPathToUri(_ioc.Path).PathAndQuery); + try + { + Android.Util.Log.Debug("NETFTP","connected: " + _client.IsConnected.ToString()); + _stream.Close(); + Android.Util.Log.Debug("NETFTP", "connected: " + _client.IsConnected.ToString()); + + //make sure target file does not exist: + //try + { + if (_client.FileExists(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery)) + _client.DeleteFile(_fileStorage.IocPathToUri(_ioc.Path).PathAndQuery); + + } + //catch (FtpCommandException) + { + //TODO get a new clien? might be stale + } + + _client.Rename(_fileStorage.IocPathToUri(_iocTemp.Path).PathAndQuery, + _fileStorage.IocPathToUri(_ioc.Path).PathAndQuery); + + } + catch (FtpCommandException ex) + { + throw NetFtpFileStorage.ConvertException(ex); + } } } @@ -374,6 +536,7 @@ namespace keepass2android.Io { private readonly IOConnectionInfo _ioc; private readonly NetFtpFileStorage _fileStorage; + private Stream _stream; public UntransactedWrite(IOConnectionInfo ioc, NetFtpFileStorage fileStorage) { @@ -383,17 +546,20 @@ namespace keepass2android.Io public void Dispose() { - + if (_stream != null) + _stream.Dispose(); + _stream = null; } public Stream OpenFile() { - return _fileStorage.OpenWrite(_ioc); + _stream = _fileStorage.OpenWrite(_ioc); + return _stream; } public void CommitWrite() { - + _stream.Close(); } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs index ba7f9a47..bf93653e 100644 --- a/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs +++ b/src/Kp2aBusinessLogic/Io/OfflineSwitchableFileStorage.cs @@ -181,11 +181,6 @@ namespace keepass2android.Io return _baseStorage.IsReadOnly(ioc, reason); } - public void ResolveAccount(IOConnectionInfo ioc) - { - _baseStorage.ResolveAccount(ioc); - } - public void OnRequestPermissionsResult(IFileStorageSetupActivity fileStorageSetupActivity, int requestCode, string[] permissions, Permission[] grantResults) { diff --git a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj index c5fa51e7..0ff26287 100644 --- a/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj +++ b/src/Kp2aBusinessLogic/Kp2aBusinessLogic.csproj @@ -90,10 +90,10 @@ - + diff --git a/src/keepass2android/app/App.cs b/src/keepass2android/app/App.cs index 0e8106e9..3daf9ae6 100644 --- a/src/keepass2android/app/App.cs +++ b/src/keepass2android/app/App.cs @@ -458,7 +458,7 @@ namespace keepass2android { IFileStorage fileStorage; if (iocInfo.IsLocalFile()) - fileStorage = new BuiltInFileStorage(this); + fileStorage = new LocalFileStorage(this); else { IFileStorage innerFileStorage = GetCloudFileStorage(iocInfo); @@ -511,9 +511,10 @@ namespace keepass2android new GoogleDriveFileStorage(Application.Context, this), new SkyDriveFileStorage(Application.Context, this), new SftpFileStorage(this), + new NetFtpFileStorage(Application.Context), #endif #endif - new BuiltInFileStorage(this) + new LocalFileStorage(this) }; } return _fileStorages; diff --git a/src/keepass2android/fileselect/FileDbHelper.cs b/src/keepass2android/fileselect/FileDbHelper.cs index 77f1e73e..3ef3deb1 100644 --- a/src/keepass2android/fileselect/FileDbHelper.cs +++ b/src/keepass2android/fileselect/FileDbHelper.cs @@ -286,8 +286,6 @@ namespace keepass2android ioc.Obfuscate(false); - //TODO enable for 1.1 release App.Kp2a.GetFileStorage(ioc).ResolveAccount(ioc); - return ioc; } } diff --git a/src/keepass2android/fileselect/FileSelectActivity.cs b/src/keepass2android/fileselect/FileSelectActivity.cs index 0581f833..a6b0a953 100644 --- a/src/keepass2android/fileselect/FileSelectActivity.cs +++ b/src/keepass2android/fileselect/FileSelectActivity.cs @@ -219,7 +219,6 @@ namespace keepass2android TextView textView = (TextView)view; IOConnectionInfo ioc = new IOConnectionInfo {Path = path}; var fileStorage = _app.GetFileStorage(ioc); - //TODO enable for 1.1 release fileStorage.ResolveAccount(ioc); textView.Text = fileStorage.GetDisplayName(ioc); textView.Tag = ioc.Path; return true; diff --git a/src/keepass2android/packages.config b/src/keepass2android/packages.config index 1f028c36..a4bd8ffe 100644 --- a/src/keepass2android/packages.config +++ b/src/keepass2android/packages.config @@ -2,6 +2,7 @@ + diff --git a/src/netftpandroid b/src/netftpandroid index 961c8718..3a5b618c 160000 --- a/src/netftpandroid +++ b/src/netftpandroid @@ -1 +1 @@ -Subproject commit 961c87189241f38332a2dc0564eacf8bddfd37a6 +Subproject commit 3a5b618ccf68d30678d6ad4d6f16cbb6685fa21d