implements Samba support to close #82
This commit is contained in:
		
							
								
								
									
										615
									
								
								src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										615
									
								
								src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,615 @@ | ||||
| using System.Net; | ||||
| using Android.Content; | ||||
| using keepass2android; | ||||
| using keepass2android.Io; | ||||
| using KeePassLib.Serialization; | ||||
| using SMBLibrary.Client; | ||||
| using SMBLibrary; | ||||
| using FileAttributes = SMBLibrary.FileAttributes; | ||||
| using KeePassLib.Utility; | ||||
| using Java.Nio.FileNio; | ||||
|  | ||||
| namespace Kp2aBusinessLogic.Io | ||||
| { | ||||
|     public class SmbFileStorage : IFileStorage | ||||
|     { | ||||
|         public IEnumerable<string> SupportedProtocols | ||||
|         { | ||||
|             get { yield return "smb"; } | ||||
|         } | ||||
|  | ||||
|         public bool UserShouldBackup | ||||
|         { | ||||
|             get { return false; } | ||||
|         } | ||||
|  | ||||
|         public void Delete(IOConnectionInfo ioc) | ||||
|         { | ||||
|             throw new NotImplementedException(); | ||||
|         } | ||||
|  | ||||
|         public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public string GetCurrentFileVersionFast(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         public struct SmbConnectionInfo | ||||
|         { | ||||
|             public string Host; | ||||
|             public string Username; | ||||
|             public string Password; | ||||
|             public string? Domain; | ||||
|             public string? Share; | ||||
|             public string? LocalPath; | ||||
|  | ||||
|             public static SmbConnectionInfo FromUrlAndCredentials(string url, string username, string password, string? domain) | ||||
|             { | ||||
|                 string userDomain = username; | ||||
|                 if (domain != null) | ||||
|                 { | ||||
|                     userDomain = domain + "\\" + username; | ||||
|                 } | ||||
|                 if (url.StartsWith("smb://")) | ||||
|                 { | ||||
|                     url = url.Substring(6); | ||||
|                 } | ||||
|  | ||||
|                 if (url.StartsWith("\\\\")) | ||||
|                 { | ||||
|                     url = url.Substring(2); | ||||
|                 } | ||||
|  | ||||
|                 url = url.Replace("\\", "/"); | ||||
|              | ||||
|                 string fullPath = "smb://" + WebUtility.UrlEncode(userDomain) + ":" + WebUtility.UrlEncode(password) + "@" + url; | ||||
|                 return new SmbConnectionInfo(new IOConnectionInfo() { Path = fullPath} ); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             public SmbConnectionInfo(IOConnectionInfo ioc) | ||||
|             { | ||||
|                 string fullpath = ioc.Path; | ||||
|                 if (!fullpath.StartsWith("smb://")) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 fullpath = fullpath.Substring(6); | ||||
|                 string[] authAndPath = fullpath.Split('@'); | ||||
|                 if (authAndPath.Length != 2) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 string[] userAndPwd = authAndPath[0].Split(':'); | ||||
|                 if (userAndPwd.Length != 2) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 string[] pathParts = authAndPath[1].Split('/'); | ||||
|                 if (pathParts.Length < 1) | ||||
|                 { | ||||
|                     throw new Exception("Invalid smb path!"); | ||||
|                 } | ||||
|  | ||||
|                 Host = pathParts[0]; | ||||
|                 if (pathParts.Length > 1) | ||||
|                 { | ||||
|                     Share = pathParts[1]; | ||||
|                 } | ||||
|                 LocalPath = String.Join("/", pathParts.Skip(2)); | ||||
|                 if (LocalPath.EndsWith("/")) | ||||
|                 { | ||||
|                     LocalPath = LocalPath.Substring(0, LocalPath.Length - 1); | ||||
|                 } | ||||
|              | ||||
|                 Username = WebUtility.UrlDecode(userAndPwd[0]); | ||||
|                 if (Username.Contains("\\")) | ||||
|                 { | ||||
|                     string[] domainAndUser = Username.Split('\\'); | ||||
|                     Domain = domainAndUser[0]; | ||||
|                     Username = domainAndUser[1]; | ||||
|                 } | ||||
|                 else Domain = null; | ||||
|  | ||||
|                 Password = WebUtility.UrlDecode(userAndPwd[1]); | ||||
|             } | ||||
|  | ||||
|             public string ToPath() | ||||
|             { | ||||
|                 string domainUser = Username; | ||||
|                 if (Domain != null) | ||||
|                 { | ||||
|                     domainUser = Domain + "\\" + Username; | ||||
|                 } | ||||
|  | ||||
|                 return "smb://" + WebUtility.UrlEncode(domainUser) + ":" + WebUtility.UrlEncode(Password) + "@" + Host + | ||||
|                        "/" + Share + "/" + LocalPath; | ||||
|             } | ||||
|  | ||||
|             public string GetPathWithoutCredentials() | ||||
|             { | ||||
|                 return "smb://" + Host + "/" + Share + "/" + LocalPath; | ||||
|             } | ||||
|  | ||||
|             public string GetLocalSmbPath() | ||||
|             { | ||||
|                 return LocalPath?.Replace("/", "\\") ?? ""; | ||||
|             } | ||||
|  | ||||
|             public SmbConnectionInfo GetParent() | ||||
|             { | ||||
|                 SmbConnectionInfo parent = new SmbConnectionInfo | ||||
|                 { | ||||
|                     Host = Host, | ||||
|                     Username = Username, | ||||
|                     Password = Password, | ||||
|                     Domain = Domain, | ||||
|                     Share = Share | ||||
|                 }; | ||||
|                 string[] pathParts = LocalPath?.Split('/') ?? []; | ||||
|                 if (pathParts.Length > 0) | ||||
|                 { | ||||
|                     parent.LocalPath = string.Join("/", pathParts.Take(pathParts.Length - 1)); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     parent.LocalPath = ""; | ||||
|                     parent.Share = ""; | ||||
|                 } | ||||
|  | ||||
|                 return parent; | ||||
|             } | ||||
|  | ||||
|             public string Stem() | ||||
|             { | ||||
|                 return LocalPath?.Split('/').Last() ?? ""; | ||||
|             } | ||||
|  | ||||
|  | ||||
|             public SmbConnectionInfo GetChild(string childName) | ||||
|             { | ||||
|                 SmbConnectionInfo child = new SmbConnectionInfo(); | ||||
|                 child.Host = Host; | ||||
|                 child.Username = Username; | ||||
|                 child.Password = Password; | ||||
|                 child.Domain = Domain; | ||||
|                 if (string.IsNullOrEmpty(Share)) | ||||
|                 { | ||||
|                     child.Share = childName; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|  | ||||
|                     child.Share = Share; | ||||
|                     var pathPartsList = LocalPath?.Split('/').Where(p => !string.IsNullOrEmpty(p)).ToList() ?? []; | ||||
|                     pathPartsList.Add(childName); | ||||
|                     child.LocalPath = string.Join("/", pathPartsList); | ||||
|                 } | ||||
|  | ||||
|                 return child; | ||||
|             } | ||||
|  | ||||
|             public string ToDisplayString() | ||||
|             { | ||||
|                 return "smb://" + Host + "/" + Share + "/" + LocalPath; | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         class SmbConnection: IDisposable | ||||
|         { | ||||
|             public SmbConnection(SmbConnectionInfo info) | ||||
|             { | ||||
|                 _isLoggedIn = false; | ||||
|                 var isConnected = Client.Connect(info.Host, SMBTransportType.DirectTCPTransport); | ||||
|                 if (!isConnected) | ||||
|                 { | ||||
|                     throw new Exception($"Failed to connect to SMB server {info.Host}"); | ||||
|                 } | ||||
|  | ||||
|                 var status = Client.Login(info.Domain ?? string.Empty, info.Username, info.Password); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 { | ||||
|                     throw new Exception($"Failed to login to SMB as {info.Username}"); | ||||
|                 } | ||||
|  | ||||
|                 _isLoggedIn = true; | ||||
|  | ||||
|                 if (!string.IsNullOrEmpty(info.Share)) | ||||
|                 { | ||||
|                     FileStore = Client.TreeConnect(info.Share, out status); | ||||
|                 } | ||||
|                  | ||||
|             } | ||||
|  | ||||
|  | ||||
|  | ||||
|             public readonly SMB2Client Client = new SMB2Client(); | ||||
|  | ||||
|  | ||||
|             public readonly ISMBFileStore? FileStore; | ||||
|             private readonly bool _isLoggedIn; | ||||
|  | ||||
|             public void Dispose() | ||||
|             { | ||||
|                 FileStore?.Disconnect(); | ||||
|                  | ||||
|                 if (_isLoggedIn) | ||||
|                     Client.Logoff(); | ||||
|  | ||||
|                 if (!Client.IsConnected) return; | ||||
|                 Client.Disconnect(); | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } | ||||
|      | ||||
|  | ||||
|  | ||||
|     public Stream OpenFileForRead(IOConnectionInfo ioc) | ||||
|         { | ||||
|  | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|  | ||||
|             if (conn.FileStore == null) | ||||
|             { | ||||
|                 throw new Exception($"Failed to read to {info.GetPathWithoutCredentials()}"); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             NTStatus status = conn.FileStore.CreateFile(out var fileHandle, out _, info.GetLocalSmbPath(), | ||||
|                 AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.Read, | ||||
|                 CreateDisposition.FILE_OPEN, | ||||
|                 CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|  | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 throw new Exception($"Failed to open file {info.LocalPath}"); | ||||
|             } | ||||
|  | ||||
|             var stream = new MemoryStream(); | ||||
|             long bytesRead = 0; | ||||
|             while (true) | ||||
|             { | ||||
|                 status = conn.FileStore.ReadFile(out var data, fileHandle, bytesRead, (int)conn.Client.MaxReadSize); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS && status != NTStatus.STATUS_END_OF_FILE) | ||||
|                 { | ||||
|                     throw new Exception("Failed to read from file"); | ||||
|                 } | ||||
|  | ||||
|                 if (status == NTStatus.STATUS_END_OF_FILE || data.Length == 0) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 bytesRead += data.Length; | ||||
|                 stream.Write(data, 0, data.Length); | ||||
|             } | ||||
|  | ||||
|             stream.Seek(0, SeekOrigin.Begin); | ||||
|             return stream; | ||||
|         } | ||||
|  | ||||
|  | ||||
|         class SmbFileStorageWriteTransaction : IWriteTransaction | ||||
|         { | ||||
|             private bool UseFileTransaction { get; } | ||||
|             private readonly string _path; | ||||
|             private readonly string _uploadPath; | ||||
|             private readonly SmbFileStorage _fileStorage; | ||||
|             private MemoryStream? _memoryStream; | ||||
|  | ||||
|             public SmbFileStorageWriteTransaction(string path, SmbFileStorage fileStorage, bool useFileTransaction) | ||||
|             { | ||||
|                 UseFileTransaction = useFileTransaction; | ||||
|                 _path = path; | ||||
|                 if (useFileTransaction) | ||||
|                 { | ||||
|                     _uploadPath = _path + Guid.NewGuid().ToString().Substring(0, 8) + ".tmp"; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     _uploadPath = _path; | ||||
|                 } | ||||
|                  | ||||
|  | ||||
|                 _fileStorage = fileStorage; | ||||
|                 _memoryStream = null; | ||||
|             } | ||||
|  | ||||
|             public void Dispose() | ||||
|             { | ||||
|                 _memoryStream?.Dispose(); | ||||
|             } | ||||
|  | ||||
|             public Stream OpenFile() | ||||
|             { | ||||
|                 _memoryStream = new MemoryStream(); | ||||
|                 return _memoryStream; | ||||
|             } | ||||
|  | ||||
|             public void CommitWrite() | ||||
|             { | ||||
|                 _fileStorage.UploadData(new MemoryStream(_memoryStream!.ToArray()), new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath})); | ||||
|                 if (UseFileTransaction) | ||||
|                 { | ||||
|                     SmbConnectionInfo uploadPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _uploadPath }); | ||||
|                     SmbConnectionInfo finalPath = new SmbConnectionInfo(new IOConnectionInfo() { Path = _path }); | ||||
|                     _fileStorage.RenameFile(uploadPath, finalPath); | ||||
|                 } | ||||
|  | ||||
|  | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private void RenameFile(SmbConnectionInfo fromPath, SmbConnectionInfo toPath) | ||||
|         { | ||||
|             using var connection = new SmbConnection(fromPath); | ||||
|  | ||||
|             // Open existing file | ||||
|             var status = connection.FileStore!.CreateFile(out var handle, out _, fromPath.GetLocalSmbPath(), AccessMask.MAXIMUM_ALLOWED, 0, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 throw new Exception($"Failed to open {fromPath.LocalPath} for renaming!"); | ||||
|          | ||||
|             FileRenameInformationType2 renameInfo = new FileRenameInformationType2 | ||||
|             { | ||||
|                 FileName = toPath.GetLocalSmbPath(), | ||||
|                 ReplaceIfExists = true | ||||
|             }; | ||||
|             connection.FileStore.SetFileInformation(handle, renameInfo); | ||||
|             connection.FileStore.CloseFile(handle); | ||||
|              | ||||
|         } | ||||
|  | ||||
|         private void UploadData(Stream data, SmbConnectionInfo uploadPath) | ||||
|         { | ||||
|             using var connection = new SmbConnection(uploadPath); | ||||
|             var status = connection.FileStore!.CreateFile(out var fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_CREATE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|             if (status == NTStatus.STATUS_OBJECT_NAME_COLLISION) | ||||
|                 status = connection.FileStore!.CreateFile(out fileHandle, out _, uploadPath.GetLocalSmbPath(), AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE, FileAttributes.Normal, ShareAccess.None, CreateDisposition.FILE_OVERWRITE, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 throw new Exception($"Failed to open {uploadPath.LocalPath} for writing!"); | ||||
|             } | ||||
|  | ||||
|             long writeOffset = 0; | ||||
|             while (data.Position < data.Length) | ||||
|             { | ||||
|                 byte[] buffer = new byte[(int)connection.Client.MaxWriteSize]; | ||||
|                 int bytesRead = data.Read(buffer, 0, buffer.Length); | ||||
|                 if (bytesRead < (int)connection.Client.MaxWriteSize) | ||||
|                 { | ||||
|                     Array.Resize(ref buffer, bytesRead); | ||||
|                 } | ||||
|  | ||||
|                 status = connection.FileStore.WriteFile(out _, fileHandle, writeOffset, buffer); | ||||
|                 if (status != NTStatus.STATUS_SUCCESS) | ||||
|                 { | ||||
|                     throw new Exception("Failed to write to file"); | ||||
|                 } | ||||
|                 writeOffset += bytesRead; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction) | ||||
|         { | ||||
|             return new SmbFileStorageWriteTransaction(ioc.Path, this, useFileTransaction); | ||||
|         } | ||||
|  | ||||
|         public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return UrlUtil.StripExtension( | ||||
|                 UrlUtil.GetFileName(ioc.Path)); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public string GetFileExtension(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return UrlUtil.GetExtension(ioc.Path); | ||||
|         } | ||||
|  | ||||
|         public bool RequiresCredentials(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public void CreateDirectory(IOConnectionInfo ioc, string newDirName) | ||||
|         { | ||||
|             throw new NotImplementedException(); | ||||
|         } | ||||
|  | ||||
|         private static IEnumerable<FileDescription> ListShares(SmbConnection conn, SmbConnectionInfo parent) | ||||
|         { | ||||
|             foreach (string share in conn.Client.ListShares(out _)) | ||||
|             { | ||||
|                 yield return new FileDescription() | ||||
|                 { | ||||
|                     CanRead = true, | ||||
|                     CanWrite = true, | ||||
|                     DisplayName = share, | ||||
|                     IsDirectory = true, | ||||
|                     Path = parent.GetChild(share).ToPath() | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc) | ||||
|         { | ||||
|             List<FileDescription> result = []; | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|             if (string.IsNullOrEmpty(info.Share)) | ||||
|             { | ||||
|                 var shares = ListShares(conn, info).ToList(); | ||||
|                 return shares; | ||||
|             } | ||||
|  | ||||
|             NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); | ||||
|             if (status == NTStatus.STATUS_SUCCESS) | ||||
|             { | ||||
|                 conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, "*", FileInformationClass.FileDirectoryInformation); | ||||
|                 foreach (var fi in fileList) | ||||
|                 { | ||||
|                     var fileDirectoryInformation = fi as FileDirectoryInformation; | ||||
|                     if (fileDirectoryInformation == null) | ||||
|                         continue; | ||||
|  | ||||
|                     if (fileDirectoryInformation.FileName is "." or "..") | ||||
|                         continue; | ||||
|  | ||||
|                     var fileDescription = FileDescriptionConvert(ioc, fileDirectoryInformation); | ||||
|  | ||||
|                     result.Add(fileDescription); | ||||
|                 } | ||||
|                 conn.FileStore.CloseFile(directoryHandle); | ||||
|             } | ||||
|  | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         private FileDescription FileDescriptionConvert(IOConnectionInfo parentIoc, | ||||
|             FileDirectoryInformation fileDirectoryInformation) | ||||
|         { | ||||
|             FileDescription fileDescription = new FileDescription | ||||
|             { | ||||
|                 CanRead = true, | ||||
|                 CanWrite = true, | ||||
|                 IsDirectory = (fileDirectoryInformation.FileAttributes & FileAttributes.Directory) != 0, | ||||
|                 DisplayName = fileDirectoryInformation.FileName | ||||
|             }; | ||||
|             fileDescription.Path = CreateFilePath(parentIoc.Path, fileDescription.DisplayName); | ||||
|             fileDescription.LastModified = fileDirectoryInformation.LastWriteTime; | ||||
|                          | ||||
|             fileDescription.SizeInBytes = fileDirectoryInformation.EndOfFile; | ||||
|             return fileDescription; | ||||
|         } | ||||
|  | ||||
|         public FileDescription GetFileDescription(IOConnectionInfo ioc) | ||||
|         { | ||||
|             SmbConnectionInfo info = new SmbConnectionInfo(ioc); | ||||
|  | ||||
|             if (string.IsNullOrEmpty(info.Share)) | ||||
|             { | ||||
|                 return new FileDescription | ||||
|                 { | ||||
|                     CanRead = true, CanWrite = true, | ||||
|                     DisplayName = info.Host, | ||||
|                     IsDirectory = true, | ||||
|                     Path = info.ToPath() | ||||
|                 }; | ||||
|             } | ||||
|  | ||||
|             using SmbConnection conn = new SmbConnection(info); | ||||
|             NTStatus status = conn.FileStore!.CreateFile(out var directoryHandle, out _, info.GetParent().GetLocalSmbPath(), AccessMask.GENERIC_READ, FileAttributes.Directory, ShareAccess.Read | ShareAccess.Write, CreateDisposition.FILE_OPEN, CreateOptions.FILE_DIRECTORY_FILE, null); | ||||
|             if (status != NTStatus.STATUS_SUCCESS) throw new Exception($"Failed to query details for {info.LocalPath}"); | ||||
|             conn.FileStore.QueryDirectory(out List<QueryDirectoryFileInformation> fileList, directoryHandle, info.Stem(), FileInformationClass.FileDirectoryInformation); | ||||
|             foreach (var fi in fileList) | ||||
|             { | ||||
|                 var fileDirectoryInformation = fi as FileDirectoryInformation; | ||||
|                 if (fileDirectoryInformation == null) | ||||
|                     continue; | ||||
|  | ||||
|                 if (fileDirectoryInformation.FileName is "." or "..") | ||||
|                     continue; | ||||
|  | ||||
|                 return FileDescriptionConvert(ioc, fileDirectoryInformation); | ||||
|  | ||||
|                          | ||||
|             } | ||||
|             conn.FileStore.CloseFile(directoryHandle); | ||||
|  | ||||
|             throw new Exception($"Failed to query details for {info.LocalPath}"); | ||||
|         } | ||||
|  | ||||
|         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 new SmbConnectionInfo(ioc).ToDisplayString(); | ||||
|         } | ||||
|  | ||||
|         public string CreateFilePath(string parent, string newFilename) | ||||
|         { | ||||
|             return new SmbConnectionInfo(new IOConnectionInfo() { Path = parent}).GetChild(newFilename).ToPath(); | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetParentPath(IOConnectionInfo ioc) | ||||
|         { | ||||
|             SmbConnectionInfo connectionInfo = new SmbConnectionInfo(ioc); | ||||
|             return new IOConnectionInfo() { Path = connectionInfo.GetParent().ToPath() }; | ||||
|         } | ||||
|  | ||||
|         public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename) | ||||
|         { | ||||
|             return new IOConnectionInfo() { Path = CreateFilePath(folderPath.Path, filename)}; | ||||
|         } | ||||
|  | ||||
|         public bool IsPermanentLocation(IOConnectionInfo ioc) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -13,6 +13,7 @@ | ||||
|     <PackageReference Include="MegaApiClient" Version="1.10.4" /> | ||||
|     <PackageReference Include="Microsoft.Graph" Version="5.68.0" /> | ||||
|     <PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" /> | ||||
|     <PackageReference Include="SMBLibrary" Version="1.5.4" /> | ||||
|     <PackageReference Include="Xamarin.AndroidX.Browser" Version="1.8.0" /> | ||||
|     <PackageReference Include="Xamarin.AndroidX.Core" Version="1.13.1.5" /> | ||||
|     <PackageReference Include="Xamarin.Google.Android.Material" Version="1.11.0.3" /> | ||||
|   | ||||
| @@ -22,6 +22,7 @@ using Keepass2android.Javafilestorage; | ||||
| #endif | ||||
| using KeePassLib.Serialization; | ||||
| using KeePassLib.Utility; | ||||
| using static Kp2aBusinessLogic.Io.SmbFileStorage; | ||||
|  | ||||
| namespace keepass2android | ||||
| { | ||||
| @@ -350,7 +351,47 @@ namespace keepass2android | ||||
| #endif | ||||
| 		} | ||||
|  | ||||
| 		private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
|  | ||||
|         private void ShowSmbDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
|         { | ||||
| #if !EXCLUDE_JAVAFILESTORAGE && !NoNet | ||||
|             MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); | ||||
|             View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.smbcredentials, null); | ||||
|             if (!defaultPath.EndsWith(_schemeSeparator)) | ||||
|             { | ||||
|                 SmbConnectionInfo ci = new SmbConnectionInfo(new IOConnectionInfo() { Path = defaultPath }); | ||||
|                  | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text = ci.GetPathWithoutCredentials(); | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text = ci.Domain; | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text = ci.Username; | ||||
|                 dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text = ci.Password; | ||||
|  | ||||
|  | ||||
|             } | ||||
|             builder.SetView(dlgContents); | ||||
|             builder.SetPositiveButton(Android.Resource.String.Ok, | ||||
|                                       (sender, args) => | ||||
|                                       { | ||||
|                                           string url = dlgContents.FindViewById<EditText>(Resource.Id.smb_url).Text; | ||||
|  | ||||
|                                           string user = dlgContents.FindViewById<EditText>(Resource.Id.smb_user).Text; | ||||
|                                           string password = dlgContents.FindViewById<EditText>(Resource.Id.smb_password).Text; | ||||
|                                           string domain = dlgContents.FindViewById<EditText>(Resource.Id.smb_domain).Text; | ||||
|  | ||||
|                                           string fullPath = SmbConnectionInfo.FromUrlAndCredentials(url, user, password, domain).ToPath(); | ||||
|                                           onStartBrowse(fullPath); | ||||
|                                       }); | ||||
|             EventHandler<DialogClickEventArgs> evtH = new EventHandler<DialogClickEventArgs>((sender, e) => onCancel()); | ||||
|  | ||||
|             builder.SetNegativeButton(Android.Resource.String.Cancel, evtH); | ||||
|             builder.SetTitle(activity.GetString(Resource.String.enter_smb_login_title)); | ||||
|             Dialog dialog = builder.Create(); | ||||
|  | ||||
|             dialog.Show(); | ||||
| #endif | ||||
|         } | ||||
|  | ||||
|         private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) | ||||
| 		{ | ||||
| #if !NoNet | ||||
| 			MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); | ||||
| @@ -476,7 +517,9 @@ namespace keepass2android | ||||
| 				ShowFtpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
| 			else if ((defaultPath.StartsWith("http://")) || (defaultPath.StartsWith("https://"))) | ||||
| 				ShowHttpDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
| 			else if (defaultPath.StartsWith("owncloud://")) | ||||
|             else if ((defaultPath.StartsWith("smb://"))) | ||||
|                 ShowSmbDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath); | ||||
|             else if (defaultPath.StartsWith("owncloud://")) | ||||
| 				ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "owncloud"); | ||||
| 			else if (defaultPath.StartsWith("nextcloud://")) | ||||
| 			    ShowOwncloudDialog(_activity, ReturnFileOrStartFileChooser, ReturnCancel, defaultPath, "nextcloud"); | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 5.8 KiB | 
							
								
								
									
										51
									
								
								src/keepass2android-app/Resources/layout/smbcredentials.axml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/keepass2android-app/Resources/layout/smbcredentials.axml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:layout_margin="12dip" | ||||
|     > | ||||
|   <LinearLayout | ||||
|     android:orientation="horizontal" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content"> | ||||
|  | ||||
|     <EditText | ||||
|             android:id="@+id/smb_url" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:singleLine="true" | ||||
| 			android:layout_weight="1" | ||||
|             android:text="" | ||||
|       android:inputType="textWebEmailAddress" | ||||
| 			android:hint="@string/hint_smb_url" /> | ||||
|     | ||||
|   </LinearLayout> | ||||
|   <EditText | ||||
|     android:id="@+id/smb_domain" | ||||
|     android:layout_width="fill_parent" | ||||
|     android:layout_height="wrap_content" | ||||
|     android:singleLine="true" | ||||
|     android:text="" | ||||
|     android:hint="@string/hint_smb_domain" /> | ||||
|   <EditText | ||||
|              android:id="@+id/smb_user" | ||||
|              android:layout_width="fill_parent" | ||||
|              android:layout_height="wrap_content" | ||||
|              android:singleLine="true" | ||||
|              android:text="" | ||||
|              android:hint="@string/hint_smb_username" /> | ||||
|  | ||||
|   <EditText | ||||
|             android:id="@+id/smb_password" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:inputType="textPassword" | ||||
|             android:singleLine="true" | ||||
|             android:text="" | ||||
|             android:hint="@string/hint_pass" | ||||
|     android:importantForAccessibility="no"/> | ||||
|  | ||||
|  | ||||
|  | ||||
| </LinearLayout> | ||||
| @@ -504,7 +504,12 @@ | ||||
|   <string name="ok_donate">Tell me more!</string> | ||||
|   <string name="no_thanks">No, I don\'t like it that much</string> | ||||
|   <string name="enter_http_login_title">Enter WebDav login data:</string> | ||||
|   <string name="enter_smb_login_title">Enter Samba login data:</string> | ||||
|   <string name="hint_http_url">URL of folder or file (ex: mycloud.me.com/webdav/)</string> | ||||
|   <string name="hint_smb_url">URL of folder or file (ex: 192.168.1.10/share/folder/)</string> | ||||
|   <string name="hint_smb_domain">Samba user\'s domain</string> | ||||
|   <string name="hint_smb_username">Samba username</string> | ||||
|    | ||||
|   <string name="enter_owncloud_login_title">Enter OwnCloud login data:</string> | ||||
|   <string name="hint_owncloud_url">OwnCloud URL (ex: owncloud.me.com)</string> | ||||
|   <string name="enter_nextcloud_login_title">Enter Nextcloud login data:</string> | ||||
| @@ -542,6 +547,7 @@ | ||||
|   <string name="filestoragename_ftp">FTP</string> | ||||
|   <string name="filestoragename_http">HTTP (WebDav)</string> | ||||
|   <string name="filestoragename_https">HTTPS (WebDav)</string> | ||||
|   <string name="filestoragename_smb">Samba (Windows Share)</string> | ||||
|   <string name="filestoragename_owncloud">OwnCloud</string> | ||||
|   <string name="filestoragename_nextcloud">Nextcloud</string> | ||||
|   <string name="filestoragename_dropbox">Dropbox</string> | ||||
|   | ||||
| @@ -45,6 +45,7 @@ using keepass2android.database.edit; | ||||
| using keepass2android; | ||||
| using KeePassLib.Interfaces; | ||||
| using KeePassLib.Utility; | ||||
| using Kp2aBusinessLogic.Io; | ||||
| #if !NoNet | ||||
| #if !EXCLUDE_JAVAFILESTORAGE | ||||
| using Android.Gms.Common; | ||||
| @@ -836,7 +837,8 @@ namespace keepass2android | ||||
|                             new SftpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled()), | ||||
| 							new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled), | ||||
| 							new WebDavFileStorage(this), | ||||
| 							new PCloudFileStorage(LocaleManager.LocalizedAppContext, this), | ||||
|                             new SmbFileStorage(), | ||||
|                             new PCloudFileStorage(LocaleManager.LocalizedAppContext, this), | ||||
|                             new PCloudFileStorageAll(LocaleManager.LocalizedAppContext, this), | ||||
|                             new MegaFileStorage(App.Context), | ||||
| 							//new LegacyWebDavStorage(this), | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Philipp Crocoll
					Philipp Crocoll