-
Notifications
You must be signed in to change notification settings - Fork 7
/
Exporter.cs
729 lines (629 loc) · 31.5 KB
/
Exporter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using KeePassLib;
using KeePassLib.Collections;
using KeePassLib.Cryptography.KeyDerivation;
using KeePassLib.Interfaces;
using KeePassLib.Keys;
using KeePassLib.Serialization;
using KeePassLib.Utility;
using System.Text.RegularExpressions;
namespace KeePassSubsetExport
{
internal static class Exporter
{
private static readonly PwUuid UuidAes = new PwUuid(new byte[] {
0xC9, 0xD9, 0xF3, 0x9A, 0x62, 0x8A, 0x44, 0x60,
0xBF, 0x74, 0x0D, 0x08, 0xC1, 0x8A, 0x4F, 0xEA });
private static readonly PwUuid UuidArgon2 = new PwUuid(new byte[] {
0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B,
0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C });
private static readonly IOConnectionInfo ConnectionInfo = new IOConnectionInfo();
/// <summary>
/// Exports all entries with the given tag to a new database at the given path (multiple jobs possible).
/// Each job is an entry in the "SubsetExportSettings" folder with a title "SubsetExport_*".
/// Password == password field of the entry
/// keyFilePath == "SubsetExport_KeyFilePath" string field on the entry
/// targetFilePath == "SubsetExport_TargetFilePath" string field on the entry
/// tag (filter) == "SubsetExport_Tag" string field on the entry
/// </summary>
/// <param name="sourceDb">The source database to run the exports on.</param>
internal static void Export(PwDatabase sourceDb)
{
// Get all entries out of the group "SubsetExportSettings" which start with "SubsetExport_"
PwGroup settingsGroup = FindSettingsGroup(sourceDb);
if (settingsGroup == null)
{
return;
}
IEnumerable<PwEntry> jobSettings = settingsGroup.Entries;
// Loop through all found entries - each on is a export job
foreach (var settingsEntry in jobSettings)
{
// Load settings for this job
var settings = Settings.Parse(settingsEntry, sourceDb);
// Skip disabled/expired jobs
if (settings.Disabled)
continue;
if (CheckKeyFile(sourceDb, settings, settingsEntry))
continue;
if (CheckTagOrGroup(settings, settingsEntry))
continue;
if (CheckTargetFilePath(settings, settingsEntry))
continue;
if (CheckPasswordOrKeyfile(settings, settingsEntry))
continue;
try
{
// Execute the export
CopyToNewDb(sourceDb, settings);
}
catch (Exception e)
{
MessageService.ShowWarning("SubsetExport failed:", e);
}
}
}
private static PwGroup FindSettingsGroup(PwDatabase sourceDb, string settingsGroupName = "SubsetExportSettings")
{
var settingsGroup = sourceDb.RootGroup.Groups.FirstOrDefault(g => g.Name == settingsGroupName);
if (settingsGroup != null)
{
return settingsGroup;
}
return FindGroupRecursive(sourceDb.RootGroup, settingsGroupName);
}
private static PwGroup FindGroupRecursive(PwGroup startGroup, string groupName)
{
if (startGroup.Name == groupName)
{
return startGroup;
}
foreach (PwGroup group in startGroup.Groups)
{
PwGroup result = FindGroupRecursive(group, groupName);
if (result != null)
{
return result;
}
}
return null;
}
private static bool CheckPasswordOrKeyfile(Settings settings, PwEntry settingsEntry)
{
// Require at least one of Password or KeyFilePath.
if (settings.Password.IsEmpty && !File.Exists(settings.KeyFilePath))
{
MessageService.ShowWarning("SubsetExport: Missing Password or valid KeyFilePath for: " +
settingsEntry.Strings.ReadSafe("Title"));
return true;
}
return false;
}
private static bool CheckTargetFilePath(Settings settings, PwEntry settingsEntry)
{
// Require targetFilePath
if (string.IsNullOrEmpty(settings.TargetFilePath))
{
MessageService.ShowWarning("SubsetExport: Missing TargetFilePath for: " +
settingsEntry.Strings.ReadSafe("Title"));
return true;
}
return false;
}
private static bool CheckTagOrGroup(Settings settings, PwEntry settingsEntry)
{
// Require at least one of Tag or Group
if (string.IsNullOrEmpty(settings.Tag) && string.IsNullOrEmpty(settings.Group))
{
MessageService.ShowWarning("SubsetExport: Missing Tag or Group for: " +
settingsEntry.Strings.ReadSafe("Title"));
return true;
}
return false;
}
private static Boolean CheckKeyFile(PwDatabase sourceDb, Settings settings, PwEntry settingsEntry)
{
// If a key file is given it must exist.
if (!string.IsNullOrEmpty(settings.KeyFilePath))
{
// Default to same folder as sourceDb for the keyfile if no directory is specified
if (!Path.IsPathRooted(settings.KeyFilePath))
{
string sourceDbPath = Path.GetDirectoryName(sourceDb.IOConnectionInfo.Path);
if (sourceDbPath != null)
{
settings.KeyFilePath = Path.Combine(sourceDbPath, settings.KeyFilePath);
}
}
if (!File.Exists(settings.KeyFilePath))
{
MessageService.ShowWarning("SubsetExport: Keyfile is given but could not be found for: " +
settingsEntry.Strings.ReadSafe("Title"), settings.KeyFilePath);
return true;
}
}
return false;
}
/// <summary>
/// Exports all entries with the given tag to a new database at the given path.
/// </summary>
/// <param name="sourceDb">The source database.</param>
/// <param name="settings">The settings for this job.</param>
private static void CopyToNewDb(PwDatabase sourceDb, Settings settings)
{
// Create a key for the target database
CompositeKey key = CreateCompositeKey(settings);
// Trigger an export for multiple target dbs (as we could also write to en existing db coping is not an option)
foreach (string targetFilePathLoopVar in settings.TargetFilePath.Split(','))
{
string targetFilePath = targetFilePathLoopVar;
// Create or open the target database
PwDatabase targetDatabase = CreateTargetDatabase(sourceDb, settings, key, ref targetFilePath);
if (settings.ExportDatebaseSettings)
{
// Copy database settings
CopyDatabaseSettings(sourceDb, targetDatabase);
}
// Copy key derivation function parameters
CopyKdfSettings(sourceDb, settings, targetDatabase);
// Assign the properties of the source root group to the target root group
targetDatabase.RootGroup.AssignProperties(sourceDb.RootGroup, false, true);
HandleCustomIcon(targetDatabase, sourceDb, sourceDb.RootGroup);
// Overwrite the root group name if requested
if (!string.IsNullOrEmpty(settings.RootGroupName))
{
targetDatabase.RootGroup.Name = settings.RootGroupName;
}
// Find all entries matching the tag
PwObjectList<PwEntry> entries = GetMatching(sourceDb, settings);
// Copy all entries to the new database
CopyEntriesAndGroups(sourceDb, settings, entries, targetDatabase);
// Save new database
SaveTargetDatabase(targetFilePath, targetDatabase, settings.OverrideTargetDatabase);
}
}
private static PwObjectList<PwEntry> GetMatching(PwDatabase sourceDb, Settings settings)
{
PwObjectList<PwEntry> entries;
if (!string.IsNullOrEmpty(settings.Tag) && string.IsNullOrEmpty(settings.Group))
{
// Tag only export
// Support multiple tags (Tag1,Tag2)
entries = FindEntriesByTag(sourceDb, settings.Tag);
}
else if (string.IsNullOrEmpty(settings.Tag) && !string.IsNullOrEmpty(settings.Group))
{
// Group only export
// Support multiple groups (Group1,Group2)
entries = FindEntriesByGroup(sourceDb, settings.Group);
}
else if (!string.IsNullOrEmpty(settings.Tag) && !string.IsNullOrEmpty(settings.Group))
{
// Group and Tag export
// Support multiple groups (Group1,Group2)
// Support multiple tags (Tag1,Tag2)
entries = FindEntriesByGroupAndTag(sourceDb, settings.Group, settings.Tag);
}
else
{
throw new ArgumentException("At least one of Tag or ExportFolderName must be set.");
}
return entries;
}
/// <summary>
/// Finds all entries with a given group and tag (or multiple)
/// </summary>
/// <param name="sourceDb">Database to search for the entries.</param>
/// <param name="groups">Groups to search for (multiple separated by ,).</param>
/// <param name="tags">Tag to search for (multiple separated by ,).</param>
/// <returns>A PwObjectList with all metching entries.</returns>
private static PwObjectList<PwEntry> FindEntriesByGroupAndTag(PwDatabase sourceDb, string groups, string tags)
{
PwObjectList<PwEntry> entries = new PwObjectList<PwEntry>();
// Tag and group export
foreach (string group in groups.Split(',').Select(x => x.Trim()))
{
PwGroup groupToExport = sourceDb.RootGroup.GetFlatGroupList().FirstOrDefault(g => g.Name == group);
if (groupToExport == null)
{
throw new ArgumentException("No group with the name of the Group-Setting found.");
}
foreach (string tag in tags.Split(',').Select(x => x.Trim()))
{
PwObjectList<PwEntry> tagEntries = new PwObjectList<PwEntry>();
groupToExport.FindEntriesByTag(tag, tagEntries, true);
// Prevent duplicated entries
IEnumerable<PwUuid> existingUuids = entries.Select(x => x.Uuid);
List<PwEntry> entriesToAdd = tagEntries.Where(x => !existingUuids.Contains(x.Uuid)).ToList();
entries.Add(entriesToAdd);
}
}
return entries;
}
/// <summary>
/// Finds all entries with a given group (or multiple)
/// </summary>
/// <param name="sourceDb">Database to search for the entries.</param>
/// <param name="groups">Groups to search for (multiple separated by ,).</param>
/// <returns>A PwObjectList with all metching entries.</returns>
private static PwObjectList<PwEntry> FindEntriesByGroup(PwDatabase sourceDb, string groups)
{
PwObjectList<PwEntry> entries = new PwObjectList<PwEntry>();
foreach (string group in groups.Split(',').Select(x => x.Trim()))
{
// Tag and group export
PwGroup groupToExport = sourceDb.RootGroup.GetFlatGroupList().FirstOrDefault(g => g.Name == group);
if (groupToExport == null)
{
throw new ArgumentException("No group with the name of the Group-Setting found.");
}
PwObjectList<PwEntry> groupEntries = groupToExport.GetEntries(true);
// Prevent duplicated entries
IEnumerable<PwUuid> existingUuids = entries.Select(x => x.Uuid);
List<PwEntry> entriesToAdd = groupEntries.Where(x => !existingUuids.Contains(x.Uuid)).ToList();
entries.Add(entriesToAdd);
}
return entries;
}
/// <summary>
/// Finds all entries with a given tag (or multiple)
/// </summary>
/// <param name="sourceDb">Database to search for the entries.</param>
/// <param name="tags">Tag to search for (multiple separated by ,).</param>
/// <returns>A PwObjectList with all metching entries.</returns>
private static PwObjectList<PwEntry> FindEntriesByTag(PwDatabase sourceDb, string tags)
{
PwObjectList<PwEntry> entries = new PwObjectList<PwEntry>();
foreach (string tag in tags.Split(',').Select(x => x.Trim()))
{
PwObjectList<PwEntry> tagEntries = new PwObjectList<PwEntry>();
sourceDb.RootGroup.FindEntriesByTag(tag, tagEntries, true);
// Prevent duplicated entries
IEnumerable<PwUuid> existingUuids = entries.Select(x => x.Uuid);
List<PwEntry> entriesToAdd = tagEntries.Where(x => !existingUuids.Contains(x.Uuid)).ToList();
entries.Add(entriesToAdd);
}
return entries;
}
private static void CopyEntriesAndGroups(PwDatabase sourceDb, Settings settings, PwObjectList<PwEntry> entries,
PwDatabase targetDatabase)
{
//If OverrideEntireGroup is set to true
if (!settings.OverrideTargetDatabase && !settings.FlatExport &&
settings.OverrideEntireGroup && !string.IsNullOrEmpty(settings.Group))
{
//Delete every entry in target database' groups to override them
IEnumerable<PwGroup> groupsToDelete = entries.Select(x => x.ParentGroup).Distinct();
DeleteTargetGroupsInDatabase(groupsToDelete, targetDatabase);
}
foreach (PwEntry entry in entries)
{
// Get or create the target group in the target database (including hierarchy)
PwGroup targetGroup = settings.FlatExport
? targetDatabase.RootGroup
: CreateTargetGroupInDatebase(entry, targetDatabase, sourceDb);
PwEntry peNew = null;
if (!settings.OverrideTargetDatabase)
{
peNew = targetGroup.FindEntry(entry.Uuid, bSearchRecursive: false);
// Check if the target entry is newer than the source entry
if (settings.OverrideEntryOnlyNewer && peNew != null &&
peNew.LastModificationTime > entry.LastModificationTime)
{
// Yes -> skip this entry
continue;
}
}
// Was no existing entry in the target database found?
if (peNew == null)
{
// Create a new entry
peNew = new PwEntry(false, false);
peNew.Uuid = entry.Uuid;
// Add entry to the target group in the new database
targetGroup.AddEntry(peNew, true);
}
// Clone entry properties if ExportUserAndPassOnly is false
if (!settings.ExportUserAndPassOnly)
{
peNew.AssignProperties(entry, false, true, true);
peNew.Strings.Set(PwDefs.UrlField,
FieldHelper.GetFieldWRef(entry, sourceDb, PwDefs.UrlField));
peNew.Strings.Set(PwDefs.NotesField,
FieldHelper.GetFieldWRef(entry, sourceDb, PwDefs.NotesField));
}
else
{
// Copy visual stuff even if settings.ExportUserAndPassOnly is set
peNew.IconId = entry.IconId;
peNew.CustomIconUuid = entry.CustomIconUuid;
peNew.BackgroundColor = entry.BackgroundColor;
}
// Copy/override some supported fields with ref resolving values
peNew.Strings.Set(PwDefs.TitleField,
FieldHelper.GetFieldWRef(entry, sourceDb, PwDefs.TitleField));
peNew.Strings.Set(PwDefs.UserNameField,
FieldHelper.GetFieldWRef(entry, sourceDb, PwDefs.UserNameField));
peNew.Strings.Set(PwDefs.PasswordField,
FieldHelper.GetFieldWRef(entry, sourceDb, PwDefs.PasswordField));
// Handle custom icon
HandleCustomIcon(targetDatabase, sourceDb, entry);
}
}
private static void SaveTargetDatabase(string targetFilePath, PwDatabase targetDatabase, bool overrideTargetDatabase)
{
Regex rg = new Regex(@".+://.+", RegexOptions.None, TimeSpan.FromMilliseconds(200));
if (!rg.IsMatch(targetFilePath))
{
// local file path
if (!overrideTargetDatabase && File.Exists(targetFilePath))
{
// Save changes to existing target database
targetDatabase.Save(new NullStatusLogger());
}
else
{
// Create target folder (if not exist)
string targetFolder = Path.GetDirectoryName(targetFilePath);
if (targetFolder == null)
{
throw new ArgumentException("Can't get target folder.");
}
Directory.CreateDirectory(targetFolder);
// Save the new database under the target path
KdbxFile kdbx = new KdbxFile(targetDatabase);
using (FileStream outputStream = new FileStream(targetFilePath, FileMode.Create))
{
kdbx.Save(outputStream, null, KdbxFormat.Default, new NullStatusLogger());
}
}
}
else
{
// Non local file (ftp, webdav, ...)
Uri targetUrl = new Uri(targetFilePath);
string[] userAndPw = targetUrl.UserInfo.Split(':');
IOConnectionInfo conInfo = new IOConnectionInfo
{
Path = Regex.Replace(targetUrl.AbsoluteUri, @"(?<=//)[^@]+@", "", RegexOptions.None, TimeSpan.FromMilliseconds(200)),
CredSaveMode = IOCredSaveMode.NoSave,
UserName = userAndPw[0],
Password = userAndPw[1]
};
targetDatabase.SaveAs(conInfo, false, new NullStatusLogger());
}
}
private static void CopyKdfSettings(PwDatabase sourceDb, Settings settings, PwDatabase targetDatabase)
{
// Create a clone of the KdfParameters object. As cloning is not supportet serialize and deserialize
targetDatabase.KdfParameters = KdfParameters.DeserializeExt(KdfParameters.SerializeExt(sourceDb.KdfParameters));
if (Equals(targetDatabase.KdfParameters.KdfUuid, UuidAes))
{
// Allow override of AesKdf transformation rounds
if (settings.KeyTransformationRounds != 0)
{
// Set keyTransformationRounds (min PwDefs.DefaultKeyEncryptionRounds)
targetDatabase.KdfParameters.SetUInt64(AesKdf.ParamRounds,
Math.Max(PwDefs.DefaultKeyEncryptionRounds, settings.KeyTransformationRounds));
}
}
else if (Equals(targetDatabase.KdfParameters.KdfUuid, UuidArgon2))
{
// Allow override of Agon2Kdf transformation rounds
if (settings.Argon2ParamIterations != 0)
{
// Set paramIterations (min default value == 2)
targetDatabase.KdfParameters.SetUInt64(Argon2Kdf.ParamIterations,
Math.Max(2, settings.Argon2ParamIterations));
}
// Allow override of Agon2Kdf memory setting
if (settings.Argon2ParamMemory != 0)
{
// Set ParamMemory (min default value == 1048576 == 1 MB)
targetDatabase.KdfParameters.SetUInt64(Argon2Kdf.ParamMemory,
Math.Max(1048576, settings.Argon2ParamMemory));
}
// Allow override of Agon2Kdf parallelism setting
if (settings.Argon2ParamParallelism != 0)
{
// Set ParamParallelism (min default value == 2 MB)
targetDatabase.KdfParameters.SetUInt32(Argon2Kdf.ParamParallelism, settings.Argon2ParamParallelism);
}
}
}
private static void CopyDatabaseSettings(PwDatabase sourceDb, PwDatabase targetDatabase)
{
targetDatabase.Color = sourceDb.Color;
targetDatabase.Compression = sourceDb.Compression;
targetDatabase.DataCipherUuid = sourceDb.DataCipherUuid;
targetDatabase.DefaultUserName = sourceDb.DefaultUserName;
targetDatabase.Description = sourceDb.Description;
targetDatabase.HistoryMaxItems = sourceDb.HistoryMaxItems;
targetDatabase.HistoryMaxSize = sourceDb.HistoryMaxSize;
targetDatabase.MaintenanceHistoryDays = sourceDb.MaintenanceHistoryDays;
targetDatabase.MasterKeyChangeForce = sourceDb.MasterKeyChangeForce;
targetDatabase.MasterKeyChangeRec = sourceDb.MasterKeyChangeRec;
targetDatabase.Name = sourceDb.Name;
targetDatabase.RecycleBinEnabled = sourceDb.RecycleBinEnabled;
}
private static PwDatabase CreateTargetDatabase(PwDatabase sourceDb, Settings settings, CompositeKey key, ref string targetFilePath)
{
Regex rg = new Regex(@".+://.+", RegexOptions.None, TimeSpan.FromMilliseconds(200));
if (rg.IsMatch(targetFilePath))
{
// Non local file (ftp, webdav, ...)
// Create a new database
PwDatabase targetDatabaseForUri = new PwDatabase();
// Apply the created key to the new database
targetDatabaseForUri.New(new IOConnectionInfo(), key);
return targetDatabaseForUri;
}
// Default to same folder as sourceDb for target if no directory is specified
if (!Path.IsPathRooted(targetFilePath))
{
string sourceDbPath = Path.GetDirectoryName(sourceDb.IOConnectionInfo.Path);
if (sourceDbPath != null)
{
targetFilePath = Path.Combine(sourceDbPath, targetFilePath);
}
}
// Create a new database
PwDatabase targetDatabase = new PwDatabase();
if (!settings.OverrideTargetDatabase && File.Exists(targetFilePath))
{
// Connect the database object to the existing database
targetDatabase.Open(new IOConnectionInfo()
{
Path = targetFilePath
}, key, new NullStatusLogger());
}
else
{
// Apply the created key to the new database
targetDatabase.New(new IOConnectionInfo(), key);
}
return targetDatabase;
}
private static CompositeKey CreateCompositeKey(Settings settings)
{
CompositeKey key = new CompositeKey();
bool hasPassword = false;
bool hasKeyFile = false;
if (!settings.Password.IsEmpty)
{
byte[] passwordByteArray = settings.Password.ReadUtf8();
hasPassword = KeyHelper.AddPasswordToKey(passwordByteArray, key);
MemUtil.ZeroByteArray(passwordByteArray);
}
// Load a keyfile for the target database if requested (and add it to the key)
if (!string.IsNullOrEmpty(settings.KeyFilePath))
{
hasKeyFile = KeyHelper.AddKeyfileToKey(settings.KeyFilePath, key, ConnectionInfo);
}
// Check if at least a password or a keyfile have been added to the key object
if (!hasPassword && !hasKeyFile)
{
// Fail if not
throw new InvalidOperationException("For the target database at least a password or a keyfile is required.");
}
return key;
}
/// <summary>
/// Get or create the target group of an entry in the target database (including hierarchy).
/// </summary>
/// <param name="entry">An entry wich is located in the folder with the target structure.</param>
/// <param name="targetDatabase">The target database in which the folder structure should be created.</param>
/// <param name="sourceDatabase">The source database from which the folder properties should be taken.</param>
/// <returns>The target folder in the target database.</returns>
private static PwGroup CreateTargetGroupInDatebase(PwEntry entry, PwDatabase targetDatabase, PwDatabase sourceDatabase)
{
// Collect all group names from the entry up to the root group
PwGroup group = entry.ParentGroup;
List<PwUuid> list = new List<PwUuid>();
while (group != null)
{
list.Add(group.Uuid);
group = group.ParentGroup;
}
// Remove root group (we already changed the root group name)
list.RemoveAt(list.Count - 1);
// groups are in a bottom-up oder -> reverse to get top-down
list.Reverse();
// Create group structure for the new entry (copying group properties)
PwGroup lastGroup = targetDatabase.RootGroup;
foreach (PwUuid id in list)
{
// Does the target group already exist?
PwGroup newGroup = lastGroup.FindGroup(id, false);
if (newGroup != null)
{
lastGroup = newGroup;
continue;
}
// Get the source group
PwGroup sourceGroup = sourceDatabase.RootGroup.FindGroup(id, true);
// Create a new group and assign all properties from the source group
newGroup = new PwGroup();
newGroup.AssignProperties(sourceGroup, false, true);
HandleCustomIcon(targetDatabase, sourceDatabase, sourceGroup);
// Add the new group at the right position in the target database
lastGroup.AddGroup(newGroup, true);
lastGroup = newGroup;
}
// Return the target folder (leaf folder)
return lastGroup;
}
/// <summary>
/// Delete every entry in the target group.
/// </summary>
/// <param name="sourceGroups">Collection of groups which counterparts should be deleted in the target database.</param>
/// <param name="targetDatabase">The target database in which the folder structure should be created.</param>
private static void DeleteTargetGroupsInDatabase(IEnumerable<PwGroup> sourceGroups, PwDatabase targetDatabase)
{
// Get the target groups ID based
foreach (PwGroup targetGroup in sourceGroups.Select(x => targetDatabase.RootGroup.FindGroup(x.Uuid, false)))
{
// If group exists in target database, delete its entries, otherwise show a warning
if (targetGroup != null)
{
targetGroup.DeleteAllObjects(targetDatabase);
}
}
}
/// <summary>
/// Copies the custom icons required for this group to the target database.
/// </summary>
/// <param name="targetDatabase">The target database where to add the icons.</param>
/// <param name="sourceDatabase">The source database where to get the icons from.</param>
/// <param name="sourceGroup">The source group which icon should be copied (if it is custom).</param>
private static void HandleCustomIcon(PwDatabase targetDatabase, PwDatabase sourceDatabase, PwGroup sourceGroup)
{
// Does the group not use a custom icon or is it already in the target database
if (sourceGroup.CustomIconUuid.Equals(PwUuid.Zero) ||
targetDatabase.GetCustomIconIndex(sourceGroup.CustomIconUuid) != -1)
{
return;
}
// Check if the custom icon really is in the source database
int iconIndex = sourceDatabase.GetCustomIconIndex(sourceGroup.CustomIconUuid);
if (iconIndex < 0 || iconIndex > sourceDatabase.CustomIcons.Count - 1)
{
MessageService.ShowWarning("Can't locate custom icon (" + sourceGroup.CustomIconUuid.ToHexString() +
") for group " + sourceGroup.Name);
}
// Get the custom icon from the source database
PwCustomIcon customIcon = sourceDatabase.CustomIcons[iconIndex];
// Copy the custom icon to the target database
targetDatabase.CustomIcons.Add(customIcon);
}
/// <summary>
/// Copies the custom icons required for this group to the target database.
/// </summary>
/// <param name="targetDatabase">The target database where to add the icons.</param>
/// <param name="sourceDb">The source database where to get the icons from.</param>
/// <param name="entry">The entry which icon should be copied (if it is custom).</param>
private static void HandleCustomIcon(PwDatabase targetDatabase, PwDatabase sourceDb, PwEntry entry)
{
// Does the entry not use a custom icon or is it already in the target database
if (entry.CustomIconUuid.Equals(PwUuid.Zero) ||
targetDatabase.GetCustomIconIndex(entry.CustomIconUuid) != -1)
{
return;
}
// Check if the custom icon really is in the source database
int iconIndex = sourceDb.GetCustomIconIndex(entry.CustomIconUuid);
if (iconIndex < 0 || iconIndex > sourceDb.CustomIcons.Count - 1)
{
MessageService.ShowWarning("Can't locate custom icon (" + entry.CustomIconUuid.ToHexString() +
") for entry " + entry.Strings.ReadSafe("Title"));
}
// Get the custom icon from the source database
PwCustomIcon customIcon = sourceDb.CustomIcons[iconIndex];
// Copy the custom icon to the target database
targetDatabase.CustomIcons.Add(customIcon);
}
}
}