This commit is contained in:
Philipp Crocoll
2025-07-15 11:07:40 +02:00
parent 3e6d86c206
commit 913222d7cb
10 changed files with 125 additions and 18 deletions

View File

@@ -140,6 +140,10 @@ namespace keepass2android
#endif #endif
int WebDavChunkedUploadSize
{
get;
}
} }
} }

View File

@@ -15,7 +15,9 @@ namespace keepass2android.Io
{ {
get { return false; } get { return false; }
} }
}
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
}
public partial class DropboxAppFolderFileStorage: JavaFileStorage public partial class DropboxAppFolderFileStorage: JavaFileStorage
{ {
@@ -29,6 +31,7 @@ namespace keepass2android.Io
get { return false; } get { return false; }
} }
static public bool IsConfigured => !string.IsNullOrEmpty(AppKey) && !string.IsNullOrEmpty(AppSecret);
} }
} }

View File

@@ -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); return new JavaFileStorageWriteTransaction(IocToPath(ioc), useFileTransaction, this);
} }

View File

@@ -6,10 +6,12 @@ using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Android.Preferences;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE #if !NoNet && !EXCLUDE_JAVAFILESTORAGE
using Keepass2android.Javafilestorage; using Keepass2android.Javafilestorage;
#endif #endif
using KeePassLib.Serialization; using KeePassLib.Serialization;
@@ -19,9 +21,15 @@ namespace keepass2android.Io
#if !NoNet && !EXCLUDE_JAVAFILESTORAGE #if !NoNet && !EXCLUDE_JAVAFILESTORAGE
public class WebDavFileStorage: 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 public override IEnumerable<string> SupportedProtocols
{ {
@@ -75,6 +83,15 @@ namespace keepass2android.Io
} }
return base.IocToPath(ioc); return base.IocToPath(ioc);
} }
}
public override IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
baseWebdavStorage.SetUploadChunkSize(_app.WebDavChunkedUploadSize);
return base.OpenWriteTransaction(ioc, useFileTransaction);
}
}
#endif #endif
} }

View File

@@ -15,7 +15,10 @@ import com.burgstaller.okhttp.basic.BasicAuthenticator;
import com.burgstaller.okhttp.digest.CachingAuthenticator; import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.DigestAuthenticator; import com.burgstaller.okhttp.digest.DigestAuthenticator;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -44,23 +47,33 @@ import keepass2android.javafilestorage.webdav.DecoratedTrustManager;
import keepass2android.javafilestorage.webdav.PropfindXmlParser; import keepass2android.javafilestorage.webdav.PropfindXmlParser;
import keepass2android.javafilestorage.webdav.WebDavUtil; import keepass2android.javafilestorage.webdav.WebDavUtil;
import okhttp3.MediaType; import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.RequestBody; import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.internal.tls.OkHostnameVerifier; import okhttp3.internal.tls.OkHostnameVerifier;
import okio.BufferedSink;
public class WebDavStorage extends JavaFileStorageBase { public class WebDavStorage extends JavaFileStorageBase {
private final ICertificateErrorHandler mCertificateErrorHandler; private final ICertificateErrorHandler mCertificateErrorHandler;
private Context appContext; private Context appContext;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler) int chunkSize;
public WebDavStorage(ICertificateErrorHandler certificateErrorHandler, int chunkSize)
{ {
this.chunkSize = chunkSize;
mCertificateErrorHandler = certificateErrorHandler; mCertificateErrorHandler = certificateErrorHandler;
} }
public void setUploadChunkSize(int chunkSize)
{
this.chunkSize = chunkSize;
}
public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException { public String buildFullPath(String url, String username, String password) throws UnsupportedEncodingException {
String scheme = url.substring(0, url.indexOf("://")); String scheme = url.substring(0, url.indexOf("://"));
url = url.substring(scheme.length() + 3); url = url.substring(scheme.length() + 3);
@@ -189,11 +202,49 @@ public class WebDavStorage extends JavaFileStorageBase {
try { try {
ConnectionInfo ci = splitStringToConnectionInfo(path); 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() Request request = new Request.Builder()
.url(new URL(ci.URL)) .url(new URL(ci.URL))
.put(RequestBody.create(MediaType.parse("application/binary"), data)) .put(requestBody)
.build(); .build();
//TODO consider writeTransactional //TODO consider writeTransactional
//TODO check for error //TODO check for error
@@ -522,3 +573,4 @@ public class WebDavStorage extends JavaFileStorageBase {
} }
} }

View File

@@ -9,7 +9,9 @@ using System.Text;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.Res;
using Android.OS; using Android.OS;
using Android.Preferences;
using Android.Runtime; using Android.Runtime;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
@@ -319,7 +321,7 @@ namespace keepass2android
View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null); View dlgContents = activity.LayoutInflater.Inflate(Resource.Layout.httpcredentials, null);
if (!defaultPath.EndsWith(_schemeSeparator)) if (!defaultPath.EndsWith(_schemeSeparator))
{ {
var webdavStorage = new Keepass2android.Javafilestorage.WebDavStorage(App.Kp2a.CertificateErrorHandler); var webdavStorage = CreateWebdavStorage(activity);
var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath); var connInfo = webdavStorage.SplitStringToConnectionInfo(defaultPath);
dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url; dlgContents.FindViewById<EditText>(Resource.Id.http_url).Text = connInfo.Url;
dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username; dlgContents.FindViewById<EditText>(Resource.Id.http_user).Text = connInfo.Username;
@@ -339,7 +341,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); string scheme = defaultPath.Substring(0, defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false) if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host; host = scheme + _schemeSeparator + host;
string httpPath = new Keepass2android.Javafilestorage.WebDavStorage(null).BuildFullPath(host, user, string httpPath = CreateWebdavStorage(activity).BuildFullPath(host, user,
password); password);
onStartBrowse(httpPath); onStartBrowse(httpPath);
}); });
@@ -353,7 +355,12 @@ namespace keepass2android
#endif #endif
} }
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath) private static WebDavStorage CreateWebdavStorage(Activity activity)
{
return new WebDavStorage(App.Kp2a.CertificateErrorHandler, App.Kp2a.WebDavChunkedUploadSize);
}
private void ShowFtpDialog(Activity activity, Util.FileSelectedHandler onStartBrowse, Action onCancel, string defaultPath)
{ {
#if !NoNet #if !NoNet
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity); MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
@@ -518,7 +525,7 @@ namespace keepass2android
string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal)); string scheme = defaultPath.Substring(0,defaultPath.IndexOf(_schemeSeparator, StringComparison.Ordinal));
if (host.Contains(_schemeSeparator) == false) if (host.Contains(_schemeSeparator) == false)
host = scheme + _schemeSeparator + host; 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); password);
onStartBrowse(httpPath); onStartBrowse(httpPath);
}); });

View File

@@ -209,6 +209,7 @@
<string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string> <string name="ShowUnlockedNotification_key">ShowUnlockedNotification</string>
<bool name="ShowUnlockedNotification_default">true</bool> <bool name="ShowUnlockedNotification_default">true</bool>
<integer name="WebDavChunkedUploadSize_default">65536</integer>
<string name="PreloadDatabaseEnabled_key">PreloadDatabaseEnabled</string> <string name="PreloadDatabaseEnabled_key">PreloadDatabaseEnabled</string>
<bool name="PreloadDatabaseEnabled_default">true</bool> <bool name="PreloadDatabaseEnabled_default">true</bool>

View File

@@ -729,6 +729,9 @@
<string name="EntryChannel_desc">Notification to simplify access to the currently selected entry.</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="CloseDbAfterFailedAttempts">Close database after three failed biometric unlock attempts.</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="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"> <string-array name="ChangeLog_1_13">
<item>Improved password quality estimation by considering most popular passwords.</item> <item>Improved password quality estimation by considering most popular passwords.</item>

View File

@@ -45,6 +45,14 @@
android:title="@string/UseFileTransactions_title" android:title="@string/UseFileTransactions_title"
android:key="@string/UseFileTransactions_key" /> 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 <CheckBoxPreference
android:enabled="true" android:enabled="true"
android:persistent="true" android:persistent="true"
@@ -80,5 +88,6 @@
android:defaultValue="true" android:defaultValue="true"
android:title="@string/CheckForDuplicateUuids_title" android:title="@string/CheckForDuplicateUuids_title"
android:key="@string/CheckForDuplicateUuids_key" /> android:key="@string/CheckForDuplicateUuids_key" />
</PreferenceScreen> </PreferenceScreen>

View File

@@ -836,8 +836,8 @@ namespace keepass2android
new AndroidContentStorage(LocaleManager.LocalizedAppContext), new AndroidContentStorage(LocaleManager.LocalizedAppContext),
#if !EXCLUDE_JAVAFILESTORAGE #if !EXCLUDE_JAVAFILESTORAGE
#if !NoNet #if !NoNet
new DropboxFileStorage(LocaleManager.LocalizedAppContext, this), DropboxFileStorage.IsConfigured ? new DropboxFileStorage(LocaleManager.LocalizedAppContext, this) : null,
new DropboxAppFolderFileStorage(LocaleManager.LocalizedAppContext, this), 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 GoogleDriveFileStorage(LocaleManager.LocalizedAppContext, this) : null,
GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveAppDataFileStorage(LocaleManager.LocalizedAppContext, this) : null, GoogleApiAvailability.Instance.IsGooglePlayServicesAvailable(LocaleManager.LocalizedAppContext)==ConnectionResult.Success ? new GoogleDriveAppDataFileStorage(LocaleManager.LocalizedAppContext, this) : null,
new OneDriveFileStorage(this), new OneDriveFileStorage(this),
@@ -846,8 +846,8 @@ namespace keepass2android
new OneDrive2AppFolderFileStorage(), new OneDrive2AppFolderFileStorage(),
new SftpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled()), new SftpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled()),
new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled), new NetFtpFileStorage(LocaleManager.LocalizedAppContext, this, IsFtpDebugEnabled),
new WebDavFileStorage(this), new WebDavFileStorage(this, WebDavChunkedUploadSize),
new PCloudFileStorage(LocaleManager.LocalizedAppContext, this), new PCloudFileStorage(LocaleManager.LocalizedAppContext, this),
new PCloudFileStorageAll(LocaleManager.LocalizedAppContext, this), new PCloudFileStorageAll(LocaleManager.LocalizedAppContext, this),
new MegaFileStorage(App.Context), new MegaFileStorage(App.Context),
//new LegacyWebDavStorage(this), //new LegacyWebDavStorage(this),
@@ -1333,6 +1333,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 +1470,7 @@ namespace keepass2android
{ {
Kp2aLog.LogUnexpectedError(e.Exception); Kp2aLog.LogUnexpectedError(e.Exception);
} }
}
}
} }