/* 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 . */ 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 _databaseData; private readonly CompositeKey _compositeKey; private readonly string _keyfileOrProvider; private readonly IKp2aApp _app; private readonly bool _rememberKeyfile; IDatabaseLoader _loader; public LoadDb(IKp2aApp app, IOConnectionInfo ioc, Task 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 { 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: _loader = new KdbxDatabaseLoader(KdbpFile.GetFormatToUse(_ioc)); TryLoad(databaseStream); } 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)); } 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.Log("Exception: " + innerException); } Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + message); return; } catch (Exception e) { Kp2aLog.Log("Exception: " + e); Finish(false, _app.GetResourceString(UiStringKey.ErrorOcurred) + " " + e.Message); return; } } 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, _loader); SaveFileData(_ioc, _keyfileOrProvider); Kp2aLog.Log("LoadDB OK"); Finish(true); } catch (OldFormatException) { _loader = new KdbDatabaseLoader(); 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); } } }