keepass2android/src/Kp2aBusinessLogic/database/edit/LoadDB.cs

179 lines
5.7 KiB
C#

/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Keepass2Android is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using KeePassLib;
using KeePassLib.Keys;
using KeePassLib.Serialization;
namespace keepass2android
{
public class LoadDb : RunnableOnFinish {
private readonly IOConnectionInfo _ioc;
private readonly Task<MemoryStream> _databaseData;
private readonly CompositeKey _compositeKey;
private readonly string _keyfileOrProvider;
private readonly IKp2aApp _app;
private readonly bool _rememberKeyfile;
IDatabaseFormat _format;
public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task<MemoryStream> databaseData, CompositeKey compositeKey, String keyfileOrProvider, OnFinish finish): base(finish)
{
_app = app;
_ioc = ioc;
_databaseData = databaseData;
_compositeKey = compositeKey;
_keyfileOrProvider = keyfileOrProvider;
_rememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
}
public override void Run()
{
try
{
try
{
StatusLogger.UpdateMessage(UiStringKey.loading_database);
//get the stream data into a single stream variable (databaseStream) regardless whether its preloaded or not:
MemoryStream preloadedMemoryStream = _databaseData == null ? null : _databaseData.Result;
MemoryStream databaseStream;
if (preloadedMemoryStream != null)
databaseStream = preloadedMemoryStream;
else
{
using (Stream s = _app.GetFileStorage(_ioc).OpenFileForRead(_ioc))
{
databaseStream = new MemoryStream();
s.CopyTo(databaseStream);
databaseStream.Seek(0, SeekOrigin.Begin);
}
}
//ok, try to load the database. Let's start with Kdbx format and retry later if that is the wrong guess:
_format = new KdbxDatabaseFormat(KdbpFile.GetFormatToUse(_ioc));
TryLoad(databaseStream);
}
catch (Exception e)
{
this.Exception = e;
throw;
}
}
catch (KeyFileException)
{
Kp2aLog.Log("KeyFileException");
Finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/
_app.GetResourceString(UiStringKey.keyfile_does_not_exist), Exception);
}
catch (AggregateException e)
{
string message = e.Message;
foreach (var innerException in e.InnerExceptions)
{
message = innerException.Message;
// Override the message shown with the last (hopefully most recent) inner exception
Kp2aLog.LogUnexpectedError(innerException);
}
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message, Exception);
return;
}
catch (DuplicateUuidsException e)
{
Kp2aLog.Log(e.ToString());
Finish(false, _app.GetResourceString(UiStringKey.DuplicateUuidsError) + " " + e.Message + _app.GetResourceString(UiStringKey.DuplicateUuidsErrorAdditional), Exception);
return;
}
catch (Exception e)
{
if (!(e is InvalidCompositeKeyException))
Kp2aLog.LogUnexpectedError(e);
Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message, Exception);
return;
}
}
/// <summary>
/// Holds the exception which was thrown during execution (if any)
/// </summary>
public Exception Exception { get; set; }
private void TryLoad(MemoryStream databaseStream)
{
//create a copy of the stream so we can try again if we get an exception which indicates we should change parameters
//This is not optimal in terms of (short-time) memory usage but is hard to avoid because the Keepass library closes streams also in case of errors.
//Alternatives would involve increased traffic (if file is on remote) and slower loading times, so this seems to be the best choice.
MemoryStream workingCopy = new MemoryStream();
databaseStream.CopyTo(workingCopy);
workingCopy.Seek(0, SeekOrigin.Begin);
//reset stream if we need to reuse it later:
databaseStream.Seek(0, SeekOrigin.Begin);
//now let's go:
try
{
_app.LoadDatabase(_ioc, workingCopy, _compositeKey, StatusLogger, _format);
SaveFileData(_ioc, _keyfileOrProvider);
Kp2aLog.Log("LoadDB OK");
Finish(true, _format.SuccessMessage);
}
catch (OldFormatException)
{
_format = new KdbDatabaseFormat(_app);
TryLoad(databaseStream);
}
catch (InvalidCompositeKeyException)
{
KcpPassword passwordKey = (KcpPassword)_compositeKey.GetUserKey(typeof(KcpPassword));
if ((passwordKey != null) && (passwordKey.Password.ReadString() == "") && (_compositeKey.UserKeyCount > 1))
{
//if we don't get a password, we don't know whether this means "empty password" or "no password"
//retry without password:
_compositeKey.RemoveUserKey(passwordKey);
//retry:
TryLoad(databaseStream);
}
else throw;
}
}
private void SaveFileData(IOConnectionInfo ioc, String keyfileOrProvider) {
if (!_rememberKeyfile)
{
keyfileOrProvider = "";
}
_app.StoreOpenedFileAsRecent(ioc, keyfileOrProvider);
}
}
}