diff --git a/src/KeePassLib2Android/IDatabaseFormat.cs b/src/KeePassLib2Android/IDatabaseFormat.cs index 5a594c83..a58b69a3 100644 --- a/src/KeePassLib2Android/IDatabaseFormat.cs +++ b/src/KeePassLib2Android/IDatabaseFormat.cs @@ -6,12 +6,14 @@ namespace KeePassLib { public interface IDatabaseFormat { - void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger); + void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger); byte[] HashOfLastStream { get; } bool CanWrite { get; } string SuccessMessage { get; } void Save(PwDatabase kpDatabase, Stream stream); + + bool CanHaveEntriesInRootGroup { get; } } } \ No newline at end of file diff --git a/src/KeePassLib2Android/PwDatabase.cs b/src/KeePassLib2Android/PwDatabase.cs index 4bfe8628..3b194722 100644 --- a/src/KeePassLib2Android/PwDatabase.cs +++ b/src/KeePassLib2Android/PwDatabase.cs @@ -600,7 +600,7 @@ namespace KeePassLib m_bModified = false; - format.PopulateDatabaseFromStream(this, pwKey, s, slLogger); + format.PopulateDatabaseFromStream(this, s, slLogger); m_pbHashOfLastIO = format.HashOfLastStream; m_pbHashOfFileOnDisk = format.HashOfLastStream; diff --git a/src/Kp2aBusinessLogic/database/Database.cs b/src/Kp2aBusinessLogic/database/Database.cs index a6baec77..f4bab2ef 100644 --- a/src/Kp2aBusinessLogic/database/Database.cs +++ b/src/Kp2aBusinessLogic/database/Database.cs @@ -75,7 +75,7 @@ namespace keepass2android private bool _loaded; private bool _reloadRequested; - private IDatabaseFormat _databaseFormat; + private IDatabaseFormat _databaseFormat = new KdbxDatabaseFormat(KdbxFormat.Default); public bool ReloadRequested { @@ -132,6 +132,12 @@ namespace keepass2android /// public bool CanWrite { get; set; } + public IDatabaseFormat DatabaseFormat + { + get { return _databaseFormat; } + set { _databaseFormat = value; } + } + protected virtual void PopulateDatabaseFromStream(PwDatabase pwDatabase, Stream s, IOConnectionInfo iocInfo, CompositeKey compositeKey, ProgressDialogStatusLogger status, IDatabaseFormat databaseFormat) { IFileStorage fileStorage = _app.GetFileStorage(iocInfo); @@ -173,7 +179,7 @@ namespace keepass2android KpDatabase.UseFileTransactions = _app.GetBooleanPreference(PreferenceKey.UseFileTransactions); using (IWriteTransaction trans = _app.GetFileStorage(Ioc).OpenWriteTransaction(Ioc, KpDatabase.UseFileTransactions)) { - _databaseFormat.Save(KpDatabase, trans.OpenFile()); + DatabaseFormat.Save(KpDatabase, trans.OpenFile()); trans.CommitWrite(); } diff --git a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs index f17d6905..4746024e 100644 --- a/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbDatabaseFormat.cs @@ -22,12 +22,12 @@ using Random = System.Random; namespace keepass2android { - class KdbDatabaseFormat: IDatabaseFormat + public class KdbDatabaseFormat: IDatabaseFormat { private Dictionary _groupData = new Dictionary(); private static readonly DateTime _expireNever = new DateTime(2999,12,28,23,59,59); - public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger) + public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger) { #if !EXCLUDE_KEYTRANSFORM var importer = new Com.Keepassdroid.Database.Load.ImporterV3(); @@ -35,13 +35,13 @@ namespace keepass2android var hashingStream = new HashingStreamEx(s, false, new SHA256Managed()); string password = "";//no need to distinguish between null and "" because empty passwords are invalid (and null is not allowed) - KcpPassword passwordKey = (KcpPassword)key.GetUserKey(typeof(KcpPassword)); + KcpPassword passwordKey = (KcpPassword)db.MasterKey.GetUserKey(typeof(KcpPassword)); if (passwordKey != null) { password = passwordKey.Password.ReadString(); } - KcpKeyFile passwordKeyfile = (KcpKeyFile)key.GetUserKey(typeof(KcpKeyFile)); + KcpKeyFile passwordKeyfile = (KcpKeyFile)db.MasterKey.GetUserKey(typeof(KcpKeyFile)); MemoryStream keyfileStream = null; if (passwordKeyfile != null) { @@ -91,22 +91,31 @@ namespace keepass2android private PwGroup ConvertGroup(PwGroupV3 groupV3) { PwGroup pwGroup = new PwGroup(true, false); - pwGroup.Name = groupV3.Name; + pwGroup.Uuid = CreateUuidFromGroupId(groupV3.Id.Id); + + //check if we have group data for this group already (from loading in a previous pass). + //then use the same UUID (important for merging) + var gdForGroup = _groupData.Where(g => g.Value.Id == groupV3.Id.Id).ToList(); + if (gdForGroup.Count == 1) + { + pwGroup.Uuid = gdForGroup.Single().Key; + } + pwGroup.Name = groupV3.Name; + Android.Util.Log.Debug("KP2A", "load kdb: group " + groupV3.Name); pwGroup.CreationTime = ConvertTime(groupV3.TCreation); pwGroup.LastAccessTime = ConvertTime(groupV3.TLastAccess); pwGroup.LastModificationTime = ConvertTime(groupV3.TLastMod); - pwGroup.Expires = !PwGroupV3.NeverExpire.Equals(groupV3.TExpire); - if (pwGroup.Expires) - pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire); + pwGroup.ExpiryTime = ConvertTime(groupV3.TExpire); + pwGroup.Expires = !(Math.Abs((pwGroup.ExpiryTime - _expireNever).TotalMilliseconds) < 500); ; if (groupV3.Icon != null) pwGroup.IconId = (PwIcon) groupV3.Icon.IconId; - _groupData.Add(pwGroup.Uuid, new AdditionalGroupData + _groupData[pwGroup.Uuid] = new AdditionalGroupData { Flags = groupV3.Flags, Id = groupV3.Id.Id - }); + }; for (int i = 0; i < groupV3.ChildGroups.Count;i++) @@ -124,6 +133,20 @@ namespace keepass2android return pwGroup; } + private PwUuid CreateUuidFromGroupId(int id) + { + byte[] template = new byte[] { 0xd2, 0x18, 0x22, 0x93, + 0x8e, 0xa4, 0x43, 0xf2, + 0xb4, 0xb5, 0x2a, 0x49, + 0x00, 0x00, 0x00, 0x00}; + byte[] idBytes = BitConverter.GetBytes(id); + for (int i = 0; i < 4; i++) + { + template[i + 12] = idBytes[i]; + } + return new PwUuid(template); + } + private PwEntry ConvertEntry(PwEntryV3 fromEntry) { PwEntry toEntry = new PwEntry(false, false); @@ -142,6 +165,7 @@ namespace keepass2android if (fromEntry.Icon != null) toEntry.IconId = (PwIcon) fromEntry.Icon.IconId; SetFieldIfAvailable(toEntry, PwDefs.TitleField, false, fromEntry.Title); + Android.Util.Log.Debug("KP2A", "load kdb: entry " + toEntry.Strings.ReadSafe(PwDefs.TitleField)); SetFieldIfAvailable(toEntry, PwDefs.UserNameField, false, fromEntry.Username); SetFieldIfAvailable(toEntry, PwDefs.UrlField, false, fromEntry.Url); SetFieldIfAvailable(toEntry, PwDefs.PasswordField, true, fromEntry.Password); @@ -223,7 +247,9 @@ namespace keepass2android } //traverse again and assign parents - db.RootGroup = new PwGroupV3() { Level = -1}; + db.RootGroup = ConvertGroup(kpDatabase.RootGroup, db); + db.RootGroup.Level = -1; + AssignParent(kpDatabase.RootGroup, db, groupV3s); @@ -238,8 +264,17 @@ namespace keepass2android } - PwDbV3Output output = new PwDbV3Output(db, stream); + HashingStreamEx hashedStream = new HashingStreamEx(stream, true, null); + PwDbV3Output output = new PwDbV3Output(db, hashedStream); output.Output(); + hashedStream.Close(); + HashOfLastStream = hashedStream.Hash; + stream.Close(); + } + + public bool CanHaveEntriesInRootGroup + { + get { return false; } } private void AssignParent(PwGroup kpParent, PwDatabaseV3 dbV3, Dictionary groupV3s) @@ -254,7 +289,7 @@ namespace keepass2android parentV3 = groupV3s[_groupData[kpParent.Uuid].Id]; } - foreach (PwGroup g in kpParent.Groups) + foreach (PwGroup g in kpParent.Groups.OrderBy(g => g.Name)) { PwGroupV3 groupV3 = groupV3s[_groupData[g.Uuid].Id]; @@ -269,6 +304,7 @@ namespace keepass2android { PwGroupV3 toGroup = new PwGroupV3(); toGroup.Name = fromGroup.Name; + Android.Util.Log.Debug("KP2A", "save kdb: group " + fromGroup.Name); toGroup.TCreation = new PwDate(ConvertTime(fromGroup.CreationTime)); toGroup.TLastAccess= new PwDate(ConvertTime(fromGroup.LastAccessTime)); @@ -279,7 +315,7 @@ namespace keepass2android } else { - toGroup.TExpire = new PwDate(PwGroupV3.NeverExpire); + toGroup.TExpire = new PwDate(ConvertTime(_expireNever)); } toGroup.Icon = dbTo.IconFactory.GetIcon((int) fromGroup.IconId); @@ -327,9 +363,12 @@ namespace keepass2android toEntry.Icon = dbTo.IconFactory.GetIcon((int) fromEntry.IconId); toEntry.SetTitle(GetString(fromEntry, PwDefs.TitleField), dbTo); + Android.Util.Log.Debug("KP2A", "save kdb: entry " + fromEntry.Strings.ReadSafe(PwDefs.TitleField)); toEntry.SetUsername(GetString(fromEntry, PwDefs.UserNameField), dbTo); toEntry.SetUrl(GetString(fromEntry, PwDefs.UrlField), dbTo); - toEntry.SetPassword(GetString(fromEntry, PwDefs.PasswordField), dbTo); + var pwd = GetString(fromEntry, PwDefs.PasswordField); + if (pwd != null) + toEntry.SetPassword(pwd, dbTo); toEntry.SetNotes(GetString(fromEntry, PwDefs.NotesField), dbTo); if (fromEntry.Binaries.Any()) { @@ -344,7 +383,7 @@ namespace keepass2android { ProtectedString protectedString = fromEntry.Strings.Get(id); if (protectedString == null) - return null; + return ""; return protectedString.ReadString(); } diff --git a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs index 8db4e88c..2fe4b0b2 100644 --- a/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs +++ b/src/Kp2aBusinessLogic/database/KdbxDatabaseFormat.cs @@ -15,7 +15,7 @@ namespace keepass2android _format = format; } - public void PopulateDatabaseFromStream(PwDatabase db, CompositeKey key, Stream s, IStatusLogger slLogger) + public void PopulateDatabaseFromStream(PwDatabase db, Stream s, IStatusLogger slLogger) { KdbxFile kdbx = new KdbxFile(db); kdbx.DetachBinaries = db.DetachBinaries; @@ -33,5 +33,10 @@ namespace keepass2android { kpDatabase.Save(stream, null); } + + public bool CanHaveEntriesInRootGroup + { + get { return true; } + } } } \ No newline at end of file diff --git a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs index 851b2de2..1d64245f 100644 --- a/src/Kp2aBusinessLogic/database/edit/SaveDB.cs +++ b/src/Kp2aBusinessLogic/database/edit/SaveDB.cs @@ -218,8 +218,10 @@ namespace keepass2android pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); pwImp.MasterKey = pwDatabase.MasterKey; - KdbxFile kdbx = new KdbxFile(pwImp); - kdbx.Load(GetStreamForBaseFile(fileStorage, ioc), KdbpFile.GetFormatToUse(ioc), null); + var stream = GetStreamForBaseFile(fileStorage, ioc); + + _app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, stream, null); + pwDatabase.MergeIn(pwImp, PwMergeMethod.Synchronize, null); diff --git a/src/Kp2aUnitTests/MainActivity.cs b/src/Kp2aUnitTests/MainActivity.cs index c2ca9fed..09b574d3 100644 --- a/src/Kp2aUnitTests/MainActivity.cs +++ b/src/Kp2aUnitTests/MainActivity.cs @@ -23,14 +23,17 @@ namespace Kp2aUnitTests //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly")); - runner.AddTests(new List { typeof(TestSelectStorageLocation) }); + //runner.AddTests(new List { typeof(TestSelectStorageLocation) }); //runner.AddTests(new List { typeof(TestSynchronizeCachedDatabase)}); //runner.AddTests(typeof(TestLoadDb).GetMethod("LoadErrorWithCertificateTrustFailure")); //runner.AddTests(typeof(TestLoadDb).GetMethod("LoadWithAcceptedCertificateTrustFailure")); - //runner.AddTests(new List { typeof(TestLoadDb) }); + //runner.AddTests(new List { typeof(TestSaveDb) }); //runner.AddTests(new List { typeof(TestCachingFileStorage) }); - //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1")); + //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdb1WithKeyfileOnly")); + runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadEditSaveWithSyncKdb")); + runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadAndSave_TestIdenticalFiles_kdb")); + runner.AddTests(typeof(TestSaveDb).GetMethod("TestCreateSaveAndLoad_TestIdenticalFiles_kdb")); //runner.AddTests(typeof(TestLoadDb).GetMethod("LoadAndSaveFromRemote1And1Ftp")); //runner.AddTests(typeof(TestLoadDb).GetMethod("TestLoadKdbpWithPasswordOnly")); //runner.AddTests(typeof(TestSaveDb).GetMethod("TestLoadKdbxAndSaveKdbp_TestIdenticalFiles")); diff --git a/src/Kp2aUnitTests/TestBase.cs b/src/Kp2aUnitTests/TestBase.cs index 7293fc9a..2b8c6d9a 100644 --- a/src/Kp2aUnitTests/TestBase.cs +++ b/src/Kp2aUnitTests/TestBase.cs @@ -136,6 +136,10 @@ namespace Kp2aUnitTests IOConnectionInfo ioc = new IOConnectionInfo {Path = filename}; Database db = app.CreateNewDatabase(); + if (filename.EndsWith(".kdb")) + { + db.DatabaseFormat = new KdbDatabaseFormat(); + } db.KpDatabase = new PwDatabase(); diff --git a/src/Kp2aUnitTests/TestCreateDb.cs b/src/Kp2aUnitTests/TestCreateDb.cs index 2b11f005..9ec352fa 100644 --- a/src/Kp2aUnitTests/TestCreateDb.cs +++ b/src/Kp2aUnitTests/TestCreateDb.cs @@ -30,9 +30,12 @@ namespace Kp2aUnitTests bool createSuccesful = false; //create the task: CreateDb createDb = new CreateDb(app, Application.Context, ioc, new ActionOnFinish((success, message) => - { createSuccesful = success; + { createSuccesful = success; + if (!success) + Android.Util.Log.Debug("KP2A_Test", message); }), false); //run it: + createDb.Run(); //check expectations: Assert.IsTrue(createSuccesful); @@ -43,7 +46,7 @@ namespace Kp2aUnitTests //ensure the the database can be loaded from file: PwDatabase loadedDb = new PwDatabase(); - loadedDb.Open(ioc, new CompositeKey(), null, new KdbxDatabaseLoader(KdbxFormat.Default)); + loadedDb.Open(ioc, new CompositeKey(), null, new KdbxDatabaseFormat(KdbxFormat.Default)); //Check whether the databases are equal AssertDatabasesAreEqual(loadedDb, app.GetDb().KpDatabase); diff --git a/src/Kp2aUnitTests/TestFileStorage.cs b/src/Kp2aUnitTests/TestFileStorage.cs index 4629e684..4b70c200 100644 --- a/src/Kp2aUnitTests/TestFileStorage.cs +++ b/src/Kp2aUnitTests/TestFileStorage.cs @@ -144,6 +144,11 @@ namespace Kp2aUnitTests throw new NotImplementedException(); } + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) { throw new NotImplementedException(); diff --git a/src/Kp2aUnitTests/TestKp2aApp.cs b/src/Kp2aUnitTests/TestKp2aApp.cs index f6123abf..526335c8 100644 --- a/src/Kp2aUnitTests/TestKp2aApp.cs +++ b/src/Kp2aUnitTests/TestKp2aApp.cs @@ -187,8 +187,12 @@ namespace Kp2aUnitTests } } - - + + public bool CheckForDuplicateUuids + { + get { return true; } + } + public bool OnServerCertificateError(int sslPolicyErrors) { diff --git a/src/Kp2aUnitTests/TestSaveDb.cs b/src/Kp2aUnitTests/TestSaveDb.cs index c5968ddc..f07af98f 100644 --- a/src/Kp2aUnitTests/TestSaveDb.cs +++ b/src/Kp2aUnitTests/TestSaveDb.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Security.Cryptography; @@ -15,6 +16,14 @@ using keepass2android; namespace Kp2aUnitTests { + static class StringExt + { + public static bool ContainsAny(this string haystack, IEnumerable needles) + { + return needles.Any(haystack.Contains); + } + } + [TestClass] class TestSaveDb: TestBase { @@ -54,39 +63,83 @@ namespace Kp2aUnitTests } + [TestMethod] + public void TestLoadEditSaveWithSyncKdb() + { + TestSync(DefaultDirectory + "savetest.kdb"); + } + private void TestSync(string filename) { //create the default database: IKp2aApp app = SetupAppWithDatabase(filename); + DisplayGroups(app, "After create"); //save it and reload it so we have a base version + Android.Util.Log.Debug("KP2A", "-- Save first version -- "); SaveDatabase(app); + Android.Util.Log.Debug("KP2A", "-- Load DB -- "); app = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); + DisplayGroups(app, "After reload"); //load it once again: + Android.Util.Log.Debug("KP2A", "-- Load DB to app 2-- "); IKp2aApp app2 = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); + DisplayGroups(app2, "After load to app2"); //modify the database by adding a group in both databases: app.GetDb().KpDatabase.RootGroup.AddGroup(new PwGroup(true, true, "TestGroup", PwIcon.Apple), true); var group2 = new PwGroup(true, true, "TestGroup2", PwIcon.Energy); app2.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); //save the database from app 1: + Android.Util.Log.Debug("KP2A", "-- Save from app 1 (with TestGroup) -- "); SaveDatabase(app); + ((TestKp2aApp) app2).SetYesNoCancelResult(TestKp2aApp.YesNoCancelResult.Yes); //save the database from app 2: This save operation must detect the changes made from app 1 and ask if it should sync: + Android.Util.Log.Debug("KP2A", "-- Save from app 2 (with TestGroup2, requires merge) -- "); SaveDatabase(app2); - + DisplayGroups(app2, "After save with merge"); //make sure the right question was asked Assert.AreEqual(UiStringKey.TitleSyncQuestion, ((TestKp2aApp) app2).LastYesNoCancelQuestionTitle); //add group 2 to app 1: app.GetDb().KpDatabase.RootGroup.AddGroup(group2, true); + app.GetDb().KpDatabase.RootGroup.SortSubGroups(true); + Android.Util.Log.Debug("KP2A", "-- Load DB to new app -- "); //load database to a new app instance: IKp2aApp resultApp = LoadDatabase(filename, DefaultPassword, DefaultKeyfile); - + resultApp.GetDb().KpDatabase.RootGroup.SortSubGroups(true); //ensure the sync was successful: - AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); + string kdbxXml = DatabaseToXml(app); + string kdbxResultXml = DatabaseToXml(resultApp); + + RemoveKdbLines(ref kdbxXml, true); + RemoveKdbLines(ref kdbxResultXml, true); + + Assert.AreEqual(kdbxXml, kdbxResultXml); + + //AssertDatabasesAreEqual(app.GetDb().KpDatabase, resultApp.GetDb().KpDatabase); + } + + private void DisplayGroups(IKp2aApp app, string name) + { + LogDebug("Groups display: " + name); + DisplayGroupRecursive(0, app.GetDb().Root); + } + + private void DisplayGroupRecursive(int level, PwGroup g) + { + LogDebug("Group name="+g.Name+", exp: " + g.Expires+ ", expTime="+g.ExpiryTime.ToString(CultureInfo.InvariantCulture)); + foreach (var ch in g.Groups) + DisplayGroupRecursive(level + 1, ch); + + } + + private static void LogDebug(string text, int indent=0) + { + Android.Util.Log.Debug("KP2A", text.PadLeft(indent*2)); } @@ -293,10 +346,98 @@ namespace Kp2aUnitTests Assert.AreEqual(kdbxXml,kdbxReloadedXml); + } + + [TestMethod] + public void TestLoadAndSave_TestIdenticalFiles_kdb() + { + IKp2aApp app = LoadDatabase(DefaultDirectory + "complexDb.kdb", "test", null); + app.GetDb().Root.SortSubGroups(true); + string kdbxXml = DatabaseToXml(app); + + newFilename = TestDbDirectory + "tmp_complexDb.kdb"; + if (File.Exists(newFilename)) + File.Delete(newFilename); + app.GetDb().KpDatabase.IOConnectionInfo.Path = newFilename; + app.GetDb().SaveData(Application.Context); + + + IKp2aApp appReloaded = LoadDatabase(newFilename, "test", null); + appReloaded.GetDb().Root.SortSubGroups(true); + string kdbxReloadedXml = DatabaseToXml(appReloaded); + + RemoveKdbLines(ref kdbxReloadedXml); + RemoveKdbLines(ref kdbxXml); + + Assert.AreEqual(kdbxXml, kdbxReloadedXml); + } + + [TestMethod] + public void TestCreateSaveAndLoad_TestIdenticalFiles_kdb() + { + string filename = DefaultDirectory + "createsaveandload.kdb"; + IKp2aApp app = SetupAppWithDatabase(filename); + string kdbxXml = DatabaseToXml(app); + //save it and reload it + Android.Util.Log.Debug("KP2A", "-- Save DB -- "); + SaveDatabase(app); + Android.Util.Log.Debug("KP2A", "-- Load DB -- "); + + + PwDatabase pwImp = new PwDatabase(); + PwDatabase pwDatabase = app.GetDb().KpDatabase; + pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey); + pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep(); + pwImp.MasterKey = pwDatabase.MasterKey; + + IOConnectionInfo ioc = new IOConnectionInfo() {Path = filename}; + using (Stream s = app.GetFileStorage(ioc).OpenFileForRead(ioc)) + { + app.GetDb().DatabaseFormat.PopulateDatabaseFromStream(pwImp, s, null); + } + string kdbxReloadedXml = DatabaseToXml(app); + + RemoveKdbLines(ref kdbxReloadedXml); + RemoveKdbLines(ref kdbxXml); + + Assert.AreEqual(kdbxXml, kdbxReloadedXml); + + + + } + private void RemoveKdbLines(ref string databaseXml, bool removeForAfterSync=false) + { + //these values are not part of .kdb and thus cannot be the same when comparing two .kdb files + // -> remove them from the .xml + var stuffToRemove = new string[] {"", + "", + "", + "", + "", + "", + "","" //key of attachments + }.ToList(); + string[] moreStuffToRemove = new string[] + { + "", + "", + "" + }; + if (removeForAfterSync) + { + stuffToRemove.AddRange(moreStuffToRemove); + } + string[] lines = databaseXml.Split(new char[] {'\n'}); + databaseXml = lines + .Where(line => !line.ContainsAny(stuffToRemove)) + .Aggregate("", (current, line) => current + (line + "\n")); + } + + [TestMethod] public void TestLoadKdbxAndSaveKdbp_TestIdenticalFiles() { diff --git a/src/Kp2aUnitTests/TestSelectStorageLocation.cs b/src/Kp2aUnitTests/TestSelectStorageLocation.cs index 2a980f77..caff215f 100644 --- a/src/Kp2aUnitTests/TestSelectStorageLocation.cs +++ b/src/Kp2aUnitTests/TestSelectStorageLocation.cs @@ -99,6 +99,11 @@ namespace Kp2aUnitTests throw new NotImplementedException(); } + public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc) + { + throw new NotImplementedException(); + } + public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState) { throw new NotImplementedException(); diff --git a/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar b/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar index d3112534..db50f3f9 100644 Binary files a/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar and b/src/java/KP2AKdbLibrary/bin/kp2akdblibrary.jar differ diff --git a/src/keepass2android/GroupActivity.cs b/src/keepass2android/GroupActivity.cs index e7d1693e..753494e0 100644 --- a/src/keepass2android/GroupActivity.cs +++ b/src/keepass2android/GroupActivity.cs @@ -85,7 +85,7 @@ namespace keepass2android } private bool AddEntryEnabled { - get { return App.Kp2a.GetDb().CanWrite; } + get { return App.Kp2a.GetDb().CanWrite && ((this.Group.ParentGroup != null) || App.Kp2a.GetDb().DatabaseFormat.CanHaveEntriesInRootGroup); } } diff --git a/src/keepass2android/keepass2android.csproj b/src/keepass2android/keepass2android.csproj index 450583ed..59ec5fd3 100644 --- a/src/keepass2android/keepass2android.csproj +++ b/src/keepass2android/keepass2android.csproj @@ -174,7 +174,6 @@ - diff --git a/src/keepass2android/views/GroupRootView.cs b/src/keepass2android/views/GroupRootView.cs deleted file mode 100644 index ca1bdaf2..00000000 --- a/src/keepass2android/views/GroupRootView.cs +++ /dev/null @@ -1,65 +0,0 @@ -/* -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 3 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 Android.Content; -using Android.Runtime; -using Android.Util; -using Android.Views; -using Android.Widget; - -namespace keepass2android.view -{ - public class GroupRootView : RelativeLayout - { - public GroupRootView (IntPtr javaReference, JniHandleOwnership transfer) - : base(javaReference, transfer) - { - - } - - - public GroupRootView (Context context) : - base (context) - { - Initialize (); - } - - public GroupRootView (Context context, IAttributeSet attrs) : - base (context, attrs) - { - Initialize (); - } - - public GroupRootView (Context context, IAttributeSet attrs, int defStyle) : - base (context, attrs, defStyle) - { - Initialize (); - } - - private void Initialize () - { - LayoutInflater inflater = (LayoutInflater) Context.GetSystemService(Context.LayoutInflaterService); - inflater.Inflate(Resource.Layout.group_add_entry, this); - - View addEntry = FindViewById(Resource.Id.add_entry); - addEntry.Visibility = ViewStates.Invisible; - - } - } -} -