Compare commits
18 Commits
1617-use-g
...
feature/82
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a3d5273285 | ||
![]() |
cd323c0a22 | ||
![]() |
99ca8bf953 | ||
![]() |
d40b3dc15c | ||
![]() |
057a7e2f7a | ||
![]() |
1b73c536d5 | ||
![]() |
13306a9076 | ||
![]() |
cfb5098b38 | ||
![]() |
b83c4b3772 | ||
![]() |
f03c11381e | ||
![]() |
913222d7cb | ||
![]() |
3e6d86c206 | ||
![]() |
16ff81cf81 | ||
![]() |
0636f687ac | ||
![]() |
60d8900473 | ||
![]() |
4b2d2ef768 | ||
![]() |
48899ba9a0 | ||
![]() |
a51bfb102f |
@@ -140,6 +140,10 @@ namespace keepass2android
|
||||
|
||||
|
||||
#endif
|
||||
int WebDavChunkedUploadSize
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -15,7 +15,9 @@ namespace keepass2android.Io
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
public partial class DropboxAppFolderFileStorage: JavaFileStorage
|
||||
{
|
||||
@@ -29,6 +31,7 @@ namespace keepass2android.Io
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -123,7 +123,7 @@ namespace keepass2android.Io
|
||||
|
||||
}
|
||||
|
||||
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
public virtual IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
|
||||
}
|
||||
|
617
src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
Normal file
617
src/Kp2aBusinessLogic/Io/SmbFileStorage.cs
Normal file
@@ -0,0 +1,617 @@
|
||||
#if !NoNet
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -6,10 +6,12 @@ using System.Text;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
|
||||
using Keepass2android.Javafilestorage;
|
||||
#endif
|
||||
using KeePassLib.Serialization;
|
||||
@@ -19,9 +21,15 @@ namespace keepass2android.Io
|
||||
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE
|
||||
public class WebDavFileStorage: JavaFileStorage
|
||||
{
|
||||
public WebDavFileStorage(IKp2aApp app) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler), app)
|
||||
{
|
||||
}
|
||||
private readonly IKp2aApp _app;
|
||||
private readonly WebDavStorage baseWebdavStorage;
|
||||
|
||||
public WebDavFileStorage(IKp2aApp app, int chunkSize) : base(new Keepass2android.Javafilestorage.WebDavStorage(app.CertificateErrorHandler, chunkSize), app)
|
||||
{
|
||||
_app = app;
|
||||
baseWebdavStorage = (WebDavStorage)Jfs;
|
||||
|
||||
}
|
||||
|
||||
public override IEnumerable<string> SupportedProtocols
|
||||
{
|
||||
@@ -75,6 +83,15 @@ namespace keepass2android.Io
|
||||
}
|
||||
return base.IocToPath(ioc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
|
||||
{
|
||||
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
|
||||
return base.OpenWriteTransaction(ioc, useFileTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
<PackageReference Include="MegaApiClient" Version="1.10.4" Condition="'$(Flavor)'!='NoNet'"/>
|
||||
<PackageReference Include="Microsoft.Graph" Version="5.68.0" Condition="'$(Flavor)'!='NoNet'"/>
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.1" Condition="'$(Flavor)'!='NoNet'"/>
|
||||
<PackageReference Include="SMBLibrary" Version="1.5.4" Condition="'$(Flavor)'!='NoNet'"/>
|
||||
<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" />
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package keepass2android.javafilestorage;
|
||||
|
||||
import android.content.Context;
|
||||
import java.math.BigInteger;
|
||||
import android.content.Intent;
|
||||
|
||||
import android.net.Uri;
|
||||
@@ -15,7 +16,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator;
|
||||
import com.burgstaller.okhttp.digest.CachingAuthenticator;
|
||||
import com.burgstaller.okhttp.digest.DigestAuthenticator;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@@ -24,6 +28,7 @@ import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@@ -44,23 +49,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager;
|
||||
import keepass2android.javafilestorage.webdav.PropfindXmlParser;
|
||||
import keepass2android.javafilestorage.webdav.WebDavUtil;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.internal.tls.OkHostnameVerifier;
|
||||
import okio.BufferedSink;
|
||||
|
||||
public class WebDavStorage extends JavaFileStorageBase {
|
||||
|
||||
private final ICertificateErrorHandler mCertificateErrorHandler;
|
||||
private Context appContext;
|
||||
|
||||
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler)
|
||||
int chunkSize;
|
||||
|
||||
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize)
|
||||
{
|
||||
this.chunkSize = chunkSize;
|
||||
|
||||
mCertificateErrorHandler = certificateErrorHandler;
|
||||
}
|
||||
|
||||
public void setUploadChunkSize(int chunkSize)
|
||||
{
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException {
|
||||
String scheme = url.substring(0, url.indexOf("://"));
|
||||
url = url.substring(scheme.length() + 3);
|
||||
@@ -181,21 +196,119 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
return client;
|
||||
}
|
||||
|
||||
public void renameOrMoveWebDavResource(String sourcePath, String destinationPath, boolean overwrite) throws Exception {
|
||||
|
||||
ConnectionInfo sourceCi = splitStringToConnectionInfo(sourcePath);
|
||||
ConnectionInfo destinationCi = splitStringToConnectionInfo(destinationPath);
|
||||
|
||||
Request.Builder requestBuilder = new Request.Builder()
|
||||
.url(new URL(sourceCi.URL))
|
||||
.method("MOVE", null) // "MOVE" is the HTTP method
|
||||
.header("Destination", destinationCi.URL); // New URI for the resource
|
||||
|
||||
// Add Overwrite header
|
||||
if (overwrite) {
|
||||
requestBuilder.header("Overwrite", "T"); // 'T' for true
|
||||
} else {
|
||||
requestBuilder.header("Overwrite", "F"); // 'F' for false
|
||||
}
|
||||
|
||||
Request request = requestBuilder.build();
|
||||
|
||||
Response response = getClient(sourceCi).newCall(request).execute();
|
||||
|
||||
// Check the status code
|
||||
if (response.isSuccessful()) {
|
||||
// WebDAV MOVE can return 201 (Created) if a new resource was created at dest,
|
||||
// or 204 (No Content) if moved to a pre-existing destination (e.g., just renamed).
|
||||
// A 200 OK might also be returned by some servers, though 201/204 are more common.
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception("Rename/Move failed for " + sourceCi.URL + " to " + destinationCi.URL + ": " + response.code() + " " + response.message());
|
||||
}
|
||||
}
|
||||
|
||||
public static String generateRandomHexString(int length) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
// Generate enough bytes to ensure we can get the desired number of hex characters.
|
||||
// Each byte converts to two hex characters.
|
||||
// For 8 hex characters, we need 4 bytes.
|
||||
int numBytes = (int) Math.ceil(length / 2.0);
|
||||
byte[] randomBytes = new byte[numBytes];
|
||||
secureRandom.nextBytes(randomBytes);
|
||||
|
||||
// Convert the byte array to a hexadecimal string
|
||||
// BigInteger(1, randomBytes) treats the byte array as a positive number.
|
||||
// toString(16) converts it to a hexadecimal string.
|
||||
String hexString = new BigInteger(1, randomBytes).toString(16);
|
||||
|
||||
// Pad with leading zeros if necessary (e.g., if the generated number is small)
|
||||
// and then take the first 'length' characters.
|
||||
// Using String.format to ensure leading zeros if the hexString is shorter.
|
||||
return String.format("%0" + length + "d", new BigInteger(hexString, 16)).substring(0, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uploadFile(String path, byte[] data, boolean writeTransactional)
|
||||
throws Exception {
|
||||
|
||||
if (writeTransactional)
|
||||
{
|
||||
String randomSuffix = ".tmp." + generateRandomHexString(8);
|
||||
uploadFile(path + randomSuffix, data, false);
|
||||
renameOrMoveWebDavResource(path+randomSuffix, path, true);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ConnectionInfo ci = splitStringToConnectionInfo(path);
|
||||
|
||||
|
||||
RequestBody requestBody;
|
||||
if (chunkSize > 0)
|
||||
{
|
||||
// use chunked upload
|
||||
requestBody = new RequestBody() {
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return MediaType.parse("application/binary");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(BufferedSink sink) throws IOException {
|
||||
try (InputStream in = new ByteArrayInputStream(data)) {
|
||||
byte[] buffer = new byte[chunkSize];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
sink.write(buffer, 0, read);
|
||||
sink.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return -1; // use chunked upload
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
requestBody = new MultipartBody.Builder()
|
||||
.addPart(RequestBody.create(data, MediaType.parse("application/binary")))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(new URL(ci.URL))
|
||||
.put(RequestBody.create(MediaType.parse("application/binary"), data))
|
||||
.put(requestBody)
|
||||
.build();
|
||||
|
||||
//TODO consider writeTransactional
|
||||
//TODO check for error
|
||||
|
||||
|
||||
Response response = getClient(ci).newCall(request).execute();
|
||||
checkStatus(response);
|
||||
@@ -290,7 +403,10 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
e.sizeInBytes = -1;
|
||||
}
|
||||
}
|
||||
e.isDirectory = r.href.endsWith("/");
|
||||
|
||||
e.isDirectory = r.href.endsWith("/") || okprop.IsCollection;
|
||||
|
||||
|
||||
|
||||
e.displayName = okprop.DisplayName;
|
||||
if (e.displayName == null)
|
||||
@@ -519,3 +635,4 @@ public class WebDavStorage extends JavaFileStorageBase {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -57,6 +57,8 @@ public class PropfindXmlParser
|
||||
public String DisplayName;
|
||||
public String LastModified;
|
||||
public String ContentLength;
|
||||
|
||||
public boolean IsCollection;
|
||||
}
|
||||
public String status;
|
||||
public Prop prop;
|
||||
@@ -191,6 +193,8 @@ public class PropfindXmlParser
|
||||
continue;
|
||||
}
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
|
||||
|
||||
android.util.Log.d("PARSE", "4name = " + name);
|
||||
if (name.equals("getcontentlength"))
|
||||
@@ -200,6 +204,9 @@ public class PropfindXmlParser
|
||||
prop.LastModified = readText(parser);
|
||||
} else if (name.equals("displayname")) {
|
||||
prop.DisplayName = readText(parser);
|
||||
} else if (name.equals("resourcetype") && namespace.equals(ns)) {
|
||||
// We found the <d:resourcetype> tag
|
||||
prop.IsCollection = readResourceType(parser);
|
||||
} else {
|
||||
skip(parser);
|
||||
}
|
||||
@@ -208,6 +215,37 @@ public class PropfindXmlParser
|
||||
return prop;
|
||||
}
|
||||
|
||||
private boolean readResourceType(XmlPullParser parser) throws IOException, XmlPullParserException {
|
||||
boolean isCollection = false;
|
||||
parser.require(XmlPullParser.START_TAG, ns, "resourcetype");
|
||||
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
|
||||
if (parser.getEventType() != XmlPullParser.START_TAG) {
|
||||
continue;
|
||||
}
|
||||
String name = parser.getName();
|
||||
String namespace = parser.getNamespace();
|
||||
|
||||
if (name.equals("collection") && namespace.equals(ns)) {
|
||||
// We found <d:collection/>, so it's a folder
|
||||
isCollection = true;
|
||||
// Since <d:collection/> is usually an empty tag, just consume it.
|
||||
// It might contain text if there's whitespace, so consume text then end tag.
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
parser.nextTag(); // Move to the end tag
|
||||
}
|
||||
parser.require(XmlPullParser.END_TAG, ns, "collection");
|
||||
} else {
|
||||
// Skip any other unexpected tags within <d:resourcetype>
|
||||
skip(parser);
|
||||
}
|
||||
}
|
||||
// After reading all children of <d:resourcetype>, ensure we are at its END_TAG
|
||||
parser.require(XmlPullParser.END_TAG, ns, "resourcetype");
|
||||
return isCollection;
|
||||
}
|
||||
|
||||
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
|
||||
android.util.Log.d("PARSE", "skipping " + parser.getName());
|
||||
|
||||
|
@@ -548,7 +548,7 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
|
||||
|
||||
//storageToTest = new GoogleDriveAppDataFileStorage();
|
||||
/*storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
storageToTest = new WebDavStorage(new ICertificateErrorHandler() {
|
||||
@Override
|
||||
public boolean onValidationError(String error) {
|
||||
return false;
|
||||
@@ -558,12 +558,12 @@ public class MainActivity extends Activity implements JavaFileStorage.FileStorag
|
||||
public boolean alwaysFailOnValidationError() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
*/
|
||||
}, 64*1024);
|
||||
|
||||
//storageToTest = new DropboxV2Storage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxFileStorage(ctx,"4ybka4p4a1027n6", "1z5lv528un9nre8", !simulateRestart);
|
||||
//storageToTest = new DropboxAppFolderFileStorage(ctx,"ax0268uydp1ya57", "3s86datjhkihwyc", true);
|
||||
storageToTest = new GoogleDriveFullFileStorage();
|
||||
// storageToTest = new GoogleDriveFullFileStorage();
|
||||
|
||||
|
||||
return storageToTest;
|
||||
|
@@ -4,12 +4,15 @@ using System.Linq;
|
||||
using System.Net;
|
||||
#if !NoNet
|
||||
using FluentFTP;
|
||||
using static Kp2aBusinessLogic.Io.SmbFileStorage;
|
||||
#endif
|
||||
using System.Text;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.Res;
|
||||
using Android.OS;
|
||||
using Android.Preferences;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
@@ -23,6 +26,7 @@ using Keepass2android.Javafilestorage;
|
||||
using KeePassLib.Serialization;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
|
||||
namespace keepass2android
|
||||
{
|
||||
public class FileSelectHelper
|
||||
@@ -319,7 +323,7 @@ namespace keepass2android
|
||||
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
|
||||
if (!defaultPath.EndsWith(_schemeSeparator))
|
||||
{
|
||||
var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler);
|
||||
var webdavStorage = CreateWebdavStorage(activity);
|
||||
var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
|
||||
dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
|
||||
dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
|
||||
@@ -339,7 +343,7 @@ namespace keepass2android
|
||||
string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
|
||||
if (host.Contains(_schemeSeparator) == false)
|
||||
host = scheme + _schemeSeparator + host;
|
||||
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user,
|
||||
string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user,
|
||||
password);
|
||||
onStartBrowse(httpPath);
|
||||
});
|
||||
@@ -353,7 +357,54 @@ 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);
|
||||
});
|
||||
builder.SetCancelable(false);
|
||||
|
||||
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
|
||||
}
|
||||
#if !NoNet
|
||||
private static WebDavStorage CreateWebdavStorage(Activity activity)
|
||||
{
|
||||
return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize);
|
||||
}
|
||||
#endif
|
||||
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
|
||||
{
|
||||
#if !NoNet
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
|
||||
@@ -479,7 +530,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");
|
||||
@@ -518,7 +571,7 @@ namespace keepass2android
|
||||
string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
|
||||
if (host.Contains(_schemeSeparator) == false)
|
||||
host = scheme + _schemeSeparator + host;
|
||||
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
|
||||
string httpPath = CreateWebdavStorage(activity).BuildFullPath(WebDavFileStorage.Owncloud2Webdav(host, subtype == "owncloud" ? WebDavFileStorage.owncloudPrefix : WebDavFileStorage.nextcloudPrefix), user,
|
||||
password);
|
||||
onStartBrowse(httpPath);
|
||||
});
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
55
src/keepass2android-app/Resources/layout/smbcredentials.axml
Normal file
55
src/keepass2android-app/Resources/layout/smbcredentials.axml
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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:padding="12dp"
|
||||
android:layout_margin="12dip"
|
||||
>
|
||||
<TextView android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/hint_smb_credentials" />
|
||||
<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>
|
@@ -93,6 +93,8 @@
|
||||
<string name="disable_fingerprint_unlock">Zakázat Biometrické odemknutí</string>
|
||||
<string name="enable_fingerprint_unlock">Povolit plné Biometrické odemknutí</string>
|
||||
<string name="enable_fingerprint_quickunlock">Povolit Biometrické odemknutí pro Rychlé odemknutí</string>
|
||||
<string name="password_based_quick_unlock_not_available">Rychlé odemknutí pomocí hesla není k dispozici</string>
|
||||
<string name="password_based_quick_unlock_not_available_text">Funkce Rychlé odemknutí pomocí části hesla je blokována, protože na vašem zařízení není aktivováno zamykání obrazovky. Toto opatření slouží k vaší ochraně pro případ, že by někdo sledoval zadávání klíče pro Rychlé odemknutí.</string>
|
||||
<string name="fingerprint_unlock_failed">Biometrické odemknutí selhalo. Dešifrovací klíč byl zneplatněn systémem Android. To se obvykle stává, pokud došlo ke změně biometrického ověření nebo bylo změněno bezpečnostní nastavení. </string>
|
||||
<string name="fingerprint_disabled_wrong_masterkey">Odemknutí databáze se nezdařilo: neplatný složený klíč. Biometrické odemknutí bylo zakázáno z důvodu pravděpodobného vypršení platnosti hlavního hesla. </string>
|
||||
<string name="fingerprint_reenable">Prosím, povolte znovu Biometrické odemknutí pro nové hlavní heslo.</string>
|
||||
@@ -319,6 +321,7 @@
|
||||
<string name="QuickUnlock_label_secure">Vložte kód Rychlého odemknutí:</string>
|
||||
<string name="QuickUnlock_button">Rychlé odemknutí</string>
|
||||
<string name="QuickUnlock_lockButton">Zavřít databázi</string>
|
||||
<string name="enable_screen_lock">Povolit zámek obrazovky</string>
|
||||
<string name="QuickUnlockDefaultEnabled_title">Ve výchozím nastavení povolit Rychlé odemknutí</string>
|
||||
<string name="QuickUnlockDefaultEnabled_summary">Určuje, zda je Rychlé odemknutí ve výchozím nastavení povoleno nebo ne.</string>
|
||||
<string name="ViewDatabaseSecure_title">Chránit zobrazení databáze</string>
|
||||
@@ -718,6 +721,11 @@
|
||||
<string name="EntryChannel_desc">Upozornění pro usnadnění přístupu k momentálně zvolené položce.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Zavřít databázi po třech neúspěšných pokusech o odemknutí.</string>
|
||||
<string name="WarnFingerprintInvalidated">Varování! Biometrické ověření může být zneplatněno Androidem, např. po přidání nového otisku prstu do nastavení zařízení. Ujistěte se, že vždy víte, jak odemknout pomocí hlavního hesla!</string>
|
||||
<string-array name="ChangeLog_1_13">
|
||||
<item>Vylepšené hodnocení kvality hesel založené na nejčastěji používaných heslech.</item>
|
||||
<item>Pokud zařízení nemá aktivovaný zámek obrazovky, blokuje se funkce Rychlé odemknutí pomocí hesla (z bezpečnostních důvodů).</item>
|
||||
<item>Aktualizovat konfiguraci zabezpečení sítě pro vypnutí přenosu textu v nešifrované podobě.</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_12">
|
||||
<item>Aktualizováno z Xamarin Android na .net 8</item>
|
||||
<item>Aktualizováno na Target SDK 34</item>
|
||||
|
@@ -93,6 +93,8 @@
|
||||
<string name="disable_fingerprint_unlock">Απενεργοποίηση βιομετρικού ξεκλειδώματος</string>
|
||||
<string name="enable_fingerprint_unlock">Ενεργοποίηση πλήρους βιομετρικού ξεκλειδώματος</string>
|
||||
<string name="enable_fingerprint_quickunlock">Ενεργοποίηση βιομετρικού ξεκλειδώματος για QuickUnlock</string>
|
||||
<string name="password_based_quick_unlock_not_available">Το QuickUnlock βασισμένο σε συνθηματικό δεν είναι διαθέσιμο.</string>
|
||||
<string name="password_based_quick_unlock_not_available_text">Το QuickUnlock που χρησιμοποιεί ένα τμήμα του συνθηματικού έχει αποκλειστεί, επειδή το κλείδωμα οθόνης είναι απενεργοποιημένο στη συσκευή σας. Αυτό σας προστατεύει σε περίπτωση που κάποιος σας παρακολουθεί όταν εισάγετε το QuickUnlock.</string>
|
||||
<string name="fingerprint_unlock_failed">Αποτυχία βιομετρικού ξεκλειδώματος. Το κλειδί αποκρυπτογράφησης ακυρώθηκε από το Android. Αυτό συμβαίνει συνήθως αν αλλάξει η βιομετρική αυθεντικοποίηση ή οι ρυθμίσεις ασφάλειας. </string>
|
||||
<string name="fingerprint_disabled_wrong_masterkey">Το ξεκλείδωμα της βάσης δεδομένων απέτυχε: άκυρο σύνθετο κλειδί. Το βιομετρικό ξεκλείδωμα απενεργοποιήθηκε επειδή το αποθηκευμένο πρωτεύον συνθηματικό δεν είναι πλέον έγκυρο. </string>
|
||||
<string name="fingerprint_reenable">Ενεργοποιήστε ξανά το βιομετρικό ξεκλείδωμα για το νέο πρωτεύον συνθηματικό.</string>
|
||||
@@ -251,7 +253,6 @@
|
||||
<string name="exclude_lookalike">Εξαίρεση παρόμοιων χαρακτήρων</string>
|
||||
<string name="password_generation_profile">Προφίλ</string>
|
||||
<string name="save_password_generation_profile_text">Εισάγετε το όνομα του προφίλ που θα αποθηκευτεί. Εισάγετε ένα υπάρχον όνομα για αντικατάσταση.</string>
|
||||
<string name="hint_wordcount">Αριθμός λέξεων συνθηματικής φράσης</string>
|
||||
<string name="hint_wordseparator">Διαχωριστικό λέξεων</string>
|
||||
<string-array name="PasswordGeneratorModes">
|
||||
<item>Συνθηματικό</item>
|
||||
@@ -319,6 +320,7 @@
|
||||
<string name="QuickUnlock_label_secure">Εισάγετε τον κωδικό QuickUnlock:</string>
|
||||
<string name="QuickUnlock_button">QuickUnlock!</string>
|
||||
<string name="QuickUnlock_lockButton">Κλείσιμο βάσης δεδομένων</string>
|
||||
<string name="enable_screen_lock">Ενεργοποίηση κλειδώματος οθόνης</string>
|
||||
<string name="QuickUnlockDefaultEnabled_title">Ενεργοποίηση QuickUnlock εξ ορισμού</string>
|
||||
<string name="QuickUnlockDefaultEnabled_summary">Ορίζει αν το QuickUnlock είναι ενεργό εξ ορισμού ή όχι.</string>
|
||||
<string name="ViewDatabaseSecure_title">Προστασία προβολής βάσης δεδομένων</string>
|
||||
@@ -332,7 +334,7 @@
|
||||
<string name="QuickUnlockHideLength_title">Απόκρυψη μήκους QuickUnlock</string>
|
||||
<string name="QuickUnlockHideLength_summary">Αν ενεργοποιηθεί, αποκρύπτει το μήκος του κωδικού QuickUnlock στη σχετική οθόνη.</string>
|
||||
<string name="QuickUnlockKeyFromDatabase_title">Κλειδί QuickUnlock από τη βάση δεδομένων</string>
|
||||
<string name="QuickUnlockKeyFromDatabase_summary">Εάν η ενεργή βάση δεδομένων περιέχει μια καταχώριση με τίτλο QuickUnlock στην ομάδα ρίζας της, ο κωδικός πρόσβασης αυτής της καταχώρισης χρησιμοποιείται ως κωδικός QuickUnlock.</string>
|
||||
<string name="QuickUnlockKeyFromDatabase_summary">Εάν η ενεργή βάση δεδομένων περιέχει μια καταχώριση με τίτλο QuickUnlock στην ομάδα ρίζας της, το συνθηματικό αυτής της καταχώρισης χρησιμοποιείται ως κωδικός QuickUnlock.</string>
|
||||
<string name="QuickUnlock_fail">Αποτυχία QuickUnlock: λανθασμένο συνθηματικό!</string>
|
||||
<string name="SaveAttachmentDialog_title">Αποθήκευση συνημμένου</string>
|
||||
<string name="SaveAttachmentDialog_text">Επιλέξτε πού θα αποθηκεύσετε το συνημμένο.</string>
|
||||
@@ -427,7 +429,7 @@
|
||||
<string name="IconVisibilityInfo_Android8_btnSettings">Άνοιγμα ρυθμίσεων</string>
|
||||
<string name="PostNotificationsPermissionInfo_text">Το Keepass2Android μπορεί να εμφανίζει μια ειδοποίηση συστήματος ενόσω η βάση δεδομένων σας παραμένει ξεκλείδωτη. Για να λειτουργήσει αυτό, χορηγήστε την άδεια.</string>
|
||||
<string name="DontCare">Δεν με νοιάζει</string>
|
||||
<string name="DocumentAccessRevoked">Το αρχείο δεν είναι πλέον προσπελάσιμο στο Keepass2Android. Είτε διαγράφτηκε ή ανακηθηκαν τα δικαιώματα πρόσβασης. Δοκιμάστε να ξανα-ανοίξετε το αρχείο, πχ με Αλλαγή βάσης δεδομένων.</string>
|
||||
<string name="DocumentAccessRevoked">Το αρχείο δεν είναι πλέον προσπελάσιμο στο Keepass2Android. Είτε διαγράφτηκε ή ανακλήθηκαν τα δικαιώματα πρόσβασης. Δοκιμάστε να ξανανοίξετε το αρχείο, πχ με Αλλαγή βάσης δεδομένων.</string>
|
||||
<string name="PreloadDatabaseEnabled_title">Προ-φόρτωση αρχείου βάσης δεδομένων</string>
|
||||
<string name="PreloadDatabaseEnabled_summary">Ξεκίνημα φόρτωσης στο παρασκήνιο ή λήψη του αρχείου της βάσης δεδομένων κατά την εισαγωγή του συνθηματικού.</string>
|
||||
<string name="SyncAfterQuickUnlock_title">Συγχρονισμός μετά το QuickUnlock</string>
|
||||
@@ -714,14 +716,19 @@
|
||||
<string name="EntryChannel_name">Ειδοποιήσεις</string>
|
||||
<string name="EntryChannel_desc">Ειδοποίηση για απλοποιημένη πρόσβαση στην τρέχουσα καταχώριση.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Κλείσιμο της βάσης δεδομένων μετά από 3 ανεπιτυχείς προσπάθειες βιομετρικού ξεκλειδώματος.</string>
|
||||
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με τον κύριο κωδικό πρόσβασης!</string>
|
||||
<string name="WarnFingerprintInvalidated">Προσοχή! Ο βιομετρικός έλεγχος ταυτότητας μπορεί να ακυρωθεί από το Android, π.χ. μετά την προσθήκη ενός νέου δακτυλικού αποτυπώματος στις ρυθμίσεις της συσκευής σας. Βεβαιωθείτε ότι ξέρετε πάντα πώς να ξεκλειδώσετε με το κύριο συνθηματικό!</string>
|
||||
<string-array name="ChangeLog_1_13">
|
||||
<item>Βελτιωμένη εκτίμηση της ποιότητας του συνθηματικού που λαμβάνει υπόψη τα πιο δημοφιλή συνθηματικά</item>
|
||||
<item>Αποκλεισμός του QuickUnlock βασισμένου στο συνθηματικό (για λόγους ασφαλείας), εάν η συσκευή δεν έχει ενεργοποιημένο το κλείδωμα οθόνης.</item>
|
||||
<item>Ενημερώστε τις ρυθμίσεις ασφαλείας δικτύου για να απενεργοποιήσετε τη μεταφορά ορατού κειμένου.</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_12">
|
||||
<item>Αναβαθμίστηκε από Xamarin Android σε .ΝΕΤ 8</item>
|
||||
<item>Αναβαθμίστηκε στοχεύοντας το SDK 34</item>
|
||||
<item>Αναβαθμίστηκε σε διεπαφή χρήστη Material 3</item>
|
||||
<item>Βελτιώστε την αυτόματη συμπλήρωση για να εργαστείτε με Compose Apps</item>
|
||||
<item>Διόρθωση ονόματος host στην αυτόματη συμπλήρωση και αναζήτηση</item>
|
||||
<item>Διόρθωση προβλήματος με τη γεννήτρια κωδικού πρόσβασης</item>
|
||||
<item>Διόρθωση προβλήματος με τη γεννήτρια συνθηματικών</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_12_net">
|
||||
<item>Αναβαθμίστηκε το OneDrive SDK στην έκδοση 5.68</item>
|
||||
@@ -747,12 +754,12 @@
|
||||
<item>Βελτίωση της υλοποίησης FTP και SFTP</item>
|
||||
<item>Προσθήκη πρόσβασης σε πλήρες pCloud</item>
|
||||
<item>Επιτρέπει την επιλογή γλώσσας συστήματος στο μενού της γλώσσας</item>
|
||||
<item>Διόρθωση προβλήματος με την απομνημόνευση Keyfile + ερώτηση για τον τύπο του κωδικού πρόσβασης</item>
|
||||
<item>Διόρθωση προβλήματος με την απομνημόνευση Keyfile + ερώτηση για το συνθηματικό</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09e">
|
||||
<item>Διόρθωση σφάλματος για απότομα κλεισίματα εφαρμογής και μη αναμενόμενες αποσυνδέσεις</item>
|
||||
<item>Μετάβαση σε νέα υλοποίηση SFTP, υποστηρίζοντας σύγχρονους αλγόριθμους δημόσιου κλειδιού όπως rsa-sha2-256</item>
|
||||
<item>Μαρκάρισμα κωδικών πρόσβασης ως ευαίσθητοι κατά την αντιγραφή στο πρόχειρο (Android 13)</item>
|
||||
<item>Μαρκάρισμα των συνθηματικών ως ευαίσθητα κατά την αντιγραφή στο πρόχειρο (Android 13)</item>
|
||||
<item>Βελτιώσεις Autofill</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_09d">
|
||||
@@ -773,7 +780,7 @@
|
||||
<string-array name="ChangeLog_1_09a">
|
||||
<item>Προστέθηκε υποστήριξη για τη μορφή αρχείου KDBX 4.1 που εισήχθη στο KeePass 2.48</item>
|
||||
<item>Προστέθηκε ο διάλογος ρύθμισης ρυθμίσεων TOTP για τις καταχωρίσεις</item>
|
||||
<item>Βελτιωμένη γεννήτρια κωδικού πρόσβασης: Προστέθηκε υποστήριξη συνθηματικής φράσης, περισσότερες επιλογές, προφίλ και εκτίμηση ισχύος κωδικού πρόσβασης</item>
|
||||
<item>Βελτιωμένη γεννήτρια συνθηματικών: Προστέθηκε υποστήριξη συνθηματικής φράσης, περισσότερες επιλογές, προφίλ και εκτίμηση ισχύος κωδικού πρόσβασης</item>
|
||||
<item>Βελτιώσεις στην αυτόματη συμπλήρωση (σταθερό αναδυόμενο παράθυρο δεν εμφανίζεται στο Chrome, καλύτερη υποστήριξη υποτομέα)</item>
|
||||
<item>Βελτιώσεις στην υλοποίηση του OneDrive: δεν υπάρχει πλέον όριο μεγέθους, ούτε περιττές αιτήσεις ελέγχου ταυτότητας</item>
|
||||
<item>Προστέθηκε επιλογή για να επιλέξετε το φωτεινό/σκούρο θέμα από τις ρυθμίσεις του συστήματος, συμπεριλαμβανομένων των νυχτερινών πλάνων, απαιτεί Android 10+</item>
|
||||
@@ -883,7 +890,7 @@
|
||||
<string name="invalid_link_association">Δεν σχετίζεται το web domain %1$s με την εφαρμογή %2$s</string>
|
||||
<string name="enable_fingerprint_hint">Το Keepass2Android ανίχνευσε βιομετρικό εξοπλισμό. Θέλετε να ενεργοποιήσετε βιομετρικό ξεκλείδωμα για αυτή τη βάση δεδομένων;</string>
|
||||
<string name="post_notifications_dialog_title">Να επιτρέπονται οι ειδοποιήσεις</string>
|
||||
<string name="post_notifications_dialog_message">Το Keepass2Android μπορεί να εμφανίσει ειδοποιήσεις με κουμπιά για να αντιγράψετε τιμές, όπως κωδικούς πρόσβασης και TOTP στο πρόχειρο, ή για να εμφανιστεί το ενσωματωμένο πληκτρολόγιο. Αυτό είναι χρήσιμο για να μεταφέρετε τιμές σε άλλες εφαρμογές, χωρίς να μεταβείτε σε Keepass2Android επανειλημμένα. Θέλετε να ενεργοποιήσετε αυτές τις ειδοποιήσεις;</string>
|
||||
<string name="post_notifications_dialog_message">Το Keepass2Android μπορεί να εμφανίσει ειδοποιήσεις με κουμπιά για να αντιγράψετε τιμές, όπως συνθηματικά και TOTP στο πρόχειρο, ή για να εμφανιστεί το ενσωματωμένο πληκτρολόγιο. Αυτό είναι χρήσιμο για να μεταφέρετε τιμές σε άλλες εφαρμογές, χωρίς να μεταβείτε σε Keepass2Android επανειλημμένα. Θέλετε να ενεργοποιήσετε αυτές τις ειδοποιήσεις;</string>
|
||||
<string name="post_notifications_dialog_allow">Να επιτρέπονται οι ειδοποιήσεις</string>
|
||||
<string name="post_notifications_dialog_disable">Απενεργοποιήστε αυτό το χαρακτηριστικό</string>
|
||||
<string name="post_notifications_dialog_notnow">Όχι τώρα</string>
|
||||
@@ -909,4 +916,8 @@
|
||||
<string name="kp2a_switch_on_sendgodone_summary">Εναλλαγή πίσω όταν πατήσετε αποστολή / λήψη / ολοκλήρωση</string>
|
||||
<string name="qr_scanning_error_no_google_play_services">Η σάρωση QR κώδικα απαιτεί Google Play Services. Παρακαλώ εγκαταστήστε ή ενημερώστε τις Google Play Services στη συσκευή σας.</string>
|
||||
<string name="english_ime_settings">Ρυθμίσεις πληκτρολογίου Android</string>
|
||||
<string name="autoswitch_enabled_but_not_setup">Σημείωση: Έχετε ενεργοποιήσει στις Ρυθμίσεις - Εφαρμογή - Πρόσβαση στα συνθηματικά - Εναλλαγή πληκτρολογίου - Αυτόματη εναλλαγή πληκτρολογίου, αλλά φαίνεται ότι δεν έχει ρυθμιστεί σωστά.</string>
|
||||
<string name="switch_keyboard_for_totp_enabled">Σημείωση: Έχετε ενεργοποιήσει στις Ρυθμίσεις - Εφαρμογή - Πρόσβαση στα συνθηματικά - Λειτουργία αυτόματης συμπλήρωσης - Αυτόματη συμπλήρωση για καταχωρίσεις TOTP. Αυτό μπορεί να προκαλέσει την εμφάνιση αυτού του παραθύρου όταν ανοίγετε μια καταχώριση με TOTP.</string>
|
||||
<string name="switch_keyboard_inside_kp2a_enabled">Σημείωση: Έχετε ενεργοποιήσει στις Ρυθμίσεις - Εφαρμογή - Ασφάλεια - Χρήση του ενσωματωμένου στο Keepass2Android πληκτρολογίου. Αυτό μπορεί να προκαλέσει την εμφάνιση αυτού του παραθύρου όταν ανοίγετε την εφαρμογή ή επεξεργάζεστε μια καταχώριση.</string>
|
||||
<string name="switch_keyboard_on_search_enabled">Σημείωση: Έχετε ενεργοποιήσει στις Ρυθμίσεις - Εφαρμογή - Πρόσβαση στα συνθηματικά - Εναλλαγή πληκτρολογίου - Εναλλαγή πληκτρολογίου. Αυτό μπορεί να προκαλέσει την εμφάνιση αυτού του παραθύρου κατά την αναζήτηση μιας καταχώρισης από το πρόγραμμα περιήγησης.</string>
|
||||
</resources>
|
||||
|
@@ -93,6 +93,8 @@
|
||||
<string name="disable_fingerprint_unlock">Desabilitar o Desbloqueio Biométrico </string>
|
||||
<string name="enable_fingerprint_unlock">Habilitar o Desbloqueio de Biométrico completo</string>
|
||||
<string name="enable_fingerprint_quickunlock">Habilitar o Desbloqueio de Biométrico para o QuickUnlock</string>
|
||||
<string name="password_based_quick_unlock_not_available">Desbloqueio rápido baseado em senha não disponível</string>
|
||||
<string name="password_based_quick_unlock_not_available_text">O desbloqueio rápido usando uma parte da sua senha está bloqueado porque o bloqueio da tela não está ativado no seu dispositivo. Esse comportamento é para protegê -lo caso alguém o veja entrando na sua chave de desbloqueio rápido.</string>
|
||||
<string name="fingerprint_unlock_failed">Desbloqueio por impressão digital falhou: Chave para desencriptação foi invalidada pelo sistema Android. Isto costuma acontecer se for adicionada uma nova impressão digital ao sistema ou se os parâmetros de segurança forem alterados. </string>
|
||||
<string name="fingerprint_disabled_wrong_masterkey">Desbloqueio da base de dados falhado: Chave composta inválida. O desbloqueio por impressão digital foi desativado porque aparentemente a chave mestra arquivada não é válida. </string>
|
||||
<string name="fingerprint_reenable">Por favor, reative o Desbloquear com Impressão Digital para a nova senha mestre.</string>
|
||||
@@ -321,6 +323,7 @@
|
||||
<string name="QuickUnlock_label_secure">Digite o código QuickUnlock:</string>
|
||||
<string name="QuickUnlock_button">Desbloqueio Rápido!</string>
|
||||
<string name="QuickUnlock_lockButton">Fechar banco de dados</string>
|
||||
<string name="enable_screen_lock">Ativar bloqueio de tela</string>
|
||||
<string name="QuickUnlockDefaultEnabled_title">Habilitar Desbloqueio Rápido por padrão</string>
|
||||
<string name="QuickUnlockDefaultEnabled_summary">Define se o Desbloqueio Rápido está habilitado por padrão ou não.</string>
|
||||
<string name="ViewDatabaseSecure_title">Proteger a exibição da base de dados</string>
|
||||
@@ -722,6 +725,11 @@
|
||||
<string name="EntryChannel_desc">Notificação para simplificar o acesso à entrada selecionada.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Fechar banco de dados após três tentativas de desbloqueio biométrico falhadas.</string>
|
||||
<string name="WarnFingerprintInvalidated">Alerta! Autenticação biométrica pode ser invalidada pelo Android, por exemplo: depois de adicionar uma nova digital nas configurações do seu dispositivo. Esteja certo de sempre saber como desbloquear com sua senha mestra!</string>
|
||||
<string-array name="ChangeLog_1_13">
|
||||
<item>Estimativa aprimorada da qualidade da senha, considerando a maioria das senhas populares.</item>
|
||||
<item>Bloqueia o desbloqueio rápido baseado em senha (por motivos de segurança) se o dispositivo não tiver uma trava de tela ativada.</item>
|
||||
<item>Atualize a configuração de segurança de rede para desativar a transferência de texto não criptografado.</item>
|
||||
</string-array>
|
||||
<string-array name="ChangeLog_1_12">
|
||||
<item>Atualizado de Xamarin Android para .NET 8</item>
|
||||
<item>Atualizado para o Target SDK 34</item>
|
||||
|
@@ -209,6 +209,7 @@
|
||||
|
||||
<string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string>
|
||||
<bool name="ShowUnlockedNotification_default">true</bool>
|
||||
<integer name="WebDavChunkedUploadSize_default">65536</integer>
|
||||
|
||||
<string name="PreloadDatabaseEnabled_key">PreloadDatabaseEnabled</string>
|
||||
<bool name="PreloadDatabaseEnabled_default">true</bool>
|
||||
|
@@ -507,7 +507,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>
|
||||
@@ -545,6 +550,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>
|
||||
@@ -728,7 +734,11 @@
|
||||
<string name="EntryChannel_name">Entry notifications</string>
|
||||
<string name="EntryChannel_desc">Notification to simplify access to the currently selected entry.</string>
|
||||
<string name="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</string>
|
||||
<string name="hint_smb_credentials">If you want to access files from your personal Windows computer, use the computer name or local IP address as host URL; enter the computer name as domain and the Windows login (often the same as your Microsoft account) as username and password.</string>
|
||||
<string name="WarnFingerprintInvalidated">Warning! Biometric authentication can be invalidated by Android, e.g. after adding a new fingerprint in your device settings. Make sure you always know how to unlock with your master password!</string>
|
||||
<string name="webdav_chunked_upload_size_title">Chunk size for WebDav upload</string>
|
||||
<string name="webdav_chunked_upload_size_summary">Size of chunks when uploading to WebDav servers in bytes. Use 0 to disable chunked upload.</string>
|
||||
|
||||
|
||||
<string-array name="ChangeLog_1_13">
|
||||
<item>Improved password quality estimation by considering most popular passwords.</item>
|
||||
|
@@ -45,6 +45,14 @@
|
||||
android:title="@string/UseFileTransactions_title"
|
||||
android:key="@string/UseFileTransactions_key" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="WebDavChunkedUploadSize_str"
|
||||
android:title="@string/webdav_chunked_upload_size_title"
|
||||
android:summary="@string/webdav_chunked_upload_size_title"
|
||||
android:defaultValue="@integer/WebDavChunkedUploadSize_default"
|
||||
android:inputType="number"
|
||||
/>
|
||||
|
||||
<CheckBoxPreference
|
||||
android:enabled="true"
|
||||
android:persistent="true"
|
||||
@@ -80,5 +88,6 @@
|
||||
android:defaultValue="true"
|
||||
android:title="@string/CheckForDuplicateUuids_title"
|
||||
android:key="@string/CheckForDuplicateUuids_key" />
|
||||
|
||||
|
||||
</PreferenceScreen>
|
@@ -46,9 +46,11 @@ using keepass2android;
|
||||
using keepass2android.Utils;
|
||||
using KeePassLib.Interfaces;
|
||||
using KeePassLib.Utility;
|
||||
|
||||
using Message = keepass2android.Utils.Message;
|
||||
#if !NoNet
|
||||
#if !EXCLUDE_JAVAFILESTORAGE
|
||||
using Kp2aBusinessLogic.Io;
|
||||
using Android.Gms.Common;
|
||||
using Keepass2android.Javafilestorage;
|
||||
using GoogleDriveFileStorage = keepass2android.Io.GoogleDriveFileStorage;
|
||||
@@ -836,8 +838,8 @@ namespace keepass2android
|
||||
new AndroidContentStorage(LocaleManager.LocalizedAppContext),
|
||||
#if !EXCLUDE_JAVAFILESTORAGE
|
||||
#if !NoNet
|
||||
new DropboxFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
DropboxFileStorage.IsConfigured ? new DropboxFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
DropboxAppFolderFileStorage.IsConfigured ? new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this): null,
|
||||
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveAppDataFileStorage(LocaleManager.LocalizedAppContext, this) : null,
|
||||
new OneDriveFileStorage(this),
|
||||
@@ -846,8 +848,9 @@ namespace keepass2android
|
||||
new OneDrive2AppFolderFileStorage(),
|
||||
new SftpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled()),
|
||||
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled),
|
||||
new WebDavFileStorage(this),
|
||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new WebDavFileStorage(this, WebDavChunkedUploadSize),
|
||||
new SmbFileStorage(),
|
||||
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
|
||||
new PCloudFileStorageAll(LocaleManager.LocalizedAppContext, this),
|
||||
new MegaFileStorage(App.Context),
|
||||
//new LegacyWebDavStorage(this),
|
||||
@@ -1333,6 +1336,18 @@ namespace keepass2android
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public int WebDavChunkedUploadSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return int.Parse(PreferenceManager.GetDefaultSharedPreferences(LocaleManager.LocalizedAppContext)
|
||||
.GetString("WebDavChunkedUploadSize_str",
|
||||
LocaleManager.LocalizedAppContext.Resources
|
||||
.GetInteger(Resource.Integer.WebDavChunkedUploadSize_default).ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1458,8 +1473,7 @@ namespace keepass2android
|
||||
{
|
||||
Kp2aLog.LogUnexpectedError(e.Exception);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user