Compare commits

...

860 Commits

Author SHA1 Message Date
Philipp Crocoll
50287025a0 manifest and changelog for 1.09d-r0 2022-02-05 16:13:53 +01:00
Philipp Crocoll
ed234c898e can now view entry history and remove/restore previous versions. closes #298 2022-02-05 15:53:08 +01:00
Philipp Crocoll
35f74f5ea4 add support for Google Drive with restricted scope, closes #622 2022-02-02 03:50:51 +01:00
Philipp Crocoll
26f0ab6661 implement support for MEGA, closes #99 2022-02-02 02:54:35 +01:00
Philipp Crocoll
3648213be2 Update AndroidX.Biometric library, an attempt to see if this helps regarding https://github.com/PhilippC/keepass2android/issues/1914 2022-01-20 20:02:16 +01:00
Philipp Crocoll
40146a42ce avoid potential crash in Android 12 while reloading the Database, closes https://github.com/PhilippC/keepass2android/issues/1909 2022-01-20 20:00:37 +01:00
Philipp Crocoll
2c93fd1542 Add option to hide the "Disable autofill for XY" prompt, closes https://github.com/PhilippC/keepass2android/issues/1843 2022-01-17 10:05:16 +01:00
Philipp Crocoll
2981497da9 start adding option for "no disable autofill" 2022-01-17 09:49:44 +01:00
Philipp Crocoll
6745e0486c unmask single field when using the toggle menu option in the popup menu, closes https://github.com/PhilippC/keepass2android/issues/71; add option for specifying the default visibility of the TOTP field, closes https://github.com/PhilippC/keepass2android/issues/1873 2022-01-17 09:46:47 +01:00
Philipp Crocoll
093ebb424e Merge branch 'master' of https://github.com/PhilippC/keepass2android 2022-01-15 20:10:53 +01:00
Philipp Crocoll
c344b0f62a add button to scan QR code for TOTP setup in the "setup TOTP" dialog. Closes https://github.com/PhilippC/keepass2android/issues/1575 2022-01-15 20:10:34 +01:00
Philipp Crocoll
2b85ed473c make sure master password is no longer in unmasked mode when using fingerprint, closes https://github.com/PhilippC/keepass2android/issues/1134 2022-01-15 16:28:00 +01:00
Philipp Crocoll
d536b38acc allow to create a special entry in the database with the QuickUnlock code as password, closes https://github.com/PhilippC/keepass2android/issues/331 2022-01-15 16:17:35 +01:00
Philipp Crocoll
b4a82511ff Add option to always merge on conflict, closes https://github.com/PhilippC/keepass2android/issues/1218 2022-01-15 15:55:34 +01:00
PhilippC
35a84adde3 Merge pull request #1902 from whalehub/patch-1
Fix homepage URL
2022-01-15 14:20:21 +01:00
Philipp Crocoll
675fd5b735 release 1.09c-r0. This solves the problem with Google Drive API blocked, closes #1833, closes #1859 and closes #1862 2022-01-12 17:30:04 +01:00
PhilippC
14c7663167 Merge pull request #1851 from PhilippC/l10n_master2
New Crowdin updates
2022-01-12 16:58:01 +01:00
PhilippC
788a04aceb New translations strings.xml (Slovenian) 2022-01-12 08:37:49 +01:00
PhilippC
99c70f96d8 New translations strings.xml (Russian) 2022-01-12 08:37:45 +01:00
PhilippC
9f729e8c36 New translations strings.xml (Polish) 2022-01-12 08:37:41 +01:00
PhilippC
35f1a351ba New translations strings.xml (Portuguese, Brazilian) 2022-01-12 08:37:32 +01:00
PhilippC
5f59de2fba New translations strings.xml (Chinese Simplified) 2022-01-12 08:37:23 +01:00
PhilippC
95b7c59606 New translations strings.xml (Ukrainian) 2022-01-12 08:37:21 +01:00
PhilippC
7962b5c8ea New translations strings.xml (Japanese) 2022-01-12 08:37:16 +01:00
PhilippC
d2818a35c6 New translations strings.xml (Slovak) 2022-01-12 08:37:07 +01:00
PhilippC
44183178c6 New translations strings.xml (Spanish) 2022-01-12 08:37:04 +01:00
PhilippC
2ebb35b631 New translations strings.xml (Spanish) 2022-01-12 08:37:03 +01:00
PhilippC
173a5fedf0 New translations strings.xml (French) 2022-01-12 08:37:00 +01:00
PhilippC
00c0bbd942 New translations strings.xml (Romanian) 2022-01-12 08:36:58 +01:00
PhilippC
1dfb38207b New translations strings.xml (Danish) 2022-01-12 08:36:57 +01:00
PhilippC
df0d087646 New translations strings.xml (Italian) 2022-01-12 08:36:53 +01:00
PhilippC
8f84e292b2 New translations strings.xml (Greek) 2022-01-12 08:36:43 +01:00
PhilippC
45bf4247c1 New translations strings.xml (German) 2022-01-12 08:36:40 +01:00
Philipp Crocoll
daecd42d8e Merge branch 'master' of https://github.com/PhilippC/keepass2android 2022-01-12 08:31:38 +01:00
Philipp Crocoll
952aaa76ff update file storage selection to use icon from https://developers.google.com/drive/api/v3/branding 2022-01-12 08:28:09 +01:00
Philipp Crocoll
fe57c05579 prototype implementation using the "Sign in with Google" button 2022-01-12 08:11:18 +01:00
Philipp Crocoll
309fd9f4d1 GoogleDriveFileStorage: update libraries and reimplement authentication workflow to show consent screen (verification by Google now passed); migrate Java filestorage and filechooser projects to AndroidX; disable "app is blocked" message; see #1833 2022-01-12 08:07:28 +01:00
Aaron
9af0d74ba4 Fix homepage URL
Signed-off-by: Aaron <admin@datahoarder.dev>
2022-01-08 05:45:32 +01:00
PhilippC
c1292b585f Merge pull request #1888 from mcpower/xamarin-ci-build-link
Fix Xamarin.Android CI build link
2022-01-06 15:16:57 +01:00
PhilippC
87a6bbe0fd Update README.md 2022-01-06 11:16:04 +01:00
PhilippC
b807431053 Update README.md 2022-01-06 11:15:47 +01:00
Philipp Crocoll
1442af3148 add Belarusian translation to close #1896 2022-01-05 12:47:30 +01:00
PhilippC
eb92645ed0 Merge pull request #1897 from IanSmith123/patch-1
chore: typo in Chinese  version ChangeLog  v1.07
2022-01-05 12:31:54 +01:00
Les1ie
714be0f6ae chore: fix Chinese version ChangeLog v1.07 typo 2022-01-04 19:23:33 +08:00
Philipp Crocoll
e77c3ce1d3 fix issue: inputstick/keelink plugins could not be enabled on Android 11+ 2022-01-03 11:13:43 +01:00
PhilippC
48d080bcda New translations strings.xml (Danish) 2022-01-02 20:39:02 +01:00
PhilippC
531cb51fac New translations strings.xml (Slovak) 2022-01-02 13:27:30 +01:00
mcpower
e8bc949230 Fix Xamarin.Android CI build link 2022-01-02 13:03:47 +11:00
PhilippC
d99d43dde7 New translations strings.xml (Croatian) 2021-12-30 09:23:34 +01:00
PhilippC
b64c304a92 New translations strings.xml (Croatian) 2021-12-30 08:21:45 +01:00
PhilippC
d392b1c434 New translations strings.xml (Croatian) 2021-12-29 15:51:28 +01:00
PhilippC
1f9b5df44b New translations strings.xml (Croatian) 2021-12-28 17:34:46 +01:00
PhilippC
edd4067efc New translations strings.xml (Croatian) 2021-12-28 17:34:46 +01:00
PhilippC
1a23b8363a New translations strings.xml (Croatian) 2021-12-28 17:34:44 +01:00
PhilippC
04df628fe8 New translations strings.xml (German) 2021-12-28 16:20:53 +01:00
PhilippC
2bb5a18f27 New translations strings.xml (Hungarian) 2021-12-26 15:48:49 +01:00
PhilippC
6cc7c3b1fb New translations strings.xml (Hungarian) 2021-12-26 14:41:24 +01:00
PhilippC
21c1b1e913 New translations strings.xml (Hungarian) 2021-12-24 13:22:51 +01:00
PhilippC
a1cdba5baa New translations strings.xml (Chinese Traditional) 2021-12-23 17:35:28 +01:00
PhilippC
3b14ffb331 New translations strings.xml (Chinese Traditional) 2021-12-23 16:36:14 +01:00
Philipp Crocoll
b0e6de8a47 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-12-20 09:27:52 +01:00
Philipp Crocoll
2a3138ca76 increase key size for Sftp publickey to 4096 to close #1874. (Note: ED25519 is not supported by standard JSch at the moment) 2021-12-20 09:14:39 +01:00
PhilippC
56a0c6325d New translations strings.xml (Spanish) 2021-12-17 13:25:21 +01:00
PhilippC
69c0485fe8 New translations strings.xml (Korean) 2021-12-17 09:23:47 +01:00
PhilippC
21205f85aa New translations strings.xml (Spanish) 2021-12-17 08:16:43 +01:00
PhilippC
9d10d75d54 New translations strings.xml (Chinese Traditional) 2021-12-16 17:06:37 +01:00
PhilippC
2757f657c5 New translations strings.xml (Chinese Traditional) 2021-12-16 16:06:26 +01:00
PhilippC
1c4040707e New translations strings.xml (Romanian) 2021-12-13 15:11:59 +01:00
PhilippC
ca4a29a991 New translations strings.xml (Romanian) 2021-12-13 14:13:56 +01:00
PhilippC
9120da776c New translations strings.xml (Romanian) 2021-12-13 14:13:55 +01:00
PhilippC
24b2f1fca5 New translations strings.xml (Italian) 2021-12-13 13:18:04 +01:00
PhilippC
f4f0bb166a New translations strings.xml (Italian) 2021-12-13 12:21:02 +01:00
PhilippC
27c8d78ac9 New translations strings.xml (Italian) 2021-12-13 11:09:44 +01:00
PhilippC
fe32105491 New translations strings.xml (Ukrainian) 2021-12-08 01:13:55 +01:00
PhilippC
ad7ef30f68 New translations strings.xml (French) 2021-12-05 19:51:53 +01:00
PhilippC
c1df53e986 New translations strings.xml (French) 2021-12-05 18:51:26 +01:00
PhilippC
36b8f52929 New translations strings.xml (Russian) 2021-12-05 03:19:40 +01:00
PhilippC
f84ab77a9a New translations strings.xml (Russian) 2021-12-05 02:20:51 +01:00
PhilippC
1ec842c274 New translations strings.xml (Arabic) 2021-12-02 16:50:21 +01:00
PhilippC
e06849cd24 New translations strings.xml (Finnish) 2021-11-29 14:19:28 +01:00
PhilippC
3f31533909 New translations strings.xml (Finnish) 2021-11-29 13:20:11 +01:00
Philipp Crocoll
5a408ccd4a manifest for 1.09b-r1 2021-11-29 11:13:29 +01:00
Philipp Crocoll
07d3a4ef84 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-11-29 10:40:39 +01:00
Philipp Crocoll
eb50ed1bc3 throw exception in Google Drive implementation as access is blocked by Google currently. 2021-11-29 10:40:27 +01:00
PhilippC
681d120e8b New translations strings.xml (Polish) 2021-11-26 19:33:42 +01:00
PhilippC
74ae266fed New translations strings.xml (Polish) 2021-11-26 18:36:29 +01:00
PhilippC
028f16af06 New translations strings.xml (Greek) 2021-11-24 16:05:30 +01:00
PhilippC
fa7980f379 New translations strings.xml (Greek) 2021-11-24 15:05:17 +01:00
PhilippC
4ddc173757 New translations strings.xml (Chinese Simplified) 2021-11-23 04:18:51 +01:00
PhilippC
8bc93d7f23 New translations strings.xml (Portuguese, Brazilian) 2021-11-22 15:04:59 +01:00
PhilippC
de72df99ea New translations strings.xml (Slovenian) 2021-11-22 13:15:06 +01:00
PhilippC
580fd60fd9 Merge pull request #1850 from PhilippC/l10n_master2
New Crowdin updates
2021-11-22 10:22:47 +01:00
PhilippC
c884f93ac3 New translations strings.xml (German) 2021-11-22 10:13:24 +01:00
PhilippC
a57ff299b5 New translations strings.xml (Slovak) 2021-11-22 10:07:51 +01:00
PhilippC
159a90d7e3 New translations strings.xml (Slovenian) 2021-11-22 10:07:43 +01:00
PhilippC
b8fe4639ba New translations strings.xml (German) 2021-11-22 10:07:41 +01:00
PhilippC
6a80021bd1 New translations strings.xml (Danish) 2021-11-22 10:07:40 +01:00
PhilippC
d2dc75d556 New translations strings.xml (Spanish) 2021-11-22 10:07:34 +01:00
PhilippC
2aa93b56cb New translations strings.xml (Ukrainian) 2021-11-22 10:07:29 +01:00
PhilippC
11037daca9 New translations strings.xml (Portuguese, Brazilian) 2021-11-22 10:07:18 +01:00
PhilippC
93932e78f9 New translations strings.xml (Chinese Simplified) 2021-11-22 10:07:13 +01:00
PhilippC
7ad476d543 New translations strings.xml (Japanese) 2021-11-22 10:07:11 +01:00
Philipp Crocoll
9b487f94de fix typo 2021-11-22 10:01:42 +01:00
Philipp Crocoll
68e34eb3bd Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-11-22 09:56:06 +01:00
PhilippC
88f95b96b0 Merge pull request #1687 from PhilippC/l10n_master2
New Crowdin updates
2021-11-22 09:55:46 +01:00
PhilippC
e22a69caf5 New translations strings.xml (Portuguese, Brazilian) 2021-11-18 12:00:25 +01:00
PhilippC
bb339504d4 New translations strings.xml (Portuguese, Brazilian) 2021-11-18 10:59:50 +01:00
PhilippC
2666c5248b New translations strings.xml (Chinese Simplified) 2021-11-18 03:12:44 +01:00
PhilippC
c0df8e9a04 New translations strings.xml (Spanish) 2021-11-17 15:57:29 +01:00
PhilippC
bead854cf1 New translations strings.xml (Spanish) 2021-11-17 14:53:54 +01:00
PhilippC
48da600598 New translations strings.xml (German) 2021-11-17 12:40:27 +01:00
Philipp Crocoll
3b0b349f4c manifest and changelog for 1.09-pre2 2021-11-17 12:06:56 +01:00
Philipp Crocoll
294e6f5edf add option to sync database after quick-unlocking the database. closes https://github.com/PhilippC/keepass2android/issues/188 2021-11-17 11:30:43 +01:00
PhilippC
02fb27f48e New translations strings.xml (Danish) 2021-11-17 00:57:06 +01:00
PhilippC
e57a02408e New translations strings.xml (Ukrainian) 2021-11-16 05:45:43 +01:00
PhilippC
5325381f2f New translations strings.xml (Slovak) 2021-11-15 22:43:41 +01:00
PhilippC
4176b06578 New translations strings.xml (Slovak) 2021-11-15 21:44:56 +01:00
Philipp Crocoll
7014f5d9f1 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-11-15 11:27:08 +01:00
Philipp Crocoll
afbef4272c don't show "Third party app" in Android 11+ (doesn't work due to file access permissions, avoids confusion). Closes https://github.com/PhilippC/keepass2android/issues/1468, closes https://github.com/PhilippC/keepass2android/issues/1606 2021-11-15 11:26:58 +01:00
Philipp Crocoll
45df2d6605 preserve filename case in Dropbox when uploading files, closes https://github.com/PhilippC/keepass2android/issues/898 2021-11-15 09:32:02 +01:00
PhilippC
1c2369c419 New translations strings.xml (Slovenian) 2021-11-15 09:06:35 +01:00
PhilippC
81211ecb74 New translations strings.xml (Japanese) 2021-11-14 15:51:46 +01:00
PhilippC
17a06adfd9 New translations strings.xml (Japanese) 2021-11-14 14:49:25 +01:00
PhilippC
da565e43bb New translations strings.xml (German) 2021-11-14 09:39:46 +01:00
PhilippC
641497a8d3 New translations strings.xml (Portuguese, Brazilian) 2021-11-12 18:50:46 +01:00
PhilippC
7cecadcf82 Merge pull request #1838 from mhayen/patch-1
Update Documentation.md
2021-11-12 11:40:16 +01:00
Philipp Crocoll
cd776754b5 add package visibility for AncientIconSet 2021-11-12 11:23:49 +01:00
Philipp Crocoll
9d2d920fda introduce <queries> in manifest to work with Android 11+ limited package visibility 2021-11-12 10:57:04 +01:00
Philipp Crocoll
cb30d3a79e Merge branch 'master' of c:/ph/keepass2android 2021-11-12 08:37:50 +01:00
Philipp Crocoll
3842cbffc4 manifest and csproj for 1.09b-pre0 2021-11-12 08:37:10 +01:00
PhilippC
1cf7cbc144 New translations strings.xml (Chinese Simplified) 2021-11-12 02:13:58 +01:00
Mark
73e8291cb0 Update Documentation.md
added missing space after github issue url
2021-11-12 00:18:04 +01:00
Philipp Crocoll
1e89c8dac1 remove log, closes https://github.com/PhilippC/keepass2android/issues/1832 2021-11-11 20:22:01 +01:00
Philipp Crocoll
81c4477453 allow to change app language, closes https://github.com/PhilippC/keepass2android/issues/201 2021-11-11 12:12:24 +01:00
Philipp Crocoll
adb0865aa7 add note for Google Drive 2021-11-11 09:16:18 +01:00
Philipp Crocoll
d65ae7767a update targetSdkVersion to 30 (as requested by Play store); manifest for 1.09b-pre0 2021-11-11 09:04:26 +01:00
Philipp Crocoll
d83474d6f6 disable "Inline suggestions preference" on Android < 11 2021-11-11 08:57:10 +01:00
Philipp Crocoll
5cffec4724 create Changelog for 1.09b 2021-11-11 08:52:28 +01:00
Philipp Crocoll
8af2f51eae implement inline suggestions for autofill service on Android 11+, closes https://github.com/PhilippC/keepass2android/issues/1374 and https://github.com/PhilippC/keepass2android/issues/1534 2021-11-10 13:02:35 +01:00
Philipp Crocoll
39579b0183 rename app project and associated file 2021-11-10 13:00:38 +01:00
Philipp Crocoll
caff2e25e2 update to build with Android 11 Build tools. Major reconfiguration of csproj file, simplifying PackageReferences. 2021-11-10 11:21:45 +01:00
Philipp Crocoll
0fee6e2298 Autofill: treat android.widget.AutoCompleteTextView as EditText as well (is direct subclass) 2021-11-08 09:30:42 +01:00
PhilippC
feaad1300a New translations strings.xml (Japanese) 2021-11-08 00:06:00 +01:00
PhilippC
1a59129d88 New translations strings.xml (Swedish) 2021-11-07 20:05:49 +01:00
PhilippC
1c7fa8d41d New translations strings.xml (Japanese) 2021-11-06 05:21:37 +01:00
PhilippC
1480657857 New translations strings.xml (Japanese) 2021-11-06 04:24:23 +01:00
PhilippC
c8ab5836eb New translations strings.xml (Japanese) 2021-11-05 10:58:39 +01:00
Philipp Crocoll
2bb7a2065a Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-10-25 12:49:00 +02:00
Philipp Crocoll
0564a103e1 fix flickering autofill popup in Firefox by disabling compat mode for fenix and later, closes #1808 2021-10-25 12:48:48 +02:00
PhilippC
cd0954badb New translations strings.xml (Portuguese) 2021-10-22 18:59:31 +02:00
PhilippC
2e5a1a9820 New translations strings.xml (Portuguese) 2021-10-22 18:03:33 +02:00
PhilippC
8acc6dec1b New translations strings.xml (German) 2021-10-15 23:16:42 +02:00
PhilippC
3f0bbbb19e New translations strings.xml (Russian) 2021-10-15 22:02:13 +02:00
PhilippC
16e9077cd4 New translations strings.xml (Hungarian) 2021-10-13 11:42:49 +02:00
PhilippC
53bb822013 New translations strings.xml (Dutch) 2021-10-03 11:36:55 +02:00
PhilippC
37c1da8962 New translations strings.xml (Dutch) 2021-10-03 10:35:45 +02:00
PhilippC
75535c8edd New translations strings.xml (Spanish) 2021-09-28 23:11:13 +02:00
PhilippC
36c7e9eecc New translations strings.xml (Spanish) 2021-09-28 22:15:09 +02:00
PhilippC
fee5103bdd New translations strings.xml (Chinese Simplified) 2021-09-22 13:25:31 +02:00
PhilippC
4a5a77de21 Merge pull request #1793 from ConorIA/patch-1
Allow autofill in prerelease Brave browser.
2021-09-20 08:46:15 +02:00
PhilippC
1b5f25f10b New translations strings.xml (Polish) 2021-09-15 20:35:10 +02:00
PhilippC
4cf67734a1 New translations strings.xml (Polish) 2021-09-15 20:35:09 +02:00
PhilippC
b6778fb331 New translations strings.xml (Polish) 2021-09-15 19:39:47 +02:00
PhilippC
2ae50c2fbd New translations strings.xml (Spanish) 2021-09-15 10:12:29 +02:00
Conor Anderson
f33ad9e403 Allow autofill in prerelease Brave browser.
Propose adding the Beta and Nightly versions for Brave. I assume prerelease is fair game since Chrome Canary is on there.
2021-09-14 18:16:41 -04:00
PhilippC
4f7d58d8b4 New translations strings.xml (Portuguese) 2021-09-14 14:46:21 +02:00
PhilippC
6582faa8d7 New translations strings.xml (Chinese Simplified) 2021-09-10 10:08:56 +02:00
PhilippC
387f584b2c New translations strings.xml (Chinese Simplified) 2021-09-10 09:12:34 +02:00
PhilippC
b320c0fd5a New translations strings.xml (Ukrainian) 2021-09-09 01:27:22 +02:00
PhilippC
30294a0195 New translations strings.xml (Polish) 2021-09-08 13:43:22 +02:00
PhilippC
252778bf7c New translations strings.xml (Czech) 2021-08-31 00:43:46 +02:00
PhilippC
80bd3c2043 New translations strings.xml (French) 2021-08-30 22:22:02 +02:00
PhilippC
eb968ba676 New translations strings.xml (Portuguese, Brazilian) 2021-08-29 12:37:30 +02:00
PhilippC
1abe428559 New translations strings.xml (Danish) 2021-08-28 11:45:46 +02:00
PhilippC
9854a2ad33 New translations strings.xml (Slovak) 2021-08-27 22:50:46 +02:00
PhilippC
54ebe89d44 New translations strings.xml (Spanish) 2021-08-24 23:58:21 +02:00
PhilippC
2565a04c83 New translations strings.xml (Slovenian) 2021-08-23 09:35:38 +02:00
PhilippC
bb11bdf423 New translations strings.xml (Chinese Simplified) 2021-08-23 04:32:23 +02:00
PhilippC
d6983265ce New translations strings.xml (Dutch) 2021-08-22 17:45:05 +02:00
PhilippC
0034999256 New translations strings.xml (Korean) 2021-08-22 17:45:03 +02:00
PhilippC
10004bd67e New translations strings.xml (Hungarian) 2021-08-22 17:45:02 +02:00
PhilippC
a5c35b8fa9 New translations strings.xml (Greek) 2021-08-22 17:44:58 +02:00
PhilippC
c0d7626ec3 New translations strings.xml (German) 2021-08-22 17:44:57 +02:00
PhilippC
1fbd310371 New translations strings.xml (Polish) 2021-08-22 17:44:56 +02:00
PhilippC
5f0b5919a4 New translations strings.xml (Danish) 2021-08-22 17:44:55 +02:00
PhilippC
ae41b34f13 New translations strings.xml (Belarusian) 2021-08-22 17:44:53 +02:00
PhilippC
4ecb7ef8a6 New translations strings.xml (Arabic) 2021-08-22 17:44:52 +02:00
PhilippC
bfef18bfe7 New translations strings.xml (Spanish) 2021-08-22 17:44:51 +02:00
PhilippC
0448aca24e New translations strings.xml (French) 2021-08-22 17:44:50 +02:00
PhilippC
af67e29ea3 New translations strings.xml (Czech) 2021-08-22 17:44:49 +02:00
PhilippC
f12ee10b9d New translations strings.xml (Catalan) 2021-08-22 17:44:48 +02:00
PhilippC
aea585a93d New translations strings.xml (Italian) 2021-08-22 17:44:47 +02:00
PhilippC
a5b30b557a New translations strings.xml (Portuguese) 2021-08-22 17:44:46 +02:00
PhilippC
a52ebcf576 New translations strings.xml (Slovak) 2021-08-22 17:44:45 +02:00
PhilippC
62fcfdfc95 New translations strings.xml (Portuguese, Brazilian) 2021-08-22 17:44:39 +02:00
PhilippC
a69e5d9c5f New translations strings.xml (Russian) 2021-08-22 17:44:38 +02:00
PhilippC
72ffaf39f4 New translations strings.xml (Chinese Traditional) 2021-08-22 17:44:36 +02:00
PhilippC
08734d733f New translations strings.xml (Chinese Simplified) 2021-08-22 17:44:35 +02:00
PhilippC
e3ddad2c64 New translations strings.xml (Ukrainian) 2021-08-22 17:44:34 +02:00
PhilippC
693cf3608a New translations strings.xml (Turkish) 2021-08-22 17:44:33 +02:00
PhilippC
ad9f49f66e New translations strings.xml (Swedish) 2021-08-22 17:44:32 +02:00
PhilippC
6910414ba3 New translations strings.xml (Slovenian) 2021-08-22 17:44:31 +02:00
PhilippC
f8bb39384b New translations strings.xml (Japanese) 2021-08-22 17:44:29 +02:00
Philipp Crocoll
e7b4cfe53e Manifest for 1.09a-r3 2021-08-22 17:40:57 +02:00
PhilippC
8e6146dd46 New translations strings.xml (Italian) 2021-08-11 19:55:41 +02:00
PhilippC
50254c497a New translations strings.xml (Japanese) 2021-08-11 05:49:57 +02:00
PhilippC
66a7469cd5 New translations strings.xml (Japanese) 2021-08-11 05:49:56 +02:00
PhilippC
70cf30b8a3 New translations strings.xml (Japanese) 2021-08-11 04:46:11 +02:00
PhilippC
1549d71af0 New translations strings.xml (Japanese) 2021-08-11 03:48:42 +02:00
PhilippC
69c95a4073 New translations strings.xml (Japanese) 2021-08-11 02:45:25 +02:00
PhilippC
6c79c1539a New translations strings.xml (Japanese) 2021-08-10 12:01:38 +02:00
PhilippC
0b7d45d3b9 New translations strings.xml (Dutch) 2021-07-26 19:12:55 +02:00
PhilippC
edd1b683dd New translations strings.xml (Dutch) 2021-07-26 18:15:05 +02:00
PhilippC
eccd8e3810 New translations strings.xml (Russian) 2021-07-22 10:03:59 +02:00
PhilippC
8855541ee1 New translations strings.xml (French) 2021-07-20 07:49:53 +02:00
PhilippC
fd300add4f New translations strings.xml (Belarusian) 2021-07-19 09:48:37 +02:00
PhilippC
3161e04752 New translations strings.xml (Belarusian) 2021-07-19 08:51:44 +02:00
PhilippC
ca1a51749a New translations strings.xml (Czech) 2021-07-05 00:21:19 +02:00
Philipp Crocoll
5aba209857 Merge branch 'master' of c:/ph/keepass2android 2021-07-03 15:50:39 +02:00
Philipp Crocoll
80cfb9e098 fix to superfluous dropbox login requests 2021-07-03 15:50:25 +02:00
Philipp Crocoll
b98c887d71 Merge branch 'l10n_master2' of https://github.com/PhilippC/keepass2android 2021-07-03 14:52:10 +02:00
PhilippC
be8e8f9b02 New translations strings.xml (Dutch) 2021-07-03 14:49:44 +02:00
PhilippC
87cbf65654 New translations strings.xml (Dutch) 2021-07-03 14:45:24 +02:00
Philipp Crocoll
6a0e132945 Merge branch 'l10n_master2' of https://github.com/PhilippC/keepass2android 2021-07-03 14:41:21 +02:00
PhilippC
438cf5793a New translations strings.xml (Dutch) 2021-07-03 14:37:44 +02:00
PhilippC
74a736ef87 New translations strings.xml (Spanish) 2021-07-03 14:37:15 +02:00
Philipp Crocoll
4ce451b99c Merge branch 'master' of c:/ph/keepass2android 2021-07-03 13:28:02 +02:00
Philipp Crocoll
f017d3fdf2 remove intermediate file 2021-07-03 13:27:54 +02:00
Philipp Crocoll
54eb656726 1.09a-r1 release manifest 2021-07-03 13:27:38 +02:00
Philipp Crocoll
9d63578110 fix potential crash with invalid OTP strings 2021-07-03 13:27:10 +02:00
Philipp Crocoll
06baeeb79a Merge branch 'l10n_master2' of https://github.com/PhilippC/keepass2android 2021-07-03 13:25:20 +02:00
PhilippC
a77fe463d8 New translations strings.xml (Ukrainian) 2021-07-02 17:09:44 +02:00
PhilippC
c016fc164d New translations strings.xml (Polish) 2021-07-02 17:09:43 +02:00
PhilippC
a409bb1318 New translations strings.xml (Polish) 2021-07-02 16:09:14 +02:00
PhilippC
7eeaf205cd New translations strings.xml (Chinese Simplified) 2021-06-29 05:47:30 +02:00
PhilippC
85882b2547 New translations strings.xml (Chinese Simplified) 2021-06-29 04:47:27 +02:00
PhilippC
12e4af7b0d New translations strings.xml (Portuguese, Brazilian) 2021-06-28 19:57:40 +02:00
PhilippC
774406fe00 New translations strings.xml (Portuguese, Brazilian) 2021-06-28 19:00:23 +02:00
PhilippC
e7c8534c01 New translations strings.xml (Slovenian) 2021-06-28 13:04:59 +02:00
PhilippC
0655dd41bb New translations strings.xml (German) 2021-06-28 12:06:31 +02:00
PhilippC
48bb71d5dc New translations strings.xml (Ukrainian) 2021-06-28 11:07:43 +02:00
PhilippC
b2641fe4d3 New translations strings.xml (Chinese Simplified) 2021-06-28 11:07:42 +02:00
PhilippC
e58de5a99f New translations strings.xml (Portuguese, Brazilian) 2021-06-28 11:07:38 +02:00
PhilippC
68df9ada01 New translations strings.xml (Slovenian) 2021-06-28 11:07:34 +02:00
PhilippC
a91a711499 New translations strings.xml (Slovak) 2021-06-28 11:07:23 +02:00
PhilippC
780ab1125d New translations strings.xml (Polish) 2021-06-28 11:07:22 +02:00
PhilippC
2e83495542 New translations strings.xml (Portuguese) 2021-06-28 11:07:18 +02:00
PhilippC
b69b7a8b5f New translations strings.xml (French) 2021-06-28 11:07:13 +02:00
PhilippC
00f264c313 New translations strings.xml (Russian) 2021-06-28 11:07:11 +02:00
PhilippC
ba9eb65fa6 New translations strings.xml (Czech) 2021-06-28 11:07:09 +02:00
PhilippC
824a7484e0 New translations strings.xml (Danish) 2021-06-28 11:07:07 +02:00
PhilippC
7ebab48b97 New translations strings.xml (German) 2021-06-28 11:07:06 +02:00
PhilippC
708bd9bc8a New translations strings.xml (Greek) 2021-06-28 11:07:05 +02:00
PhilippC
960b00b9ca New translations strings.xml (Portuguese) 2021-06-28 11:06:33 +02:00
PhilippC
d654137caa New translations strings.xml (Belarusian) 2021-06-28 11:06:15 +02:00
Philipp Crocoll
e4b36c172b extended changelog 1.09a 2021-06-28 11:02:46 +02:00
Philipp Crocoll
05f8a067f1 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-06-28 10:56:19 +02:00
Philipp Crocoll
d3db23d1b7 update to Dropbox Core SDK v4.0.0, add support for short-lived access tokens + refresh token (as the previous method will be removed) 2021-06-28 10:56:04 +02:00
PhilippC
30288a3106 New translations strings.xml (Belarusian) 2021-06-25 21:36:19 +02:00
PhilippC
89410e60c7 New translations strings.xml (Belarusian) 2021-06-25 20:36:35 +02:00
PhilippC
3e971b5fe9 New translations strings.xml (Belarusian) 2021-06-25 17:39:25 +02:00
PhilippC
7673e4d638 New translations strings.xml (Portuguese) 2021-06-25 16:32:44 +02:00
PhilippC
9a76ea1433 New translations strings.xml (Portuguese) 2021-06-25 15:26:11 +02:00
PhilippC
92ff60c66d New translations strings.xml (Belarusian) 2021-06-25 15:26:10 +02:00
PhilippC
dfad45f83d New translations strings.xml (Belarusian) 2021-06-25 14:28:35 +02:00
PhilippC
6c0a11d300 New translations strings.xml (Belarusian) 2021-06-25 13:16:55 +02:00
PhilippC
54f58067d3 New translations strings.xml (Azerbaijani) 2021-06-25 12:14:28 +02:00
PhilippC
edb23bef1e New translations strings.xml (Azerbaijani) 2021-06-25 12:14:27 +02:00
PhilippC
cddd0705d8 New translations strings.xml (Portuguese) 2021-06-25 12:14:26 +02:00
PhilippC
fc4447748b New translations strings.xml (Belarusian) 2021-06-25 12:14:25 +02:00
PhilippC
433310030f New translations strings.xml (Azerbaijani) 2021-06-25 11:19:05 +02:00
PhilippC
388fb2ba34 New translations strings.xml (Azerbaijani) 2021-06-25 11:19:04 +02:00
PhilippC
d2a19967bc New translations strings.xml (Azerbaijani) 2021-06-25 11:19:03 +02:00
PhilippC
b2538c0133 New translations strings.xml (Portuguese) 2021-06-25 11:19:01 +02:00
PhilippC
dde2fdb103 New translations strings.xml (Azerbaijani) 2021-06-25 10:22:31 +02:00
PhilippC
7a83064745 New translations strings.xml (Swedish) 2021-06-25 08:33:18 +02:00
PhilippC
970ebfede5 New translations strings.xml (Belarusian) 2021-06-25 08:33:17 +02:00
PhilippC
4ecc56fa79 New translations strings.xml (Azerbaijani) 2021-06-25 07:30:19 +02:00
PhilippC
1e25bc6c7c New translations strings.xml (Azerbaijani) 2021-06-25 07:30:18 +02:00
PhilippC
8eca4a1d19 New translations strings.xml (Azerbaijani) 2021-06-25 07:30:17 +02:00
PhilippC
c6f7a0ba1a New translations strings.xml (Belarusian) 2021-06-25 07:30:16 +02:00
PhilippC
fe07cf6060 New translations strings.xml (Belarusian) 2021-06-25 06:31:31 +02:00
PhilippC
022c26c89d New translations strings.xml (Portuguese) 2021-06-24 22:12:06 +02:00
PhilippC
a13bcaf223 New translations strings.xml (Portuguese) 2021-06-24 21:13:33 +02:00
PhilippC
8b469ed263 New translations strings.xml (Portuguese) 2021-06-24 21:13:32 +02:00
PhilippC
ff631935fc New translations strings.xml (Belarusian) 2021-06-24 08:39:37 +02:00
PhilippC
6fd363e071 New translations strings.xml (Belarusian) 2021-06-24 07:34:00 +02:00
PhilippC
57556764e9 New translations strings.xml (Belarusian) 2021-06-24 06:33:51 +02:00
PhilippC
fbef5de705 New translations strings.xml (Belarusian) 2021-06-23 17:06:40 +02:00
PhilippC
bff035f8a5 New translations strings.xml (Belarusian) 2021-06-23 16:05:18 +02:00
PhilippC
272a124d3a New translations strings.xml (Belarusian) 2021-06-23 07:49:26 +02:00
PhilippC
ae5fd9765d New translations strings.xml (Portuguese) 2021-06-22 19:09:30 +02:00
PhilippC
2900b293c3 New translations strings.xml (Portuguese) 2021-06-22 18:02:48 +02:00
PhilippC
b8fbca0d49 New translations strings.xml (Bulgarian) 2021-06-19 16:14:03 +02:00
PhilippC
370f5a5b4e New translations strings.xml (Bulgarian) 2021-06-19 09:42:54 +02:00
PhilippC
9558786b76 New translations strings.xml (Bulgarian) 2021-06-19 09:42:53 +02:00
PhilippC
c0260974bd New translations strings.xml (Bulgarian) 2021-06-19 08:31:57 +02:00
PhilippC
f864af6003 New translations strings.xml (Bulgarian) 2021-06-19 08:31:56 +02:00
PhilippC
30fc5e4646 New translations strings.xml (Bulgarian) 2021-06-19 07:33:12 +02:00
PhilippC
3dee4935c8 New translations strings.xml (Belarusian) 2021-06-18 15:56:37 +02:00
PhilippC
0e088a48b8 New translations strings.xml (Belarusian) 2021-06-18 14:53:27 +02:00
PhilippC
0928ab0224 New translations strings.xml (Belarusian) 2021-06-18 09:53:48 +02:00
PhilippC
ec07a61cab New translations strings.xml (Belarusian) 2021-06-18 08:55:55 +02:00
PhilippC
14d7eaf361 New translations strings.xml (Belarusian) 2021-06-18 07:48:54 +02:00
PhilippC
0f2892bd28 New translations strings.xml (Belarusian) 2021-06-17 16:56:44 +02:00
PhilippC
51ae5a2a0c New translations strings.xml (Belarusian) 2021-06-17 15:55:16 +02:00
PhilippC
5bb5fc9496 New translations strings.xml (Belarusian) 2021-06-17 09:30:17 +02:00
PhilippC
9e550f85cc New translations strings.xml (Belarusian) 2021-06-17 08:16:41 +02:00
PhilippC
4ef46e302d New translations strings.xml (Belarusian) 2021-06-17 07:10:51 +02:00
PhilippC
079806c857 New translations strings.xml (Belarusian) 2021-06-16 15:57:43 +02:00
PhilippC
deed92af0d New translations strings.xml (Belarusian) 2021-06-16 14:53:47 +02:00
PhilippC
48367c5926 New translations strings.xml (Belarusian) 2021-06-16 13:37:40 +02:00
PhilippC
0f3cdf77d4 New translations strings.xml (Belarusian) 2021-06-16 12:38:04 +02:00
PhilippC
4b06833c3f New translations strings.xml (Belarusian) 2021-06-16 10:10:39 +02:00
PhilippC
8a6bfe3288 New translations strings.xml (Belarusian) 2021-06-16 08:57:17 +02:00
PhilippC
2d106f7b87 New translations strings.xml (Belarusian) 2021-06-16 08:57:16 +02:00
PhilippC
130ef249d7 New translations strings.xml (Belarusian) 2021-06-16 08:57:15 +02:00
PhilippC
5b55cf0edc New translations strings.xml (Belarusian) 2021-06-16 07:45:40 +02:00
PhilippC
1d824049cb New translations strings.xml (Belarusian) 2021-06-16 06:37:46 +02:00
PhilippC
2eef3cfa00 New translations strings.xml (Belarusian) 2021-06-14 15:50:17 +02:00
PhilippC
f82cfeed3d New translations strings.xml (Belarusian) 2021-06-14 15:50:14 +02:00
PhilippC
3aba034155 New translations strings.xml (Belarusian) 2021-06-14 14:48:40 +02:00
PhilippC
114a0832cb New translations strings.xml (Belarusian) 2021-06-14 14:48:39 +02:00
PhilippC
66f627fd1a New translations strings.xml (Belarusian) 2021-06-14 13:48:35 +02:00
PhilippC
1ce915cd9e New translations strings.xml (Belarusian) 2021-06-14 12:39:16 +02:00
Philipp Crocoll
712f476da8 call SetInvalidatedByBiometricEnrollment for security reasons, but show a warning to users (again) that they should not forget their master key 2021-06-14 11:43:33 +02:00
PhilippC
2d0e5d208b New translations strings.xml (Belarusian) 2021-06-14 09:55:24 +02:00
PhilippC
053cef944a New translations strings.xml (Belarusian) 2021-06-11 08:40:22 +02:00
PhilippC
ff2ba3f4ed New translations strings.xml (Belarusian) 2021-06-11 07:35:26 +02:00
PhilippC
ec7d908941 New translations strings.xml (Belarusian) 2021-06-11 06:36:43 +02:00
PhilippC
1afbd4c632 New translations strings.xml (Belarusian) 2021-06-10 07:52:15 +02:00
PhilippC
a5af23b2f7 New translations strings.xml (Belarusian) 2021-06-10 07:52:15 +02:00
PhilippC
78ac8f45e9 New translations strings.xml (Belarusian) 2021-06-10 06:55:42 +02:00
PhilippC
c9ade3ad3f New translations strings.xml (Belarusian) 2021-06-09 11:12:10 +02:00
PhilippC
c588d3d581 New translations strings.xml (Belarusian) 2021-06-09 10:04:57 +02:00
PhilippC
0140b4f74e New translations strings.xml (Belarusian) 2021-06-09 09:09:44 +02:00
PhilippC
eb42d33630 New translations strings.xml (Belarusian) 2021-06-09 09:09:43 +02:00
PhilippC
f6b0eab8b1 New translations strings.xml (Belarusian) 2021-06-09 08:04:33 +02:00
PhilippC
b316c21623 New translations strings.xml (Belarusian) 2021-06-09 08:04:32 +02:00
PhilippC
bd661fddb1 New translations strings.xml (Belarusian) 2021-06-09 07:01:16 +02:00
PhilippC
fd5ca57f36 New translations strings.xml (Portuguese, Brazilian) 2021-06-06 13:32:27 +02:00
PhilippC
d38d446a70 New translations strings.xml (Ukrainian) 2021-06-06 00:02:01 +02:00
PhilippC
4f97e6e8b9 New translations strings.xml (Ukrainian) 2021-06-05 23:02:21 +02:00
PhilippC
e79450a54d New translations strings.xml (Belarusian) 2021-06-04 13:05:49 +02:00
PhilippC
44fd071c9d New translations strings.xml (Belarusian) 2021-06-04 13:05:48 +02:00
PhilippC
8487b935c4 New translations strings.xml (Belarusian) 2021-06-04 12:02:23 +02:00
PhilippC
f497194281 New translations strings.xml (Belarusian) 2021-06-04 09:00:43 +02:00
PhilippC
614e014cab New translations strings.xml (Belarusian) 2021-06-04 08:05:06 +02:00
PhilippC
5bf976747c New translations strings.xml (Belarusian) 2021-06-04 07:08:18 +02:00
PhilippC
5c2a7f27e6 New translations strings.xml (Belarusian) 2021-06-01 09:10:28 +02:00
PhilippC
4c059b625c New translations strings.xml (Belarusian) 2021-06-01 08:05:22 +02:00
PhilippC
1860a92fa0 New translations strings.xml (Belarusian) 2021-06-01 08:05:21 +02:00
PhilippC
f4799acf31 New translations strings.xml (Belarusian) 2021-06-01 06:58:46 +02:00
PhilippC
a704e523d9 New translations strings.xml (Belarusian) 2021-05-31 19:42:56 +02:00
PhilippC
725cdf3ec3 New translations strings.xml (Belarusian) 2021-05-31 18:34:07 +02:00
PhilippC
f3a3956d79 New translations strings.xml (Belarusian) 2021-05-31 10:59:49 +02:00
PhilippC
167cd39c8f New translations strings.xml (Belarusian) 2021-05-31 10:00:19 +02:00
PhilippC
681492f5f8 New translations strings.xml (Belarusian) 2021-05-31 10:00:18 +02:00
PhilippC
80766034a6 New translations strings.xml (Belarusian) 2021-05-31 09:00:58 +02:00
PhilippC
4e7b96462c New translations strings.xml (Belarusian) 2021-05-31 07:59:43 +02:00
PhilippC
761cae4a2b New translations strings.xml (Slovak) 2021-05-28 15:02:13 +02:00
PhilippC
d0d526d36d New translations strings.xml (Slovak) 2021-05-28 15:02:12 +02:00
PhilippC
f29cfd55fa New translations strings.xml (Slovak) 2021-05-28 14:05:32 +02:00
PhilippC
7a4116c016 New translations strings.xml (Belarusian) 2021-05-28 10:08:24 +02:00
PhilippC
5639517128 New translations strings.xml (Belarusian) 2021-05-28 09:10:25 +02:00
PhilippC
17c7b9c526 New translations strings.xml (Belarusian) 2021-05-28 09:10:23 +02:00
PhilippC
e40c5aa821 New translations strings.xml (Belarusian) 2021-05-28 09:10:23 +02:00
PhilippC
1915a8b1db New translations strings.xml (Belarusian) 2021-05-28 08:11:46 +02:00
PhilippC
c105312b99 New translations strings.xml (Belarusian) 2021-05-28 08:11:45 +02:00
PhilippC
2b5d4365c2 New translations strings.xml (Belarusian) 2021-05-28 06:54:24 +02:00
PhilippC
f145c68cfe New translations strings.xml (Belarusian) 2021-05-28 06:54:23 +02:00
PhilippC
2fff53f468 New translations strings.xml (Belarusian) 2021-05-27 17:07:32 +02:00
PhilippC
6a1e75bf51 New translations strings.xml (Belarusian) 2021-05-27 17:07:31 +02:00
PhilippC
4d903239ca New translations strings.xml (Belarusian) 2021-05-27 17:07:30 +02:00
PhilippC
570133d365 New translations strings.xml (Belarusian) 2021-05-27 16:09:15 +02:00
PhilippC
9cc49389e4 New translations strings.xml (Belarusian) 2021-05-27 16:09:14 +02:00
PhilippC
cfcc472c4c New translations strings.xml (Belarusian) 2021-05-27 09:36:50 +02:00
PhilippC
79ff2c1adf New translations strings.xml (Belarusian) 2021-05-27 09:36:49 +02:00
PhilippC
01e5aad370 New translations strings.xml (Russian) 2021-05-27 09:36:48 +02:00
PhilippC
50ba6c2750 New translations strings.xml (Czech) 2021-05-27 01:59:51 +02:00
PhilippC
49a4c8e771 New translations strings.xml (Czech) 2021-05-27 00:58:59 +02:00
PhilippC
97de25e2ed New translations strings.xml (Czech) 2021-05-27 00:00:45 +02:00
PhilippC
7a6d9392d3 New translations strings.xml (Russian) 2021-05-26 16:02:19 +02:00
PhilippC
4738db9b7d New translations strings.xml (Belarusian) 2021-05-26 15:06:28 +02:00
PhilippC
172e75e4e8 New translations strings.xml (Russian) 2021-05-26 15:06:27 +02:00
PhilippC
c4795547c8 New translations strings.xml (Belarusian) 2021-05-26 14:08:05 +02:00
PhilippC
fefc6ca04b New translations strings.xml (Danish) 2021-05-26 00:15:45 +02:00
PhilippC
b00945a815 New translations strings.xml (Danish) 2021-05-25 23:14:40 +02:00
PhilippC
3ad7112011 New translations strings.xml (Greek) 2021-05-25 10:58:25 +02:00
PhilippC
9b49df08e3 New translations strings.xml (Greek) 2021-05-25 09:41:14 +02:00
PhilippC
06ba923c28 New translations strings.xml (Danish) 2021-05-24 14:44:22 +02:00
PhilippC
67c5e58c2c New translations strings.xml (Belarusian) 2021-05-24 13:33:32 +02:00
PhilippC
e8c5e0d911 New translations strings.xml (Belarusian) 2021-05-24 13:33:31 +02:00
PhilippC
668836e646 New translations strings.xml (Belarusian) 2021-05-24 13:33:30 +02:00
Philipp Crocoll
e1cd8123cf improve pCloud warning to close #796 2021-05-24 10:37:56 +02:00
PhilippC
6cfeb715e5 New translations strings.xml (Spanish) 2021-05-23 23:03:12 +02:00
PhilippC
19419dc483 New translations strings.xml (Danish) 2021-05-23 18:59:35 +02:00
PhilippC
1aac650a3d New translations strings.xml (Danish) 2021-05-23 18:59:34 +02:00
PhilippC
7030c9e29b New translations strings.xml (Danish) 2021-05-23 18:01:51 +02:00
PhilippC
730fbe211c New translations strings.xml (Danish) 2021-05-23 10:24:09 +02:00
PhilippC
0993ea5e46 New translations strings.xml (Polish) 2021-05-21 17:15:30 +02:00
PhilippC
0cc4fc76fd New translations strings.xml (Polish) 2021-05-21 16:17:01 +02:00
PhilippC
3a6f536cd9 New translations strings.xml (Chinese Simplified) 2021-05-21 05:40:06 +02:00
PhilippC
4809037fdc New translations strings.xml (Portuguese, Brazilian) 2021-05-19 22:54:47 +02:00
PhilippC
3d9c084e59 New translations strings.xml (Portuguese, Brazilian) 2021-05-19 22:54:46 +02:00
PhilippC
75f64c31a9 New translations strings.xml (Portuguese, Brazilian) 2021-05-19 22:54:45 +02:00
PhilippC
4d64c06f79 New translations strings.xml (Portuguese, Brazilian) 2021-05-19 21:58:10 +02:00
PhilippC
f6a4f46b12 New translations strings.xml (French) 2021-05-19 16:45:14 +02:00
PhilippC
df68fd70f7 New translations strings.xml (French) 2021-05-19 15:48:20 +02:00
PhilippC
2631982e62 New translations strings.xml (Polish) 2021-05-19 14:53:11 +02:00
PhilippC
bcbc89f3a3 New translations strings.xml (Slovenian) 2021-05-18 21:53:27 +02:00
PhilippC
d5f5d9a6b1 Merge pull request #1676 from PhilippC/l10n_master2
New Crowdin updates
2021-05-18 20:55:44 +02:00
PhilippC
01028a956a New translations strings.xml (German) 2021-05-18 20:53:38 +02:00
PhilippC
eb55d15a2f New translations strings.xml (Chinese Simplified) 2021-05-18 20:53:36 +02:00
PhilippC
8219ea74b2 New translations strings.xml (Slovenian) 2021-05-18 20:53:18 +02:00
PhilippC
18b1cb6805 New translations strings.xml (Chinese Simplified) 2021-05-18 20:45:26 +02:00
PhilippC
b0f9dbb062 New translations strings.xml (German) 2021-05-18 20:45:14 +02:00
PhilippC
94118d31f1 New translations strings.xml (Slovenian) 2021-05-18 20:44:49 +02:00
Philipp Crocoll
44c52ee47c Merge branch 'master' of c:/ph/keepass2android 2021-05-18 20:43:49 +02:00
Philipp Crocoll
b71b9c523c add password changes to changelog 2021-05-18 20:43:39 +02:00
PhilippC
06b19dc05f New translations strings.xml (Basque) 2021-05-18 20:32:35 +02:00
PhilippC
6a05c0348b New translations strings.xml (Finnish) 2021-05-18 20:32:33 +02:00
PhilippC
c04184d715 New translations strings.xml (Hebrew) 2021-05-18 20:32:31 +02:00
PhilippC
c3bfdfcc29 New translations strings.xml (Hungarian) 2021-05-18 20:32:29 +02:00
PhilippC
b6a83339e8 New translations strings.xml (Italian) 2021-05-18 20:32:27 +02:00
PhilippC
5a50b1b91f New translations strings.xml (Japanese) 2021-05-18 20:32:26 +02:00
PhilippC
31f3b70fdb New translations strings.xml (Korean) 2021-05-18 20:32:24 +02:00
PhilippC
c4673f87d7 New translations strings.xml (Dutch) 2021-05-18 20:32:22 +02:00
PhilippC
3b5822fd8f New translations strings.xml (Polish) 2021-05-18 20:32:20 +02:00
PhilippC
f0d40713ae New translations strings.xml (Greek) 2021-05-18 20:32:18 +02:00
PhilippC
a1a249421c New translations strings.xml (Romanian) 2021-05-18 20:32:16 +02:00
PhilippC
fe927f8111 New translations strings.xml (French) 2021-05-18 20:32:14 +02:00
PhilippC
b639a0bc9f New translations strings.xml (Arabic) 2021-05-18 20:32:11 +02:00
PhilippC
1ce9afb8be New translations strings.xml (Catalan) 2021-05-18 20:32:08 +02:00
PhilippC
a277511be9 New translations strings.xml (Czech) 2021-05-18 20:32:06 +02:00
PhilippC
25e9b51d04 New translations strings.xml (Danish) 2021-05-18 20:32:04 +02:00
PhilippC
b20076e7b8 New translations strings.xml (German) 2021-05-18 20:32:02 +02:00
PhilippC
0c4d096e36 New translations strings.xml (Bulgarian) 2021-05-18 20:32:01 +02:00
PhilippC
6b3ab59f13 New translations strings.xml (Chinese Simplified) 2021-05-18 20:32:00 +02:00
PhilippC
98e937a61b New translations strings.xml (Portuguese) 2021-05-18 20:31:59 +02:00
PhilippC
b10893dfc6 New translations strings.xml (Portuguese, Brazilian) 2021-05-18 20:31:58 +02:00
PhilippC
ec6d5bb56e New translations strings.xml (Indonesian) 2021-05-18 20:31:56 +02:00
PhilippC
7dbaf6feaa New translations strings.xml (Persian) 2021-05-18 20:31:54 +02:00
PhilippC
6cffa74196 New translations strings.xml (Croatian) 2021-05-18 20:31:52 +02:00
PhilippC
7906c477c4 New translations strings.xml (Norwegian Nynorsk) 2021-05-18 20:31:50 +02:00
PhilippC
5cc139e7f0 New translations strings.xml (Azerbaijani) 2021-05-18 20:31:48 +02:00
PhilippC
a3b2ccfbb9 New translations strings.xml (Malayalam) 2021-05-18 20:31:47 +02:00
PhilippC
f0cde44dc1 New translations strings.xml (Norwegian Bokmal) 2021-05-18 20:31:43 +02:00
PhilippC
c7cf38de48 New translations strings.xml (Russian) 2021-05-18 20:31:40 +02:00
PhilippC
dad72764e8 New translations strings.xml (Slovak) 2021-05-18 20:31:38 +02:00
PhilippC
10ef165112 New translations strings.xml (Slovenian) 2021-05-18 20:31:36 +02:00
PhilippC
47fcc8007d New translations strings.xml (Serbian (Cyrillic)) 2021-05-18 20:31:34 +02:00
PhilippC
bb9a53e6cc New translations strings.xml (Galician) 2021-05-18 20:31:32 +02:00
PhilippC
46544570ad New translations strings.xml (Swedish) 2021-05-18 20:31:31 +02:00
PhilippC
db71a06371 New translations strings.xml (Turkish) 2021-05-18 20:31:30 +02:00
PhilippC
f5b88df4c1 New translations strings.xml (Ukrainian) 2021-05-18 20:31:27 +02:00
PhilippC
a4dfc59126 New translations strings.xml (Chinese Traditional) 2021-05-18 20:31:26 +02:00
PhilippC
c980d37aa2 New translations strings.xml (Vietnamese) 2021-05-18 20:31:24 +02:00
PhilippC
411740cf01 New translations strings.xml (Spanish) 2021-05-18 20:31:22 +02:00
Philipp Crocoll
9d4ca67722 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-05-18 20:30:39 +02:00
PhilippC
ad5b7e5b46 New translations strings.xml (Spanish) 2021-05-18 20:27:26 +02:00
PhilippC
785c0bba8d New translations strings.xml (German) 2021-05-18 20:27:14 +02:00
Philipp Crocoll
f6395908ee add back custom ColorTranslator, fixes PlatformnotSupportedException, closes https://github.com/PhilippC/keepass2android/issues/1679 2021-05-18 20:26:08 +02:00
Philipp Crocoll
4636f5800c add passphrase, password generator profiles, password strength estimation, closes https://github.com/PhilippC/keepass2android/issues/81 2021-05-18 08:42:47 +02:00
Philipp Crocoll
212418f11a include some more options in the password generator (extended special group, at least one from every group, exclude look-alike), closes some items of https://github.com/PhilippC/keepass2android/issues/81 2021-05-17 11:56:16 +02:00
PhilippC
7f443e8505 New translations strings.xml (Slovenian) 2021-05-16 21:08:23 +02:00
PhilippC
488d11b693 New translations strings.xml (Chinese Simplified) 2021-05-16 07:10:30 +02:00
Philipp Crocoll
f0255703c1 1.09a-r0 2021-05-15 16:24:32 +02:00
PhilippC
f7e33728f2 Merge pull request #1579 from PhilippC/l10n_master2
New Crowdin updates
2021-05-15 15:46:47 +02:00
PhilippC
86fcfb49eb New translations strings.xml (German) 2021-05-15 15:42:27 +02:00
Philipp Crocoll
17ae8f7e99 Merge branch 'master' of c:/ph/keepass2android 2021-05-15 15:26:19 +02:00
Philipp Crocoll
638a2ec462 extend changelog for 1.09a 2021-05-15 15:26:06 +02:00
PhilippC
789e474c04 New translations strings.xml (Chinese Simplified) 2021-05-15 06:41:58 +02:00
PhilippC
65ade96fd8 New translations strings.xml (Slovenian) 2021-05-14 18:12:39 +02:00
PhilippC
05e2502831 New translations strings.xml (Basque) 2021-05-14 08:37:34 +02:00
PhilippC
f1a4536a42 New translations strings.xml (Finnish) 2021-05-14 08:37:32 +02:00
PhilippC
0bb372c2e9 New translations strings.xml (Hebrew) 2021-05-14 08:37:29 +02:00
PhilippC
98c38e6f8f New translations strings.xml (Hungarian) 2021-05-14 08:37:26 +02:00
PhilippC
7739a1bb85 New translations strings.xml (Italian) 2021-05-14 08:37:22 +02:00
PhilippC
5dbc4df7c1 New translations strings.xml (Japanese) 2021-05-14 08:37:20 +02:00
PhilippC
3bee5fc4c9 New translations strings.xml (Korean) 2021-05-14 08:37:17 +02:00
PhilippC
62fe696921 New translations strings.xml (Dutch) 2021-05-14 08:37:14 +02:00
PhilippC
603f30cd9d New translations strings.xml (Polish) 2021-05-14 08:37:11 +02:00
PhilippC
764d208695 New translations strings.xml (Greek) 2021-05-14 08:37:07 +02:00
PhilippC
147c83f62b New translations strings.xml (Romanian) 2021-05-14 08:37:03 +02:00
PhilippC
da89fd674f New translations strings.xml (French) 2021-05-14 08:37:00 +02:00
PhilippC
6bc4677f3a New translations strings.xml (Arabic) 2021-05-14 08:36:55 +02:00
PhilippC
0676cd6e7c New translations strings.xml (Catalan) 2021-05-14 08:36:50 +02:00
PhilippC
7dceb7e363 New translations strings.xml (Czech) 2021-05-14 08:36:47 +02:00
PhilippC
6a2be81241 New translations strings.xml (Danish) 2021-05-14 08:36:44 +02:00
PhilippC
b26c9bfa35 New translations strings.xml (German) 2021-05-14 08:36:41 +02:00
PhilippC
19ecbfae85 New translations strings.xml (Bulgarian) 2021-05-14 08:36:40 +02:00
PhilippC
221277bf24 New translations strings.xml (Chinese Simplified) 2021-05-14 08:36:39 +02:00
PhilippC
41d864e69f New translations strings.xml (Portuguese) 2021-05-14 08:36:37 +02:00
PhilippC
46341b5d37 New translations strings.xml (Portuguese, Brazilian) 2021-05-14 08:36:35 +02:00
PhilippC
b3569f1459 New translations strings.xml (Indonesian) 2021-05-14 08:36:32 +02:00
PhilippC
963df05ed3 New translations strings.xml (Persian) 2021-05-14 08:36:29 +02:00
PhilippC
f693d93e03 New translations strings.xml (Croatian) 2021-05-14 08:36:27 +02:00
PhilippC
b3a78194dd New translations strings.xml (Norwegian Bokmal) 2021-05-14 08:36:12 +02:00
PhilippC
2d27c95388 New translations strings.xml (Russian) 2021-05-14 08:36:07 +02:00
PhilippC
3e760df174 New translations strings.xml (Slovak) 2021-05-14 08:36:04 +02:00
PhilippC
9dca986efe New translations strings.xml (Slovenian) 2021-05-14 08:36:01 +02:00
PhilippC
6967bd2b84 New translations strings.xml (Serbian (Cyrillic)) 2021-05-14 08:35:58 +02:00
PhilippC
b32e599223 New translations strings.xml (Galician) 2021-05-14 08:35:55 +02:00
PhilippC
727863e4ee New translations strings.xml (Swedish) 2021-05-14 08:35:54 +02:00
PhilippC
2c157bc76f New translations strings.xml (Turkish) 2021-05-14 08:35:51 +02:00
PhilippC
f78f2b9361 New translations strings.xml (Ukrainian) 2021-05-14 08:35:49 +02:00
PhilippC
3859ce78a5 New translations strings.xml (Chinese Traditional) 2021-05-14 08:35:46 +02:00
PhilippC
1e1c79e8f8 New translations strings.xml (Vietnamese) 2021-05-14 08:35:43 +02:00
PhilippC
beadd426c4 New translations strings.xml (Spanish) 2021-05-14 08:35:40 +02:00
Philipp Crocoll
edc650a66e Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-05-14 08:01:47 +02:00
Philipp Crocoll
562a80132a merge changes from KeePass 2.48 with support for KDBX 4.1 and slightly improved merging behavior with respect to custom icons, closes https://github.com/PhilippC/keepass2android/issues/1670 2021-05-14 08:01:34 +02:00
Philipp Crocoll
f5ee9e3955 fix bad strings 2021-05-13 16:21:12 +02:00
Philipp Crocoll
a0a8a2810c add github sponsor as supporter 2021-05-13 16:21:02 +02:00
Philipp Crocoll
1d6dadea58 implement editor for TOTP settings in EntryEditActivity, closes https://github.com/PhilippC/keepass2android/issues/770 2021-05-13 16:20:17 +02:00
PhilippC
5144fe6fc9 New translations strings.xml (Chinese Simplified) 2021-05-11 02:46:09 +02:00
PhilippC
7e0913fe8b New translations strings.xml (Chinese Simplified) 2021-05-10 20:13:58 +02:00
PhilippC
15f1aa68da New translations strings.xml (Chinese Simplified) 2021-05-10 19:08:32 +02:00
PhilippC
1eb11928c2 New translations strings.xml (Chinese Simplified) 2021-05-10 19:08:31 +02:00
PhilippC
982527b4bf New translations strings.xml (Chinese Simplified) 2021-05-10 19:08:30 +02:00
PhilippC
2a2622ddd8 New translations strings.xml (Chinese Simplified) 2021-05-10 18:09:00 +02:00
PhilippC
39939790c2 New translations strings.xml (Chinese Simplified) 2021-05-10 17:12:52 +02:00
PhilippC
ddabf82ea7 New translations strings.xml (Chinese Simplified) 2021-05-10 16:07:28 +02:00
PhilippC
7dd6745cd9 New translations strings.xml (Spanish) 2021-05-08 21:47:06 +02:00
PhilippC
3ac02da522 New translations strings.xml (Spanish) 2021-05-08 20:47:32 +02:00
PhilippC
d5982ae1e3 New translations strings.xml (Arabic) 2021-04-25 13:38:03 +02:00
PhilippC
fe2ebfcfd9 New translations strings.xml (Arabic) 2021-04-25 12:38:11 +02:00
PhilippC
977fe33d6a New translations strings.xml (Arabic) 2021-04-25 12:38:09 +02:00
PhilippC
951e11f6b0 New translations strings.xml (Swedish) 2021-04-23 00:12:19 +02:00
PhilippC
2fa1cd175d New translations strings.xml (Swedish) 2021-04-22 23:06:55 +02:00
PhilippC
5c9ab11aff New translations strings.xml (Vietnamese) 2021-04-13 11:39:41 +02:00
PhilippC
1f9327db80 Merge pull request #1612 from TiloHeidasch/master
Fixes Typo
2021-04-10 16:27:28 +02:00
PhilippC
e16429f77b New translations strings.xml (Portuguese) 2021-04-05 22:25:20 +02:00
PhilippC
3e58ef5dba New translations strings.xml (Italian) 2021-03-29 12:05:05 +02:00
Philipp Crocoll
84ac5fef4f remove option "Binary directory" (no longer used) 2021-03-29 10:11:02 +02:00
PhilippC
303d98bad7 New translations strings.xml (Norwegian Bokmal) 2021-03-28 19:53:28 +02:00
PhilippC
e39a126229 New translations strings.xml (Italian) 2021-03-27 12:23:29 +01:00
PhilippC
7257657432 New translations strings.xml (Portuguese, Brazilian) 2021-03-26 13:33:06 +01:00
PhilippC
99999dde2c New translations strings.xml (Portuguese, Brazilian) 2021-03-26 13:33:04 +01:00
PhilippC
60fc41d524 New translations strings.xml (Portuguese, Brazilian) 2021-03-26 12:12:23 +01:00
PhilippC
559a6f87b2 New translations strings.xml (Chinese Simplified) 2021-03-23 16:48:57 +01:00
PhilippC
02fbe3ce93 New translations strings.xml (German) 2021-03-17 09:54:00 +01:00
PhilippC
aa221c221e New translations strings.xml (Russian) 2021-03-17 00:00:37 +01:00
PhilippC
796efc490e New translations strings.xml (German) 2021-03-17 00:00:36 +01:00
PhilippC
b3c6b9b8e3 New translations strings.xml (German) 2021-03-16 22:47:57 +01:00
PhilippC
bc44d8a0a6 New translations strings.xml (Greek) 2021-03-16 16:22:39 +01:00
PhilippC
a43c291794 New translations strings.xml (Greek) 2021-03-16 16:22:38 +01:00
TiloHeidasch
2a4c0a8427 Fixes Typo 2021-03-16 11:45:39 +01:00
PhilippC
bca9156a38 New translations strings.xml (Chinese Simplified) 2021-03-14 13:26:34 +01:00
PhilippC
cd97ac8106 New translations strings.xml (Persian) 2021-03-12 11:24:36 +01:00
PhilippC
e464413b05 New translations strings.xml (Portuguese) 2021-03-10 20:42:31 +01:00
PhilippC
39c50eda0c New translations strings.xml (Portuguese) 2021-03-10 20:42:30 +01:00
PhilippC
4901485dc3 New translations strings.xml (Portuguese) 2021-03-10 20:42:28 +01:00
PhilippC
0762c227c1 New translations strings.xml (Portuguese) 2021-03-10 19:36:01 +01:00
Philipp Crocoll
09537689cf additional logging for https://github.com/PhilippC/keepass2android/issues/1257 2021-03-08 09:50:09 +01:00
Philipp Crocoll
92ffe596ce additional logging for keyboard issue 2021-03-08 09:49:48 +01:00
Philipp Crocoll
7037fbfe94 fix URL has two "Open URL" context menu entries 2021-03-08 09:49:21 +01:00
Philipp Crocoll
9eab59dcbe fix broken steam support 2021-03-08 09:48:56 +01:00
PhilippC
d705371f87 New translations strings.xml (Chinese Traditional) 2021-03-02 13:35:20 +01:00
PhilippC
09206a387b New translations strings.xml (Hungarian) 2021-02-24 20:05:26 +01:00
PhilippC
65fc67a4b2 New translations strings.xml (Hungarian) 2021-02-24 20:05:24 +01:00
PhilippC
7faa6a8efc New translations strings.xml (Ukrainian) 2021-02-24 18:54:22 +01:00
PhilippC
4f4b7fdfa4 New translations strings.xml (German) 2021-02-23 15:42:08 +01:00
PhilippC
ce09d9c8a9 New translations strings.xml (Polish) 2021-02-23 13:02:58 +01:00
PhilippC
c9744aeb94 New translations strings.xml (Polish) 2021-02-23 13:02:57 +01:00
PhilippC
a65dbfafa3 New translations strings.xml (Polish) 2021-02-23 13:02:56 +01:00
PhilippC
e40c117228 New translations strings.xml (Polish) 2021-02-23 12:01:21 +01:00
PhilippC
46bded5c16 New translations strings.xml (Japanese) 2021-02-23 10:22:04 +01:00
PhilippC
47ab6df725 New translations strings.xml (Russian) 2021-02-22 12:19:59 +01:00
PhilippC
94a366a13e New translations strings.xml (Dutch) 2021-02-21 21:14:00 +01:00
PhilippC
f23de8f380 New translations strings.xml (French) 2021-02-20 22:56:49 +01:00
PhilippC
f1fb2a47f0 New translations strings.xml (Slovenian) 2021-02-20 21:21:46 +01:00
PhilippC
175c64bdc8 New translations strings.xml (Chinese Simplified) 2021-02-20 10:07:48 +01:00
PhilippC
07ab9f21db New translations strings.xml (Malayalam) 2021-02-20 07:57:13 +01:00
PhilippC
56ece19994 New translations strings.xml (Malayalam) 2021-02-20 07:57:11 +01:00
PhilippC
1163e9956a New translations strings.xml (Japanese) 2021-02-20 05:22:09 +01:00
PhilippC
d02b9438eb New translations strings.xml (Portuguese, Brazilian) 2021-02-20 00:40:22 +01:00
Philipp Crocoll
40cbd51e8b Merge branch 'master' of c:/ph/keepass2android 2021-02-19 12:23:38 +01:00
Philipp Crocoll
490de22dc7 changelog and manifest for 1.09a-pre1 2021-02-19 12:23:18 +01:00
Philipp Crocoll
ef71df7d5f Merge remote-tracking branch 'remotes/origin/l10n_master2'
# resolved Conflicts:
#	src/keepass2android/Resources/values-de/strings.xml
2021-02-19 12:19:24 +01:00
PhilippC
0fbf0179f3 New translations strings.xml (Portuguese, Brazilian) 2021-02-19 12:17:44 +01:00
PhilippC
5a45360ab6 New translations strings.xml (Indonesian) 2021-02-19 12:17:42 +01:00
PhilippC
94ac246d1f New translations strings.xml (Persian) 2021-02-19 12:17:41 +01:00
PhilippC
85ef205fab New translations strings.xml (Croatian) 2021-02-19 12:17:40 +01:00
PhilippC
4dd0215ac8 New translations strings.xml (Norwegian Bokmal) 2021-02-19 12:17:34 +01:00
PhilippC
4fa940af47 New translations strings.xml (Galician) 2021-02-19 12:17:30 +01:00
PhilippC
6e76e4908e New translations strings.xml (Vietnamese) 2021-02-19 12:17:18 +01:00
PhilippC
8991538449 New translations strings.xml (Chinese Simplified) 2021-02-19 12:17:17 +01:00
PhilippC
9a51267b8f New translations strings.xml (Ukrainian) 2021-02-19 12:17:16 +01:00
PhilippC
373128f678 New translations strings.xml (Czech) 2021-02-19 12:17:15 +01:00
PhilippC
563faf6cc5 New translations strings.xml (Romanian) 2021-02-19 12:17:14 +01:00
PhilippC
aff243389d New translations strings.xml (French) 2021-02-19 12:17:12 +01:00
PhilippC
a38be4ad80 New translations strings.xml (Spanish) 2021-02-19 12:17:11 +01:00
PhilippC
bd52090236 New translations strings.xml (Arabic) 2021-02-19 12:17:09 +01:00
PhilippC
5ae11a91c1 New translations strings.xml (Catalan) 2021-02-19 12:17:07 +01:00
PhilippC
f1cc61e8ce New translations strings.xml (Danish) 2021-02-19 12:17:06 +01:00
PhilippC
5af68c5d9b New translations strings.xml (German) 2021-02-19 12:17:04 +01:00
PhilippC
dbe804f223 New translations strings.xml (Greek) 2021-02-19 12:17:03 +01:00
PhilippC
2bbbc4fe94 New translations strings.xml (Basque) 2021-02-19 12:17:02 +01:00
PhilippC
9cd66fc30c New translations strings.xml (Chinese Traditional) 2021-02-19 12:17:00 +01:00
PhilippC
65f6731b61 New translations strings.xml (Finnish) 2021-02-19 12:16:59 +01:00
PhilippC
25defeaf3a New translations strings.xml (Hungarian) 2021-02-19 12:16:58 +01:00
PhilippC
e8d8f13888 New translations strings.xml (Italian) 2021-02-19 12:16:57 +01:00
PhilippC
4a9a767fc6 New translations strings.xml (Japanese) 2021-02-19 12:16:55 +01:00
PhilippC
d8b22b4914 New translations strings.xml (Korean) 2021-02-19 12:16:54 +01:00
PhilippC
ef22f19dec New translations strings.xml (Polish) 2021-02-19 12:16:52 +01:00
PhilippC
4bcf651e5e New translations strings.xml (Portuguese) 2021-02-19 12:16:51 +01:00
PhilippC
19aea7808d New translations strings.xml (Russian) 2021-02-19 12:16:49 +01:00
PhilippC
afa4c9c0f2 New translations strings.xml (Slovak) 2021-02-19 12:16:48 +01:00
PhilippC
d2d853e916 New translations strings.xml (Slovenian) 2021-02-19 12:16:47 +01:00
PhilippC
3ead147be4 New translations strings.xml (Swedish) 2021-02-19 12:16:45 +01:00
PhilippC
c863ff1fd1 New translations strings.xml (Turkish) 2021-02-19 12:16:43 +01:00
PhilippC
6028b10bf1 New translations strings.xml (Dutch) 2021-02-19 12:15:56 +01:00
PhilippC
e74bb8d3b5 New translations strings.xml (Japanese) 2021-02-19 12:06:48 +01:00
Philipp Crocoll
7bbe3fb9b5 allow to choose Light/Dark Design as indicated by the system settings (Android 10+), closes https://github.com/PhilippC/keepass2android/issues/966 2021-02-19 12:04:47 +01:00
Philipp Crocoll
e00eb2a5bc commit unsaved change regarding read-only access to content storage 2021-02-19 11:53:45 +01:00
Philipp Crocoll
23ca92039b pCloud: recover from bad stored access tokens 2021-02-19 11:41:23 +01:00
Philipp Crocoll
54f8916ddd no longer checking for FLAG_SUPPORTS_WRITE for ContontStorage - this often returns false even for non-readonly files, closes https://github.com/PhilippC/keepass2android/issues/1212 2021-02-19 11:41:03 +01:00
Philipp Crocoll
a4b3c933ef allow to move an entry from the EntryActivity menu, closes https://github.com/PhilippC/keepass2android/issues/24 2021-02-19 09:11:35 +01:00
Philipp Crocoll
d0633e883e go back to dx+proguard+shared runtime (r8 seems to break GDrive implementation; shared runtime required for debugging) 2021-02-19 08:44:41 +01:00
Philipp Crocoll
8071e0692a make sure AutoExec items with unknown protocols don't crash the app, closes #1452 2021-02-19 08:44:08 +01:00
Philipp Crocoll
47f07c66bf Merge remote-tracking branch 'remotes/origin/l10n_master2' 2021-02-19 07:04:51 +01:00
Philipp Crocoll
5db30a2b98 Merge branch 'master' of c:/ph/keepass2android 2021-02-19 07:03:15 +01:00
Philipp Crocoll
fcd3cddbc7 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-02-19 07:02:49 +01:00
Philipp Crocoll
3c49d30de6 Merge branch 'master' of c:/ph/keepass2android 2021-02-18 17:57:37 +01:00
Philipp Crocoll
377bffd7a4 Merge branch 'Branch_3fb358c-b' 2021-02-18 17:49:30 +01:00
Philipp Crocoll
6af1da01db fix to autofill: continue if dataset has no name 2021-02-18 17:48:25 +01:00
Philipp Crocoll
a6e430e569 biometric unlock: SetInvalidatedByBiometricEnrollment(false) for API level >= 24 2021-02-18 16:23:23 +01:00
Philipp Crocoll
15d3492ae3 fix for OneDrive file storage unnecessarily prompting for login, closes https://github.com/PhilippC/keepass2android/issues/1038 2021-02-18 16:20:03 +01:00
Philipp Crocoll
7a731da44f code formatting 2021-02-18 16:19:05 +01:00
PhilippC
64a53ce887 New translations strings.xml (Czech) 2021-02-17 00:37:42 +01:00
PhilippC
45355ba972 New translations strings.xml (Ukrainian) 2021-02-16 09:07:54 +01:00
PhilippC
3b769e2a9f New translations strings.xml (Ukrainian) 2021-02-16 08:12:32 +01:00
PhilippC
ed70c3fcb1 New translations strings.xml (Ukrainian) 2021-02-15 11:58:07 +01:00
PhilippC
86d56be044 New translations strings.xml (Ukrainian) 2021-02-15 10:44:25 +01:00
PhilippC
448492cbc1 New translations strings.xml (Ukrainian) 2021-02-12 11:47:00 +01:00
PhilippC
5010eb3a7c New translations strings.xml (Ukrainian) 2021-02-12 11:46:58 +01:00
PhilippC
e429f082e4 New translations strings.xml (Ukrainian) 2021-02-12 10:41:11 +01:00
PhilippC
53be95ac35 New translations strings.xml (Dutch) 2021-02-10 00:48:43 +01:00
PhilippC
1a47b603c7 New translations strings.xml (Dutch) 2021-02-09 23:48:32 +01:00
Philipp Crocoll
4b956af0f7 add debugging to diagnose an issue reported by a user which I can't reproduce 2021-02-09 16:17:47 +01:00
Philipp Crocoll
8a9495edce include subdomains when trying to find an entry from Autofill 2021-02-09 14:45:40 +01:00
Philipp Crocoll
b96e8a5b2b add github support name 2021-02-09 14:45:17 +01:00
Philipp Crocoll
a37789fbfe minor changes adapting to VS upgrade 2021-02-09 14:44:50 +01:00
Philipp Crocoll
8d95fb1660 implement upload sessions for OneDrive to resolve file size restriction (closes https://github.com/PhilippC/keepass2android/issues/1036) 2021-02-09 14:44:02 +01:00
PhilippC
db83855fee New translations strings.xml (Russian) 2021-02-03 22:31:17 +01:00
PhilippC
56d896ca22 New translations strings.xml (Russian) 2021-02-03 21:25:30 +01:00
PhilippC
1169e7183e New translations strings.xml (French) 2021-02-02 22:13:42 +01:00
PhilippC
fe0b377561 New translations strings.xml (Hungarian) 2021-02-01 18:16:36 +01:00
PhilippC
5528d6bc8d New translations strings.xml (Hungarian) 2021-02-01 17:11:32 +01:00
PhilippC
7856ea8130 New translations strings.xml (Chinese Traditional) 2021-01-24 10:29:16 +01:00
PhilippC
dbc684306b New translations strings.xml (Chinese Traditional) 2021-01-24 09:26:46 +01:00
Philipp Crocoll
19187352b5 Merge branch 'master' of c:/ph/keepass2android 2021-01-24 07:06:12 +01:00
Philipp Crocoll
cc7ee35500 fix to all entries being recognized as TOTP entries 2021-01-24 07:05:58 +01:00
PhilippC
63ffd56fd9 New translations strings.xml (German) 2021-01-24 06:42:14 +01:00
PhilippC
4ea3da158d New translations strings.xml (Czech) 2021-01-23 03:14:44 +01:00
PhilippC
00daf35918 New translations strings.xml (Czech) 2021-01-23 02:17:08 +01:00
PhilippC
18bfc06091 New translations strings.xml (Czech) 2021-01-23 01:05:44 +01:00
PhilippC
0cf78f5c8e New translations strings.xml (Japanese) 2021-01-21 11:17:01 +01:00
PhilippC
1193b37a53 New translations strings.xml (Chinese Simplified) 2021-01-19 17:45:56 +01:00
Philipp Crocoll
6cb3fb2354 manifest for 1.08d-r4 2021-01-17 04:54:33 +01:00
PhilippC
1109b40af4 New translations strings.xml (Finnish) 2021-01-17 04:06:04 +01:00
PhilippC
479ab9377e New translations strings.xml (Finnish) 2021-01-17 04:06:02 +01:00
PhilippC
5e01b3c0ab New translations strings.xml (Hebrew) 2021-01-17 04:06:00 +01:00
PhilippC
3b876dde1c New translations strings.xml (Hebrew) 2021-01-17 04:05:58 +01:00
PhilippC
36fdc5e737 New translations strings.xml (Greek) 2021-01-17 04:05:52 +01:00
PhilippC
0410687ae1 New translations strings.xml (Italian) 2021-01-17 04:05:49 +01:00
PhilippC
d05be2b599 New translations strings.xml (Japanese) 2021-01-17 04:05:47 +01:00
PhilippC
39b0d4f0c2 New translations strings.xml (Japanese) 2021-01-17 04:05:45 +01:00
PhilippC
661aefaebc New translations strings.xml (Italian) 2021-01-17 04:05:36 +01:00
PhilippC
d25c951c88 New translations strings.xml (Portuguese) 2021-01-17 04:05:34 +01:00
PhilippC
6d5d8142b3 New translations strings.xml (German) 2021-01-17 04:05:30 +01:00
PhilippC
dee8bdec24 New translations strings.xml (Romanian) 2021-01-17 04:05:25 +01:00
PhilippC
e5010b761d New translations strings.xml (French) 2021-01-17 04:05:21 +01:00
PhilippC
8b345d4fac New translations strings.xml (Spanish) 2021-01-17 04:05:17 +01:00
PhilippC
d9679801e2 New translations strings.xml (Arabic) 2021-01-17 04:05:13 +01:00
PhilippC
293864e62b New translations strings.xml (German) 2021-01-17 04:05:12 +01:00
PhilippC
ddd1a25d93 New translations strings.xml (Arabic) 2021-01-17 04:05:09 +01:00
PhilippC
a44e3bae90 New translations strings.xml (Bulgarian) 2021-01-17 04:05:07 +01:00
PhilippC
97c682416c New translations strings.xml (Czech) 2021-01-17 04:05:01 +01:00
PhilippC
490d32e8d6 New translations strings.xml (Czech) 2021-01-17 04:04:59 +01:00
PhilippC
36739e1b11 New translations strings.xml (Danish) 2021-01-17 04:04:56 +01:00
PhilippC
8639cc313a New translations strings.xml (Danish) 2021-01-17 04:04:54 +01:00
PhilippC
20cacc6dd5 New translations strings.xml (Portuguese) 2021-01-17 04:04:50 +01:00
PhilippC
b60b0f2bf6 New translations strings.xml (Russian) 2021-01-17 04:04:48 +01:00
PhilippC
4db37a0f18 New translations strings.xml (Indonesian) 2021-01-17 04:04:43 +01:00
PhilippC
6a28219b7f New translations strings.xml (Norwegian Bokmal) 2021-01-17 04:04:23 +01:00
PhilippC
904885db6c New translations strings.xml (Norwegian Bokmal) 2021-01-17 04:04:21 +01:00
PhilippC
6a7b003246 New translations strings.xml (Galician) 2021-01-17 04:04:18 +01:00
PhilippC
a2aeb67d6d New translations strings.xml (Russian) 2021-01-17 04:04:16 +01:00
PhilippC
412b4f26b7 New translations strings.xml (Slovenian) 2021-01-17 04:04:10 +01:00
PhilippC
b95e380bd8 New translations strings.xml (Slovenian) 2021-01-17 04:04:08 +01:00
PhilippC
bf46b015a5 New translations strings.xml (Galician) 2021-01-17 04:04:02 +01:00
PhilippC
d23f4c8095 New translations strings.xml (Ukrainian) 2021-01-17 04:03:56 +01:00
PhilippC
70c6c59e74 New translations strings.xml (Ukrainian) 2021-01-17 04:03:54 +01:00
PhilippC
e69f662608 New translations strings.xml (Chinese Traditional) 2021-01-17 04:03:50 +01:00
PhilippC
8d934663c5 New translations strings.xml (Vietnamese) 2021-01-17 04:03:47 +01:00
PhilippC
12070b042f New translations strings.xml (Chinese Traditional) 2021-01-17 04:03:43 +01:00
Philipp Crocoll
2e5ee5f836 Merge branch 'l10n_master2' of https://github.com/PhilippC/keepass2android 2021-01-17 03:17:15 +01:00
Philipp Crocoll
177b36d869 fix bug preventing screen protection to work, closes #1543 2021-01-17 03:13:24 +01:00
Philipp Crocoll
cd484c0398 remove no longer valid note regarding pCloud 2021-01-17 03:12:51 +01:00
PhilippC
a6e3c2024f New translations strings.xml (Hungarian) 2021-01-17 03:07:37 +01:00
PhilippC
31e7037d4f New translations strings.xml (Greek) 2021-01-17 03:07:35 +01:00
PhilippC
13c2e297a8 New translations strings.xml (Japanese) 2021-01-17 03:07:30 +01:00
PhilippC
45d08a5762 New translations strings.xml (Dutch) 2021-01-17 03:07:26 +01:00
PhilippC
0568efc94b New translations strings.xml (Polish) 2021-01-17 03:07:22 +01:00
PhilippC
b61edb44bf New translations strings.xml (Italian) 2021-01-17 03:07:19 +01:00
PhilippC
415d01b36d New translations strings.xml (Portuguese) 2021-01-17 03:07:17 +01:00
PhilippC
b2864feb73 New translations strings.xml (German) 2021-01-17 03:07:14 +01:00
PhilippC
9ddafb6525 New translations strings.xml (Korean) 2021-01-17 03:07:12 +01:00
PhilippC
3771e86169 New translations strings.xml (French) 2021-01-17 03:07:07 +01:00
PhilippC
e8533afe78 New translations strings.xml (Spanish) 2021-01-17 03:07:03 +01:00
PhilippC
28361f4105 New translations strings.xml (Catalan) 2021-01-17 03:06:55 +01:00
PhilippC
110ef46f56 New translations strings.xml (Czech) 2021-01-17 03:06:52 +01:00
PhilippC
5cb23cd9b4 New translations strings.xml (Danish) 2021-01-17 03:06:48 +01:00
PhilippC
6d368e57af New translations strings.xml (Russian) 2021-01-17 03:06:42 +01:00
PhilippC
36bbaa7270 New translations strings.xml (Portuguese, Brazilian) 2021-01-17 03:06:41 +01:00
PhilippC
eb4888c361 New translations strings.xml (Slovak) 2021-01-17 03:06:15 +01:00
PhilippC
1773f9672e New translations strings.xml (Slovenian) 2021-01-17 03:06:11 +01:00
PhilippC
59fbaf87e6 New translations strings.xml (Swedish) 2021-01-17 03:06:05 +01:00
PhilippC
305a2aa6dc New translations strings.xml (Turkish) 2021-01-17 03:06:01 +01:00
PhilippC
ed92b3476f New translations strings.xml (Ukrainian) 2021-01-17 03:05:58 +01:00
PhilippC
b32fa9672f New translations strings.xml (Chinese Simplified) 2021-01-17 03:05:54 +01:00
PhilippC
dd07271550 New translations strings.xml (Chinese Traditional) 2021-01-17 03:05:48 +01:00
Philipp Crocoll
430db12d6f re-add Dutch translations to close #1526. They had been removed because of invalid strings causing crashes, these are now fixed. 2021-01-16 12:27:42 +01:00
PhilippC
53b881c451 New translations strings.xml (French) 2021-01-16 12:27:22 +01:00
Philipp Crocoll
a4219bb9c7 Merge branch 'master' of https://github.com/PhilippC/keepass2android 2021-01-16 11:50:43 +01:00
Philipp Crocoll
7609260218 fix incorrect Unregister/RegisterReceiver calls, closes #1539 2021-01-16 11:50:31 +01:00
PhilippC
2c8bd15474 New translations strings.xml (Slovenian) 2021-01-14 12:20:52 +01:00
PhilippC
d6f3ad8b56 New translations strings.xml (Chinese Simplified) 2021-01-14 04:53:57 +01:00
PhilippC
b9d2f57d41 New translations strings.xml (Greek) 2021-01-13 21:56:45 +01:00
PhilippC
cd34858f9a New translations strings.xml (Japanese) 2021-01-13 21:56:39 +01:00
PhilippC
1639d5abee New translations strings.xml (Dutch) 2021-01-13 21:56:33 +01:00
PhilippC
dd2e4546b9 New translations strings.xml (Korean) 2021-01-13 21:56:21 +01:00
PhilippC
5b6af723b0 New translations strings.xml (Romanian) 2021-01-13 21:56:18 +01:00
PhilippC
9e660f957f New translations strings.xml (French) 2021-01-13 21:56:14 +01:00
PhilippC
1960af5149 New translations strings.xml (Russian) 2021-01-13 21:55:48 +01:00
PhilippC
792d16aba7 New translations strings.xml (Slovenian) 2021-01-13 21:55:06 +01:00
PhilippC
e6e854138b New translations strings.xml (Ukrainian) 2021-01-13 21:54:52 +01:00
PhilippC
2117e5abf9 New translations strings.xml (Chinese Simplified) 2021-01-13 21:54:48 +01:00
PhilippC
04151becfe New translations strings.xml (Chinese Traditional) 2021-01-13 21:54:39 +01:00
Philipp Crocoll
6cc007e47e changelog and manifest for 1.08d-r3 2021-01-13 21:49:47 +01:00
Philipp Crocoll
cd1877d34d fix issue with selecting one of multiple entries for autofill as reported on #1399; also another attempt to work-around the buggy Firefox Autofill 2021-01-13 21:36:56 +01:00
PhilippC
df3bc19fc0 New translations strings.xml (Romanian) 2021-01-13 10:14:58 +01:00
PhilippC
e4e7b1cb48 New translations strings.xml (Romanian) 2021-01-13 09:14:55 +01:00
Philipp Crocoll
c46b7be051 support SHA256 and SHA512 hash algorithm for otpauth:// TOTPs, closes https://github.com/PhilippC/keepass2android/issues/1155 2021-01-12 17:08:55 +01:00
PhilippC
5a8fa21cd1 New translations strings.xml (Slovenian) 2021-01-12 13:13:16 +01:00
Philipp Crocoll
7df048263b add compatibility to Keepass 2.47 TimeOTP-Secret values (but not supporting {TIMEOTP} placeholder) 2021-01-12 12:14:34 +01:00
PhilippC
aba45f4cf5 New translations strings.xml (Slovenian) 2021-01-12 11:18:38 +01:00
Philipp Crocoll
d9bcac600c support for Keyfiles like in Keepass 2.47 2021-01-12 07:31:18 +01:00
Philipp Crocoll
533021da3f Manifest for 1.08d-r2 2021-01-12 07:30:44 +01:00
PhilippC
ceeb7e23b4 New translations strings.xml (Japanese) 2021-01-12 01:25:04 +01:00
PhilippC
f226edf018 New translations strings.xml (Japanese) 2021-01-12 00:18:21 +01:00
PhilippC
e6f0f12240 New translations strings.xml (Japanese) 2021-01-12 00:18:19 +01:00
PhilippC
9df7c03e02 New translations strings.xml (Japanese) 2021-01-11 23:18:59 +01:00
Philipp Crocoll
1b8e290b30 Merge branch 'master' of c:/ph/keepass2android 2021-01-10 22:45:07 +01:00
Philipp Crocoll
a90fd17bea fix issue with incorrect launching of PasswordActivity for AutoExecItems leading to incorrect behavior when launching the app from Autofill (when child databases are opened) 2021-01-10 22:44:33 +01:00
Philipp Crocoll
3fb358ca85 further improvements for Autofill for usability with Firefox (see https://github.com/PhilippC/keepass2android/issues/1399), also fix security bug (closes https://github.com/PhilippC/keepass2android/issues/1527) 2021-01-10 15:25:26 +01:00
PhilippC
83d638e0e5 New translations strings.xml (Dutch) 2021-01-09 14:00:09 +01:00
PhilippC
20a147fd04 New translations strings.xml (Dutch) 2021-01-09 13:00:09 +01:00
Philipp Crocoll
730af8b9ac make sure autofill only returns results when database is unlocked 2021-01-09 11:42:38 +01:00
PhilippC
9a400cecd2 New translations strings.xml (Chinese Simplified) 2021-01-09 08:30:08 +01:00
PhilippC
b5d9ac859f New translations strings.xml (Chinese Simplified) 2021-01-09 07:30:09 +01:00
PhilippC
144cc3c40a New translations strings.xml (Greek) 2021-01-08 12:20:09 +01:00
PhilippC
2b514598d4 New translations strings.xml (Greek) 2021-01-08 11:20:12 +01:00
PhilippC
9c55f7d9e0 New translations strings.xml (Greek) 2021-01-08 11:20:10 +01:00
Philipp Crocoll
7fb070c5fc manifest for 1.08d-r1 2021-01-08 06:45:52 +01:00
PhilippC
b0dccaea98 New translations strings.xml (Chinese Simplified) 2021-01-08 06:42:33 +01:00
PhilippC
7939a0c9e7 New translations strings.xml (Portuguese, Brazilian) 2021-01-08 06:42:30 +01:00
PhilippC
5bccc32bb1 New translations strings.xml (Turkish) 2021-01-08 06:42:26 +01:00
PhilippC
1bf7ce27da New translations strings.xml (Swedish) 2021-01-08 06:42:22 +01:00
PhilippC
59085fd790 New translations strings.xml (Ukrainian) 2021-01-08 06:42:16 +01:00
PhilippC
7f004ca720 New translations strings.xml (Chinese Traditional) 2021-01-08 06:42:13 +01:00
PhilippC
71098fd9ac New translations strings.xml (Czech) 2021-01-08 06:41:48 +01:00
PhilippC
1ec17c77b5 New translations strings.xml (Danish) 2021-01-08 06:41:44 +01:00
PhilippC
adb3a81b2a New translations strings.xml (German) 2021-01-08 06:41:40 +01:00
PhilippC
ccf3a53253 New translations strings.xml (Greek) 2021-01-08 06:41:37 +01:00
PhilippC
9a7a3c6400 New translations strings.xml (Catalan) 2021-01-08 06:41:33 +01:00
PhilippC
1963e2d7bb New translations strings.xml (Korean) 2021-01-08 06:41:28 +01:00
PhilippC
e385eb78e2 New translations strings.xml (French) 2021-01-08 06:41:23 +01:00
PhilippC
e80ac513c4 New translations strings.xml (Spanish) 2021-01-08 06:41:19 +01:00
PhilippC
cf36613c65 New translations strings.xml (Italian) 2021-01-08 06:41:11 +01:00
PhilippC
ca3a9a7722 New translations strings.xml (Polish) 2021-01-08 06:41:08 +01:00
PhilippC
e85d5d519a New translations strings.xml (Portuguese) 2021-01-08 06:41:04 +01:00
PhilippC
2eccf190b4 New translations strings.xml (Russian) 2021-01-08 06:41:01 +01:00
PhilippC
2c18da0fbc New translations strings.xml (Slovak) 2021-01-08 06:40:57 +01:00
PhilippC
776a13ef28 New translations strings.xml (Slovenian) 2021-01-08 06:40:53 +01:00
PhilippC
b5e6e4904c New translations strings.xml (Japanese) 2021-01-08 06:40:44 +01:00
PhilippC
1e2d1503fe New translations strings.xml (Hungarian) 2021-01-08 06:40:39 +01:00
PhilippC
50d16acee9 New translations strings.xml (Dutch) 2021-01-08 06:40:32 +01:00
Philipp Crocoll
710e0f9bba Merge branch 'master' of https://github.com/PhilippC/keepass2android
# Conflicts:
#	src/keepass2android/Resources/values/strings.xml
2021-01-08 06:37:57 +01:00
Philipp Crocoll
84efc14b00 changelog for 1.08d-r1 2021-01-08 06:36:23 +01:00
Philipp Crocoll
ddb58119be update to pCloud SDK 1.2.0 and implement support for different dataHosts (depending on data server location). Closes #1257 2021-01-08 06:35:49 +01:00
PhilippC
3ced852eb4 New translations strings.xml (Korean) 2021-01-08 00:20:13 +01:00
PhilippC
9928c85d0a New translations strings.xml (Korean) 2021-01-07 23:20:11 +01:00
PhilippC
8a095a4032 New translations strings.xml (Korean) 2021-01-07 23:20:09 +01:00
Philipp Crocoll
9372cb7190 avoid potential crash 2021-01-06 18:05:55 +01:00
Philipp Crocoll
bf3a5891e3 bug fixes to activity behavior of PasswordActivity/SelectCurrentDbActivity:
* when a PasswordActivity was still in stopped state and the user triggered opening from Share URL or Autofill and had a child database, the child would not be opened correctly because the child IOC was delivered in OnNewIntent (but not correctly loaded from there)
 * when locking the database from the SelectCurrentDbActivity, the activity would remain active (not really a big problem, DB was locked anyway, but bad UI)
2021-01-06 18:05:35 +01:00
Philipp Crocoll
ca61fa9d1d several changes to fix https://github.com/PhilippC/keepass2android/issues/1399:
* adding a lock to avoid flickering/disappearing prompt
 * returning a fill-response instead of a dataset from authentication activity
 * immediately present one search result (if there is a match) as a dataset item in the autofill prompt
2021-01-02 12:31:19 +01:00
Philipp Crocoll
42eb8222fc use special icon to indicate in search results if an entry is expired 2021-01-02 12:20:47 +01:00
PhilippC
2aca631390 New translations strings.xml (Chinese Simplified) 2021-01-02 02:10:09 +01:00
PhilippC
b175295d43 New translations strings.xml (Chinese Traditional) 2020-12-28 14:30:11 +01:00
PhilippC
0226456d41 New translations strings.xml (Chinese Traditional) 2020-12-28 13:30:10 +01:00
PhilippC
d18aba0747 New translations strings.xml (French) 2020-12-22 08:10:09 +01:00
PhilippC
449b5e3f0a New translations strings.xml (French) 2020-12-19 18:00:09 +01:00
Philipp Crocoll
301216fc2e improve error logging 2020-12-18 20:11:02 +01:00
Philipp Crocoll
c3bef01fe9 add another supporter name 2020-12-17 19:27:25 +01:00
PhilippC
19abf7a1b0 New translations strings.xml (Russian) 2020-12-16 13:40:09 +01:00
PhilippC
3e36a6e5fa New translations strings.xml (Russian) 2020-12-16 12:40:10 +01:00
PhilippC
e65bd9a524 New translations strings.xml (Ukrainian) 2020-12-12 19:50:09 +01:00
Philipp Crocoll
697b331a65 allow to disable DAL verification in Autofill for testing/debugging 2020-12-12 14:16:12 +01:00
Philipp Crocoll
3767450343 allow to disable check for secure screen to solve #1501 2020-12-12 14:14:05 +01:00
PhilippC
2ffa9b06c5 New translations strings.xml (Chinese Simplified) 2020-12-12 11:40:08 +01:00
PhilippC
c1e80d209f New translations strings.xml (Romanian) 2020-12-11 20:00:09 +01:00
PhilippC
a36f5a375a New translations strings.xml (Romanian) 2020-12-11 19:00:10 +01:00
PhilippC
4e7f6d62d2 New translations strings.xml (Russian) 2020-12-08 10:20:10 +01:00
PhilippC
f8e637cdbc New translations strings.xml (Slovenian) 2020-12-07 12:50:10 +01:00
Philipp Crocoll
abf32cf499 manifest and chanelog for 1.08d-r0 2020-12-07 11:03:23 +01:00
Philipp Crocoll
eee24819d5 add support for Argon2id (see https://github.com/keepassxreboot/keepassxc/issues/4317#issuecomment-738101431) 2020-12-07 10:43:46 +01:00
Philipp Crocoll
b7e4bf0be5 fix strings.xml 2020-12-07 10:43:10 +01:00
PhilippC
be65fe2521 New translations strings.xml (Sinhala) 2020-12-05 20:00:08 +01:00
PhilippC
05e62bd40d New translations strings.xml (Sinhala) 2020-12-05 10:00:12 +01:00
PhilippC
ca4e819183 New translations strings.xml (Sinhala) 2020-12-05 10:00:10 +01:00
PhilippC
80b75f11a0 New translations strings.xml (Sinhala) 2020-12-05 10:00:08 +01:00
PhilippC
a18f013d8c New translations strings.xml (Russian) 2020-12-03 11:40:10 +01:00
PhilippC
ffc06404c1 New translations strings.xml (German) 2020-11-30 12:32:18 +01:00
Philipp Crocoll
849cb0c707 changelog for 1.08d 2020-11-30 12:25:12 +01:00
Philipp Crocoll
219932258c Merge branch 'master' of https://github.com/PhilippC/keepass2android 2020-11-30 12:23:24 +01:00
Philipp Crocoll
59a5bb0c89 add menu in SelectCurrentDbActivity, closes https://github.com/PhilippC/keepass2android/issues/988 2020-11-30 12:16:04 +01:00
Philipp Crocoll
29fca5c1f0 allow to export imported keyfiles, closes https://github.com/PhilippC/keepass2android/issues/599 2020-11-30 11:56:39 +01:00
Philipp Crocoll
fec0e7768a add more logging output to diagnose an issue describe by a user where merge conflicts seem to be detected even if they are not there 2020-11-30 11:55:07 +01:00
Philipp Crocoll
3dcb8ed32b update JSch to version 0.1.55, closes #1478 2020-11-30 10:10:13 +01:00
PhilippC
2b1f5cfd0a New translations strings.xml (Italian) 2020-11-26 14:20:13 +01:00
PhilippC
84e08d39a4 New translations strings.xml (Czech) 2020-11-23 01:20:13 +01:00
PhilippC
0a171a6a72 New translations strings.xml (Czech) 2020-11-23 01:20:11 +01:00
PhilippC
cdcbe148c9 Merge pull request #1472 from joschahenningsen/readme-patch
Remove Google+ group from readme
2020-11-21 19:20:46 +01:00
Joscha Henningsen
fea28640b5 update readme 2020-11-20 16:47:39 +01:00
PhilippC
295f19c54e New translations strings.xml (Malayalam) 2020-11-18 14:00:09 +01:00
PhilippC
de759e5870 New translations strings.xml (German) 2020-11-18 13:00:09 +01:00
PhilippC
e9995e0c80 New translations strings.xml (Malayalam) 2020-11-18 10:50:10 +01:00
PhilippC
234fd59d22 New translations strings.xml (Malayalam) 2020-11-18 09:50:13 +01:00
PhilippC
7617012292 New translations strings.xml (Malayalam) 2020-11-18 09:50:11 +01:00
PhilippC
e8085c1c0e New translations strings.xml (Malayalam) 2020-11-17 03:30:07 +01:00
PhilippC
9e8a9e7b9e New translations strings.xml (Malayalam) 2020-11-17 02:30:09 +01:00
PhilippC
e47516b0e0 New translations strings.xml (Malayalam) 2020-11-17 02:30:08 +01:00
Philipp Crocoll
b85cb20177 improve logging on SwitchImeActivity 2020-11-16 12:14:42 +01:00
Philipp Crocoll
612c7927e9 fix to keyboard not opening on QuickUnlock screen, closes #1362 2020-11-16 11:35:09 +01:00
PhilippC
e69af9cfbe New translations strings.xml (Ukrainian) 2020-11-08 20:30:09 +01:00
PhilippC
23f324b1d8 New translations strings.xml (Ukrainian) 2020-11-08 20:30:07 +01:00
PhilippC
a1f7a4245a New translations strings.xml (Slovak) 2020-11-07 23:00:08 +01:00
PhilippC
5dab309921 New translations strings.xml (Japanese) 2020-11-07 16:30:09 +01:00
PhilippC
44d324bc7d New translations strings.xml (Serbian (Cyrillic)) 2020-10-30 19:50:10 +01:00
PhilippC
ebadedae41 New translations strings.xml (Czech) 2020-10-27 14:30:12 +01:00
PhilippC
3f89a34946 New translations strings.xml (Czech) 2020-10-27 14:30:09 +01:00
PhilippC
d823768464 New translations strings.xml (Italian) 2020-10-26 18:30:09 +01:00
PhilippC
ee340b069b New translations strings.xml (Chinese Simplified) 2020-10-24 06:50:08 +02:00
PhilippC
d13a35b9a8 New translations strings.xml (French) 2020-10-22 22:20:08 +02:00
PhilippC
0ce41b9f54 New translations strings.xml (Slovenian) 2020-10-19 18:20:09 +02:00
PhilippC
1be1829cfe New translations strings.xml (Chinese Traditional) 2020-10-19 14:20:10 +02:00
PhilippC
160922c9fb New translations strings.xml (Dutch) 2020-10-19 11:31:39 +02:00
421 changed files with 29444 additions and 8902 deletions

3
.gitignore vendored
View File

@@ -170,3 +170,6 @@ src/java/Keepass2AndroidPluginSDK2/build/generated/mockable-Google-Inc.-Google-A
/src/java/KP2ASoftkeyboard_AS/app/.cxx
/src/java/KP2ASoftkeyboard_AS/app/src/main/libs
/src/java/KP2AKdbLibrary/app/.cxx
/src/ActionViewFilterTest
/docs/gdrive-verification
/src/MegaTest

View File

@@ -99,7 +99,7 @@ It's time for action! As soon as possible, select Settings - Database - Export a
Please see [Keepass2Android Apk](Keepass2Android-Apk.md) for more information.
## I get a message "File is trashed" when reading or writing a file on Google Drive
This happens because ocaml-fuse (I guess you are on Linux  and use that) moves files to trash and then creates a new one instead of correctly updating the file on Google Drive (each file has a unique ID which Keepass2Android uses). Fortunately, this was fixed: https://github.com/astrada/google-drive-ocamlfuse/issues/494After activating this option, please select "Change database" in KP2A, tap ,"Open file" and browse to the file on Google Drive again. After that, the message should no longer pop up.
This happens because ocaml-fuse (I guess you are on Linux  and use that) moves files to trash and then creates a new one instead of correctly updating the file on Google Drive (each file has a unique ID which Keepass2Android uses). Fortunately, this was fixed: https://github.com/astrada/google-drive-ocamlfuse/issues/494. After activating this option, please select "Change database" in KP2A, tap ,"Open file" and browse to the file on Google Drive again. After that, the message should no longer pop up.
## I get a message "The name must not be empty: null" when opening from Google Drive
Please follow these steps:

View File

@@ -8,7 +8,7 @@ The password database file can be synchronized across different devices. This wo
# Where to get it?
Regular stable releases of Keepass2Android are available on [Google Play](https://play.google.com/store/apps/details?id=keepass2android.keepass2android).
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). Please join the [Beta tester group](https://plus.google.com/communities/107293657110547776032) for news and discussions about the latest beta releases.
Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet).
# How can I contribute?
* Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android)
@@ -19,4 +19,6 @@ Beta-releases can be obtained by opting in to the [Beta testing channel](https:/
# How do I learn more?
Please see the [documentation](Documentation.md).
The project homepage is https://philipp.crocoll.net/keepass2android/index.php
[![Build Status](https://www.bitrise.io/app/43a23ab54dee9f7e/status.svg?token=2vryTsMQzTX3XRPikhgRwA&branch=master)](https://www.bitrise.io/app/43a23ab54dee9f7e)

Binary file not shown.

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.props" Condition="Exists('..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.props')" />
<Import Project="..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.props" Condition="Exists('..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -11,9 +13,11 @@
<AssemblyName>JavaFileStorageBindings</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v8.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<AndroidClassParser>class-parse</AndroidClassParser>
<AndroidCodegenTarget>XAJavaInterop1</AndroidCodegenTarget>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -46,11 +50,22 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Java.Interop" />
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="GooglePlayServicesLib">
<HintPath>..\Components\googleplayservices-19.0.0\lib\android\GooglePlayServicesLib.dll</HintPath>
<Reference Include="System.Net.Http" />
<Reference Include="System.Numerics" />
<Reference Include="System.Numerics.Vectors" />
<Reference Include="System.Xml" />
<Reference Include="Xamarin.Google.Guava, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Xamarin.Google.Guava.28.2.0.1\lib\monoandroid90\Xamarin.Google.Guava.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Google.Guava.FailureAccess, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Xamarin.Google.Guava.FailureAccess.1.0.1.3\lib\monoandroid90\Xamarin.Google.Guava.FailureAccess.dll</HintPath>
</Reference>
<Reference Include="Xamarin.Google.Guava.ListenableFuture, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Xamarin.Google.Guava.ListenableFuture.1.0.0.3\lib\monoandroid90\Xamarin.Google.Guava.ListenableFuture.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -62,6 +77,7 @@
</LibraryProjectZip>
<None Include="Jars\AboutJars.txt" />
<None Include="Additions\AboutAdditions.txt" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Transforms\Metadata.xml" />
@@ -83,7 +99,10 @@
</XamarinComponentReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PCloudBindings\PCloudBindings.csproj" />
<ProjectReference Include="..\PCloudBindings\PCloudBindings.csproj">
<Project>{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}</Project>
<Name>PCloudBindings</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\msa-auth-0.8.6\classes-msa-auth.jar" />
@@ -94,30 +113,12 @@
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\commons-logging-1.1.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-api-client-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-api-client-android-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-api-services-drive-v2-rev102-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-android-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-jackson-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-jackson2-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-oauth-client-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\httpclient-4.0.3.jar" />
</ItemGroup>
@@ -130,21 +131,12 @@
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\json_simple-1.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\jsr305-1.3.9.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-gson-1.16.0-rc.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\jackson-core-2.7.4.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedJar Include="Jars\dropbox-core-sdk-3.1.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gson-2.8.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\okhttp-digest-2.5.jar" />
</ItemGroup>
@@ -154,4 +146,105 @@
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\okhttp-4.10.0-RC1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedJar Include="Jars\dropbox-core-sdk-4.0.0.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gson-2.8.6.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-android-1.32.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\jsr305-3.0.2.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-1.32.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-http-client-jackson2-1.32.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-api-client-1.30.5.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-api-client-android-1.30.5.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\google-oauth-client-1.30.4.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\opencensus-contrib-http-util-0.24.0.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\grpc-context-1.22.1.jar" />
</ItemGroup>
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\gdrive\opencensus-api-0.24.0.jar" />
</ItemGroup>
<Import Project="..\packages\Xamarin.AndroidX.Annotation.Experimental.1.0.0.9\build\monoandroid9.0\Xamarin.AndroidX.Annotation.Experimental.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Annotation.Experimental.1.0.0.9\build\monoandroid9.0\Xamarin.AndroidX.Annotation.Experimental.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>Dieses Projekt verweist auf mindestens ein NuGet-Paket, das auf diesem Computer fehlt. Verwenden Sie die Wiederherstellung von NuGet-Paketen, um die fehlenden Dateien herunterzuladen. Weitere Informationen finden Sie unter "http://go.microsoft.com/fwlink/?LinkID=322105". Die fehlende Datei ist "{0}".</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Annotation.Experimental.1.0.0.9\build\monoandroid9.0\Xamarin.AndroidX.Annotation.Experimental.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Annotation.Experimental.1.0.0.9\build\monoandroid9.0\Xamarin.AndroidX.Annotation.Experimental.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.MultiDex.2.0.1.5\build\monoandroid90\Xamarin.AndroidX.MultiDex.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.MultiDex.2.0.1.5\build\monoandroid90\Xamarin.AndroidX.MultiDex.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.props'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Annotation.1.2.0\build\monoandroid9.0\Xamarin.AndroidX.Annotation.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Annotation.1.2.0\build\monoandroid9.0\Xamarin.AndroidX.Annotation.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Arch.Core.Common.2.1.0.8\build\monoandroid9.0\Xamarin.AndroidX.Arch.Core.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Arch.Core.Common.2.1.0.8\build\monoandroid9.0\Xamarin.AndroidX.Arch.Core.Common.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Collection.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Collection.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Collection.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Collection.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Lifecycle.Common.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Lifecycle.Common.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Common.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Lifecycle.LiveData.Core.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.LiveData.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Lifecycle.LiveData.Core.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.LiveData.Core.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Lifecycle.Runtime.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Runtime.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Lifecycle.Runtime.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Runtime.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Lifecycle.ViewModel.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModel.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Lifecycle.ViewModel.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModel.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.SavedState.1.1.0.1\build\monoandroid9.0\Xamarin.AndroidX.SavedState.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.SavedState.1.1.0.1\build\monoandroid9.0\Xamarin.AndroidX.SavedState.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.VersionedParcelable.1.1.1.7\build\monoandroid9.0\Xamarin.AndroidX.VersionedParcelable.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.VersionedParcelable.1.1.1.7\build\monoandroid9.0\Xamarin.AndroidX.VersionedParcelable.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Core.1.3.2.3\build\monoandroid9.0\Xamarin.AndroidX.Core.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Core.1.3.2.3\build\monoandroid9.0\Xamarin.AndroidX.Core.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Activity.1.2.2\build\monoandroid9.0\Xamarin.AndroidX.Activity.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Activity.1.2.2\build\monoandroid9.0\Xamarin.AndroidX.Activity.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.CustomView.1.1.0.6\build\monoandroid9.0\Xamarin.AndroidX.CustomView.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.CustomView.1.1.0.6\build\monoandroid9.0\Xamarin.AndroidX.CustomView.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Loader.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Loader.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Loader.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Loader.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.ViewPager.1.0.0.7\build\monoandroid9.0\Xamarin.AndroidX.ViewPager.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.ViewPager.1.0.0.7\build\monoandroid9.0\Xamarin.AndroidX.ViewPager.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.AndroidX.Fragment.1.3.2\build\monoandroid9.0\Xamarin.AndroidX.Fragment.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.AndroidX.Fragment.1.3.2\build\monoandroid9.0\Xamarin.AndroidX.Fragment.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.props'))" />
<Error Condition="!Exists('..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Basement.117.6.0.4\build\monoandroid90\Xamarin.GooglePlayServices.Basement.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Basement.117.6.0.4\build\monoandroid90\Xamarin.GooglePlayServices.Basement.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Tasks.117.2.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Tasks.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Tasks.117.2.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Tasks.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Base.117.6.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Base.117.6.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Base.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Auth.Api.Phone.117.5.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Api.Phone.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Auth.Api.Phone.117.5.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Api.Phone.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Auth.Base.117.1.4.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Base.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Auth.Base.117.1.4.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Base.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.GooglePlayServices.Auth.119.2.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.GooglePlayServices.Auth.119.2.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.Google.Guava.FailureAccess.1.0.1.3\build\monoandroid90\Xamarin.Google.Guava.FailureAccess.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Google.Guava.FailureAccess.1.0.1.3\build\monoandroid90\Xamarin.Google.Guava.FailureAccess.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.Google.Guava.ListenableFuture.1.0.0.3\build\monoandroid90\Xamarin.Google.Guava.ListenableFuture.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Google.Guava.ListenableFuture.1.0.0.3\build\monoandroid90\Xamarin.Google.Guava.ListenableFuture.targets'))" />
<Error Condition="!Exists('..\packages\Xamarin.Google.Guava.28.2.0.1\build\monoandroid90\Xamarin.Google.Guava.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Xamarin.Google.Guava.28.2.0.1\build\monoandroid90\Xamarin.Google.Guava.targets'))" />
</Target>
<Import Project="..\packages\Xamarin.AndroidX.MultiDex.2.0.1.5\build\monoandroid90\Xamarin.AndroidX.MultiDex.targets" Condition="Exists('..\packages\Xamarin.AndroidX.MultiDex.2.0.1.5\build\monoandroid90\Xamarin.AndroidX.MultiDex.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Migration.1.0.8\build\monoandroid90\Xamarin.AndroidX.Migration.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Annotation.1.2.0\build\monoandroid9.0\Xamarin.AndroidX.Annotation.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Annotation.1.2.0\build\monoandroid9.0\Xamarin.AndroidX.Annotation.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Arch.Core.Common.2.1.0.8\build\monoandroid9.0\Xamarin.AndroidX.Arch.Core.Common.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Arch.Core.Common.2.1.0.8\build\monoandroid9.0\Xamarin.AndroidX.Arch.Core.Common.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Collection.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Collection.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Collection.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Collection.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Lifecycle.Common.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Common.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Lifecycle.Common.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Common.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Lifecycle.LiveData.Core.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.LiveData.Core.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Lifecycle.LiveData.Core.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.LiveData.Core.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Lifecycle.Runtime.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Runtime.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Lifecycle.Runtime.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.Runtime.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Lifecycle.ViewModel.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModel.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Lifecycle.ViewModel.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModel.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.SavedState.1.1.0.1\build\monoandroid9.0\Xamarin.AndroidX.SavedState.targets" Condition="Exists('..\packages\Xamarin.AndroidX.SavedState.1.1.0.1\build\monoandroid9.0\Xamarin.AndroidX.SavedState.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.2.3.1\build\monoandroid9.0\Xamarin.AndroidX.Lifecycle.ViewModelSavedState.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.VersionedParcelable.1.1.1.7\build\monoandroid9.0\Xamarin.AndroidX.VersionedParcelable.targets" Condition="Exists('..\packages\Xamarin.AndroidX.VersionedParcelable.1.1.1.7\build\monoandroid9.0\Xamarin.AndroidX.VersionedParcelable.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Core.1.3.2.3\build\monoandroid9.0\Xamarin.AndroidX.Core.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Core.1.3.2.3\build\monoandroid9.0\Xamarin.AndroidX.Core.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Activity.1.2.2\build\monoandroid9.0\Xamarin.AndroidX.Activity.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Activity.1.2.2\build\monoandroid9.0\Xamarin.AndroidX.Activity.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.CustomView.1.1.0.6\build\monoandroid9.0\Xamarin.AndroidX.CustomView.targets" Condition="Exists('..\packages\Xamarin.AndroidX.CustomView.1.1.0.6\build\monoandroid9.0\Xamarin.AndroidX.CustomView.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Loader.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Loader.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Loader.1.1.0.7\build\monoandroid9.0\Xamarin.AndroidX.Loader.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.ViewPager.1.0.0.7\build\monoandroid9.0\Xamarin.AndroidX.ViewPager.targets" Condition="Exists('..\packages\Xamarin.AndroidX.ViewPager.1.0.0.7\build\monoandroid9.0\Xamarin.AndroidX.ViewPager.targets')" />
<Import Project="..\packages\Xamarin.AndroidX.Fragment.1.3.2\build\monoandroid9.0\Xamarin.AndroidX.Fragment.targets" Condition="Exists('..\packages\Xamarin.AndroidX.Fragment.1.3.2\build\monoandroid9.0\Xamarin.AndroidX.Fragment.targets')" />
<Import Project="..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.targets" Condition="Exists('..\packages\Xamarin.Build.Download.0.10.0\build\Xamarin.Build.Download.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Basement.117.6.0.4\build\monoandroid90\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Basement.117.6.0.4\build\monoandroid90\Xamarin.GooglePlayServices.Basement.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Tasks.117.2.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Tasks.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Tasks.117.2.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Tasks.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Base.117.6.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Base.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Base.117.6.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Base.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Auth.Api.Phone.117.5.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Api.Phone.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Auth.Api.Phone.117.5.1.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Api.Phone.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Auth.Base.117.1.4.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Base.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Auth.Base.117.1.4.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.Base.targets')" />
<Import Project="..\packages\Xamarin.GooglePlayServices.Auth.119.2.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.targets" Condition="Exists('..\packages\Xamarin.GooglePlayServices.Auth.119.2.0.3\build\monoandroid90\Xamarin.GooglePlayServices.Auth.targets')" />
<Import Project="..\packages\Xamarin.Google.Guava.FailureAccess.1.0.1.3\build\monoandroid90\Xamarin.Google.Guava.FailureAccess.targets" Condition="Exists('..\packages\Xamarin.Google.Guava.FailureAccess.1.0.1.3\build\monoandroid90\Xamarin.Google.Guava.FailureAccess.targets')" />
<Import Project="..\packages\Xamarin.Google.Guava.ListenableFuture.1.0.0.3\build\monoandroid90\Xamarin.Google.Guava.ListenableFuture.targets" Condition="Exists('..\packages\Xamarin.Google.Guava.ListenableFuture.1.0.0.3\build\monoandroid90\Xamarin.Google.Guava.ListenableFuture.targets')" />
<Import Project="..\packages\Xamarin.Google.Guava.28.2.0.1\build\monoandroid90\Xamarin.Google.Guava.targets" Condition="Exists('..\packages\Xamarin.Google.Guava.28.2.0.1\build\monoandroid90\Xamarin.Google.Guava.targets')" />
</Project>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Xamarin.AndroidX.Activity" version="1.2.2" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Annotation" version="1.2.0" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Annotation.Experimental" version="1.0.0.9" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Arch.Core.Common" version="2.1.0.8" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Collection" version="1.1.0.7" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Core" version="1.3.2.3" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.CustomView" version="1.1.0.6" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Fragment" version="1.3.2" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Lifecycle.Common" version="2.3.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Lifecycle.LiveData.Core" version="2.3.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Lifecycle.Runtime" version="2.3.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Lifecycle.ViewModel" version="2.3.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Lifecycle.ViewModelSavedState" version="2.3.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Loader" version="1.1.0.7" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.Migration" version="1.0.8" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.MultiDex" version="2.0.1.5" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.SavedState" version="1.1.0.1" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.VersionedParcelable" version="1.1.1.7" targetFramework="monoandroid90" />
<package id="Xamarin.AndroidX.ViewPager" version="1.0.0.7" targetFramework="monoandroid90" />
<package id="Xamarin.Build.Download" version="0.10.0" targetFramework="monoandroid90" />
<package id="Xamarin.Google.Guava" version="28.2.0.1" targetFramework="monoandroid90" />
<package id="Xamarin.Google.Guava.FailureAccess" version="1.0.1.3" targetFramework="monoandroid90" />
<package id="Xamarin.Google.Guava.ListenableFuture" version="1.0.0.3" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Auth" version="119.2.0.3" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Auth.Api.Phone" version="117.5.1.3" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Auth.Base" version="117.1.4.3" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Base" version="117.6.0.3" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Basement" version="117.6.0.4" targetFramework="monoandroid90" />
<package id="Xamarin.GooglePlayServices.Tasks" version="117.2.1.3" targetFramework="monoandroid90" />
</packages>

View File

@@ -5,8 +5,6 @@ VisualStudioVersion = 16.0.29418.71
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassLib2Android", "KeePassLib2Android\KeePassLib2Android.csproj", "{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "keepass2android", "keepass2android\keepass2android.csproj", "{A6CF8A86-37C1-4197-80FE-519DE2C842F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aKeyboardBinding", "Kp2aKeyboardBinding\Kp2aKeyboardBinding.csproj", "{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aBusinessLogic", "Kp2aBusinessLogic\Kp2aBusinessLogic.csproj", "{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}"
@@ -23,10 +21,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginSdkBinding", "PluginS
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZlibAndroid", "ZlibAndroid\ZlibAndroid.csproj", "{6C29A7E7-E016-4FC1-B1A0-DEE26AC711BB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\Xamarin.SamsungPass\SamsungPass\SamsungPass.csproj", "{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PCloudBindings", "PCloudBindings\PCloudBindings.csproj", "{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "keepass2android-app", "keepass2android\keepass2android-app.csproj", "{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SamsungPass", "SamsungPass\Xamarin.SamsungPass\SamsungPass\SamsungPass.csproj", "{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -65,34 +65,6 @@ Global
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.Deploy.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.Deploy.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.Deploy.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -253,24 +225,6 @@ Global
{6C29A7E7-E016-4FC1-B1A0-DEE26AC711BB}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU
{6C29A7E7-E016-4FC1-B1A0-DEE26AC711BB}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{6C29A7E7-E016-4FC1-B1A0-DEE26AC711BB}.ReleaseNoNet|x64.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|x64.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Win32.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|x64.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@@ -295,6 +249,66 @@ Global
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|Win32.Build.0 = ReleaseNoNet|Any CPU
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{2DB80C77-D46F-4970-B967-E9FFA9B2AC2E}.ReleaseNoNet|x64.Build.0 = ReleaseNoNet|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Win32.ActiveCfg = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Win32.Build.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|Win32.Deploy.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|x64.ActiveCfg = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|x64.Build.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Debug|x64.Deploy.0 = Debug|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Any CPU.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Any CPU.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Win32.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Win32.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|Win32.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|x64.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|x64.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.Release|x64.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Any CPU.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Win32.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|Win32.Deploy.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.Build.0 = Release|Any CPU
{D4C32E0A-0193-4496-9DB4-02CC126FD9F3}.ReleaseNoNet|x64.Deploy.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Win32.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|Win32.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|x64.ActiveCfg = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Debug|x64.Build.0 = Debug|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Any CPU.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Win32.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|Win32.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|x64.ActiveCfg = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.Release|x64.Build.0 = Release|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|Win32.Build.0 = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{3A4B8E88-FA9B-4663-BCDA-21C12E3AF98A}.ReleaseNoNet|x64.Build.0 = ReleaseNoNet|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,8 +33,11 @@ namespace KeePassLib.Collections
private Dictionary<int, ProtectedBinary> m_d =
new Dictionary<int, ProtectedBinary>();
public ProtectedBinarySet()
private readonly bool m_bDedupAdd;
public ProtectedBinarySet(bool bDedupAdd)
{
m_bDedupAdd = bDedupAdd;
}
IEnumerator IEnumerable.GetEnumerator()
@@ -47,15 +50,10 @@ namespace KeePassLib.Collections
return m_d.GetEnumerator();
}
public void Clear()
{
m_d.Clear();
}
private int GetFreeID()
{
int i = m_d.Count;
while(m_d.ContainsKey(i)) { ++i; }
while (m_d.ContainsKey(i)) { ++i; }
Debug.Assert(i == m_d.Count); // m_d.Count should be free
return i;
}
@@ -63,7 +61,7 @@ namespace KeePassLib.Collections
public ProtectedBinary Get(int iID)
{
ProtectedBinary pb;
if(m_d.TryGetValue(iID, out pb)) return pb;
if (m_d.TryGetValue(iID, out pb)) return pb;
// Debug.Assert(false); // No assert
return null;
@@ -71,12 +69,12 @@ namespace KeePassLib.Collections
public int Find(ProtectedBinary pb)
{
if(pb == null) { Debug.Assert(false); return -1; }
if (pb == null) { Debug.Assert(false); return -1; }
// Fast search by reference
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{
if(object.ReferenceEquals(pb, kvp.Value))
if (object.ReferenceEquals(pb, kvp.Value))
{
Debug.Assert(pb.Equals(kvp.Value));
return kvp.Key;
@@ -84,9 +82,9 @@ namespace KeePassLib.Collections
}
// Slow search by content
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{
if(pb.Equals(kvp.Value)) return kvp.Key;
if (pb.Equals(kvp.Value)) return kvp.Key;
}
// Debug.Assert(false); // No assert
@@ -95,28 +93,26 @@ namespace KeePassLib.Collections
public void Set(int iID, ProtectedBinary pb)
{
if(iID < 0) { Debug.Assert(false); return; }
if(pb == null) { Debug.Assert(false); return; }
if (iID < 0) { Debug.Assert(false); return; }
if (pb == null) { Debug.Assert(false); return; }
m_d[iID] = pb;
}
public void Add(ProtectedBinary pb)
{
if(pb == null) { Debug.Assert(false); return; }
if (pb == null) { Debug.Assert(false); return; }
int i = Find(pb);
if(i >= 0) return; // Exists already
if (m_bDedupAdd && (Find(pb) >= 0)) return; // Exists already
i = GetFreeID();
m_d[i] = pb;
m_d[GetFreeID()] = pb;
}
public void AddFrom(ProtectedBinaryDictionary d)
{
if(d == null) { Debug.Assert(false); return; }
if (d == null) { Debug.Assert(false); return; }
foreach(KeyValuePair<string, ProtectedBinary> kvp in d)
foreach (KeyValuePair<string, ProtectedBinary> kvp in d)
{
Add(kvp.Value);
}
@@ -124,16 +120,16 @@ namespace KeePassLib.Collections
public void AddFrom(PwGroup pg)
{
if(pg == null) { Debug.Assert(false); return; }
if (pg == null) { Debug.Assert(false); return; }
EntryHandler eh = delegate(PwEntry pe)
EntryHandler eh = delegate (PwEntry pe)
{
if(pe == null) { Debug.Assert(false); return true; }
if (pe == null) { Debug.Assert(false); return true; }
AddFrom(pe.Binaries);
foreach(PwEntry peHistory in pe.History)
foreach (PwEntry peHistory in pe.History)
{
if(peHistory == null) { Debug.Assert(false); continue; }
if (peHistory == null) { Debug.Assert(false); continue; }
AddFrom(peHistory.Binaries);
}
@@ -148,9 +144,9 @@ namespace KeePassLib.Collections
int n = m_d.Count;
ProtectedBinary[] v = new ProtectedBinary[n];
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d)
foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{
if((kvp.Key < 0) || (kvp.Key >= n))
if ((kvp.Key < 0) || (kvp.Key >= n))
{
Debug.Assert(false);
throw new InvalidOperationException();
@@ -159,9 +155,9 @@ namespace KeePassLib.Collections
v[kvp.Key] = kvp.Value;
}
for(int i = 0; i < n; ++i)
for (int i = 0; i < n; ++i)
{
if(v[i] == null)
if (v[i] == null)
{
Debug.Assert(false);
throw new InvalidOperationException();

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,8 +20,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Text;
using KeePassLib.Interfaces;
@@ -34,48 +34,74 @@ namespace KeePassLib.Collections
public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>,
IEnumerable<KeyValuePair<string, string>>, IEquatable<StringDictionaryEx>
{
private SortedDictionary<string, string> m_dict =
private SortedDictionary<string, string> m_d =
new SortedDictionary<string, string>();
// Non-null if and only if last mod. times should be remembered
private Dictionary<string, DateTime> m_dLastMod = null;
public int Count
{
get { return m_dict.Count; }
get { return m_d.Count; }
}
public StringDictionaryEx()
{
}
internal StringDictionaryEx(bool bRememberLastMod)
{
if (bRememberLastMod) m_dLastMod = new Dictionary<string, DateTime>();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_dict.GetEnumerator();
return m_d.GetEnumerator();
}
public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return m_dict.GetEnumerator();
return m_d.GetEnumerator();
}
public StringDictionaryEx CloneDeep()
{
StringDictionaryEx sdNew = new StringDictionaryEx();
foreach(KeyValuePair<string, string> kvp in m_dict)
sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable
foreach (KeyValuePair<string, string> kvp in m_d)
sdNew.m_d[kvp.Key] = kvp.Value;
if (m_dLastMod != null)
sdNew.m_dLastMod = new Dictionary<string, DateTime>(m_dLastMod);
Debug.Assert(Equals(sdNew));
return sdNew;
}
public bool Equals(StringDictionaryEx sdOther)
{
if(sdOther == null) { Debug.Assert(false); return false; }
if (sdOther == null) { Debug.Assert(false); return false; }
if(m_dict.Count != sdOther.m_dict.Count) return false;
if (m_d.Count != sdOther.m_d.Count) return false;
foreach(KeyValuePair<string, string> kvp in sdOther.m_dict)
foreach (KeyValuePair<string, string> kvp in sdOther.m_d)
{
string str = Get(kvp.Key);
if((str == null) || (str != kvp.Value)) return false;
if ((str == null) || (str != kvp.Value)) return false;
}
int cLastModT = ((m_dLastMod != null) ? m_dLastMod.Count : -1);
int cLastModO = ((sdOther.m_dLastMod != null) ? sdOther.m_dLastMod.Count : -1);
if (cLastModT != cLastModO) return false;
if (m_dLastMod != null)
{
foreach (KeyValuePair<string, DateTime> kvp in sdOther.m_dLastMod)
{
DateTime? odt = GetLastModificationTime(kvp.Key);
if (!odt.HasValue) return false;
if (odt.Value != kvp.Value) return false;
}
}
return true;
@@ -83,48 +109,62 @@ namespace KeePassLib.Collections
public string Get(string strName)
{
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
string s;
if(m_dict.TryGetValue(strName, out s)) return s;
string str;
m_d.TryGetValue(strName, out str);
return str;
}
internal DateTime? GetLastModificationTime(string strName)
{
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (m_dLastMod == null) return null;
DateTime dt;
if (m_dLastMod.TryGetValue(strName, out dt)) return dt;
return null;
}
public bool Exists(string strName)
{
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
return m_dict.ContainsKey(strName);
return m_d.ContainsKey(strName);
}
/// <summary>
/// Set a string.
/// </summary>
/// <param name="strField">Identifier of the string field to modify.</param>
/// <param name="strNewValue">New value. This parameter must not be <c>null</c>.</param>
/// <exception cref="System.ArgumentNullException">Thrown if one of the input
/// parameters is <c>null</c>.</exception>
public void Set(string strField, string strNewValue)
public void Set(string strName, string strValue)
{
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); }
if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); }
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
m_dict[strField] = strNewValue;
m_d[strName] = strValue;
if (m_dLastMod != null) m_dLastMod[strName] = DateTime.UtcNow;
}
/// <summary>
/// Delete a string.
/// </summary>
/// <param name="strField">Name of the string field to delete.</param>
/// <returns>Returns <c>true</c> if the field has been successfully
/// removed, otherwise the return value is <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if the input
/// parameter is <c>null</c>.</exception>
public bool Remove(string strField)
internal void Set(string strName, string strValue, DateTime? odtLastMod)
{
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); }
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
return m_dict.Remove(strField);
m_d[strName] = strValue;
if (m_dLastMod != null)
{
if (odtLastMod.HasValue) m_dLastMod[strName] = odtLastMod.Value;
else m_dLastMod.Remove(strName);
}
}
public bool Remove(string strName)
{
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (m_dLastMod != null) m_dLastMod.Remove(strName);
return m_d.Remove(strName);
}
}
}

View File

@@ -28,15 +28,29 @@ using KeePassLib.Resources;
namespace KeePassLib.Cryptography.Cipher
{
public sealed class ChaCha20Engine : ICipherEngine2
{
private PwUuid m_uuid = new PwUuid(new byte[] {
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A
});
{
private static PwUuid m_uuid = null;
internal static PwUuid ChaCha20Uuid
{
get
{
PwUuid pu = m_uuid;
if (pu == null)
{
pu = new PwUuid(new byte[] {
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A });
m_uuid = pu;
}
return pu;
}
}
public PwUuid CipherUuid
{
get { return m_uuid; }
get { return ChaCha20Uuid; }
}
public string DisplayName

View File

@@ -367,5 +367,27 @@ namespace KeePassLib.Cryptography
Debug.Assert(iPos == pbRes.Length);
return pbRes;
}
private static int g_iWeakSeed = 0;
public static Random NewWeakRandom()
{
long s64 = DateTime.UtcNow.ToBinary();
int s32 = (int)((s64 >> 32) ^ s64);
lock (g_oSyncRoot)
{
unchecked
{
g_iWeakSeed += 0x78A8C4B7; // Prime number
s32 ^= g_iWeakSeed;
}
}
// Prevent overflow in the Random constructor of .NET 2.0
if (s32 == int.MinValue) s32 = int.MaxValue;
return new Random(s32);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2020 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,28 +20,67 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
#if !KeePassUAP
using System.Security.Cryptography;
#endif
using KeePassLib.Native;
using KeePassLib.Utility;
namespace KeePassLib.Cryptography
{
public static class CryptoUtil
{
private static bool? g_obProtData = null;
public static bool IsProtectedDataSupported
{
get
{
if (g_obProtData.HasValue) return g_obProtData.Value;
bool b = false;
try
{
Random r = CryptoRandom.NewWeakRandom();
byte[] pbData = new byte[137];
r.NextBytes(pbData);
byte[] pbEnt = new byte[41];
r.NextBytes(pbEnt);
byte[] pbEnc = ProtectedData.Protect(pbData, pbEnt,
DataProtectionScope.CurrentUser);
if ((pbEnc != null) && !MemUtil.ArraysEqual(pbEnc, pbData))
{
byte[] pbDec = ProtectedData.Unprotect(pbEnc, pbEnt,
DataProtectionScope.CurrentUser);
if ((pbDec != null) && MemUtil.ArraysEqual(pbDec, pbData))
b = true;
}
}
catch (Exception) { Debug.Assert(false); }
Debug.Assert(b); // Should be supported on all systems
g_obProtData = b;
return b;
}
}
public static byte[] HashSha256(byte[] pbData)
{
if(pbData == null) throw new ArgumentNullException("pbData");
if (pbData == null) throw new ArgumentNullException("pbData");
return HashSha256(pbData, 0, pbData.Length);
}
public static byte[] HashSha256(byte[] pbData, int iOffset, int cbCount)
{
if(pbData == null) throw new ArgumentNullException("pbData");
if (pbData == null) throw new ArgumentNullException("pbData");
#if DEBUG
byte[] pbCopy = new byte[pbData.Length];
@@ -49,7 +88,7 @@ namespace KeePassLib.Cryptography
#endif
byte[] pbHash;
using(SHA256Managed h = new SHA256Managed())
using (SHA256Managed h = new SHA256Managed())
{
pbHash = h.ComputeHash(pbData, iOffset, cbCount);
}
@@ -66,6 +105,22 @@ namespace KeePassLib.Cryptography
return pbHash;
}
internal static byte[] HashSha256(string strFilePath)
{
byte[] pbHash = null;
using (FileStream fs = new FileStream(strFilePath, FileMode.Open,
FileAccess.Read, FileShare.Read))
{
using (SHA256Managed h = new SHA256Managed())
{
pbHash = h.ComputeHash(fs);
}
}
return pbHash;
}
/// <summary>
/// Create a cryptographic key of length <paramref name="cbOut" />
/// (in bytes) from <paramref name="pbIn" />.
@@ -73,34 +128,34 @@ namespace KeePassLib.Cryptography
public static byte[] ResizeKey(byte[] pbIn, int iInOffset,
int cbIn, int cbOut)
{
if(pbIn == null) throw new ArgumentNullException("pbIn");
if(cbOut < 0) throw new ArgumentOutOfRangeException("cbOut");
if (pbIn == null) throw new ArgumentNullException("pbIn");
if (cbOut < 0) throw new ArgumentOutOfRangeException("cbOut");
if(cbOut == 0) return MemUtil.EmptyByteArray;
if (cbOut == 0) return MemUtil.EmptyByteArray;
byte[] pbHash;
if(cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn);
if (cbOut <= 32) pbHash = HashSha256(pbIn, iInOffset, cbIn);
else
{
using(SHA512Managed h = new SHA512Managed())
using (SHA512Managed h = new SHA512Managed())
{
pbHash = h.ComputeHash(pbIn, iInOffset, cbIn);
}
}
if(cbOut == pbHash.Length) return pbHash;
if (cbOut == pbHash.Length) return pbHash;
byte[] pbRet = new byte[cbOut];
if(cbOut < pbHash.Length)
if (cbOut < pbHash.Length)
Array.Copy(pbHash, pbRet, cbOut);
else
{
int iPos = 0;
ulong r = 0;
while(iPos < cbOut)
while (iPos < cbOut)
{
Debug.Assert(pbHash.Length == 64);
using(HMACSHA256 h = new HMACSHA256(pbHash))
using (HMACSHA256 h = new HMACSHA256(pbHash))
{
byte[] pbR = MemUtil.UInt64ToBytes(r);
byte[] pbPart = h.ComputeHash(pbR);
@@ -125,5 +180,75 @@ namespace KeePassLib.Cryptography
MemUtil.ZeroByteArray(pbHash);
return pbRet;
}
#if !KeePassUAP
private static bool? g_obAesCsp = null;
internal static SymmetricAlgorithm CreateAes()
{
if (g_obAesCsp.HasValue)
return (g_obAesCsp.Value ? CreateAesCsp() : new RijndaelManaged());
SymmetricAlgorithm a = CreateAesCsp();
g_obAesCsp = (a != null);
return (a ?? new RijndaelManaged());
}
private static SymmetricAlgorithm CreateAesCsp()
{
try
{
// On Windows, the CSP implementation is only minimally
// faster (and for key derivations it's not used anyway,
// as KeePass uses a native implementation based on
// CNG/BCrypt, which is much faster)
if (!NativeLib.IsUnix()) return null;
string strFqn = Assembly.CreateQualifiedName(
"System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Security.Cryptography.AesCryptoServiceProvider");
Type t = Type.GetType(strFqn);
if (t == null) return null;
return (Activator.CreateInstance(t) as SymmetricAlgorithm);
}
catch (Exception) { Debug.Assert(false); }
return null;
}
#endif
public static byte[] ProtectData(byte[] pb, byte[] pbOptEntropy,
DataProtectionScope s)
{
return ProtectDataPriv(pb, true, pbOptEntropy, s);
}
public static byte[] UnprotectData(byte[] pb, byte[] pbOptEntropy,
DataProtectionScope s)
{
return ProtectDataPriv(pb, false, pbOptEntropy, s);
}
private static byte[] ProtectDataPriv(byte[] pb, bool bProtect,
byte[] pbOptEntropy, DataProtectionScope s)
{
if (pb == null) throw new ArgumentNullException("pb");
if ((pbOptEntropy != null) && (pbOptEntropy.Length == 0))
pbOptEntropy = null;
if (CryptoUtil.IsProtectedDataSupported)
{
if (bProtect)
return ProtectedData.Protect(pb, pbOptEntropy, s);
return ProtectedData.Unprotect(pb, pbOptEntropy, s);
}
Debug.Assert(false);
byte[] pbCopy = new byte[pb.Length];
Array.Copy(pb, pbCopy, pb.Length);
return pbCopy;
}
}
}

View File

@@ -46,6 +46,8 @@ namespace KeePassLib.Cryptography.KeyDerivation
private const ulong NbBlockSizeInQW = NbBlockSize / 8UL;
private const ulong NbSyncPoints = 4;
private const ulong NbAddressesInBlock = 128;
private const int NbPreHashDigestLength = 64;
private const int NbPreHashSeedLength = NbPreHashDigestLength + 8;
@@ -56,6 +58,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
private sealed class Argon2Ctx
{
public Argon2Type Type = Argon2Type.D;
public uint Version = 0;
public ulong Lanes = 0;
@@ -89,9 +92,9 @@ namespace KeePassLib.Cryptography.KeyDerivation
}
}
private static byte[] Argon2d(byte[] pbMsg, byte[] pbSalt, uint uParallel,
ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey,
byte[] pbAssocData)
private byte[] Argon2Transform(byte[] pbMsg, byte[] pbSalt, uint uParallel,
ulong uMem, ulong uIt, int cbOut, uint uVersion, byte[] pbSecretKey,
byte[] pbAssocData)
{
pbSecretKey = (pbSecretKey ?? MemUtil.EmptyByteArray);
pbAssocData = (pbAssocData ?? MemUtil.EmptyByteArray);
@@ -101,6 +104,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
#endif
Argon2Ctx ctx = new Argon2Ctx();
ctx.Type = m_t;
ctx.Version = uVersion;
ctx.Lanes = uParallel;
@@ -137,7 +141,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
MemUtil.UInt32ToBytesEx(uVersion, pbBuf, 0);
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
MemUtil.UInt32ToBytesEx(0, pbBuf, 0); // Argon2d type = 0
MemUtil.UInt32ToBytesEx((uint)m_t, pbBuf, 0);
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
MemUtil.UInt32ToBytesEx((uint)pbMsg.Length, pbBuf, 0);
h.TransformBlock(pbBuf, 0, pbBuf.Length, pbBuf, 0);
@@ -487,18 +491,43 @@ namespace KeePassLib.Cryptography.KeyDerivation
private static void FillSegmentThr(object o)
{
Argon2ThreadInfo ti = (o as Argon2ThreadInfo);
if(ti == null) { Debug.Assert(false); return; }
if (ti == null) { Debug.Assert(false); return; }
try
{
Argon2Ctx ctx = ti.Context;
if(ctx == null) { Debug.Assert(false); return; }
if (ctx == null) { Debug.Assert(false); return; }
Debug.Assert(ctx.Version >= MinVersion);
bool bCanXor = (ctx.Version >= 0x13U);
ulong[] pbR = new ulong[NbBlockSizeInQW];
ulong[] pbTmp = new ulong[NbBlockSizeInQW];
ulong[] pbAddrInputZero = null;
bool bDataIndependentAddr = ((ctx.Type == Argon2Type.ID) &&
(ti.Pass == 0) && (ti.Slice < (NbSyncPoints / 2)));
if (bDataIndependentAddr)
{
pbAddrInputZero = new ulong[NbBlockSizeInQW * 3];
const int iInput = (int)NbBlockSizeInQW;
pbAddrInputZero[iInput] = ti.Pass;
pbAddrInputZero[iInput + 1] = ti.Lane;
pbAddrInputZero[iInput + 2] = ti.Slice;
pbAddrInputZero[iInput + 3] = ctx.MemoryBlocks;
pbAddrInputZero[iInput + 4] = ctx.TCost;
pbAddrInputZero[iInput + 5] = (ulong)ctx.Type;
}
ulong uStart = 0;
if((ti.Pass == 0) && (ti.Slice == 0)) uStart = 2;
if ((ti.Pass == 0) && (ti.Slice == 0))
{
uStart = 2;
if (bDataIndependentAddr)
NextAddresses(pbAddrInputZero, pbR, pbTmp);
}
ulong uCur = (ti.Lane * ctx.LaneLength) + (ti.Slice *
ctx.SegmentLength) + uStart;
@@ -506,17 +535,23 @@ namespace KeePassLib.Cryptography.KeyDerivation
ulong uPrev = (((uCur % ctx.LaneLength) == 0) ?
(uCur + ctx.LaneLength - 1UL) : (uCur - 1UL));
ulong[] pbR = new ulong[NbBlockSizeInQW];
ulong[] pbTmp = new ulong[NbBlockSizeInQW];
for(ulong i = uStart; i < ctx.SegmentLength; ++i)
for (ulong i = uStart; i < ctx.SegmentLength; ++i)
{
if((uCur % ctx.LaneLength) == 1)
if ((uCur % ctx.LaneLength) == 1)
uPrev = uCur - 1UL;
ulong uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW];
ulong uPseudoRand;
if (bDataIndependentAddr)
{
ulong iMod = i % NbAddressesInBlock;
if (iMod == 0)
NextAddresses(pbAddrInputZero, pbR, pbTmp);
uPseudoRand = pbAddrInputZero[iMod];
}
else uPseudoRand = ctx.Mem[uPrev * NbBlockSizeInQW];
ulong uRefLane = (uPseudoRand >> 32) % ctx.Lanes;
if((ti.Pass == 0) && (ti.Slice == 0))
if ((ti.Pass == 0) && (ti.Slice == 0))
uRefLane = ti.Lane;
ti.Index = i;
@@ -536,11 +571,12 @@ namespace KeePassLib.Cryptography.KeyDerivation
MemUtil.ZeroArray<ulong>(pbR);
MemUtil.ZeroArray<ulong>(pbTmp);
if (pbAddrInputZero != null) MemUtil.ZeroArray<ulong>(pbAddrInputZero);
}
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
try { ti.Finished.Set(); }
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
}
#if ARGON2_B2ROUND_ARRAYS
@@ -610,6 +646,19 @@ namespace KeePassLib.Cryptography.KeyDerivation
XorBlock(pMem, uNext, pbR, 0);
}
private static void NextAddresses(ulong[] pbAddrInputZero, ulong[] pbR,
ulong[] pbTmp)
{
// pbAddrInputZero contains an address block, an input block and a zero block
const ulong uAddr = 0;
const ulong uInput = NbBlockSizeInQW;
const ulong uZero = NbBlockSizeInQW * 2;
++pbAddrInputZero[uInput + 6];
FillBlock(pbAddrInputZero, uZero, uInput, uAddr, false, pbR, pbTmp);
FillBlock(pbAddrInputZero, uZero, uAddr, uAddr, false, pbR, pbTmp);
}
private static byte[] FinalHash(Argon2Ctx ctx, int cbOut, Blake2b h)
{
ulong[] pqBlockHash = new ulong[NbBlockSizeInQW];

View File

@@ -25,11 +25,22 @@ using System.Text;
namespace KeePassLib.Cryptography.KeyDerivation
{
public sealed partial class Argon2Kdf : KdfEngine
public enum Argon2Type
{
// The values must be the same as in the Argon2 specification
D = 0,
ID = 2
}
public sealed partial class Argon2Kdf : KdfEngine
{
private static readonly PwUuid g_uuid = new PwUuid(new byte[] {
0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B,
0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C });
private static readonly PwUuid g_uuidD = new PwUuid(new byte[] {
0xEF, 0x63, 0x6D, 0xDF, 0x8C, 0x29, 0x44, 0x4B,
0x91, 0xF7, 0xA9, 0xA4, 0x03, 0xE3, 0x0A, 0x0C });
private static readonly PwUuid g_uuidID = new PwUuid(new byte[] {
0x9E, 0x29, 0x8B, 0x19, 0x56, 0xDB, 0x47, 0x73,
0xB2, 0x3D, 0xFC, 0x3E, 0xC6, 0xF0, 0xA1, 0xE6 });
public const string ParamSalt = "S"; // Byte[]
public const string ParamParallelism = "P"; // UInt32
@@ -55,28 +66,37 @@ namespace KeePassLib.Cryptography.KeyDerivation
internal const uint MinParallelism = 1;
internal const uint MaxParallelism = (1 << 24) - 1;
internal const ulong DefaultIterations = 2;
internal const ulong DefaultMemory = 1024 * 1024; // 1 MB
internal const uint DefaultParallelism = 2;
internal const ulong DefaultIterations = 2;
internal const ulong DefaultMemory = 64 * 1024 * 1024; // 64 MB
internal const uint DefaultParallelism = 2;
public override PwUuid Uuid
{
get { return g_uuid; }
}
private readonly Argon2Type m_t;
public override string Name
{
get { return "Argon2"; }
}
public override PwUuid Uuid
{
get { return ((m_t == Argon2Type.D) ? g_uuidD : g_uuidID); }
}
public override string Name
{
get { return ((m_t == Argon2Type.D) ? "Argon2d" : "Argon2id"); }
}
public Argon2Kdf() : this(Argon2Type.D)
{
}
public Argon2Kdf(Argon2Type t)
{
if ((t != Argon2Type.D) && (t != Argon2Type.ID))
throw new NotSupportedException();
m_t = t;
}
public override byte[] GetSeed(KdfParameters p)
{ return p.GetByteArray(ParamSalt); }
public Argon2Kdf()
{
}
public override KdfParameters GetDefaultParameters()
public override KdfParameters GetDefaultParameters()
{
KdfParameters p = base.GetDefaultParameters();
@@ -92,7 +112,7 @@ namespace KeePassLib.Cryptography.KeyDerivation
public override void Randomize(KdfParameters p)
{
if(p == null) { Debug.Assert(false); return; }
Debug.Assert(g_uuid.Equals(p.KdfUuid));
Debug.Assert(p.KdfUuid.Equals(this.Uuid));
byte[] pb = CryptoRandom.Instance.GetRandomBytes(32);
p.SetByteArray(ParamSalt, pb);
@@ -128,46 +148,59 @@ namespace KeePassLib.Cryptography.KeyDerivation
byte[] pbSecretKey = p.GetByteArray(ParamSecretKey);
byte[] pbAssocData = p.GetByteArray(ParamAssocData);
if (pbSecretKey != null) {
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbSecretKey");
byte[] pbRet;
if (m_t == Argon2Type.ID)
{
pbRet = Argon2Transform(pbMsg, pbSalt, uPar, uMem,
uIt, 32, v, pbSecretKey, pbAssocData);
}
else
{
if (pbSecretKey != null)
{
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbSecretKey");
}
if (pbAssocData != null)
{
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbAssocData");
}
/*
byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt,
32, v, pbSecretKey, pbAssocData);
*/
IntPtr msgPtr = Marshal.AllocHGlobal(pbMsg.Length);
IntPtr saltPtr = Marshal.AllocHGlobal(pbSalt.Length);
IntPtr retPtr = Marshal.AllocHGlobal(32);
Marshal.Copy(pbMsg, 0, msgPtr, pbMsg.Length);
Marshal.Copy(pbSalt, 0, saltPtr, pbSalt.Length);
const UInt32 Argon2_d = 0;
int ret = argon2_hash(
(UInt32)uIt, (UInt32)(uMem / 1024), uPar,
msgPtr, (IntPtr)pbMsg.Length,
saltPtr, (IntPtr)pbSalt.Length,
retPtr, (IntPtr)32,
(IntPtr)0, (IntPtr)0, Argon2_d, v);
if (ret != 0)
{
throw new Exception("argon2_hash failed with " + ret);
}
pbRet = new byte[32];
Marshal.Copy(retPtr, pbRet, 0, 32);
Marshal.FreeHGlobal(msgPtr);
Marshal.FreeHGlobal(saltPtr);
Marshal.FreeHGlobal(retPtr);
}
if (pbAssocData != null) {
throw new ArgumentOutOfRangeException("Unsupported configuration: non-null pbAssocData");
}
/*
byte[] pbRet = Argon2d(pbMsg, pbSalt, uPar, uMem, uIt,
32, v, pbSecretKey, pbAssocData);
*/
IntPtr msgPtr = Marshal.AllocHGlobal(pbMsg.Length);
IntPtr saltPtr = Marshal.AllocHGlobal(pbSalt.Length);
IntPtr retPtr = Marshal.AllocHGlobal(32);
Marshal.Copy(pbMsg, 0, msgPtr, pbMsg.Length);
Marshal.Copy(pbSalt, 0, saltPtr, pbSalt.Length);
const UInt32 Argon2_d = 0;
int ret = argon2_hash(
(UInt32)uIt, (UInt32)(uMem / 1024), uPar,
msgPtr, (IntPtr)pbMsg.Length,
saltPtr, (IntPtr)pbSalt.Length,
retPtr, (IntPtr)32,
(IntPtr)0, (IntPtr)0, Argon2_d, v);
if (ret != 0) {
throw new Exception("argon2_hash failed with " + ret);
}
byte[] pbRet = new byte[32];
Marshal.Copy(retPtr, pbRet, 0, 32);
Marshal.FreeHGlobal(msgPtr);
Marshal.FreeHGlobal(saltPtr);
Marshal.FreeHGlobal(retPtr);
if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect();
if(uMem > (100UL * 1024UL * 1024UL)) GC.Collect();
return pbRet;
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2020 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2020 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -41,10 +41,11 @@ namespace KeePassLib.Cryptography.KeyDerivation
private static void EnsureInitialized()
{
if(g_l.Count > 0) return;
if(g_l.Count != 0) return;
g_l.Add(new AesKdf());
g_l.Add(new Argon2Kdf());
g_l.Add(new Argon2Kdf(Argon2Type.D));
g_l.Add(new Argon2Kdf(Argon2Type.ID));
}
internal static KdfParameters GetDefaultParameters()

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,17 +21,22 @@ using System;
namespace KeePassLib.Interfaces
{
public interface IStructureItem : ITimeLogger // Provides LocationChanged
{
PwUuid Uuid
{
get;
set;
}
public interface IStructureItem : ITimeLogger // Provides LocationChanged
{
PwUuid Uuid
{
get;
set;
}
PwGroup ParentGroup
{
get;
}
}
}
PwGroup ParentGroup
{
get;
}
PwUuid PreviousParentGroup
{
get;
}
}
}

View File

@@ -50,6 +50,7 @@
<HintPath>..\ProtoBuf\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Security" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Mono.Android" />
@@ -71,7 +72,9 @@
<Compile Include="Cryptography\KeyDerivation\KdfParameters.cs" />
<Compile Include="Cryptography\KeyDerivation\KdfPool.cs" />
<Compile Include="IDatabaseFormat.cs" />
<Compile Include="Keys\KcpKeyFile.Xml.cs" />
<Compile Include="Kp2aLog.cs" />
<Compile Include="PwGroup.Search.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Resources\KLRes.Generated.cs" />
<Compile Include="Resources\KSRes.Generated.cs" />
@@ -160,6 +163,7 @@
<Compile Include="Utility\UrlUtil.cs" />
<Compile Include="Utility\TimeUtil.cs" />
<Compile Include="Delegates\Handlers.cs" />
<Compile Include="Utility\XmlUtilEx.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />

View File

@@ -0,0 +1,281 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using KeePassLib.Cryptography;
using KeePassLib.Resources;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace KeePassLib.Keys
{
[XmlType("KeyFile")]
public sealed class KfxFile
{
private const ulong KfxVersionCriticalMask = 0xFFFF000000000000UL;
private const int KfxDataHashLength = 4;
private KfxMeta m_meta = new KfxMeta();
public KfxMeta Meta
{
get { return m_meta; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_meta = value;
}
}
private KfxKey m_key = new KfxKey();
public KfxKey Key
{
get { return m_key; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_key = value;
}
}
public static KfxFile Create(ulong uVersion, byte[] pbKey, byte[] pbHash)
{
if(pbKey == null) throw new ArgumentNullException("pbKey");
if(pbKey.Length == 0) throw new ArgumentOutOfRangeException("pbKey");
if(uVersion == 0) uVersion = 0x0002000000000000;
// Null hash: generate one, empty hash: store no hash
if(pbHash == null) pbHash = HashData(pbKey);
VerifyHash(pbKey, pbHash);
KfxFile kf = new KfxFile();
if(uVersion == 0x0001000000000000)
kf.Meta.Version = "1.00"; // KeePass <= 2.46 used two zeros
else kf.Meta.Version = StrUtil.VersionToString(uVersion, 2);
if(uVersion == 0x0001000000000000)
kf.Key.Data.Value = Convert.ToBase64String(pbKey);
else if(uVersion == 0x0002000000000000)
{
kf.Key.Data.Value = FormatKeyHex(pbKey, 3);
if(pbHash.Length != 0)
kf.Key.Data.Hash = MemUtil.ByteArrayToHexString(pbHash);
}
else throw new NotSupportedException(KLRes.FileVersionUnsupported);
return kf;
}
internal static KfxFile Create(ulong uVersion, string strKey, string strHash)
{
byte[] pbKey = ParseKey(uVersion, strKey);
byte[] pbHash = ((strHash != null) ? ParseHash(strHash) : null);
return Create(uVersion, pbKey, pbHash);
}
internal static bool CanLoad(string strFilePath)
{
if(string.IsNullOrEmpty(strFilePath)) { Debug.Assert(false); return false; }
try
{
IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath);
using(Stream s = IOConnection.OpenRead(ioc))
{
return (Load(s) != null);
}
}
catch(Exception) { }
return false;
}
public static KfxFile Load(Stream s)
{
return XmlUtilEx.Deserialize<KfxFile>(s);
}
public void Save(Stream s)
{
XmlUtilEx.Serialize<KfxFile>(s, this, true);
}
private static string FormatKeyHex(byte[] pb, int cTabs)
{
StringBuilder sb = new StringBuilder();
string str = MemUtil.ByteArrayToHexString(pb);
for(int i = 0; i < str.Length; ++i)
{
if((i & 0x1F) == 0)
{
sb.AppendLine();
sb.Append('\t', cTabs);
}
else if((i & 0x07) == 0) sb.Append(' ');
sb.Append(str[i]);
}
sb.AppendLine();
if(cTabs > 0) sb.Append('\t', cTabs - 1);
return sb.ToString();
}
private ulong GetVersion()
{
string str = m_meta.Version;
if(string.IsNullOrEmpty(str)) return 0;
return StrUtil.ParseVersion(str);
}
public byte[] GetKey()
{
ulong uVersion = GetVersion();
byte[] pbKey = ParseKey(uVersion, m_key.Data.Value);
if((pbKey == null) || (pbKey.Length == 0))
throw new FormatException(KLRes.FileCorrupted);
byte[] pbHash = ParseHash(m_key.Data.Hash);
VerifyHash(pbKey, pbHash);
return pbKey;
}
private static byte[] HashData(byte[] pb)
{
return MemUtil.Mid(CryptoUtil.HashSha256(pb), 0, KfxDataHashLength);
}
private static void VerifyHash(byte[] pbKey, byte[] pbHash)
{
// The hash is optional; empty hash means success
if((pbHash == null) || (pbHash.Length == 0)) return;
byte[] pbHashCmp = HashData(pbKey);
if(!MemUtil.ArraysEqual(pbHash, pbHashCmp))
throw new Exception("Keyfile hash mismatch!");
}
private static byte[] ParseKey(ulong uVersion, string strKey)
{
if(strKey == null) throw new ArgumentNullException("strKey");
strKey = StrUtil.RemoveWhiteSpace(strKey);
if(string.IsNullOrEmpty(strKey)) return MemUtil.EmptyByteArray;
uVersion &= KfxVersionCriticalMask;
byte[] pbKey;
if(uVersion == 0x0001000000000000)
pbKey = Convert.FromBase64String(strKey);
else if(uVersion == 0x0002000000000000)
pbKey = ParseHex(strKey);
else throw new NotSupportedException(KLRes.FileVersionUnsupported);
return pbKey;
}
private static byte[] ParseHash(string strHash)
{
return ParseHex(strHash);
}
private static byte[] ParseHex(string str)
{
if(str == null) throw new ArgumentNullException("str");
if(str.Length == 0) return MemUtil.EmptyByteArray;
if(((str.Length & 1) != 0) || !StrUtil.IsHexString(str, true))
throw new FormatException();
return MemUtil.HexStringToByteArray(str);
}
}
public sealed class KfxMeta
{
private string m_strVersion = string.Empty;
[DefaultValue("")]
public string Version
{
get { return m_strVersion; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strVersion = value;
}
}
}
public sealed class KfxKey
{
private KfxData m_data = new KfxData();
public KfxData Data
{
get { return m_data; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_data = value;
}
}
}
public sealed class KfxData
{
private string m_strHash = string.Empty;
[DefaultValue("")]
[XmlAttribute("Hash")]
public string Hash
{
get { return m_strHash; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strHash = value;
}
}
private string m_strValue = string.Empty;
[DefaultValue("")]
[XmlText]
public string Value
{
get { return m_strValue; }
set
{
if(value == null) throw new ArgumentNullException("value");
m_strValue = value;
}
}
}
}

View File

@@ -127,10 +127,9 @@ namespace KeePassLib.Keys
#endif
}
byte[] pbKey = LoadXmlKeyFile(pbFileData);
if(pbKey == null) pbKey = LoadKeyFile(pbFileData);
byte[] pbKey = LoadKeyFile(pbFileData);
if(pbKey == null) throw new InvalidOperationException();
if (pbKey == null) throw new InvalidOperationException();
m_ioc = iocKeyFile;
m_pbKeyData = new ProtectedBinary(true, pbKey);
@@ -150,54 +149,60 @@ namespace KeePassLib.Keys
// }
private static byte[] LoadKeyFile(byte[] pbFileData)
{
if(pbFileData == null) { Debug.Assert(false); return null; }
private static byte[] LoadKeyFile(byte[] pbFileData)
{
if (pbFileData == null) throw new ArgumentNullException("pbFileData");
int iLength = pbFileData.Length;
byte[] pbKey = LoadKeyFileXml(pbFileData);
if (pbKey != null) return pbKey;
byte[] pbKey = null;
if(iLength == 32) pbKey = LoadBinaryKey32(pbFileData);
else if(iLength == 64) pbKey = LoadHexKey32(pbFileData);
int cb = pbFileData.Length;
if (cb == 32) return pbFileData;
if(pbKey == null)
pbKey = CryptoUtil.HashSha256(pbFileData);
if (cb == 64)
{
pbKey = LoadKeyFileHex(pbFileData);
if (pbKey != null) return pbKey;
}
return pbKey;
}
return CryptoUtil.HashSha256(pbFileData);
}
private static byte[] LoadBinaryKey32(byte[] pbFileData)
{
if(pbFileData == null) { Debug.Assert(false); return null; }
if(pbFileData.Length != 32) { Debug.Assert(false); return null; }
private static byte[] LoadKeyFileXml(byte[] pbFileData)
{
KfxFile kf;
try
{
using (MemoryStream ms = new MemoryStream(pbFileData, false))
{
kf = KfxFile.Load(ms);
}
}
catch (Exception) { return null; }
return pbFileData;
}
// We have a syntactically valid XML key file;
// failing to verify the key should throw an exception
return ((kf != null) ? kf.GetKey() : null);
}
private static byte[] LoadHexKey32(byte[] pbFileData)
{
if(pbFileData == null) { Debug.Assert(false); return null; }
if(pbFileData.Length != 64) { Debug.Assert(false); return null; }
private static byte[] LoadKeyFileHex(byte[] pbFileData)
{
if (pbFileData == null) { Debug.Assert(false); return null; }
try
{
if(!StrUtil.IsHexString(pbFileData, true)) return null;
try
{
int cc = pbFileData.Length;
if ((cc & 1) != 0) { Debug.Assert(false); return null; }
string strHex = StrUtil.Utf8.GetString(pbFileData);
byte[] pbKey = MemUtil.HexStringToByteArray(strHex);
if((pbKey == null) || (pbKey.Length != 32))
{
Debug.Assert(false);
return null;
}
if (!StrUtil.IsHexString(pbFileData, true)) return null;
return pbKey;
}
catch(Exception) { Debug.Assert(false); }
return null;
}
string strHex = StrUtil.Utf8.GetString(pbFileData);
return MemUtil.HexStringToByteArray(strHex);
}
catch (Exception) { Debug.Assert(false); }
return null;
}
/// <summary>
/// Create a new, random key-file.
/// </summary>

View File

@@ -118,5 +118,10 @@ namespace keepass2android
sendIntent.SetType("text/plain");
ctx.StartActivity(Intent.CreateChooser(sendIntent, "Send log to..."));
}
}
public static void LogTask(object task, string activityName)
{
Log($"Task in activity {activityName} changed to {task?.GetType()?.Name ?? "null"}");
}
}
}

View File

@@ -1,8 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 Dominik Reichl <dominik.reichl@t-online.de>
Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,61 +18,106 @@
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
#if !KeePassUAP
using System.Drawing;
using System.IO;
#endif
using KeePassLib.Utility;
namespace KeePassLib
{
/// <summary>
/// Custom icon. <c>PwCustomIcon</c> objects are immutable.
/// </summary>
public sealed class PwCustomIcon
{
private PwUuid m_pwUuid;
private byte[] m_pbImageDataPng;
private Android.Graphics.Bitmap m_pCachedImage;
// Recommended maximum sizes, not obligatory
internal const int MaxWidth = 128;
internal const int MaxHeight = 128;
private readonly PwUuid m_uuid;
private readonly byte[] m_pbImageDataPng;
private string m_strName = string.Empty;
private DateTime? m_odtLastMod = null;
private Dictionary<long, Android.Graphics.Bitmap> m_dImageCache = new Dictionary<long, Android.Graphics.Bitmap>();
public PwUuid Uuid
{
get { return m_pwUuid; }
get { return m_uuid; }
}
public byte[] ImageDataPng
{
get { return m_pbImageDataPng; }
// When allowing 'set', do not copy the cache in 'Clone'
}
public string Name
{
get { return m_strName; }
set
{
if (value == null) throw new ArgumentNullException("value");
m_strName = value;
}
}
public DateTime? LastModificationTime
{
get { return m_odtLastMod; }
set { m_odtLastMod = value; }
}
[Obsolete("Use GetImage instead.")]
public Android.Graphics.Bitmap Image
{
get { return m_pCachedImage; }
get { return GetImage(); } // Backward compatibility
}
public PwCustomIcon(PwUuid pu, byte[] pbImageDataPng)
{
if (pu == null) { Debug.Assert(false); throw new ArgumentNullException("pu"); }
if (pu.Equals(PwUuid.Zero)) { Debug.Assert(false); throw new ArgumentOutOfRangeException("pu"); }
if (pbImageDataPng == null) { Debug.Assert(false); throw new ArgumentNullException("pbImageDataPng"); }
m_uuid = pu;
m_pbImageDataPng = pbImageDataPng;
}
public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng)
private static long GetKey(int w, int h)
{
Debug.Assert(pwUuid != null);
if(pwUuid == null) throw new ArgumentNullException("pwUuid");
Debug.Assert(!pwUuid.Equals(PwUuid.Zero));
if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0");
return (((long)w << 32) ^ (long)h);
}
Debug.Assert(pbImageDataPng != null);
if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng");
/// <summary>
/// Get the icon as an <c>Image</c> (original size).
/// </summary>
public Android.Graphics.Bitmap GetImage()
{
const long lKey = -1;
m_pwUuid = pwUuid;
m_pbImageDataPng = pbImageDataPng;
Android.Graphics.Bitmap img;
if (m_dImageCache.TryGetValue(lKey, out img)) return img;
#if !KeePassLibSD
// MemoryStream ms = new MemoryStream(m_pbImageDataPng, false);
// m_pCachedImage = Image.FromStream(ms);
// ms.Close();
m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng);
#else
m_pCachedImage = null;
#endif
try { img = GfxUtil.LoadImage(m_pbImageDataPng); }
catch (Exception) { Debug.Assert(false); }
m_dImageCache[lKey] = img;
return img;
}
internal PwCustomIcon Clone()
{
PwCustomIcon ico = new PwCustomIcon(m_uuid, m_pbImageDataPng);
ico.m_strName = m_strName;
ico.m_odtLastMod = m_odtLastMod;
ico.m_dImageCache = m_dImageCache; // Same image data
return ico;
}
}
}

View File

@@ -97,7 +97,7 @@ namespace KeePassLib
private int m_nHistoryMaxItems = DefaultHistoryMaxItems;
private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
private StringDictionaryEx m_dCustomData = new StringDictionaryEx(true);
private VariantDictionary m_dPublicCustomData = new VariantDictionary();
private byte[] m_pbHashOfFileOnDisk = null;
@@ -690,9 +690,9 @@ namespace KeePassLib
public void MergeIn(PwDatabase pdSource, PwMergeMethod mm,
IStatusLogger slStatus)
{
if(pdSource == null) throw new ArgumentNullException("pdSource");
if (pdSource == null) throw new ArgumentNullException("pdSource");
if(mm == PwMergeMethod.CreateNewUuids)
if (mm == PwMergeMethod.CreateNewUuids)
{
pdSource.RootGroup.Uuid = new PwUuid(true);
pdSource.RootGroup.CreateNewItemUuids(true, true, true);
@@ -707,7 +707,7 @@ namespace KeePassLib
PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup);
PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup);
GroupHandler ghSrc = delegate(PwGroup pg)
GroupHandler ghSrc = delegate (PwGroup pg)
{
// if(pg == pdSource.m_pgRootGroup) return true;
@@ -716,11 +716,11 @@ namespace KeePassLib
// pool should not be modified)
PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true);
if(pgLocal == null)
if (pgLocal == null)
{
PwGroup pgSourceParent = pg.ParentGroup;
PwGroup pgLocalContainer;
if(pgSourceParent == null)
if (pgSourceParent == null)
{
// pg is the root group of pdSource, and no corresponding
// local group was found; create the group within the
@@ -728,17 +728,23 @@ namespace KeePassLib
Debug.Assert(pg == pdSource.m_pgRootGroup);
pgLocalContainer = m_pgRootGroup;
}
else if(pgSourceParent == pdSource.m_pgRootGroup)
else if (pgSourceParent == pdSource.m_pgRootGroup)
pgLocalContainer = m_pgRootGroup;
else
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
Debug.Assert(pgLocalContainer != null);
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
PwGroup pgNew = new PwGroup(false, false);
pgNew.Uuid = pg.Uuid;
pgNew.AssignProperties(pg, false, true);
if (!pgLocalContainer.CanAddGroup(pgNew))
{
Debug.Assert(false);
pgLocalContainer = m_pgRootGroup;
pgLocalContainer.CheckCanAddGroup(pgNew);
}
// pgLocalContainer.AddGroup(pgNew, true);
InsertObjectAtBestPos<PwGroup>(pgLocalContainer.Groups, pgNew, ppSrc);
pgNew.ParentGroup = pgLocalContainer;
@@ -747,9 +753,9 @@ namespace KeePassLib
{
Debug.Assert(mm != PwMergeMethod.CreateNewUuids);
if(mm == PwMergeMethod.OverwriteExisting)
if (mm == PwMergeMethod.OverwriteExisting)
pgLocal.AssignProperties(pg, false, false);
else if((mm == PwMergeMethod.OverwriteIfNewer) ||
else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
(mm == PwMergeMethod.Synchronize))
{
pgLocal.AssignProperties(pg, true, false);
@@ -760,23 +766,23 @@ namespace KeePassLib
return ((slStatus != null) ? slStatus.ContinueWork() : true);
};
EntryHandler ehSrc = delegate(PwEntry pe)
EntryHandler ehSrc = delegate (PwEntry pe)
{
// PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true);
PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry);
Debug.Assert(object.ReferenceEquals(peLocal,
m_pgRootGroup.FindEntry(pe.Uuid, true)));
if(peLocal == null)
if (peLocal == null)
{
PwGroup pgSourceParent = pe.ParentGroup;
PwGroup pgLocalContainer;
if(pgSourceParent == pdSource.m_pgRootGroup)
if (pgSourceParent == pdSource.m_pgRootGroup)
pgLocalContainer = m_pgRootGroup;
else
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
Debug.Assert(pgLocalContainer != null);
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
PwEntry peNew = new PwEntry(false, false);
peNew.Uuid = pe.Uuid;
@@ -796,19 +802,19 @@ namespace KeePassLib
bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None);
bool bOrgBackup = !bEquals;
if(mm != PwMergeMethod.OverwriteExisting)
if (mm != PwMergeMethod.OverwriteExisting)
bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0);
bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true);
if(bOrgBackup) peLocal.CreateBackup(null); // Maintain at end
if (bOrgBackup) peLocal.CreateBackup(null); // Maintain at end
bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting);
bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0);
bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true);
if(bSrcBackup) pe.CreateBackup(null); // Maintain at end
if (bSrcBackup) pe.CreateBackup(null); // Maintain at end
if(mm == PwMergeMethod.OverwriteExisting)
if (mm == PwMergeMethod.OverwriteExisting)
peLocal.AssignProperties(pe, false, false, false);
else if((mm == PwMergeMethod.OverwriteIfNewer) ||
else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
(mm == PwMergeMethod.Synchronize))
{
peLocal.AssignProperties(pe, true, false, false);
@@ -822,13 +828,13 @@ namespace KeePassLib
};
ghSrc(pdSource.RootGroup);
if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc))
if (!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc))
throw new InvalidOperationException();
IStatusLogger slPrevStatus = m_slStatus;
m_slStatus = slStatus;
if(mm == PwMergeMethod.Synchronize)
if (mm == PwMergeMethod.Synchronize)
{
RelocateGroups(ppOrg, ppSrc);
RelocateEntries(ppOrg, ppSrc);
@@ -838,24 +844,24 @@ namespace KeePassLib
MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc);
ppOrg = null; // Pools are now invalid, because the location
ppSrc = null; // changed times have been merged in
// Delete *after* relocating, because relocating might
// empty some groups that are marked for deletion (and
// objects that weren't relocated yet might prevent the
// deletion)
Dictionary<PwUuid, PwDeletedObject> dOrgDel = CreateDeletedObjectsPool();
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel);
ApplyDeletions(m_pgRootGroup, dOrgDel);
// The list and the dictionary should be kept in sync
Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count);
}
// Delete *after* relocating, because relocating might empty
// some groups that are marked for deletion (and objects
// that weren't relocated yet might prevent the deletion)
Dictionary<PwUuid, PwDeletedObject> dDel = CreateDeletedObjectsPool();
if (mm == PwMergeMethod.Synchronize)
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dDel);
ApplyDeletions(m_pgRootGroup, dDel);
// The list and the dictionary should be kept in sync
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
// Must be called *after* merging groups, because group UUIDs
// are required for recycle bin and entry template UUIDs
MergeInDbProperties(pdSource, mm);
MergeInCustomIcons(pdSource);
MergeInCustomIcons(pdSource, dDel);
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
MaintainBackups();
@@ -863,15 +869,79 @@ namespace KeePassLib
m_slStatus = slPrevStatus;
}
private void MergeInCustomIcons(PwDatabase pdSource)
{
foreach(PwCustomIcon pwci in pdSource.CustomIcons)
{
if(GetCustomIconIndex(pwci.Uuid) >= 0) continue;
m_vCustomIcons.Add(pwci); // PwCustomIcon is immutable
m_bUINeedsIconUpdate = true;
private void MergeInCustomIcons(PwDatabase pdSource,
Dictionary<PwUuid, PwDeletedObject> dDel)
{
bool bIconsMod = false;
Dictionary<PwUuid, int> d = new Dictionary<PwUuid, int>();
for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
d[m_vCustomIcons[i].Uuid] = i;
Debug.Assert(d.Count == m_vCustomIcons.Count); // UUIDs unique
foreach (PwCustomIcon ciS in pdSource.m_vCustomIcons)
{
int iT;
if (d.TryGetValue(ciS.Uuid, out iT))
{
PwCustomIcon ciT = m_vCustomIcons[iT];
DateTime? odtT = ciT.LastModificationTime;
DateTime? odtS = ciS.LastModificationTime;
if (odtT.HasValue && odtS.HasValue)
{
if (odtT.Value >= odtS.Value) continue;
}
else if (odtT.HasValue) continue;
else if (!odtS.HasValue) continue; // Both no time
m_vCustomIcons[iT] = ciS.Clone();
}
else
{
d[ciS.Uuid] = m_vCustomIcons.Count;
m_vCustomIcons.Add(ciS.Clone());
}
bIconsMod = true;
}
List<PwDeletedObject> lObsoleteDel = new List<PwDeletedObject>();
foreach (KeyValuePair<PwUuid, PwDeletedObject> kvpDel in dDel)
{
int iT;
if (d.TryGetValue(kvpDel.Key, out iT))
{
PwCustomIcon ci = m_vCustomIcons[iT];
if (ci == null) { Debug.Assert(false); continue; } // Dup. del. obj.?
DateTime? odt = ci.LastModificationTime;
if (odt.HasValue && (odt.Value > kvpDel.Value.DeletionTime))
lObsoleteDel.Add(kvpDel.Value);
else
{
m_vCustomIcons[iT] = null; // Preserve indices, removed below
bIconsMod = true;
}
}
}
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
m_vCustomIcons.RemoveAll(f);
foreach (PwDeletedObject pdo in lObsoleteDel)
{
// Prevent future deletion attempts
if (!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); }
if (!dDel.Remove(pdo.Uuid)) { Debug.Assert(false); }
}
if (bIconsMod) m_bUINeedsIconUpdate = true;
FixCustomIconRefs();
}
private Dictionary<PwUuid, PwDeletedObject> CreateDeletedObjectsPool()
@@ -1212,7 +1282,9 @@ namespace KeePassLib
PwObjectBlock<T> b = new PwObjectBlock<T>();
DateTime dtLoc;
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc);
PwUuid puPrevParent;
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc,
out puPrevParent);
b.Add(t, dtLoc, pPool);
lBlocks.Add(b);
@@ -1247,7 +1319,7 @@ namespace KeePassLib
}
if(idSrcNext == 0) break;
pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc);
pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc, out puPrevParent);
b.Add(tNext, dtLoc, pPool);
++u;
@@ -1260,28 +1332,31 @@ namespace KeePassLib
}
private static PwObjectPoolEx GetBestPool<T>(T t, PwObjectPoolEx ppOrg,
PwObjectPoolEx ppSrc, out DateTime dtLoc)
PwObjectPoolEx ppSrc, out DateTime dtLoc, out PwUuid puPrevParent)
where T : class, ITimeLogger, IStructureItem, IDeepCloneable<T>
{
PwObjectPoolEx p = null;
dtLoc = TimeUtil.SafeMinValueUtc;
PwObjectPoolEx p = null;
dtLoc = TimeUtil.SafeMinValueUtc;
puPrevParent = PwUuid.Zero;
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
if(ptOrg != null)
{
dtLoc = ptOrg.LocationChanged;
p = ppOrg;
}
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
if (ptOrg != null)
{
dtLoc = ptOrg.LocationChanged;
puPrevParent = ptOrg.PreviousParentGroup;
p = ppOrg;
}
IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid);
if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
{
dtLoc = ptSrc.LocationChanged;
p = ppSrc;
}
IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid);
if ((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
{
dtLoc = ptSrc.LocationChanged;
puPrevParent = ptSrc.PreviousParentGroup;
p = ppSrc;
}
Debug.Assert(p != null);
return p;
Debug.Assert(p != null);
return p;
}
private static int FindLocationChangedPivot<T>(List<PwObjectBlock<T>> lBlocks,
@@ -1307,30 +1382,40 @@ namespace KeePassLib
return iPosMax;
}
private static void MergeInLocationChanged(PwGroup pg,
PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc)
{
GroupHandler gh = delegate(PwGroup pgSub)
{
DateTime dt;
if(GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt) != null)
pgSub.LocationChanged = dt;
else { Debug.Assert(false); }
return true;
};
private static void MergeInLocationChanged(PwGroup pg,
PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc)
{
GroupHandler gh = delegate (PwGroup pgSub)
{
DateTime dt;
PwUuid puPrevParent;
if (GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt,
out puPrevParent) != null)
{
pgSub.LocationChanged = dt;
pgSub.PreviousParentGroup = puPrevParent;
}
else { Debug.Assert(false); }
return true;
};
EntryHandler eh = delegate(PwEntry pe)
{
DateTime dt;
if(GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt) != null)
pe.LocationChanged = dt;
else { Debug.Assert(false); }
return true;
};
EntryHandler eh = delegate (PwEntry pe)
{
DateTime dt;
PwUuid puPrevParent;
if (GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt,
out puPrevParent) != null)
{
pe.LocationChanged = dt;
pe.PreviousParentGroup = puPrevParent;
}
else { Debug.Assert(false); }
return true;
};
gh(pg);
pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
}
gh(pg);
pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
}
private static void InsertObjectAtBestPos<T>(PwObjectList<T> lItems,
T tNew, PwObjectPoolEx ppSrc)
@@ -1445,12 +1530,18 @@ namespace KeePassLib
foreach(KeyValuePair<string, string> kvp in pdSource.m_dCustomData)
{
if(bSourceNewer || !m_dCustomData.Exists(kvp.Key))
m_dCustomData.Set(kvp.Key, kvp.Value);
m_dCustomData.Set(kvp.Key, kvp.Value, null);
}
VariantDictionary vdLocal = m_dPublicCustomData; // Backup
m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone();
if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge
// 'Clone' duplicates deep values (e.g. byte arrays)
VariantDictionary vdS = (VariantDictionary)pdSource.m_dPublicCustomData.Clone();
if (bForce || bSourceNewer)
vdS.CopyTo(m_dPublicCustomData);
else
{
m_dPublicCustomData.CopyTo(vdS);
m_dPublicCustomData = vdS;
}
}
private void MergeEntryHistory(PwEntry pe, PwEntry peSource,
@@ -1543,12 +1634,12 @@ namespace KeePassLib
/// <returns>Index of the icon.</returns>
public int GetCustomIconIndex(PwUuid pwIconId)
{
for(int i = 0; i < m_vCustomIcons.Count; ++i)
{
PwCustomIcon pwci = m_vCustomIcons[i];
if(pwci.Uuid.Equals(pwIconId))
return i;
}
for (int i = 0; i < m_vCustomIcons.Count; ++i)
{
PwCustomIcon pwci = m_vCustomIcons[i];
if (pwci.Uuid.Equals(pwIconId))
return i;
}
// Debug.Assert(false); // Do not assert
return -1;
@@ -1558,15 +1649,15 @@ namespace KeePassLib
{
if(pbPngData == null) { Debug.Assert(false); return -1; }
for(int i = 0; i < m_vCustomIcons.Count; ++i)
{
PwCustomIcon pwci = m_vCustomIcons[i];
byte[] pbEx = pwci.ImageDataPng;
if(pbEx == null) { Debug.Assert(false); continue; }
for (int i = 0; i < m_vCustomIcons.Count; ++i)
{
PwCustomIcon pwci = m_vCustomIcons[i];
byte[] pbEx = pwci.ImageDataPng;
if (pbEx == null) { Debug.Assert(false); continue; }
if(MemUtil.ArraysEqual(pbEx, pbPngData))
return i;
}
if (MemUtil.ArraysEqual(pbEx, pbPngData))
return i;
}
return -1;
}
@@ -1585,68 +1676,64 @@ namespace KeePassLib
else { Debug.Assert(false); return null; }
}
public bool DeleteCustomIcons(List<PwUuid> vUuidsToDelete)
{
Debug.Assert(vUuidsToDelete != null);
if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete");
if(vUuidsToDelete.Count <= 0) return true;
public bool DeleteCustomIcons(List<PwUuid> lUuids)
{
if (lUuids == null) { Debug.Assert(false); throw new ArgumentNullException("lUuids"); }
if (lUuids.Count == 0) return false;
GroupHandler gh = delegate(PwGroup pg)
{
PwUuid uuidThis = pg.CustomIconUuid;
if(uuidThis.Equals(PwUuid.Zero)) return true;
Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
foreach (PwUuid pu in lUuids) { dToDel[pu] = true; }
foreach(PwUuid uuidDelete in vUuidsToDelete)
{
if(uuidThis.Equals(uuidDelete))
{
pg.CustomIconUuid = PwUuid.Zero;
break;
}
}
DateTime dt = DateTime.UtcNow;
for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
{
PwUuid pu = m_vCustomIcons[i].Uuid;
if (dToDel.ContainsKey(pu))
{
m_vCustomIcons[i] = null; // Removed below
m_vDeletedObjects.Add(new PwDeletedObject(pu, dt));
}
}
return true;
};
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
m_vCustomIcons.RemoveAll(f);
EntryHandler eh = delegate(PwEntry pe)
{
RemoveCustomIconUuid(pe, vUuidsToDelete);
return true;
};
FixCustomIconRefs();
return true;
}
gh(m_pgRootGroup);
if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh))
{
Debug.Assert(false);
return false;
}
private void FixCustomIconRefs()
{
Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>();
foreach (PwCustomIcon ci in m_vCustomIcons) { d[ci.Uuid] = true; }
foreach(PwUuid pwUuid in vUuidsToDelete)
{
int nIndex = GetCustomIconIndex(pwUuid);
if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex);
}
GroupHandler gh = delegate (PwGroup pg)
{
PwUuid pu = pg.CustomIconUuid;
if (pu.Equals(PwUuid.Zero)) return true;
if (!d.ContainsKey(pu)) pg.CustomIconUuid = PwUuid.Zero;
return true;
};
return true;
}
EntryHandler eh = delegate (PwEntry pe)
{
FixCustomIconRefs(pe, d);
return true;
};
private static void RemoveCustomIconUuid(PwEntry pe, List<PwUuid> vToDelete)
{
PwUuid uuidThis = pe.CustomIconUuid;
if(uuidThis.Equals(PwUuid.Zero)) return;
gh(m_pgRootGroup);
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
}
foreach(PwUuid uuidDelete in vToDelete)
{
if(uuidThis.Equals(uuidDelete))
{
pe.CustomIconUuid = PwUuid.Zero;
break;
}
}
private void FixCustomIconRefs(PwEntry pe, Dictionary<PwUuid, bool> d)
{
PwUuid pu = pe.CustomIconUuid;
if (pu.Equals(PwUuid.Zero)) return;
if (!d.ContainsKey(pu)) pe.CustomIconUuid = PwUuid.Zero;
foreach (PwEntry peH in pe.History) FixCustomIconRefs(peH, d);
}
foreach(PwEntry peHistory in pe.History)
RemoveCustomIconUuid(peHistory, vToDelete);
}
private int GetTotalObjectUuidCount()
{
@@ -1935,61 +2022,119 @@ namespace KeePassLib
return uDeleted;
}
public uint DeleteUnusedCustomIcons()
{
List<PwUuid> lToDelete = new List<PwUuid>();
foreach(PwCustomIcon pwci in m_vCustomIcons)
lToDelete.Add(pwci.Uuid);
Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
foreach (PwCustomIcon ci in m_vCustomIcons) { dToDel[ci.Uuid] = true; }
GroupHandler gh = delegate(PwGroup pg)
GroupHandler gh = delegate (PwGroup pg)
{
PwUuid pwUuid = pg.CustomIconUuid;
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true;
for(int i = 0; i < lToDelete.Count; ++i)
{
if(lToDelete[i].Equals(pwUuid))
{
lToDelete.RemoveAt(i);
break;
}
}
PwUuid pu = pg.CustomIconUuid;
if (!pu.Equals(PwUuid.Zero)) dToDel.Remove(pu);
return true;
};
EntryHandler eh = delegate(PwEntry pe)
EntryHandler eh = delegate (PwEntry pe)
{
PwUuid pwUuid = pe.CustomIconUuid;
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true;
for(int i = 0; i < lToDelete.Count; ++i)
{
if(lToDelete[i].Equals(pwUuid))
{
lToDelete.RemoveAt(i);
break;
}
}
RemoveCustomIconsFromDict(dToDel, pe);
return true;
};
gh(m_pgRootGroup);
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
uint uDeleted = 0;
foreach(PwUuid pwDel in lToDelete)
uint cDel = (uint)dToDel.Count;
if (cDel != 0)
{
int nIndex = GetCustomIconIndex(pwDel);
if(nIndex < 0) { Debug.Assert(false); continue; }
m_vCustomIcons.RemoveAt(nIndex);
++uDeleted;
DeleteCustomIcons(new List<PwUuid>(dToDel.Keys));
m_bUINeedsIconUpdate = true;
}
if(uDeleted > 0) m_bUINeedsIconUpdate = true;
return uDeleted;
return cDel;
}
private static void RemoveCustomIconsFromDict(Dictionary<PwUuid, bool> d,
PwEntry pe)
{
PwUuid pu = pe.CustomIconUuid;
if (!pu.Equals(PwUuid.Zero)) d.Remove(pu);
foreach (PwEntry peH in pe.History) RemoveCustomIconsFromDict(d, peH);
}
internal static void CopyCustomIcons(PwDatabase pdFrom, PwDatabase pdTo,
PwGroup pgSelect, bool bResetIfUnknown)
{
if (pgSelect == null) { Debug.Assert(false); return; }
Dictionary<PwUuid, PwCustomIcon> dFrom = new Dictionary<PwUuid, PwCustomIcon>();
if (pdFrom != null)
{
foreach (PwCustomIcon ci in pdFrom.m_vCustomIcons)
dFrom[ci.Uuid] = ci;
}
Dictionary<PwUuid, int> dTo = new Dictionary<PwUuid, int>();
if (pdTo != null)
{
for (int i = pdTo.m_vCustomIcons.Count - 1; i >= 0; --i)
dTo[pdTo.m_vCustomIcons[i].Uuid] = i;
}
Func<PwUuid, bool> fEnsureIcon = delegate (PwUuid puIcon)
{
if (puIcon.Equals(PwUuid.Zero)) return true;
if (pdTo == null) { Debug.Assert(false); return false; }
PwCustomIcon ciFrom;
if (!dFrom.TryGetValue(puIcon, out ciFrom)) { Debug.Assert(false); return false; }
int iTo;
if (dTo.TryGetValue(puIcon, out iTo))
{
PwCustomIcon ciTo = pdTo.m_vCustomIcons[iTo];
DateTime? odtFrom = ciFrom.LastModificationTime;
DateTime? odtTo = ciTo.LastModificationTime;
if (odtFrom.HasValue && odtTo.HasValue)
{
if (odtFrom.Value <= odtTo.Value) return true;
}
else if (odtTo.HasValue) return true;
else if (!odtFrom.HasValue) return true; // Both no time
pdTo.m_vCustomIcons[iTo] = ciFrom.Clone();
}
else
{
dTo[puIcon] = pdTo.m_vCustomIcons.Count;
pdTo.m_vCustomIcons.Add(ciFrom.Clone());
}
pdTo.Modified = true;
pdTo.UINeedsIconUpdate = true;
return true;
};
GroupHandler gh = delegate (PwGroup pgCur)
{
bool bTo = fEnsureIcon(pgCur.CustomIconUuid);
if (!bTo && bResetIfUnknown) pgCur.CustomIconUuid = PwUuid.Zero;
return true;
};
EntryHandler eh = delegate (PwEntry peCur)
{
bool bTo = fEnsureIcon(peCur.CustomIconUuid);
if (!bTo && bResetIfUnknown) peCur.CustomIconUuid = PwUuid.Zero;
return true;
};
gh(pgSelect);
pgSelect.TraverseTree(TraversalMethod.PreOrder, gh, eh);
}
}
}

View File

@@ -228,6 +228,18 @@ namespace KeePassLib
/// </summary>
public sealed class SearchParameters
{
private string m_strName = string.Empty;
[DefaultValue("")]
public string Name
{
get { return m_strName; }
set
{
if (value == null) throw new ArgumentNullException("value");
m_strName = value;
}
}
private string m_strText = string.Empty;
[DefaultValue("")]
public string SearchString
@@ -235,17 +247,25 @@ namespace KeePassLib
get { return m_strText; }
set
{
if(value == null) throw new ArgumentNullException("value");
if (value == null) throw new ArgumentNullException("value");
m_strText = value;
}
}
private bool m_bRegex = false;
private PwSearchMode m_sm = PwSearchMode.Simple;
public PwSearchMode SearchMode
{
get { return m_sm; }
set { m_sm = value; }
}
[DefaultValue(false)]
[Obsolete]
[XmlIgnore]
public bool RegularExpression
{
get { return m_bRegex; }
set { m_bRegex = value; }
get { return (m_sm == PwSearchMode.Regular); }
set { m_sm = (value ? PwSearchMode.Regular : PwSearchMode.Simple); }
}
private bool m_bSearchInTitles = true;
@@ -296,6 +316,22 @@ namespace KeePassLib
set { m_bSearchInOther = value; }
}
private bool m_bSearchInStringNames = false;
[DefaultValue(false)]
public bool SearchInStringNames
{
get { return m_bSearchInStringNames; }
set { m_bSearchInStringNames = value; }
}
private bool m_bSearchInTags = true;
[DefaultValue(true)]
public bool SearchInTags
{
get { return m_bSearchInTags; }
set { m_bSearchInTags = value; }
}
private bool m_bSearchInUuids = false;
[DefaultValue(false)]
public bool SearchInUuids
@@ -304,6 +340,14 @@ namespace KeePassLib
set { m_bSearchInUuids = value; }
}
private bool m_bSearchInGroupPaths = false;
[DefaultValue(false)]
public bool SearchInGroupPaths
{
get { return m_bSearchInGroupPaths; }
set { m_bSearchInGroupPaths = value; }
}
private bool m_bSearchInGroupNames = false;
[DefaultValue(false)]
public bool SearchInGroupNames
@@ -312,12 +356,12 @@ namespace KeePassLib
set { m_bSearchInGroupNames = value; }
}
private bool m_bSearchInTags = true;
[DefaultValue(true)]
public bool SearchInTags
private bool m_bSearchInHistory = false;
[DefaultValue(false)]
public bool SearchInHistory
{
get { return m_bSearchInTags; }
set { m_bSearchInTags = value; }
get { return m_bSearchInHistory; }
set { m_bSearchInHistory = value; }
}
#if KeePassUAP
@@ -369,7 +413,7 @@ namespace KeePassLib
get { return m_strDataTrf; }
set
{
if(value == null) throw new ArgumentNullException("value");
if (value == null) throw new ArgumentNullException("value");
m_strDataTrf = value;
}
}
@@ -381,20 +425,24 @@ namespace KeePassLib
{
SearchParameters sp = new SearchParameters();
// sp.m_strText = string.Empty;
// sp.m_bRegex = false;
Debug.Assert(sp.m_strName.Length == 0);
Debug.Assert(sp.m_strText.Length == 0);
Debug.Assert(sp.m_sm == PwSearchMode.Simple);
sp.m_bSearchInTitles = false;
sp.m_bSearchInUserNames = false;
// sp.m_bSearchInPasswords = false;
Debug.Assert(!sp.m_bSearchInPasswords);
sp.m_bSearchInUrls = false;
sp.m_bSearchInNotes = false;
sp.m_bSearchInOther = false;
// sp.m_bSearchInUuids = false;
// sp.SearchInGroupNames = false;
Debug.Assert(!sp.m_bSearchInStringNames);
sp.m_bSearchInTags = false;
// sp.m_scType = StringComparison.InvariantCultureIgnoreCase;
// sp.m_bExcludeExpired = false;
// m_bRespectEntrySearchingDisabled = true;
Debug.Assert(!sp.m_bSearchInUuids);
Debug.Assert(!sp.m_bSearchInGroupPaths);
Debug.Assert(!sp.m_bSearchInGroupNames);
Debug.Assert(!sp.m_bSearchInHistory);
// Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase);
Debug.Assert(!sp.m_bExcludeExpired);
Debug.Assert(sp.m_bRespectEntrySearchingDisabled);
return sp;
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -42,14 +42,15 @@ namespace KeePassLib
private PwUuid m_uuid = PwUuid.Zero;
private PwGroup m_pParentGroup = null;
private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
private PwUuid m_puPrevParentGroup = PwUuid.Zero;
private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary();
private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary();
private AutoTypeConfig m_listAutoType = new AutoTypeConfig();
private PwObjectList<PwEntry> m_listHistory = new PwObjectList<PwEntry>();
private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary();
private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary();
private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig();
private PwObjectList<PwEntry> m_lHistory = new PwObjectList<PwEntry>();
private PwIcon m_pwIcon = PwIcon.Key;
private PwUuid m_pwCustomIconID = PwUuid.Zero;
private PwUuid m_puCustomIcon = PwUuid.Zero;
private Color m_clrForeground = Color.Empty;
private Color m_clrBackground = Color.Empty;
@@ -62,20 +63,21 @@ namespace KeePassLib
private ulong m_uUsageCount = 0;
private string m_strOverrideUrl = string.Empty;
private bool m_bQualityCheck = true;
private List<string> m_vTags = new List<string>();
private List<string> m_lTags = new List<string>();
private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
/// <summary>
/// UUID of this entry.
/// </summary>
/// </summary>
public PwUuid Uuid
{
get { return m_uuid; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_uuid = value;
}
}
@@ -87,7 +89,7 @@ namespace KeePassLib
{
get { return m_pParentGroup; }
/// Plugins: use <c>PwGroup.AddEntry</c> instead.
// Plugins: use <c>PwGroup.AddEntry</c> instead.
internal set { m_pParentGroup = value; }
}
@@ -100,17 +102,26 @@ namespace KeePassLib
set { m_tParentGroupLastMod = value; }
}
public PwUuid PreviousParentGroup
{
get { return m_puPrevParentGroup; }
set
{
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_puPrevParentGroup = value;
}
}
/// <summary>
/// Get or set all entry strings.
/// </summary>
public ProtectedStringDictionary Strings
{
get { return m_listStrings; }
get { return m_dStrings; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_listStrings = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_dStrings = value;
}
}
@@ -119,11 +130,11 @@ namespace KeePassLib
/// </summary>
public ProtectedBinaryDictionary Binaries
{
get { return m_listBinaries; }
get { return m_dBinaries; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_listBinaries = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_dBinaries = value;
}
}
@@ -132,11 +143,11 @@ namespace KeePassLib
/// </summary>
public AutoTypeConfig AutoType
{
get { return m_listAutoType; }
get { return m_cfgAutoType; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_listAutoType = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_cfgAutoType = value;
}
}
@@ -145,11 +156,11 @@ namespace KeePassLib
/// </summary>
public PwObjectList<PwEntry> History
{
get { return m_listHistory; }
get { return m_lHistory; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_listHistory = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_lHistory = value;
}
}
@@ -169,11 +180,11 @@ namespace KeePassLib
/// </summary>
public PwUuid CustomIconUuid
{
get { return m_pwCustomIconID; }
get { return m_puCustomIcon; }
set
{
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_pwCustomIconID = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_puCustomIcon = value;
}
}
@@ -252,28 +263,34 @@ namespace KeePassLib
}
/// <summary>
/// Entry-specific override URL. If this string is non-empty,
/// Entry-specific override URL.
/// </summary>
public string OverrideUrl
{
get { return m_strOverrideUrl; }
set
{
if (value == null) throw new ArgumentNullException("value");
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_strOverrideUrl = value;
}
}
public bool QualityCheck
{
get { return m_bQualityCheck; }
set { m_bQualityCheck = value; }
}
/// <summary>
/// List of tags associated with this entry.
/// </summary>
public List<string> Tags
{
get { return m_vTags; }
get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
set
{
if (value == null) throw new ArgumentNullException("value");
m_vTags = value;
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_lTags = value;
}
}
@@ -349,19 +366,19 @@ namespace KeePassLib
}
#if DEBUG
/// <summary>
// For display in debugger
public override string ToString()
{
return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'");
return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'");
}
#endif
/// <summary>
/// Clone the current entry. The returned entry is an exact value copy
/// of the current entry (including UUID and parent group reference).
/// All mutable members are cloned.
/// </summary>
/// <returns>Exact value clone. All references to mutable values changed.</returns>
/// </summary>
/// <returns>Exact value clone. All references to mutable values changed.</returns>
public PwEntry CloneDeep()
{
PwEntry peNew = new PwEntry(false, false);
@@ -369,14 +386,15 @@ namespace KeePassLib
peNew.m_uuid = m_uuid; // PwUuid is immutable
peNew.m_pParentGroup = m_pParentGroup;
peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
peNew.m_puPrevParentGroup = m_puPrevParentGroup;
peNew.m_listStrings = m_listStrings.CloneDeep();
peNew.m_listBinaries = m_listBinaries.CloneDeep();
peNew.m_listAutoType = m_listAutoType.CloneDeep();
peNew.m_listHistory = m_listHistory.CloneDeep();
peNew.m_dStrings = m_dStrings.CloneDeep();
peNew.m_dBinaries = m_dBinaries.CloneDeep();
peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep();
peNew.m_lHistory = m_lHistory.CloneDeep();
peNew.m_pwIcon = m_pwIcon;
peNew.m_pwCustomIconID = m_pwCustomIconID;
peNew.m_puCustomIcon = m_puCustomIcon;
peNew.m_clrForeground = m_clrForeground;
peNew.m_clrBackground = m_clrBackground;
@@ -389,8 +407,9 @@ namespace KeePassLib
peNew.m_uUsageCount = m_uUsageCount;
peNew.m_strOverrideUrl = m_strOverrideUrl;
peNew.m_bQualityCheck = m_bQualityCheck;
peNew.m_vTags = new List<string>(m_vTags);
peNew.m_lTags.AddRange(m_lTags);
peNew.m_dCustomData = m_dCustomData.CloneDeep();
@@ -457,27 +476,29 @@ namespace KeePassLib
if (m_pParentGroup != pe.m_pParentGroup) return false;
if (!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod))
return false;
if (!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup))
return false;
}
if (!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr))
if (!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr))
return false;
if (!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false;
if (!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false;
if (!m_listAutoType.Equals(pe.m_listAutoType)) return false;
if (!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false;
if ((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None)
{
bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) !=
PwCompareOptions.None);
if (!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount))
if (!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount))
return false;
if (bIgnoreLastBackup && (m_listHistory.UCount == 0))
if (bIgnoreLastBackup && (m_lHistory.UCount == 0))
{
Debug.Assert(false);
return false;
}
if (bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount))
if (bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount))
return false;
PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup;
@@ -485,16 +506,16 @@ namespace KeePassLib
if (bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod;
if (bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess;
for (uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist)
for (uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist)
{
if (!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt(
if (!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt(
uHist), cmpSub, MemProtCmpMode.None))
return false;
}
}
if (m_pwIcon != pe.m_pwIcon) return false;
if (!m_pwCustomIconID.Equals(pe.m_pwCustomIconID)) return false;
if (!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false;
if (m_clrForeground != pe.m_clrForeground) return false;
if (m_clrBackground != pe.m_clrBackground) return false;
@@ -507,12 +528,10 @@ namespace KeePassLib
if (!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false;
if (m_strOverrideUrl != pe.m_strOverrideUrl) return false;
if (m_bQualityCheck != pe.m_bQualityCheck) return false;
if (m_vTags.Count != pe.m_vTags.Count) return false;
for (int iTag = 0; iTag < m_vTags.Count; ++iTag)
{
if (m_vTags[iTag] != pe.m_vTags[iTag]) return false;
}
// The Tags property normalizes
if (!MemUtil.ListsEqual<string>(this.Tags, pe.Tags)) return false;
if (!m_dCustomData.Equals(pe.m_dCustomData)) return false;
@@ -543,16 +562,19 @@ namespace KeePassLib
m_uuid = peTemplate.m_uuid;
if (bAssignLocationChanged)
{
m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
m_puPrevParentGroup = peTemplate.m_puPrevParentGroup;
}
m_listStrings = peTemplate.m_listStrings.CloneDeep();
m_listBinaries = peTemplate.m_listBinaries.CloneDeep();
m_listAutoType = peTemplate.m_listAutoType.CloneDeep();
m_dStrings = peTemplate.m_dStrings.CloneDeep();
m_dBinaries = peTemplate.m_dBinaries.CloneDeep();
m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep();
if (bIncludeHistory)
m_listHistory = peTemplate.m_listHistory.CloneDeep();
m_lHistory = peTemplate.m_lHistory.CloneDeep();
m_pwIcon = peTemplate.m_pwIcon;
m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable
m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable
m_clrForeground = peTemplate.m_clrForeground;
m_clrBackground = peTemplate.m_clrBackground;
@@ -565,8 +587,9 @@ namespace KeePassLib
m_uUsageCount = peTemplate.m_uUsageCount;
m_strOverrideUrl = peTemplate.m_strOverrideUrl;
m_bQualityCheck = peTemplate.m_bQualityCheck;
m_vTags = new List<string>(peTemplate.m_vTags);
m_lTags = new List<string>(peTemplate.m_lTags);
m_dCustomData = peTemplate.m_dCustomData.CloneDeep();
}
@@ -629,9 +652,9 @@ namespace KeePassLib
public void CreateBackup(PwDatabase pwHistMntcSettings)
{
PwEntry peCopy = CloneDeep();
peCopy.History = new PwObjectList<PwEntry>(); // Remove history
peCopy.m_lHistory.Clear();
m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry
m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry
if (pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings);
}
@@ -658,12 +681,14 @@ namespace KeePassLib
/// This parameter may be <c>null</c> (no maintenance then).</param>
public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)
{
Debug.Assert(uBackupIndex < m_listHistory.UCount);
if (uBackupIndex >= m_listHistory.UCount)
if (uBackupIndex >= m_lHistory.UCount)
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("uBackupIndex");
}
PwEntry pe = m_listHistory.GetAt(uBackupIndex);
Debug.Assert(pe != null); if (pe == null) throw new InvalidOperationException();
PwEntry pe = m_lHistory.GetAt(uBackupIndex);
if (pe == null) { Debug.Assert(false); throw new InvalidOperationException(); }
CreateBackup(pwHistMntcSettings); // Backup current data before restoring
AssignProperties(pe, false, false, false);
@@ -679,7 +704,7 @@ namespace KeePassLib
if (bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod;
if (bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess;
foreach (PwEntry pe in m_listHistory)
foreach (PwEntry pe in m_lHistory)
{
if (pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true;
}
@@ -688,21 +713,28 @@ namespace KeePassLib
}
/// <summary>
/// Delete old history items if there are too many or the history
/// size is too large.
/// <returns>If one or more history items have been deleted, <c>true</c>
/// is returned. Otherwise <c>false</c>.</returns>
/// Delete old history entries if there are too many or the
/// history size is too large.
/// <returns>If one or more history entries have been deleted,
/// <c>true</c> is returned. Otherwise <c>false</c>.</returns>
/// </summary>
public bool MaintainBackups(PwDatabase pwSettings)
{
if (pwSettings == null) { Debug.Assert(false); return false; }
// Fix UUIDs of history entries; should not be necessary
PwUuid pu = m_uuid;
foreach (PwEntry pe in m_lHistory)
{
if (!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; }
}
bool bDeleted = false;
int nMaxItems = pwSettings.HistoryMaxItems;
if (nMaxItems >= 0)
{
while (m_listHistory.UCount > (uint)nMaxItems)
while (m_lHistory.UCount > (uint)nMaxItems)
{
RemoveOldestBackup();
bDeleted = true;
@@ -715,7 +747,7 @@ namespace KeePassLib
while (true)
{
ulong uHistSize = 0;
foreach (PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); }
foreach (PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); }
if (uHistSize > (ulong)lMaxSize)
{
@@ -734,9 +766,9 @@ namespace KeePassLib
DateTime dtMin = TimeUtil.SafeMaxValueUtc;
uint idxRemove = uint.MaxValue;
for (uint u = 0; u < m_listHistory.UCount; ++u)
for (uint u = 0; u < m_lHistory.UCount; ++u)
{
PwEntry pe = m_listHistory.GetAt(u);
PwEntry pe = m_lHistory.GetAt(u);
if (TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0)
{
idxRemove = u;
@@ -744,12 +776,12 @@ namespace KeePassLib
}
}
if (idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove);
if (idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove);
}
public bool GetAutoTypeEnabled()
{
if (!m_listAutoType.Enabled) return false;
if (!m_cfgAutoType.Enabled) return false;
if (m_pParentGroup != null)
return m_pParentGroup.GetAutoTypeEnabledInherited();
@@ -759,7 +791,7 @@ namespace KeePassLib
public string GetAutoTypeSequence()
{
string strSeq = m_listAutoType.DefaultSequence;
string strSeq = m_cfgAutoType.DefaultSequence;
PwGroup pg = m_pParentGroup;
while (pg != null)
@@ -785,69 +817,67 @@ namespace KeePassLib
}
/// <summary>
/// Approximate the total size of this entry in bytes (including
/// strings, binaries and history entries).
/// Approximate the total size (in process memory) of this entry
/// in bytes (including strings, binaries and history entries).
/// </summary>
/// <returns>Size in bytes.</returns>
public ulong GetSize()
{
ulong uSize = 128; // Approx fixed length data
// This method assumes 64-bit pointers/references and Unicode
// strings (i.e. 2 bytes per character)
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_listStrings)
ulong cb = 276; // Number of bytes; approx. fixed length data
ulong cc = 0; // Number of characters
cb += (ulong)m_dStrings.UCount * 40;
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_dStrings)
cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length;
cb += (ulong)m_dBinaries.UCount * 65;
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_dBinaries)
{
uSize += (ulong)kvpStr.Key.Length;
uSize += (ulong)kvpStr.Value.Length;
cc += (ulong)kvpBin.Key.Length;
cb += (ulong)kvpBin.Value.Length;
}
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_listBinaries)
{
uSize += (ulong)kvpBin.Key.Length;
uSize += kvpBin.Value.Length;
}
cc += (ulong)m_cfgAutoType.DefaultSequence.Length;
cb += (ulong)m_cfgAutoType.AssociationsCount * 24;
foreach (AutoTypeAssociation a in m_cfgAutoType.Associations)
cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length;
uSize += (ulong)m_listAutoType.DefaultSequence.Length;
foreach (AutoTypeAssociation a in m_listAutoType.Associations)
{
uSize += (ulong)a.WindowName.Length;
uSize += (ulong)a.Sequence.Length;
}
cb += (ulong)m_lHistory.UCount * 8;
foreach (PwEntry peHistory in m_lHistory)
cb += peHistory.GetSize();
foreach (PwEntry peHistory in m_listHistory)
uSize += peHistory.GetSize();
cc += (ulong)m_strOverrideUrl.Length;
uSize += (ulong)m_strOverrideUrl.Length;
foreach (string strTag in m_vTags)
uSize += (ulong)strTag.Length;
cb += (ulong)m_lTags.Count * 8;
foreach (string strTag in m_lTags)
cc += (ulong)strTag.Length;
cb += (ulong)m_dCustomData.Count * 16;
foreach (KeyValuePair<string, string> kvp in m_dCustomData)
uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
return uSize;
return (cb + (cc << 1));
}
public bool HasTag(string strTag)
{
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i)
{
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true;
}
return false;
// this.Tags normalizes
return this.Tags.Contains(StrUtil.NormalizeTag(strTag));
}
public bool AddTag(string strTag)
{
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i)
{
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false;
}
strTag = StrUtil.NormalizeTag(strTag);
if (this.Tags.Contains(strTag)) return false; // this.Tags normalizes
m_vTags.Add(strTag);
m_lTags.Add(strTag);
return true;
}
@@ -855,16 +885,17 @@ namespace KeePassLib
{
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i)
{
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp))
{
m_vTags.RemoveAt(i);
return true;
}
}
// this.Tags normalizes
return this.Tags.Remove(StrUtil.NormalizeTag(strTag));
}
return false;
internal List<string> GetTagsInherited()
{
List<string> l = ((m_pParentGroup != null) ?
m_pParentGroup.GetTagsInherited(false) : new List<string>());
l.AddRange(this.Tags);
StrUtil.NormalizeTags(l);
return l;
}
public bool IsContainedIn(PwGroup pgContainer)
@@ -886,10 +917,8 @@ namespace KeePassLib
if (bAlsoChangeHistoryUuids)
{
foreach (PwEntry peHist in m_listHistory)
{
foreach (PwEntry peHist in m_lHistory)
peHist.Uuid = pwNewUuid;
}
}
}

View File

@@ -316,4 +316,13 @@ namespace KeePassLib
Cinnamon,
Pantheon
}
public enum PwSearchMode
{
None = 0,
Simple,
Regular,
XPath
}
}

View File

@@ -0,0 +1,372 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.XPath;
using KeePassLib.Collections;
using KeePassLib.Delegates;
using KeePassLib.Interfaces;
using KeePassLib.Security;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace KeePassLib
{
public sealed partial class PwGroup
{
private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added
public const string SearchContextUuid = "Uuid";
public const string SearchContextParentGroup = "Parent Group";
public const string SearchContextTags = "Tags";
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage)
{
SearchEntries(sp, listStorage, null);
}
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
/// <param name="slStatus">Optional status reporting object.</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
IStatusLogger slStatus)
{
SearchEntries(sp, listStorage, null, slStatus);
}
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
/// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
IStatusLogger slStatus)
{
if (sp == null)
{
Debug.Assert(false);
return;
}
if (listStorage == null)
{
Debug.Assert(false);
return;
}
ulong uCurEntries = 0, uTotalEntries = 0;
List<string> lTerms = StrUtil.SplitSearchTerms(sp.SearchString);
if ((lTerms.Count <= 1) || sp.RegularExpression)
{
if (slStatus != null) uTotalEntries = GetEntriesCount(true);
SearchEntriesSingle(sp, listStorage, resultContexts, slStatus, ref uCurEntries,
uTotalEntries);
return;
}
// Search longer strings first (for improved performance)
lTerms.Sort(StrUtil.CompareLengthGt);
string strFullSearch = sp.SearchString; // Backup
PwGroup pg = this;
for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm)
{
// Update counters for a better state guess
if (slStatus != null)
{
ulong uRemRounds = (ulong) (lTerms.Count - iTerm);
uTotalEntries = uCurEntries + (uRemRounds *
pg.GetEntriesCount(true));
}
PwGroup pgNew = new PwGroup();
sp.SearchString = lTerms[iTerm];
bool bNegate = false;
if (sp.SearchString.StartsWith("-"))
{
sp.SearchString = sp.SearchString.Substring(1);
bNegate = (sp.SearchString.Length > 0);
}
if (!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
ref uCurEntries, uTotalEntries))
{
pg = null;
break;
}
if (bNegate)
{
PwObjectList<PwEntry> lCand = pg.GetEntries(true);
pg = new PwGroup();
foreach (PwEntry peCand in lCand)
{
if (pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand);
}
}
else pg = pgNew;
}
if (pg != null) listStorage.Add(pg.Entries);
sp.SearchString = strFullSearch; // Restore
}
private bool SearchEntriesSingle(SearchParameters spIn,
PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
IStatusLogger slStatus,
ref ulong uCurEntries, ulong uTotalEntries)
{
SearchParameters sp = spIn.Clone();
if (sp.SearchString == null)
{
Debug.Assert(false);
return true;
}
sp.SearchString = sp.SearchString.Trim();
bool bTitle = sp.SearchInTitles;
bool bUserName = sp.SearchInUserNames;
bool bPassword = sp.SearchInPasswords;
bool bUrl = sp.SearchInUrls;
bool bNotes = sp.SearchInNotes;
bool bOther = sp.SearchInOther;
bool bUuids = sp.SearchInUuids;
bool bGroupName = sp.SearchInGroupNames;
bool bTags = sp.SearchInTags;
bool bExcludeExpired = sp.ExcludeExpired;
bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled;
DateTime dtNow = DateTime.Now;
Regex rx = null;
if (sp.RegularExpression)
{
RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled
if ((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) ||
#if !KeePassUAP
(sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) ||
#endif
(sp.ComparisonMode == StringComparison.OrdinalIgnoreCase))
{
ro |= RegexOptions.IgnoreCase;
}
rx = new Regex(sp.SearchString, ro);
}
ulong uLocalCurEntries = uCurEntries;
EntryHandler eh = null;
if (sp.SearchString.Length <= 0) // Report all
{
eh = delegate(PwEntry pe)
{
if (slStatus != null)
{
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
100UL) / uTotalEntries))) return false;
++uLocalCurEntries;
}
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
return true; // Skip
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
return true; // Skip
listStorage.Add(pe);
return true;
};
}
else
{
eh = delegate(PwEntry pe)
{
if (slStatus != null)
{
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
100UL) / uTotalEntries))) return false;
++uLocalCurEntries;
}
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
return true; // Skip
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
return true; // Skip
uint uInitialResults = listStorage.UCount;
foreach (KeyValuePair<string, ProtectedString> kvp in pe.Strings)
{
string strKey = kvp.Key;
if (strKey == PwDefs.TitleField)
{
if (bTitle)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.UserNameField)
{
if (bUserName)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.PasswordField)
{
if (bPassword)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.UrlField)
{
if (bUrl)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.NotesField)
{
if (bNotes)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (bOther)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
// An entry can match only once => break if we have added it
if (listStorage.UCount > uInitialResults) break;
}
if (bUuids && (listStorage.UCount == uInitialResults))
SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts,
SearchContextTags);
if (bGroupName && (listStorage.UCount == uInitialResults) &&
(pe.ParentGroup != null))
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts,
SearchContextParentGroup);
if (bTags)
{
foreach (string strTag in pe.Tags)
{
if (listStorage.UCount != uInitialResults) break; // Match
SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags);
}
}
return true;
};
}
if (!PreOrderTraverseTree(null, eh)) return false;
uCurEntries = uLocalCurEntries;
return true;
}
private static void SearchEvalAdd(SearchParameters sp, string strDataField,
Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults,
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts, string contextFieldName)
{
bool bMatch = false;
int matchPos;
if (rx == null)
{
matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strDataField);
bMatch = match.Success;
matchPos = match.Index;
}
if (!bMatch && (sp.DataTransformationFn != null))
{
string strCmp = sp.DataTransformationFn(strDataField, pe);
if (!object.ReferenceEquals(strCmp, strDataField))
{
if (rx == null)
{
matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strCmp);
bMatch = match.Success;
matchPos = match.Index;
}
}
}
if (bMatch)
{
lResults.Add(pe);
if (resultContexts != null)
{
// Trim the value if necessary
var contextString = strDataField;
if (contextString.Length > SearchContextStringMaxLength)
{
// Start 10% before actual data, and don't run over
var startPos = Math.Max(0,
Math.Min(matchPos - (SearchContextStringMaxLength / 10),
contextString.Length - SearchContextStringMaxLength));
contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) +
((startPos + SearchContextStringMaxLength < contextString.Length)
? " …"
: null);
}
resultContexts[pe.Uuid] = new KeyValuePair<string, string>(contextFieldName, contextString);
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ namespace KeePassLib.Resources
{
string strTemp;
if(dictNew.TryGetValue(strName, out strTemp))
if (dictNew.TryGetValue(strName, out strTemp))
return strTemp;
return strDefault;
@@ -24,8 +24,11 @@ namespace KeePassLib.Resources
public static void SetTranslatedStrings(Dictionary<string, string> dictNew)
{
if(dictNew == null) throw new ArgumentNullException("dictNew");
if (dictNew == null) throw new ArgumentNullException("dictNew");
m_strAlgorithmUnknown = TryGetEx(dictNew, "AlgorithmUnknown", m_strAlgorithmUnknown);
m_strCharSetInvalid = TryGetEx(dictNew, "CharSetInvalid", m_strCharSetInvalid);
m_strCharSetTooFewChars = TryGetEx(dictNew, "CharSetTooFewChars", m_strCharSetTooFewChars);
m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed);
m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge);
m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard);
@@ -41,7 +44,7 @@ namespace KeePassLib.Resources
m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq);
m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq);
m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning);
m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed);
m_strFileSaveFailed2 = TryGetEx(dictNew, "FileSaveFailed2", m_strFileSaveFailed2);
m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid);
m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher);
m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression);
@@ -55,12 +58,18 @@ namespace KeePassLib.Resources
m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint);
m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits);
m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel);
m_strKeyHashMismatch = TryGetEx(dictNew, "KeyHashMismatch", m_strKeyHashMismatch);
m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid);
m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat);
m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive);
m_strPathBackslash = TryGetEx(dictNew, "PathBackslash", m_strPathBackslash);
m_strPatternInvalid = TryGetEx(dictNew, "PatternInvalid", m_strPatternInvalid);
m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth);
m_strPwGenFailed = TryGetEx(dictNew, "PwGenFailed", m_strPwGenFailed);
m_strStructsTooDeep = TryGetEx(dictNew, "StructsTooDeep", m_strStructsTooDeep);
m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout);
m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs);
m_strUnknownError = TryGetEx(dictNew, "UnknownError", m_strUnknownError);
m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId);
m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf);
m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError);
@@ -68,6 +77,9 @@ namespace KeePassLib.Resources
}
private static readonly string[] m_vKeyNames = {
"AlgorithmUnknown",
"CharSetInvalid",
"CharSetTooFewChars",
"CryptoStreamFailed",
"EncDataTooLarge",
"ErrorInClipboard",
@@ -83,7 +95,7 @@ namespace KeePassLib.Resources
"FileNewVerOrPlgReq",
"FileNewVerReq",
"FileSaveCorruptionWarning",
"FileSaveFailed",
"FileSaveFailed2",
"FileSigInvalid",
"FileUnknownCipher",
"FileUnknownCompression",
@@ -97,12 +109,18 @@ namespace KeePassLib.Resources
"KeePass1xHint",
"KeyBits",
"KeyFileDbSel",
"KeyHashMismatch",
"MasterSeedLengthInvalid",
"OldFormat",
"Passive",
"PathBackslash",
"PatternInvalid",
"PreAuth",
"PwGenFailed",
"StructsTooDeep",
"Timeout",
"TryAgainSecs",
"UnknownError",
"UnknownHeaderId",
"UnknownKdf",
"UserAccountKeyError",
@@ -114,6 +132,39 @@ namespace KeePassLib.Resources
return m_vKeyNames;
}
private static string m_strAlgorithmUnknown =
@"The algorithm is unknown.";
/// <summary>
/// Look up a localized string similar to
/// 'The algorithm is unknown.'.
/// </summary>
public static string AlgorithmUnknown
{
get { return m_strAlgorithmUnknown; }
}
private static string m_strCharSetInvalid =
@"The character set is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The character set is invalid.'.
/// </summary>
public static string CharSetInvalid
{
get { return m_strCharSetInvalid; }
}
private static string m_strCharSetTooFewChars =
@"There are too few characters in the character set.";
/// <summary>
/// Look up a localized string similar to
/// 'There are too few characters in the character set.'.
/// </summary>
public static string CharSetTooFewChars
{
get { return m_strCharSetTooFewChars; }
}
private static string m_strCryptoStreamFailed =
@"Failed to initialize encryption/decryption stream!";
/// <summary>
@@ -279,15 +330,15 @@ namespace KeePassLib.Resources
get { return m_strFileSaveCorruptionWarning; }
}
private static string m_strFileSaveFailed =
@"Failed to save the current database to the specified location!";
private static string m_strFileSaveFailed2 =
@"Failed to save to the specified file!";
/// <summary>
/// Look up a localized string similar to
/// 'Failed to save the current database to the specified location!'.
/// 'Failed to save to the specified file!'.
/// </summary>
public static string FileSaveFailed
public static string FileSaveFailed2
{
get { return m_strFileSaveFailed; }
get { return m_strFileSaveFailed2; }
}
private static string m_strFileSigInvalid =
@@ -346,10 +397,10 @@ namespace KeePassLib.Resources
}
private static string m_strFrameworkNotImplExcp =
@"The .NET framework/runtime under which KeePass is currently running does not support this operation.";
@"The .NET Framework/runtime under which KeePass is currently running does not support this operation.";
/// <summary>
/// Look up a localized string similar to
/// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'.
/// 'The .NET Framework/runtime under which KeePass is currently running does not support this operation.'.
/// </summary>
public static string FrameworkNotImplExcp
{
@@ -368,10 +419,10 @@ namespace KeePassLib.Resources
}
private static string m_strInvalidCompositeKey =
@"The composite key is invalid!";
@"The master key is invalid!";
/// <summary>
/// Look up a localized string similar to
/// 'The composite key is invalid!'.
/// 'The master key is invalid!'.
/// </summary>
public static string InvalidCompositeKey
{
@@ -379,10 +430,10 @@ namespace KeePassLib.Resources
}
private static string m_strInvalidCompositeKeyHint =
@"Make sure the composite key is correct and try again.";
@"Make sure that the master key is correct and try it again.";
/// <summary>
/// Look up a localized string similar to
/// 'Make sure the composite key is correct and try again.'.
/// 'Make sure that the master key is correct and try it again.'.
/// </summary>
public static string InvalidCompositeKeyHint
{
@@ -433,6 +484,17 @@ namespace KeePassLib.Resources
get { return m_strKeyFileDbSel; }
}
private static string m_strKeyHashMismatch =
@"The key and the hash do not match, i.e. the key or the hash is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The key and the hash do not match, i.e. the key or the hash is invalid.'.
/// </summary>
public static string KeyHashMismatch
{
get { return m_strKeyHashMismatch; }
}
private static string m_strMasterSeedLengthInvalid =
@"The length of the master key seed is invalid!";
/// <summary>
@@ -466,6 +528,28 @@ namespace KeePassLib.Resources
get { return m_strPassive; }
}
private static string m_strPathBackslash =
@"The path contains a backslash. Such paths are not supported (for security reasons).";
/// <summary>
/// Look up a localized string similar to
/// 'The path contains a backslash. Such paths are not supported (for security reasons).'.
/// </summary>
public static string PathBackslash
{
get { return m_strPathBackslash; }
}
private static string m_strPatternInvalid =
@"The pattern is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The pattern is invalid.'.
/// </summary>
public static string PatternInvalid
{
get { return m_strPatternInvalid; }
}
private static string m_strPreAuth =
@"Pre-authenticate";
/// <summary>
@@ -477,6 +561,28 @@ namespace KeePassLib.Resources
get { return m_strPreAuth; }
}
private static string m_strPwGenFailed =
@"Failed to generate a password.";
/// <summary>
/// Look up a localized string similar to
/// 'Failed to generate a password.'.
/// </summary>
public static string PwGenFailed
{
get { return m_strPwGenFailed; }
}
private static string m_strStructsTooDeep =
@"Structures are nested too deeply.";
/// <summary>
/// Look up a localized string similar to
/// 'Structures are nested too deeply.'.
/// </summary>
public static string StructsTooDeep
{
get { return m_strStructsTooDeep; }
}
private static string m_strTimeout =
@"Timeout";
/// <summary>
@@ -499,6 +605,17 @@ namespace KeePassLib.Resources
get { return m_strTryAgainSecs; }
}
private static string m_strUnknownError =
@"An unknown error occurred.";
/// <summary>
/// Look up a localized string similar to
/// 'An unknown error occurred.'.
/// </summary>
public static string UnknownError
{
get { return m_strUnknownError; }
}
private static string m_strUnknownHeaderId =
@"Unknown header ID!";
/// <summary>

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -33,11 +33,11 @@ using KeePassLibSD;
namespace KeePassLib.Security
{
/// <summary>
/// Represents an in-memory encrypted string.
/// A string that is protected in process memory.
/// <c>ProtectedString</c> objects are immutable and thread-safe.
/// </summary>
#if (DEBUG && !KeePassLibSD)
[DebuggerDisplay(@"{ReadString()}")]
[DebuggerDisplay("{ReadString()}")]
#endif
public sealed class ProtectedString
{
@@ -48,11 +48,24 @@ namespace KeePassLib.Security
private bool m_bIsProtected;
private static readonly ProtectedString m_psEmpty = new ProtectedString();
/// <summary>
/// Get an empty <c>ProtectedString</c> object, without protection.
/// </summary>
public static ProtectedString Empty
{
get { return m_psEmpty; }
}
private static readonly ProtectedString m_psEmptyEx = new ProtectedString(
true, new byte[0]);
/// <summary>
/// Get an empty <c>ProtectedString</c> object, with protection turned on.
/// </summary>
public static ProtectedString EmptyEx
{
get { return m_psEmptyEx; }
}
/// <summary>
/// A flag specifying whether the <c>ProtectedString</c> object
/// has turned on memory protection or not.
@@ -66,8 +79,8 @@ namespace KeePassLib.Security
{
get
{
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
if(pBin != null) return (pBin.Length == 0);
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if (p != null) return (p.Length == 0);
Debug.Assert(m_strPlainText != null);
return (m_strPlainText.Length == 0);
@@ -75,18 +88,21 @@ namespace KeePassLib.Security
}
private int m_nCachedLength = -1;
/// <summary>
/// Length of the protected string, in characters.
/// </summary>
public int Length
{
get
{
if(m_nCachedLength >= 0) return m_nCachedLength;
if (m_nCachedLength >= 0) return m_nCachedLength;
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
if(pBin != null)
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if (p != null)
{
byte[] pbPlain = pBin.ReadData();
m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain);
MemUtil.ZeroByteArray(pbPlain);
byte[] pbPlain = p.ReadData();
try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); }
finally { MemUtil.ZeroByteArray(pbPlain); }
}
else
{
@@ -140,53 +156,51 @@ namespace KeePassLib.Security
/// to the value passed in the <c>XorredBuffer</c> object.
/// </summary>
/// <param name="bEnableProtection">Enable protection or not.</param>
/// <param name="xbProtected"><c>XorredBuffer</c> object containing the
/// <param name="xb"><c>XorredBuffer</c> object containing the
/// string in UTF-8 representation. The UTF-8 string must not
/// be <c>null</c>-terminated.</param>
public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected)
public ProtectedString(bool bEnableProtection, XorredBuffer xb)
{
Debug.Assert(xbProtected != null);
if(xbProtected == null) throw new ArgumentNullException("xbProtected");
if (xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
byte[] pb = xbProtected.ReadPlainText();
Init(bEnableProtection, pb);
if(bEnableProtection) MemUtil.ZeroByteArray(pb);
byte[] pb = xb.ReadPlainText();
try { Init(bEnableProtection, pb); }
finally { if (bEnableProtection) MemUtil.ZeroByteArray(pb); }
}
private void Init(bool bEnableProtection, string str)
{
if(str == null) throw new ArgumentNullException("str");
if (str == null) throw new ArgumentNullException("str");
m_bIsProtected = bEnableProtection;
// The string already is in memory and immutable,
// As the string already is in memory and immutable,
// protection would be useless
m_strPlainText = str;
}
private void Init(bool bEnableProtection, byte[] pbUtf8)
{
if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8");
if (pbUtf8 == null) throw new ArgumentNullException("pbUtf8");
m_bIsProtected = bEnableProtection;
if(bEnableProtection)
if (bEnableProtection)
m_pbUtf8 = new ProtectedBinary(true, pbUtf8);
else
m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length);
}
/// <summary>
/// Convert the protected string to a normal string object.
/// Be careful with this function, the returned string object
/// Convert the protected string to a standard string object.
/// Be careful with this function, as the returned string object
/// isn't protected anymore and stored in plain-text in the
/// process memory.
/// </summary>
/// <returns>Plain-text string. Is never <c>null</c>.</returns>
public string ReadString()
{
if(m_strPlainText != null) return m_strPlainText;
if (m_strPlainText != null) return m_strPlainText;
byte[] pb = ReadUtf8();
string str = ((pb.Length == 0) ? string.Empty :
@@ -194,82 +208,120 @@ namespace KeePassLib.Security
// No need to clear pb
// As the text is now visible in process memory anyway,
// there's no need to protect it anymore
// there's no need to protect it anymore (strings are
// immutable and thus cannot be overwritten)
m_strPlainText = str;
m_pbUtf8 = null; // Thread-safe order
return str;
}
/// <summary>
/// Read out the string and return it as a char array.
/// The returned array is not protected and should be cleared by
/// the caller.
/// </summary>
/// <returns>Plain-text char array.</returns>
public char[] ReadChars()
{
if (m_strPlainText != null) return m_strPlainText.ToCharArray();
byte[] pb = ReadUtf8();
char[] v;
try { v = StrUtil.Utf8.GetChars(pb); }
finally { MemUtil.ZeroByteArray(pb); }
return v;
}
/// <summary>
/// Read out the string and return a byte array that contains the
/// string encoded using UTF-8. The returned string is not protected
/// anymore!
/// string encoded using UTF-8.
/// The returned array is not protected and should be cleared by
/// the caller.
/// </summary>
/// <returns>Plain-text UTF-8 byte array.</returns>
public byte[] ReadUtf8()
{
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety
if(pBin != null) return pBin.ReadData();
ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if (p != null) return p.ReadData();
return StrUtil.Utf8.GetBytes(m_strPlainText);
}
/// <summary>
/// Read the protected string and return it protected with a sequence
/// of bytes generated by a random stream.
/// Get the string as an UTF-8 sequence xorred with bytes
/// from a <c>CryptoRandomStream</c>.
/// </summary>
/// <param name="crsRandomSource">Random number source.</param>
/// <returns>Protected string.</returns>
public byte[] ReadXorredString(CryptoRandomStream crsRandomSource)
{
Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource");
if (crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
byte[] pbData = ReadUtf8();
uint uLen = (uint)pbData.Length;
int cb = pbData.Length;
byte[] randomPad = crsRandomSource.GetRandomBytes(uLen);
Debug.Assert(randomPad.Length == pbData.Length);
byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb);
Debug.Assert(pbPad.Length == cb);
for(uint i = 0; i < uLen; ++i)
pbData[i] ^= randomPad[i];
for (int i = 0; i < cb; ++i)
pbData[i] ^= pbPad[i];
MemUtil.ZeroByteArray(pbPad);
return pbData;
}
public ProtectedString WithProtection(bool bProtect)
{
if(bProtect == m_bIsProtected) return this;
if (bProtect == m_bIsProtected) return this;
byte[] pb = ReadUtf8();
ProtectedString ps = new ProtectedString(bProtect, pb);
if(bProtect) MemUtil.ZeroByteArray(pb);
return ps;
// No need to clear pb; either the current or the new object is unprotected
return new ProtectedString(bProtect, pb);
}
public bool Equals(ProtectedString ps, bool bCheckProtEqual)
{
if (ps == null) throw new ArgumentNullException("ps");
if (object.ReferenceEquals(this, ps)) return true; // Perf. opt.
bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected;
if (bCheckProtEqual && (bPA != bPB)) return false;
if (!bPA && !bPB) return (ReadString() == ps.ReadString());
byte[] pbA = ReadUtf8(), pbB = null;
bool bEq;
try
{
pbB = ps.ReadUtf8();
bEq = MemUtil.ArraysEqual(pbA, pbB);
}
finally
{
if (bPA) MemUtil.ZeroByteArray(pbA);
if (bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB);
}
return bEq;
}
public ProtectedString Insert(int iStart, string strInsert)
{
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if(strInsert == null) throw new ArgumentNullException("strInsert");
if(strInsert.Length == 0) return this;
if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if (strInsert == null) throw new ArgumentNullException("strInsert");
if (strInsert.Length == 0) return this;
// Only operate directly with strings when m_bIsProtected is
// false, not in the case of non-null m_strPlainText, because
// the operation creates a new sequence in memory
if(!m_bIsProtected)
if (!m_bIsProtected)
return new ProtectedString(false, ReadString().Insert(
iStart, strInsert));
UTF8Encoding utf8 = StrUtil.Utf8;
byte[] pb = ReadUtf8();
char[] v = utf8.GetChars(pb);
char[] vNew;
char[] v = ReadChars(), vNew = null;
byte[] pbNew = null;
ProtectedString ps;
try
{
if(iStart > v.Length)
if (iStart > v.Length)
throw new ArgumentOutOfRangeException("iStart");
char[] vIns = strInsert.ToCharArray();
@@ -279,68 +331,104 @@ namespace KeePassLib.Security
Array.Copy(vIns, 0, vNew, iStart, vIns.Length);
Array.Copy(v, iStart, vNew, iStart + vIns.Length,
v.Length - iStart);
pbNew = utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Insert(iStart, strInsert));
}
finally
{
MemUtil.ZeroArray<char>(v);
MemUtil.ZeroByteArray(pb);
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
}
byte[] pbNew = utf8.GetBytes(vNew);
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Insert(iStart, strInsert));
MemUtil.ZeroArray<char>(vNew);
MemUtil.ZeroByteArray(pbNew);
return ps;
}
public ProtectedString Remove(int iStart, int nCount)
{
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if(nCount < 0) throw new ArgumentOutOfRangeException("nCount");
if(nCount == 0) return this;
if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if (nCount < 0) throw new ArgumentOutOfRangeException("nCount");
if (nCount == 0) return this;
// Only operate directly with strings when m_bIsProtected is
// false, not in the case of non-null m_strPlainText, because
// the operation creates a new sequence in memory
if(!m_bIsProtected)
if (!m_bIsProtected)
return new ProtectedString(false, ReadString().Remove(
iStart, nCount));
UTF8Encoding utf8 = StrUtil.Utf8;
byte[] pb = ReadUtf8();
char[] v = utf8.GetChars(pb);
char[] vNew;
char[] v = ReadChars(), vNew = null;
byte[] pbNew = null;
ProtectedString ps;
try
{
if((iStart + nCount) > v.Length)
throw new ArgumentException("iStart + nCount");
if ((iStart + nCount) > v.Length)
throw new ArgumentException("(iStart + nCount) > v.Length");
vNew = new char[v.Length - nCount];
Array.Copy(v, 0, vNew, 0, iStart);
Array.Copy(v, iStart + nCount, vNew, iStart, v.Length -
(iStart + nCount));
pbNew = utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Remove(iStart, nCount));
}
finally
{
MemUtil.ZeroArray<char>(v);
MemUtil.ZeroByteArray(pb);
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
}
byte[] pbNew = utf8.GetBytes(vNew);
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Remove(iStart, nCount));
MemUtil.ZeroArray<char>(vNew);
MemUtil.ZeroByteArray(pbNew);
return ps;
}
public static ProtectedString operator +(ProtectedString a, ProtectedString b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
if (b.IsEmpty) return a.WithProtection(a.IsProtected || b.IsProtected);
if (a.IsEmpty) return b.WithProtection(a.IsProtected || b.IsProtected);
if (!a.IsProtected && !b.IsProtected)
return new ProtectedString(false, a.ReadString() + b.ReadString());
char[] vA = a.ReadChars(), vB = null, vNew = null;
byte[] pbNew = null;
ProtectedString ps;
try
{
vB = b.ReadChars();
vNew = new char[vA.Length + vB.Length];
Array.Copy(vA, vNew, vA.Length);
Array.Copy(vB, 0, vNew, vA.Length, vB.Length);
pbNew = StrUtil.Utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
}
finally
{
MemUtil.ZeroArray<char>(vA);
if (vB != null) MemUtil.ZeroArray<char>(vB);
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
}
return ps;
}
public static ProtectedString operator +(ProtectedString a, string b)
{
ProtectedString psB = new ProtectedString(false, b);
return (a + psB);
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,97 +20,90 @@
using System;
using System.Diagnostics;
using KeePassLib.Utility;
namespace KeePassLib.Security
{
/// <summary>
/// Represents an object that is encrypted using a XOR pad until
/// it is read. <c>XorredBuffer</c> objects are immutable and
/// thread-safe.
/// A <c>XorredBuffer</c> object stores data that is encrypted
/// using a XOR pad.
/// </summary>
public sealed class XorredBuffer
public sealed class XorredBuffer : IDisposable
{
private byte[] m_pbData; // Never null
private byte[] m_pbXorPad; // Always valid for m_pbData
private byte[] m_pbCT;
private byte[] m_pbXorPad;
/// <summary>
/// Length of the protected data in bytes.
/// </summary>
public uint Length
{
get { return (uint)m_pbData.Length; }
get
{
if (m_pbCT == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
return (uint)m_pbCT.Length;
}
}
/// <summary>
/// Construct a new XOR-protected object using a protected byte array
/// and a XOR pad that decrypts the protected data. The
/// <paramref name="pbProtectedData" /> byte array must have the same size
/// as the <paramref name="pbXorPad" /> byte array.
/// Construct a new <c>XorredBuffer</c> object.
/// The <paramref name="pbCT" /> byte array must have the same
/// length as the <paramref name="pbXorPad" /> byte array.
/// The <c>XorredBuffer</c> object takes ownership of the two byte
/// arrays, i.e. the caller must not use or modify them afterwards.
/// arrays, i.e. the caller must not use them afterwards.
/// </summary>
/// <param name="pbProtectedData">Protected data (XOR pad applied).</param>
/// <param name="pbCT">Data with XOR pad applied.</param>
/// <param name="pbXorPad">XOR pad that can be used to decrypt the
/// <paramref name="pbProtectedData" /> parameter.</param>
/// <exception cref="System.ArgumentNullException">Thrown if one of the input
/// parameters is <c>null</c>.</exception>
/// <exception cref="System.ArgumentException">Thrown if the byte arrays are
/// of different size.</exception>
public XorredBuffer(byte[] pbProtectedData, byte[] pbXorPad)
/// <paramref name="pbCT" /> byte array.</param>
public XorredBuffer(byte[] pbCT, byte[] pbXorPad)
{
if(pbProtectedData == null) { Debug.Assert(false); throw new ArgumentNullException("pbProtectedData"); }
if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
if (pbCT == null) { Debug.Assert(false); throw new ArgumentNullException("pbCT"); }
if (pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
if (pbCT.Length != pbXorPad.Length)
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("pbXorPad");
}
Debug.Assert(pbProtectedData.Length == pbXorPad.Length);
if(pbProtectedData.Length != pbXorPad.Length) throw new ArgumentException();
m_pbData = pbProtectedData;
m_pbCT = pbCT;
m_pbXorPad = pbXorPad;
}
#if DEBUG
~XorredBuffer()
{
Debug.Assert((m_pbCT == null) && (m_pbXorPad == null));
}
#endif
public void Dispose()
{
if (m_pbCT == null) return;
MemUtil.ZeroByteArray(m_pbCT);
m_pbCT = null;
MemUtil.ZeroByteArray(m_pbXorPad);
m_pbXorPad = null;
}
/// <summary>
/// Get a copy of the plain-text. The caller is responsible
/// for clearing the byte array safely after using it.
/// </summary>
/// <returns>Unprotected plain-text byte array.</returns>
/// <returns>Plain-text byte array.</returns>
public byte[] ReadPlainText()
{
byte[] pbPlain = new byte[m_pbData.Length];
for(int i = 0; i < pbPlain.Length; ++i)
pbPlain[i] = (byte)(m_pbData[i] ^ m_pbXorPad[i]);
return pbPlain;
}
/* public bool EqualsValue(XorredBuffer xb)
{
if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
if(xb.m_pbData.Length != m_pbData.Length) return false;
for(int i = 0; i < m_pbData.Length; ++i)
byte[] pbCT = m_pbCT, pbX = m_pbXorPad;
if ((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length))
{
byte bt1 = (byte)(m_pbData[i] ^ m_pbXorPad[i]);
byte bt2 = (byte)(xb.m_pbData[i] ^ xb.m_pbXorPad[i]);
if(bt1 != bt2) return false;
Debug.Assert(false);
throw new ObjectDisposedException(null);
}
return true;
byte[] pbPT = new byte[pbCT.Length];
for (int i = 0; i < pbPT.Length; ++i)
pbPT[i] = (byte)(pbCT[i] ^ pbX[i]);
return pbPT;
}
public bool EqualsValue(byte[] pb)
{
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if(pb.Length != m_pbData.Length) return false;
for(int i = 0; i < m_pbData.Length; ++i)
{
if((byte)(m_pbData[i] ^ m_pbXorPad[i]) != pb[i]) return false;
}
return true;
} */
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -78,9 +78,9 @@ namespace KeePassLib.Serialization
public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
{
Debug.Assert(sSource != null);
if(sSource == null) throw new ArgumentNullException("sSource");
if (sSource == null) throw new ArgumentNullException("sSource");
if(m_bUsedOnce)
if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true;
@@ -91,7 +91,8 @@ namespace KeePassLib.Serialization
m_format = fmt;
m_slLogger = slLogger;
m_pbsBinaries.Clear();
// Other applications might not perform a deduplication
m_pbsBinaries = new ProtectedBinarySet(false);
UTF8Encoding encNoBom = StrUtil.Utf8;
byte[] pbCipherKey = null;
@@ -103,153 +104,156 @@ namespace KeePassLib.Serialization
HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
lStreams.Add(sHashing);
try
{
Stream sXml;
if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers)
{
BinaryReaderEx br = new BinaryReaderEx(sHashing,
encNoBom, KLRes.FileCorrupted);
byte[] pbHeader = LoadHeader(br);
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
try
{
Stream sXml;
if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers)
{
BinaryReaderEx br = new BinaryReaderEx(sHashing,
encNoBom, KLRes.FileCorrupted);
byte[] pbHeader = LoadHeader(br);
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
int cbEncKey, cbEncIV;
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
int cbEncKey, cbEncIV;
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
KLRes.FileIncomplete;
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
Stream sPlain;
if(m_uFileVersion < FileVersion32_4)
{
Stream sDecrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, false);
if((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
string strIncomplete = KLRes.FileHeaderCorrupted + " " +
KLRes.FileIncomplete;
lStreams.Add(sDecrypted);
Stream sPlain;
if (m_uFileVersion < FileVersion32_4)
{
Stream sDecrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, false);
if ((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
encNoBom, strIncomplete);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
throw new InvalidCompositeKeyException();
lStreams.Add(sDecrypted);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
encNoBom, strIncomplete);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
throw new InvalidCompositeKeyException();
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
}
else // KDBX >= 4
{
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
if((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
throw new InvalidCompositeKeyException();
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
}
else // KDBX >= 4
{
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
if ((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
false, !m_bRepairMode, pbHmacKey64);
lStreams.Add(sBlocks);
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
if ((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
throw new InvalidCompositeKeyException();
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
cbEncIV, false);
if((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed);
}
lStreams.Add(sPlain);
HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
false, !m_bRepairMode, pbHmacKey64);
lStreams.Add(sBlocks);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
lStreams.Add(sXml);
}
else sXml = sPlain;
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
cbEncIV, false);
if ((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed);
}
if(m_uFileVersion >= FileVersion32_4)
LoadInnerHeader(sXml); // Binary header before XML
}
else if(fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
lStreams.Add(sPlain);
if(fmt == KdbxFormat.Default)
{
if(m_pbInnerRandomStreamKey == null)
{
Debug.Assert(false);
throw new SecurityException("Invalid inner random stream key!");
}
if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{
sXml = new GZipStream(sPlain, CompressionMode.Decompress);
lStreams.Add(sXml);
}
else sXml = sPlain;
if (m_uFileVersion >= FileVersion32_4)
LoadInnerHeader(sXml); // Binary header before XML
}
else if (fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("fmt");
}
if (fmt == KdbxFormat.Default)
{
if (m_pbInnerRandomStreamKey == null)
{
Debug.Assert(false);
throw new SecurityException("Invalid inner random stream key!");
}
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
#if KeePassDebug_WriteXml
// FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
// FileAccess.Write, FileShare.None);
// try
// {
// while(true)
// {
// int b = sXml.ReadByte();
// if(b == -1) break;
// fsOut.WriteByte((byte)b);
// }
// }
// catch(Exception) { }
// fsOut.Close();
#warning XML output is enabled!
/* using(FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
FileAccess.Write, FileShare.None))
{
while(true)
{
int b = sXml.ReadByte();
if(b == -1) throw new EndOfStreamException();
fsOut.WriteByte((byte)b);
}
} */
#endif
var stopWatch = Stopwatch.StartNew();
if (fmt == KdbxFormat.ProtocolBuffers)
{
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
var stopWatch = Stopwatch.StartNew();
Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds));
if (fmt == KdbxFormat.ProtocolBuffers)
{
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
}
else
{
Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds));
ReadXmlStreamed(sXml, sHashing);
}
else
{
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
}
// ReadXmlDom(sXml);
}
catch(CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
}
finally
{
if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
ReadXmlStreamed(sXml, sHashing);
CommonCleanUpRead(lStreams, sHashing);
}
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
}
}
// ReadXmlDom(sXml);
catch (CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
}
finally
{
if (pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
if (pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
CommonCleanUpRead(lStreams, sHashing);
}
#if KDBX_BENCHMARK
swTime.Stop();
@@ -267,9 +271,9 @@ namespace KeePassLib.Serialization
Debug.Assert(m_pbHashOfFileOnDisk != null);
CleanUpInnerRandomStream();
// Reset memory protection settings (to always use reasonable
// defaults)
// defaults)
m_pwDatabase.MemoryProtection = new MemoryProtectionConfig();
// Remove old backups (this call is required here in order to apply
@@ -282,7 +286,7 @@ namespace KeePassLib.Serialization
// Expand the root group, such that in case the user accidently
// collapses the root group he can simply reopen the database
PwGroup pgRoot = m_pwDatabase.RootGroup;
if(pgRoot != null) pgRoot.IsExpanded = true;
if (pgRoot != null) pgRoot.IsExpanded = true;
else { Debug.Assert(false); }
m_pbHashOfHeader = null;
@@ -303,25 +307,25 @@ namespace KeePassLib.Serialization
byte[] pbSig2 = br.ReadBytes(4);
uint uSig2 = MemUtil.BytesToUInt32(pbSig2);
if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
if ((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
throw new OldFormatException(PwDefs.ShortProductName + @" 1.x",
OldFormatException.OldFormatType.KeePass1x);
if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
else if((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
if ((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
else if ((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
FileSignaturePreRelease2)) { }
else throw new FormatException(KLRes.FileSigInvalid);
byte[] pb = br.ReadBytes(4);
uint uVersion = MemUtil.BytesToUInt32(pb);
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
if ((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
throw new FormatException(KLRes.FileVersionUnsupported +
MessageService.NewParagraph + KLRes.FileNewVerReq);
m_uFileVersion = uVersion;
while(true)
while (true)
{
if(!ReadHeaderField(br)) break;
if (!ReadHeaderField(br)) break;
}
br.CopyDataTo = null;
@@ -335,23 +339,23 @@ namespace KeePassLib.Serialization
private bool ReadHeaderField(BinaryReaderEx brSource)
{
Debug.Assert(brSource != null);
if(brSource == null) throw new ArgumentNullException("brSource");
if (brSource == null) throw new ArgumentNullException("brSource");
byte btFieldID = brSource.ReadByte();
int cbSize;
Debug.Assert(m_uFileVersion > 0);
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2));
else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = brSource.ReadBytes(cbSize);
if (cbSize > 0) pbData = brSource.ReadBytes(cbSize);
bool bResult = true;
KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID;
switch(kdbID)
switch (kdbID)
{
case KdbxHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfS = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters();
// m_pbTransformSeed = pbData;
@@ -389,7 +393,7 @@ namespace KeePassLib.Serialization
Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfR = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters();
// m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData);
@@ -429,8 +433,8 @@ namespace KeePassLib.Serialization
default:
Debug.Assert(false);
if(m_slLogger != null)
m_slLogger.SetText(KLRes.UnknownHeaderId + @": " +
if (m_slLogger != null)
m_slLogger.SetText(KLRes.UnknownHeaderId + ": " +
kdbID.ToString() + "!", LogStatusType.Warning);
break;
}
@@ -443,28 +447,28 @@ namespace KeePassLib.Serialization
BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8,
KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc);
while(true)
while (true)
{
if(!ReadInnerHeaderField(br)) break;
if (!ReadInnerHeaderField(br)) break;
}
}
private bool ReadInnerHeaderField(BinaryReaderEx br)
{
Debug.Assert(br != null);
if(br == null) throw new ArgumentNullException("br");
if (br == null) throw new ArgumentNullException("br");
byte btFieldID = br.ReadByte();
int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = br.ReadBytes(cbSize);
if (cbSize > 0) pbData = br.ReadBytes(cbSize);
bool bResult = true;
KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID;
switch(kdbID)
switch (kdbID)
{
case KdbxInnerHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header
@@ -481,15 +485,16 @@ namespace KeePassLib.Serialization
break;
case KdbxInnerHeaderFieldID.Binary:
if(pbData.Length < 1) throw new FormatException();
if (pbData.Length < 1) throw new FormatException();
KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0];
bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None);
ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
1, pbData.Length - 1);
Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
m_pbsBinaries.Add(pb);
if(bProt) MemUtil.ZeroByteArray(pbData);
if (bProt) MemUtil.ZeroByteArray(pbData);
break;
default:
@@ -502,7 +507,7 @@ namespace KeePassLib.Serialization
private void SetCipher(byte[] pbID)
{
if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
if ((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
throw new FormatException(KLRes.FileUnknownCipher);
m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
@@ -511,7 +516,7 @@ namespace KeePassLib.Serialization
private void SetCompressionFlags(byte[] pbFlags)
{
int nID = (int)MemUtil.BytesToUInt32(pbFlags);
if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
if ((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
throw new FormatException(KLRes.FileUnknownCompression);
m_pwDatabase.Compression = (PwCompressionAlgorithm)nID;
@@ -520,12 +525,35 @@ namespace KeePassLib.Serialization
private void SetInnerRandomStreamID(byte[] pbID)
{
uint uID = MemUtil.BytesToUInt32(pbID);
if(uID >= (uint)CrsAlgorithm.Count)
if (uID >= (uint)CrsAlgorithm.Count)
throw new FormatException(KLRes.FileUnknownCipher);
m_craInnerRandomStream = (CrsAlgorithm)uID;
}
internal static PwGroup ReadGroup(Stream msData, PwDatabase pdContext,
bool bCopyIcons, bool bNewUuids, bool bSetCreatedNow)
{
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
KdbxFile f = new KdbxFile(pd);
f.Load(msData, KdbxFormat.PlainXml, null);
if (bCopyIcons)
PwDatabase.CopyCustomIcons(pd, pdContext, pd.RootGroup, true);
if (bNewUuids)
{
pd.RootGroup.Uuid = new PwUuid(true);
pd.RootGroup.CreateNewItemUuids(true, true, true);
}
if (bSetCreatedNow) pd.RootGroup.SetCreatedNow(true);
return pd.RootGroup;
}
[Obsolete]
public static List<PwEntry> ReadEntries(Stream msData)
{
@@ -537,81 +565,14 @@ namespace KeePassLib.Serialization
{
return ReadEntries(msData, pdContext, true);
}
/// <summary>
/// Read entries from a stream.
/// </summary>
/// <param name="msData">Input stream to read the entries from.</param>
/// <returns>Extracted entries.</returns>
/// <param name="pdContext">Context database (e.g. for storing icons).</param>
/// <param name="bCopyIcons">If <c>true</c>, custom icons required by
/// the loaded entries are copied to the context database.</param>
/// <returns>Loaded entries.</returns>
public static List<PwEntry> ReadEntries(Stream msData, PwDatabase pdContext,
bool bCopyIcons)
{
List<PwEntry> lEntries = new List<PwEntry>();
/* KdbxFile f = new KdbxFile(pwDatabase);
if(msData == null) { Debug.Assert(false); return lEntries; }
f.m_format = KdbxFormat.PlainXml;
if (msData == null) { Debug.Assert(false); return new List<PwEntry>(); }
XmlDocument doc = new XmlDocument();
doc.Load(msData);
XmlElement el = doc.DocumentElement;
if(el.Name != ElemRoot) throw new FormatException();
List<PwEntry> vEntries = new List<PwEntry>();
foreach(XmlNode xmlChild in el.ChildNodes)
{
if(xmlChild.Name == ElemEntry)
{
PwEntry pe = f.ReadEntry(xmlChild);
pe.Uuid = new PwUuid(true);
foreach(PwEntry peHistory in pe.History)
peHistory.Uuid = pe.Uuid;
vEntries.Add(pe);
}
else { Debug.Assert(false); }
}
return vEntries; */
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
KdbxFile f = new KdbxFile(pd);
f.Load(msData, KdbxFormat.PlainXml, null);
foreach(PwEntry pe in pd.RootGroup.Entries)
{
pe.SetUuid(new PwUuid(true), true);
lEntries.Add(pe);
if(bCopyIcons && (pdContext != null))
{
PwUuid pu = pe.CustomIconUuid;
if(!pu.Equals(PwUuid.Zero))
{
int iSrc = pd.GetCustomIconIndex(pu);
int iDst = pdContext.GetCustomIconIndex(pu);
if(iSrc < 0) { Debug.Assert(false); }
else if(iDst < 0)
{
pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]);
pdContext.Modified = true;
pdContext.UINeedsIconUpdate = true;
}
}
}
}
return lEntries;
PwGroup pg = ReadGroup(msData, pdContext, bCopyIcons, true, true);
return pg.GetEntries(true).CloneShallowToList();
}
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ using System.IO;
using System.Security;
using System.Text;
using System.Xml;
using keepass2android;
#if !KeePassUAP
using System.Drawing;
using System.Security.Cryptography;
@@ -48,8 +48,6 @@ using KeePassLib.Resources;
using KeePassLib.Security;
using KeePassLib.Utility;
using keepass2android;
namespace KeePassLib.Serialization
{
/// <summary>
@@ -81,14 +79,15 @@ namespace KeePassLib.Serialization
IStatusLogger slLogger)
{
Debug.Assert(sSaveTo != null);
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo");
if (sSaveTo == null) throw new ArgumentNullException("sSaveTo");
if(m_bUsedOnce)
if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true;
m_format = fmt;
m_slLogger = slLogger;
m_xmlWriter = null;
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
UTF8Encoding encNoBom = StrUtil.Utf8;
@@ -96,7 +95,7 @@ namespace KeePassLib.Serialization
byte[] pbCipherKey = null;
byte[] pbHmacKey64 = null;
m_pbsBinaries.Clear();
m_pbsBinaries = new ProtectedBinarySet(true);
m_pbsBinaries.AddFrom(pgRoot);
List<Stream> lStreams = new List<Stream>();
@@ -107,6 +106,10 @@ namespace KeePassLib.Serialization
try
{
// Fix history entries (should not be necessary; just for safety,
// as e.g. XPath searches depend on correct history entry UUIDs)
if (m_pwDatabase.MaintainBackups()) { Debug.Assert(false); }
m_uFileVersion = GetMinKdbxVersion();
int cbEncKey, cbEncIV;
@@ -118,30 +121,30 @@ namespace KeePassLib.Serialization
// m_pbTransformSeed = cr.GetRandomBytes(32);
PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid;
KdfEngine kdf = KdfPool.Get(puKdf);
if(kdf == null)
if (kdf == null)
throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
// KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
"UUID: " + puKdf.ToHexString() + ".");
kdf.Randomize(m_pwDatabase.KdfParameters);
if(m_format == KdbxFormat.Default)
if (m_format == KdbxFormat.Default)
{
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
{
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
}
else // KDBX >= 4
{
m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
}
else // KDBX >= 4
{
m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
}
}
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
m_pbStreamStartBytes = cr.GetRandomBytes(32);
Stream sXml;
@@ -156,11 +159,11 @@ namespace KeePassLib.Serialization
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
Stream sPlain;
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
{
Stream sEncrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, true);
if((sEncrypted == null) || (sEncrypted == sHashing))
if ((sEncrypted == null) || (sEncrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
lStreams.Add(sEncrypted);
@@ -182,22 +185,22 @@ namespace KeePassLib.Serialization
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
cbEncIV, true);
if((sPlain == null) || (sPlain == sBlocks))
if ((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed);
}
lStreams.Add(sPlain);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{
sXml = new GZipStream(sPlain, CompressionMode.Compress);
lStreams.Add(sXml);
}
else sXml = sPlain;
if(m_uFileVersion >= FileVersion32_4)
if (m_uFileVersion >= FileVersion32_4)
WriteInnerHeader(sXml); // Binary header before XML
}
else if(m_format == KdbxFormat.PlainXml)
else if (m_format == KdbxFormat.PlainXml)
sXml = sHashing;
else
{
@@ -251,6 +254,8 @@ namespace KeePassLib.Serialization
private void CommonCleanUpWrite(List<Stream> lStreams, HashingStreamEx sHashing)
{
if (m_xmlWriter != null) { m_xmlWriter.Close(); m_xmlWriter = null; }
CloseStreams(lStreams);
Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
@@ -259,14 +264,13 @@ namespace KeePassLib.Serialization
CleanUpInnerRandomStream();
m_xmlWriter = null;
m_pbHashOfHeader = null;
}
private byte[] GenerateHeader()
{
byte[] pbHeader;
using(MemoryStream ms = new MemoryStream())
using (MemoryStream ms = new MemoryStream())
{
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
@@ -281,7 +285,7 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
{
Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals(
(new AesKdf()).Uuid));
@@ -295,10 +299,10 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters,
KdfParameters.SerializeExt(m_pwDatabase.KdfParameters));
if(m_pbEncryptionIV.Length > 0)
if (m_pbEncryptionIV.Length > 0)
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
{
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey,
m_pbInnerRandomStreamKey);
@@ -306,14 +310,14 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes,
m_pbStreamStartBytes);
int nIrsID = (int)m_craInnerRandomStream;
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
MemUtil.Int32ToBytes(nIrsID));
int nIrsID = (int)m_craInnerRandomStream;
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
MemUtil.Int32ToBytes(nIrsID));
}
// Write public custom data only when there is at least one item,
// because KDBX 3.1 didn't support this field yet
if(m_pwDatabase.PublicCustomData.Count > 0)
if (m_pwDatabase.PublicCustomData.Count > 0)
WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData,
VariantDictionary.Serialize(m_pwDatabase.PublicCustomData));
@@ -333,12 +337,12 @@ namespace KeePassLib.Serialization
byte[] pb = (pbData ?? MemUtil.EmptyByteArray);
int cb = pb.Length;
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
Debug.Assert(m_uFileVersion > 0);
if(m_uFileVersion < FileVersion32_4)
if (m_uFileVersion < FileVersion32_4)
{
if(cb > (int)ushort.MaxValue)
if (cb > (int)ushort.MaxValue)
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("pbData");
@@ -361,13 +365,13 @@ namespace KeePassLib.Serialization
m_pbInnerRandomStreamKey, null);
ProtectedBinary[] vBin = m_pbsBinaries.ToArray();
for(int i = 0; i < vBin.Length; ++i)
for (int i = 0; i < vBin.Length; ++i)
{
ProtectedBinary pb = vBin[i];
if(pb == null) throw new InvalidOperationException();
if (pb == null) throw new InvalidOperationException();
KdbxBinaryFlags f = KdbxBinaryFlags.None;
if(pb.IsProtected) f |= KdbxBinaryFlags.Protected;
if (pb.IsProtected) f |= KdbxBinaryFlags.Protected;
byte[] pbFlags = new byte[1] { (byte)f };
byte[] pbData = pb.ReadData();
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary,
pbFlags, pbData);
if(pb.IsProtected) MemUtil.ZeroByteArray(pbData);
if (pb.IsProtected) MemUtil.ZeroByteArray(pbData);
}
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader,
@@ -391,7 +395,7 @@ namespace KeePassLib.Serialization
byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray);
int cb = pb1.Length + pb2.Length;
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
MemUtil.Write(s, MemUtil.Int32ToBytes(cb));
MemUtil.Write(s, pb1);
@@ -401,7 +405,7 @@ namespace KeePassLib.Serialization
private void WriteDocument(PwGroup pgRoot)
{
Debug.Assert(m_xmlWriter != null);
if(m_xmlWriter == null) throw new InvalidOperationException();
if (m_xmlWriter == null) throw new InvalidOperationException();
uint uNumGroups, uNumEntries, uCurEntry = 0;
pgRoot.GetCounts(true, out uNumGroups, out uNumEntries);
@@ -417,14 +421,14 @@ namespace KeePassLib.Serialization
Stack<PwGroup> groupStack = new Stack<PwGroup>();
groupStack.Push(pgRoot);
GroupHandler gh = delegate(PwGroup pg)
GroupHandler gh = delegate (PwGroup pg)
{
Debug.Assert(pg != null);
if(pg == null) throw new ArgumentNullException("pg");
if (pg == null) throw new ArgumentNullException("pg");
while(true)
while (true)
{
if(pg.ParentGroup == groupStack.Peek())
if (pg.ParentGroup == groupStack.Peek())
{
groupStack.Push(pg);
StartGroup(pg);
@@ -433,7 +437,7 @@ namespace KeePassLib.Serialization
else
{
groupStack.Pop();
if(groupStack.Count <= 0) return false;
if (groupStack.Count <= 0) return false;
EndGroup();
}
@@ -442,23 +446,25 @@ namespace KeePassLib.Serialization
return true;
};
EntryHandler eh = delegate(PwEntry pe)
EntryHandler eh = delegate (PwEntry pe)
{
Debug.Assert(pe != null);
WriteEntry(pe, false);
++uCurEntry;
if(m_slLogger != null)
if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
if (m_slLogger != null)
{
if (!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
return false;
}
return true;
};
if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
throw new InvalidOperationException();
if (!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
throw new OperationCanceledException();
while(groupStack.Count > 1)
while (groupStack.Count > 1)
{
m_xmlWriter.WriteEndElement();
groupStack.Pop();
@@ -479,11 +485,11 @@ namespace KeePassLib.Serialization
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false);
if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
if ((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
WriteObject(ElemHeaderHash, Convert.ToBase64String(
m_pbHashOfHeader), false);
if(m_uFileVersion >= FileVersion32_4)
if (m_uFileVersion >= FileVersion32_4)
WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged);
WriteObject(ElemDbName, m_pwDatabase.Name, true);
@@ -497,7 +503,7 @@ namespace KeePassLib.Serialization
WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged);
WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec);
WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce);
if(m_pwDatabase.MasterKeyChangeForceOnce)
if (m_pwDatabase.MasterKeyChangeForceOnce)
WriteObject(ElemDbKeyChangeForceOnce, true);
WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection);
@@ -515,8 +521,8 @@ namespace KeePassLib.Serialization
WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup);
WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup);
if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
WriteBinPool();
if ((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
WriteBinPool();
WriteList(ElemCustomData, m_pwDatabase.CustomData);
@@ -530,10 +536,10 @@ namespace KeePassLib.Serialization
WriteObject(ElemName, pg.Name, true);
WriteObject(ElemNotes, pg.Notes, true);
WriteObject(ElemIcon, (int)pg.IconId);
if(!pg.CustomIconUuid.Equals(PwUuid.Zero))
if (!pg.CustomIconUuid.Equals(PwUuid.Zero))
WriteObject(ElemCustomIconID, pg.CustomIconUuid);
WriteList(ElemTimes, pg);
WriteObject(ElemIsExpanded, pg.IsExpanded);
WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true);
@@ -541,7 +547,17 @@ namespace KeePassLib.Serialization
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
if(pg.CustomData.Count > 0)
if (m_uFileVersion >= FileVersion32_4_1)
{
if (!pg.PreviousParentGroup.Equals(PwUuid.Zero))
WriteObject(ElemPreviousParentGroup, pg.PreviousParentGroup);
List<string> lTags = pg.Tags;
if (lTags.Count != 0)
WriteObject(ElemTags, StrUtil.TagsToString(lTags, false), true);
}
if (pg.CustomData.Count > 0)
WriteList(ElemCustomData, pg.CustomData);
}
@@ -552,31 +568,39 @@ namespace KeePassLib.Serialization
private void WriteEntry(PwEntry pe, bool bIsHistory)
{
Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe");
Debug.Assert(pe != null); if (pe == null) throw new ArgumentNullException("pe");
m_xmlWriter.WriteStartElement(ElemEntry);
WriteObject(ElemUuid, pe.Uuid);
WriteObject(ElemIcon, (int)pe.IconId);
if(!pe.CustomIconUuid.Equals(PwUuid.Zero))
if (!pe.CustomIconUuid.Equals(PwUuid.Zero))
WriteObject(ElemCustomIconID, pe.CustomIconUuid);
WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false);
WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false);
WriteObject(ElemOverrideUrl, pe.OverrideUrl, true);
if ((m_uFileVersion >= FileVersion32_4_1) && !pe.QualityCheck)
WriteObject(ElemQualityCheck, false);
WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true);
if ((m_uFileVersion >= FileVersion32_4_1) &&
!pe.PreviousParentGroup.Equals(PwUuid.Zero))
WriteObject(ElemPreviousParentGroup, pe.PreviousParentGroup);
WriteList(ElemTimes, pe);
WriteList(pe.Strings, true);
WriteList(pe.Binaries);
WriteList(ElemAutoType, pe.AutoType);
if(pe.CustomData.Count > 0)
if (pe.CustomData.Count > 0)
WriteList(ElemCustomData, pe.CustomData);
if(!bIsHistory) WriteList(ElemHistory, pe.History, true);
if (!bIsHistory) WriteList(ElemHistory, pe.History, true);
else { Debug.Assert(pe.History.UCount == 0); }
m_xmlWriter.WriteEndElement();
@@ -585,18 +609,18 @@ namespace KeePassLib.Serialization
private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings)
{
Debug.Assert(dictStrings != null);
if(dictStrings == null) throw new ArgumentNullException("dictStrings");
if (dictStrings == null) throw new ArgumentNullException("dictStrings");
foreach(KeyValuePair<string, ProtectedString> kvp in dictStrings)
foreach (KeyValuePair<string, ProtectedString> kvp in dictStrings)
WriteObject(kvp.Key, kvp.Value, bEntryStrings);
}
private void WriteList(ProtectedBinaryDictionary dictBinaries)
{
Debug.Assert(dictBinaries != null);
if(dictBinaries == null) throw new ArgumentNullException("dictBinaries");
if (dictBinaries == null) throw new ArgumentNullException("dictBinaries");
foreach(KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
foreach (KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
WriteObject(kvp.Key, kvp.Value, true);
}
@@ -604,19 +628,19 @@ namespace KeePassLib.Serialization
{
Debug.Assert(name != null);
Debug.Assert(cfgAutoType != null);
if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
if (cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled);
WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions);
if(cfgAutoType.DefaultSequence.Length > 0)
if (cfgAutoType.DefaultSequence.Length > 0)
WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true);
foreach(AutoTypeAssociation a in cfgAutoType.Associations)
foreach (AutoTypeAssociation a in cfgAutoType.Associations)
WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence,
new KeyValuePair<string, string>(a.WindowName, a.Sequence));
new KeyValuePair<string, string>(a.WindowName, a.Sequence), null);
m_xmlWriter.WriteEndElement();
}
@@ -624,7 +648,7 @@ namespace KeePassLib.Serialization
private void WriteList(string name, ITimeLogger times)
{
Debug.Assert(name != null);
Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times");
Debug.Assert(times != null); if (times == null) throw new ArgumentNullException("times");
m_xmlWriter.WriteStartElement(name);
@@ -636,17 +660,17 @@ namespace KeePassLib.Serialization
WriteObject(ElemUsageCount, times.UsageCount);
WriteObject(ElemLocationChanged, times.LocationChanged);
m_xmlWriter.WriteEndElement(); // Name
m_xmlWriter.WriteEndElement();
}
private void WriteList(string name, PwObjectList<PwEntry> value, bool bIsHistory)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(PwEntry pe in value)
foreach (PwEntry pe in value)
WriteEntry(pe, bIsHistory);
m_xmlWriter.WriteEndElement();
@@ -655,11 +679,11 @@ namespace KeePassLib.Serialization
private void WriteList(string name, PwObjectList<PwDeletedObject> value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(PwDeletedObject pdo in value)
foreach (PwDeletedObject pdo in value)
WriteObject(ElemDeletedObject, pdo);
m_xmlWriter.WriteEndElement();
@@ -685,31 +709,45 @@ namespace KeePassLib.Serialization
private void WriteList(string name, StringDictionaryEx value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(KeyValuePair<string, string> kvp in value)
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp);
foreach (KeyValuePair<string, string> kvp in value)
{
DateTime? odtLastMod = null;
if (m_uFileVersion >= FileVersion32_4_1)
odtLastMod = value.GetLastModificationTime(kvp.Key);
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp, odtLastMod);
}
m_xmlWriter.WriteEndElement();
}
private void WriteCustomIconList()
{
if(m_pwDatabase.CustomIcons.Count == 0) return;
if (m_pwDatabase.CustomIcons.Count == 0) return;
m_xmlWriter.WriteStartElement(ElemCustomIcons);
foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons)
foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
{
m_xmlWriter.WriteStartElement(ElemCustomIconItem);
WriteObject(ElemCustomIconItemID, pwci.Uuid);
WriteObject(ElemCustomIconItemID, ci.Uuid);
string strData = Convert.ToBase64String(pwci.ImageDataPng);
string strData = Convert.ToBase64String(ci.ImageDataPng);
WriteObject(ElemCustomIconItemData, strData, false);
if (m_uFileVersion >= FileVersion32_4_1)
{
if (ci.Name.Length != 0)
WriteObject(ElemName, ci.Name, true);
if (ci.LastModificationTime.HasValue)
WriteObject(ElemLastModTime, ci.LastModificationTime.Value);
}
m_xmlWriter.WriteEndElement();
}
@@ -724,7 +762,7 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(name);
if(bFilterValueXmlChars)
if (bFilterValueXmlChars)
m_xmlWriter.WriteString(StrUtil.SafeXmlString(value));
else m_xmlWriter.WriteString(value);
@@ -741,7 +779,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, PwUuid value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
WriteObject(name, Convert.ToBase64String(value.UuidBytes), false);
}
@@ -788,7 +826,7 @@ namespace KeePassLib.Serialization
Debug.Assert(value.Kind == DateTimeKind.Utc);
// Cf. ReadTime
if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
if ((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
{
DateTime dt = TimeUtil.ToUtc(value, false);
@@ -805,29 +843,33 @@ namespace KeePassLib.Serialization
byte[] pb = MemUtil.Int64ToBytes(lSec);
WriteObject(name, Convert.ToBase64String(pb), false);
}
}
else WriteObject(name, TimeUtil.SerializeUtc(value), false);
}
private void WriteObject(string name, string strKeyName,
string strValueName, KeyValuePair<string, string> kvp)
private void WriteObject(string name, string strKeyName, string strValueName,
KeyValuePair<string, string> kvp, DateTime? odtLastMod)
{
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteStartElement(strKeyName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key));
m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteStartElement(strValueName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value));
m_xmlWriter.WriteEndElement();
if (odtLastMod.HasValue)
WriteObject(ElemLastModTime, odtLastMod.Value);
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, ProtectedString value, bool bIsEntryString)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemString);
m_xmlWriter.WriteStartElement(ElemKey);
@@ -836,30 +878,30 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemValue);
bool bProtected = value.IsProtected;
if(bIsEntryString)
if (bIsEntryString)
{
// Adjust memory protection setting (which might be different
// from the database default, e.g. due to an import which
// didn't specify the correct setting)
if(name == PwDefs.TitleField)
if (name == PwDefs.TitleField)
bProtected = m_pwDatabase.MemoryProtection.ProtectTitle;
else if(name == PwDefs.UserNameField)
else if (name == PwDefs.UserNameField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUserName;
else if(name == PwDefs.PasswordField)
else if (name == PwDefs.PasswordField)
bProtected = m_pwDatabase.MemoryProtection.ProtectPassword;
else if(name == PwDefs.UrlField)
else if (name == PwDefs.UrlField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUrl;
else if(name == PwDefs.NotesField)
else if (name == PwDefs.NotesField)
bProtected = m_pwDatabase.MemoryProtection.ProtectNotes;
}
if(bProtected && (m_format == KdbxFormat.Default))
if (bProtected && (m_format == KdbxFormat.Default))
{
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredString(m_randomStream);
if(pbEncoded.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
byte[] pbEnc = value.ReadXorredString(m_randomStream);
if (pbEnc.Length > 0)
m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
}
else
{
@@ -869,25 +911,24 @@ namespace KeePassLib.Serialization
// string transformation here. By default, language-dependent conversions
// should be applied, otherwise characters could be rendered incorrectly
// (code page problems).
if(m_bLocalizedNames)
if (g_bLocalizedNames)
{
StringBuilder sb = new StringBuilder();
foreach(char ch in strValue)
foreach (char ch in strValue)
{
char chMapped = ch;
// Symbols and surrogates must be moved into the correct code
// page area
if(char.IsSymbol(ch) || char.IsSurrogate(ch))
if (char.IsSymbol(ch) || char.IsSurrogate(ch))
{
System.Globalization.UnicodeCategory cat =
CharUnicodeInfo.GetUnicodeCategory(ch);
UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch);
// Map character to correct position in code page
chMapped = (char)((int)cat * 32 + ch);
}
else if(char.IsControl(ch))
else if (char.IsControl(ch))
{
if(ch >= 256) // Control character in high ANSI code page
if (ch >= 256) // Control character in high ANSI code page
{
// Some of the control characters map to corresponding ones
// in the low ANSI range (up to 255) when calling
@@ -907,7 +948,7 @@ namespace KeePassLib.Serialization
strValue = sb.ToString(); // Correct string for current code page
}
if((m_format == KdbxFormat.PlainXml) && bProtected)
if ((m_format == KdbxFormat.PlainXml) && bProtected)
m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue));
@@ -920,7 +961,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, ProtectedBinary value, bool bAllowRef)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteStartElement(ElemKey);
@@ -929,13 +970,13 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemValue);
string strRef = null;
if(bAllowRef)
if (bAllowRef)
{
int iRef = m_pbsBinaries.Find(value);
if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
if (iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
else { Debug.Assert(false); }
}
if(strRef != null)
if (strRef != null)
m_xmlWriter.WriteAttributeString(AttrRef, strRef);
else SubWriteValue(value);
@@ -945,17 +986,17 @@ namespace KeePassLib.Serialization
private void SubWriteValue(ProtectedBinary value)
{
if(value.IsProtected && (m_format == KdbxFormat.Default))
if (value.IsProtected && (m_format == KdbxFormat.Default))
{
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredData(m_randomStream);
if(pbEncoded.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
byte[] pbEnc = value.ReadXorredData(m_randomStream);
if (pbEnc.Length > 0)
m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
}
else
{
if(m_pwDatabase.Compression != PwCompressionAlgorithm.None)
if (m_pwDatabase.Compression != PwCompressionAlgorithm.None)
{
m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue);
@@ -963,18 +1004,18 @@ namespace KeePassLib.Serialization
byte[] pbCmp = MemUtil.Compress(pbRaw);
m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length);
if(value.IsProtected)
if (value.IsProtected)
{
MemUtil.ZeroByteArray(pbRaw);
MemUtil.ZeroByteArray(pbCmp);
}
}
}
else
{
byte[] pbRaw = value.ReadData();
m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length);
if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
if (value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
}
}
}
@@ -982,7 +1023,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, PwDeletedObject value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemUuid, value.Uuid);
@@ -995,7 +1036,7 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemBinaries);
ProtectedBinary[] v = m_pbsBinaries.ToArray();
for(int i = 0; i < v.Length; ++i)
for (int i = 0; i < v.Length; ++i)
{
m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteAttributeString(AttrId,
@@ -1007,6 +1048,25 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteEndElement();
}
internal static void WriteGroup(Stream msOutput, PwDatabase pdContext,
PwGroup pg)
{
if (msOutput == null) throw new ArgumentNullException("msOutput");
// pdContext may be null
if (pg == null) throw new ArgumentNullException("pg");
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), pg.Name);
pd.RootGroup = pg.CloneDeep();
pd.RootGroup.ParentGroup = null;
PwDatabase.CopyCustomIcons(pdContext, pd, pd.RootGroup, true);
KdbxFile f = new KdbxFile(pd);
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
}
[Obsolete]
public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries)
{
@@ -1014,66 +1074,21 @@ namespace KeePassLib.Serialization
}
public static bool WriteEntries(Stream msOutput, PwDatabase pdContext,
PwEntry[] vEntries)
PwEntry[] vEntries)
{
if (msOutput == null) { Debug.Assert(false); return false; }
// pdContext may be null
if (vEntries == null) { Debug.Assert(false); return false; }
/* KdbxFile f = new KdbxFile(pwDatabase);
f.m_format = KdbxFormat.PlainXml;
XmlTextWriter xtw = null;
try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); }
catch(Exception) { Debug.Assert(false); return false; }
if(xtw == null) { Debug.Assert(false); return false; }
f.m_xmlWriter = xtw;
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
xtw.WriteStartDocument(true);
xtw.WriteStartElement(ElemRoot);
foreach(PwEntry pe in vEntries)
f.WriteEntry(pe, false);
xtw.WriteEndElement();
xtw.WriteEndDocument();
xtw.Flush();
xtw.Close();
return true; */
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
PwGroup pg = pd.RootGroup;
if (pg == null) { Debug.Assert(false); return false; }
PwGroup pg = new PwGroup(true, true);
foreach (PwEntry pe in vEntries)
{
PwUuid pu = pe.CustomIconUuid;
if (!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0))
{
int i = -1;
if (pdContext != null) i = pdContext.GetCustomIconIndex(pu);
if (i >= 0)
{
PwCustomIcon ci = pdContext.CustomIcons[i];
pd.CustomIcons.Add(ci);
}
else { Debug.Assert(pdContext == null); }
}
PwEntry peCopy = pe.CloneDeep();
pg.AddEntry(peCopy, true);
}
KdbxFile f = new KdbxFile(pd);
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
WriteGroup(msOutput, pdContext, pg);
return true;
}
}

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -67,47 +67,51 @@ namespace KeePassLib.Serialization
/// </summary>
public sealed partial class KdbxFile
{
private class ColorTranslator
{
public static Color FromHtml(String colorString)
{
Color color;
/// <summary>
/// System.Drawing.ColorTranslator is not supported on Android. Provide a custom implementation here for loading colors from file.
/// </summary>
private class ColorTranslator
{
public static Color FromHtml(String colorString)
{
Color color;
if (colorString.StartsWith("#"))
{
colorString = colorString.Substring(1);
}
if (colorString.EndsWith(";"))
{
colorString = colorString.Substring(0, colorString.Length - 1);
}
if (colorString.StartsWith("#"))
{
colorString = colorString.Substring(1);
}
if (colorString.EndsWith(";"))
{
colorString = colorString.Substring(0, colorString.Length - 1);
}
int red, green, blue;
switch (colorString.Length)
{
case 6:
red = int.Parse(colorString.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 3:
red = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(1, 1), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(2, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 1:
red = green = blue = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
default:
throw new ArgumentException("Invalid color: " + colorString);
}
return color;
}
int red, green, blue;
switch (colorString.Length)
{
case 6:
red = int.Parse(colorString.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 3:
red = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(1, 1), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(2, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 1:
red = green = blue = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
default:
throw new ArgumentException("Invalid color: " + colorString);
}
return color;
}
}
}
/// <summary>
/// File identifier, first 32-bit value.
/// </summary>
@@ -119,16 +123,17 @@ namespace KeePassLib.Serialization
internal const uint FileSignature2 = 0xB54BFB67;
/// <summary>
/// File version of files saved by the current <c>KdbxFile</c> class.
/// Maximum supported version of database files.
/// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00,
/// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01.
/// The first 2 bytes are critical (i.e. loading will fail, if the
/// file version is too high), the last 2 bytes are informational.
/// </summary>
private const uint FileVersion32 = 0x00040000;
private const uint FileVersion32 = 0x00040001;
public const uint FileVersion32_4 = 0x00040000; // First of 4.x series
public const uint FileVersion32_3 = 0x00030001; // Old format 3.1
public const uint FileVersion32_4_1 = 0x00040001; // 4.1
public const uint FileVersion32_4 = 0x00040000; // 4.0
public const uint FileVersion32_3_1 = 0x00030001; // 3.1
private const uint FileVersionCriticalMask = 0xFFFF0000;
@@ -143,7 +148,7 @@ namespace KeePassLib.Serialization
private const string ElemMeta = "Meta";
private const string ElemRoot = "Root";
private const string ElemGroup = "Group";
private const string ElemEntry = "Entry";
internal const string ElemEntry = "Entry";
private const string ElemGenerator = "Generator";
private const string ElemHeaderHash = "HeaderHash";
@@ -188,12 +193,13 @@ namespace KeePassLib.Serialization
private const string ElemName = "Name";
private const string ElemNotes = "Notes";
private const string ElemUuid = "UUID";
internal const string ElemUuid = "UUID";
private const string ElemIcon = "IconID";
private const string ElemCustomIconID = "CustomIconUUID";
private const string ElemFgColor = "ForegroundColor";
private const string ElemBgColor = "BackgroundColor";
private const string ElemOverrideUrl = "OverrideURL";
private const string ElemQualityCheck = "QualityCheck";
private const string ElemTimes = "Times";
private const string ElemTags = "Tags";
@@ -205,6 +211,8 @@ namespace KeePassLib.Serialization
private const string ElemUsageCount = "UsageCount";
private const string ElemLocationChanged = "LocationChanged";
private const string ElemPreviousParentGroup = "PreviousParentGroup";
private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence";
private const string ElemEnableAutoType = "EnableAutoType";
private const string ElemEnableSearching = "EnableSearching";
@@ -261,7 +269,7 @@ namespace KeePassLib.Serialization
private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant;
private byte[] m_pbInnerRandomStreamKey = null;
private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet();
private ProtectedBinarySet m_pbsBinaries = null;
private byte[] m_pbHashOfHeader = null;
private byte[] m_pbHashOfFileOnDisk = null;
@@ -271,7 +279,7 @@ namespace KeePassLib.Serialization
private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs
private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs
private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec;
private static bool m_bLocalizedNames = false;
private static bool g_bLocalizedNames = false;
private enum KdbxHeaderFieldID : byte
{
@@ -345,7 +353,7 @@ namespace KeePassLib.Serialization
public KdbxFile(PwDatabase pwDataStore)
{
Debug.Assert(pwDataStore != null);
if(pwDataStore == null) throw new ArgumentNullException("pwDataStore");
if (pwDataStore == null) throw new ArgumentNullException("pwDataStore");
m_pwDatabase = pwDataStore;
}
@@ -356,57 +364,92 @@ namespace KeePassLib.Serialization
public static void DetermineLanguageId()
{
// Test if localized names should be used. If localized names are used,
// the m_bLocalizedNames value must be set to true. By default, localized
// names should be used! (Otherwise characters could be corrupted
// the g_bLocalizedNames value must be set to true. By default, localized
// names should be used (otherwise characters could be corrupted
// because of different code pages).
unchecked
{
uint uTest = 0;
foreach(char ch in PwDatabase.LocalizedAppName)
foreach (char ch in PwDatabase.LocalizedAppName)
uTest = uTest * 5 + ch;
m_bLocalizedNames = (uTest != NeutralLanguageID);
g_bLocalizedNames = (uTest != NeutralLanguageID);
}
}
private uint GetMinKdbxVersion()
private uint GetMinKdbxVersion()
{
if (m_uForceVersion != 0) return m_uForceVersion;
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
return Math.Max(minRequiredVersion, GetMinKdbxVersionOrig());
}
private uint GetMinKdbxVersionOrig()
{
if(m_uForceVersion != 0) return m_uForceVersion;
if (m_uForceVersion != 0) return m_uForceVersion;
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
// See also KeePassKdb2x3.Export (KDBX 3.1 export module)
uint uMin = 0;
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
AesKdf kdfAes = new AesKdf();
if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid))
return Math.Max(FileVersion32, minRequiredVersion);
if(m_pwDatabase.PublicCustomData.Count > 0)
return Math.Max(FileVersion32, minRequiredVersion);
bool bCustomData = false;
GroupHandler gh = delegate(PwGroup pg)
GroupHandler gh = delegate (PwGroup pg)
{
if(pg == null) { Debug.Assert(false); return true; }
if(pg.CustomData.Count > 0) { bCustomData = true; return false; }
if (pg == null) { Debug.Assert(false); return true; }
if (pg.Tags.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4_1);
if (pg.CustomData.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4);
return true;
};
EntryHandler eh = delegate(PwEntry pe)
EntryHandler eh = delegate (PwEntry pe)
{
if(pe == null) { Debug.Assert(false); return true; }
if(pe.CustomData.Count > 0) { bCustomData = true; return false; }
if (pe == null) { Debug.Assert(false); return true; }
if (!pe.QualityCheck)
uMin = Math.Max(uMin, FileVersion32_4_1);
if (pe.CustomData.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4);
return true;
};
gh(m_pwDatabase.RootGroup);
m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
if(bCustomData)
return Math.Max(FileVersion32, minRequiredVersion);
return Math.Max(FileVersion32_3, minRequiredVersion); ; // KDBX 3.1 is sufficient
if (uMin >= FileVersion32_4_1) return uMin; // All below is <= 4.1
foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
{
if ((ci.Name.Length != 0) || ci.LastModificationTime.HasValue)
return FileVersion32_4_1;
}
foreach (KeyValuePair<string, string> kvp in m_pwDatabase.CustomData)
{
DateTime? odt = m_pwDatabase.CustomData.GetLastModificationTime(kvp.Key);
if (odt.HasValue) return FileVersion32_4_1;
}
if (uMin >= FileVersion32_4) return uMin; // All below is <= 4
if (m_pwDatabase.DataCipherUuid.Equals(ChaCha20Engine.ChaCha20Uuid))
return FileVersion32_4;
AesKdf kdfAes = new AesKdf();
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfAes.Uuid))
return FileVersion32_4;
if (m_pwDatabase.PublicCustomData.Count != 0)
return FileVersion32_4;
return FileVersion32_3_1; // KDBX 3.1 is sufficient
}
private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey,
@@ -416,12 +459,12 @@ namespace KeePassLib.Serialization
try
{
Debug.Assert(m_pbMasterSeed != null);
if(m_pbMasterSeed == null)
if (m_pbMasterSeed == null)
throw new ArgumentNullException("m_pbMasterSeed");
Debug.Assert(m_pbMasterSeed.Length == 32);
if(m_pbMasterSeed.Length != 32)
if (m_pbMasterSeed.Length != 32)
throw new FormatException(KLRes.MasterSeedLengthInvalid);
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
Debug.Assert(m_pwDatabase != null);
Debug.Assert(m_pwDatabase.MasterKey != null);
@@ -431,10 +474,10 @@ namespace KeePassLib.Serialization
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
Debug.Assert(pbinUser != null);
if(pbinUser == null)
if (pbinUser == null)
throw new SecurityException(KLRes.InvalidCompositeKey);
byte[] pUserKey32 = pbinUser.ReadData();
if((pUserKey32 == null) || (pUserKey32.Length != 32))
if ((pUserKey32 == null) || (pUserKey32.Length != 32))
throw new SecurityException(KLRes.InvalidCompositeKey);
Array.Copy(pUserKey32, 0, pbCmp, 32, 32);
MemUtil.ZeroByteArray(pUserKey32);
@@ -442,7 +485,7 @@ namespace KeePassLib.Serialization
pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey);
pbCmp[64] = 1;
using(SHA512Managed h = new SHA512Managed())
using (SHA512Managed h = new SHA512Managed())
{
pbHmacKey64 = h.ComputeHash(pbCmp);
}
@@ -454,19 +497,19 @@ namespace KeePassLib.Serialization
{
PwUuid pu = m_pwDatabase.DataCipherUuid;
ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu);
if(iCipher == null) // CryptographicExceptions are translated to "file corrupted"
if (iCipher == null) // CryptographicExceptions are translated to "file corrupted"
throw new Exception(KLRes.FileUnknownCipher +
MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq +
MessageService.NewParagraph + "UUID: " + pu.ToHexString() + ".");
ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2);
if(iCipher2 != null)
if (iCipher2 != null)
{
cbEncKey = iCipher2.KeyLength;
if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
if (cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
cbEncIV = iCipher2.IVLength;
if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
if (cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
}
else
{
@@ -481,13 +524,13 @@ namespace KeePassLib.Serialization
byte[] pbKey, int cbIV, bool bEncrypt)
{
byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
if(pbIV.Length != cbIV)
if (pbIV.Length != cbIV)
{
Debug.Assert(false);
throw new Exception(KLRes.FileCorrupted);
}
if(bEncrypt)
if (bEncrypt)
return iCipher.EncryptStream(s, pbKey, pbIV);
return iCipher.DecryptStream(s, pbKey, pbIV);
}
@@ -497,7 +540,7 @@ namespace KeePassLib.Serialization
byte[] pbHeaderHmac;
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(
pbKey, ulong.MaxValue);
using(HMACSHA256 h = new HMACSHA256(pbBlockKey))
using (HMACSHA256 h = new HMACSHA256(pbBlockKey))
{
pbHeaderHmac = h.ComputeHash(pbHeader);
}
@@ -508,7 +551,7 @@ namespace KeePassLib.Serialization
private void CloseStreams(List<Stream> lStreams)
{
if(lStreams == null) { Debug.Assert(false); return; }
if (lStreams == null) { Debug.Assert(false); return; }
// Typically, closing a stream also closes its base
// stream; however, there may be streams that do not
@@ -516,14 +559,14 @@ namespace KeePassLib.Serialization
// we close all streams manually, from the innermost
// to the outermost
for(int i = lStreams.Count - 1; i >= 0; --i)
for (int i = lStreams.Count - 1; i >= 0; --i)
{
// Check for duplicates
Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) &&
(lStreams.LastIndexOf(lStreams[i]) == i));
try { lStreams[i].Close(); }
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
}
// Do not clear the list
@@ -531,18 +574,18 @@ namespace KeePassLib.Serialization
private void CleanUpInnerRandomStream()
{
if(m_randomStream != null) m_randomStream.Dispose();
if (m_randomStream != null) m_randomStream.Dispose();
if(m_pbInnerRandomStreamKey != null)
if (m_pbInnerRandomStreamKey != null)
MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey);
}
private static void SaveBinary(string strName, ProtectedBinary pb,
string strSaveDir)
{
if(pb == null) { Debug.Assert(false); return; }
if (pb == null) { Debug.Assert(false); return; }
if(string.IsNullOrEmpty(strName)) strName = "File.bin";
strName = UrlUtil.GetSafeFileName(strName);
string strPath;
int iTry = 1;
@@ -550,31 +593,23 @@ namespace KeePassLib.Serialization
{
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
string strExt = UrlUtil.GetExtension(strName);
string strDesc = UrlUtil.StripExtension(strName);
string strExt = UrlUtil.GetExtension(strName);
strPath += strDesc;
if(iTry > 1)
if (iTry > 1)
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
")";
if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
++iTry;
}
while(File.Exists(strPath));
while (File.Exists(strPath));
#if !KeePassLibSD
byte[] pbData = pb.ReadData();
File.WriteAllBytes(strPath, pbData);
MemUtil.ZeroByteArray(pbData);
#else
FileStream fs = new FileStream(strPath, FileMode.Create,
FileAccess.Write, FileShare.None);
byte[] pbData = pb.ReadData();
fs.Write(pbData, 0, pbData.Length);
fs.Close();
#endif
try { File.WriteAllBytes(strPath, pbData); }
finally { if (pb.IsProtected) MemUtil.ZeroByteArray(pbData); }
}
}
}

View File

@@ -37,7 +37,7 @@ namespace KeePassLib.Utility
/// </summary>
public static class MemUtil
{
internal static readonly byte[] EmptyByteArray = new byte[0];
public static readonly byte[] EmptyByteArray = new byte[0];
private static readonly uint[] m_vSBox = new uint[256] {
0xCD2FACB3, 0xE78A7F5C, 0x6F0803FC, 0xBCF6E230,
@@ -786,5 +786,28 @@ namespace KeePassLib.Utility
yield break;
}
internal static bool ListsEqual<T>(List<T> a, List<T> b)
where T : class, IEquatable<T>
{
if (object.ReferenceEquals(a, b)) return true;
if ((a == null) || (b == null)) return false;
int n = a.Count;
if (n != b.Count) return false;
for (int i = 0; i < n; ++i)
{
T tA = a[i], tB = b[i];
if (tA == null)
{
if (tB != null) return false;
}
else if (tB == null) return false;
else if (!tA.Equals(tB)) return false;
}
return true;
}
}
}

View File

@@ -429,7 +429,7 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/
if ((strFilePath != null) && (strFilePath.Length > 0))
str += strFilePath + MessageService.NewParagraph;
str += KLRes.FileSaveFailed;
str += KLRes.FileSaveFailed2;
if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0))
str += MessageService.NewParagraph + ex.Message;

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de>
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -20,8 +20,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using KeePassLib.Native;
@@ -33,9 +35,7 @@ namespace KeePassLib.Utility
/// </summary>
public static class UrlUtil
{
private static readonly char[] m_vDirSeps = new char[] {
'\\', '/', UrlUtil.LocalDirSepChar };
private static readonly char[] m_vPathTrimCharsWs = new char[] {
private static readonly char[] g_vPathTrimCharsWs = new char[] {
'\"', ' ', '\t', '\r', '\n' };
public static char LocalDirSepChar
@@ -43,6 +43,32 @@ namespace KeePassLib.Utility
get { return Path.DirectorySeparatorChar; }
}
private static char[] g_vDirSepChars = null;
private static char[] DirSepChars
{
get
{
if (g_vDirSepChars == null)
{
List<char> l = new List<char>();
l.Add('/'); // For URLs, also on Windows
// On Unix-like systems, '\\' is not a separator
if (!NativeLib.IsUnix()) l.Add('\\');
if (!l.Contains(UrlUtil.LocalDirSepChar))
{
Debug.Assert(false);
l.Add(UrlUtil.LocalDirSepChar);
}
g_vDirSepChars = l.ToArray();
}
return g_vDirSepChars;
}
}
/// <summary>
/// Get the directory (path) of a file name. The returned string may be
/// terminated by a directory separator character. Example:
@@ -63,16 +89,16 @@ namespace KeePassLib.Utility
bool bEnsureValidDirSpec)
{
Debug.Assert(strFile != null);
if(strFile == null) throw new ArgumentNullException("strFile");
if (strFile == null) throw new ArgumentNullException("strFile");
int nLastSep = strFile.LastIndexOfAny(m_vDirSeps);
if(nLastSep < 0) return string.Empty; // No directory
int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars);
if (nLastSep < 0) return string.Empty; // No directory
if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
if (bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
(strFile[2] == '\\')) // Length >= 3 and Windows root directory
bAppendTerminatingChar = true;
if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep);
if (!bAppendTerminatingChar) return strFile.Substring(0, nLastSep);
return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep),
(strFile[nLastSep] == '/'));
}
@@ -87,12 +113,12 @@ namespace KeePassLib.Utility
/// an empty string (<c>""</c>) if the input parameter is <c>null</c>.</returns>
public static string GetFileName(string strPath)
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastSep = strPath.LastIndexOfAny(m_vDirSeps);
int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
if(nLastSep < 0) return strPath;
if(nLastSep >= (strPath.Length - 1)) return string.Empty;
if (nLastSep < 0) return strPath;
if (nLastSep >= (strPath.Length - 1)) return string.Empty;
return strPath.Substring(nLastSep + 1);
}
@@ -104,12 +130,12 @@ namespace KeePassLib.Utility
/// <returns>File name without extension.</returns>
public static string StripExtension(string strPath)
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return strPath;
if (nLastExtDot <= nLastDirSep) return strPath;
return strPath.Substring(0, nLastExtDot);
}
@@ -121,13 +147,13 @@ namespace KeePassLib.Utility
/// <returns>Extension without prepending dot.</returns>
public static string GetExtension(string strPath)
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps);
int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return string.Empty;
if(nLastExtDot == (strPath.Length - 1)) return string.Empty;
if (nLastExtDot <= nLastDirSep) return string.Empty;
if (nLastExtDot == (strPath.Length - 1)) return string.Empty;
return strPath.Substring(nLastExtDot + 1);
}
@@ -142,19 +168,16 @@ namespace KeePassLib.Utility
/// <returns>Path having a directory separator as last character.</returns>
public static string EnsureTerminatingSeparator(string strPath, bool bUrl)
{
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLength = strPath.Length;
if(nLength <= 0) return string.Empty;
if (nLength <= 0) return string.Empty;
char chLast = strPath[nLength - 1];
if (Array.IndexOf<char>(UrlUtil.DirSepChars, chLast) >= 0)
return strPath;
for(int i = 0; i < m_vDirSeps.Length; ++i)
{
if(chLast == m_vDirSeps[i]) return strPath;
}
if(bUrl) return (strPath + '/');
if (bUrl) return (strPath + '/');
return (strPath + UrlUtil.LocalDirSepChar);
}
@@ -216,55 +239,78 @@ namespace KeePassLib.Utility
return false;
} */
internal static int IndexOfSecondEnclQuote(string str)
{
if (str == null) { Debug.Assert(false); return -1; }
if (str.Length <= 1) return -1;
if (str[0] != '\"') { Debug.Assert(false); return -1; }
if (NativeLib.IsUnix())
{
// Find non-escaped quote
string strFlt = str.Replace("\\\\", new string(
StrUtil.GetUnusedChar(str + "\\\""), 2)); // Same length
Match m = Regex.Match(strFlt, "[^\\\\]\\u0022");
int i = (((m != null) && m.Success) ? m.Index : -1);
return ((i >= 0) ? (i + 1) : -1); // Index of quote
}
// Windows does not allow quotes in folder/file names
return str.IndexOf('\"', 1);
}
public static string GetQuotedAppPath(string strPath)
{
if(strPath == null) { Debug.Assert(false); return string.Empty; }
// int nFirst = strPath.IndexOf('\"');
// int nSecond = strPath.IndexOf('\"', nFirst + 1);
// if((nFirst >= 0) && (nSecond >= 0))
// return strPath.Substring(nFirst + 1, nSecond - nFirst - 1);
// return strPath;
if (strPath == null) { Debug.Assert(false); return string.Empty; }
string str = strPath.Trim();
if(str.Length <= 1) return str;
if(str[0] != '\"') return str;
if (str.Length <= 1) return str;
if (str[0] != '\"') return str;
int iSecond = str.IndexOf('\"', 1);
if(iSecond <= 0) return str;
int iSecond = IndexOfSecondEnclQuote(str);
if (iSecond <= 0) return str;
return str.Substring(1, iSecond - 1);
}
public static string FileUrlToPath(string strUrl)
{
Debug.Assert(strUrl != null);
if(strUrl == null) throw new ArgumentNullException("strUrl");
if (strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); }
if (strUrl.Length == 0) { Debug.Assert(false); return string.Empty; }
string str = strUrl;
if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp))
str = str.Substring(8, str.Length - 8);
if (!strUrl.StartsWith(Uri.UriSchemeFile + ":", StrUtil.CaseIgnoreCmp))
{
Debug.Assert(false);
return strUrl;
}
str = str.Replace('/', UrlUtil.LocalDirSepChar);
try
{
Uri uri = new Uri(strUrl);
string str = uri.LocalPath;
if (!string.IsNullOrEmpty(str)) return str;
}
catch (Exception) { Debug.Assert(false); }
return str;
Debug.Assert(false);
return strUrl;
}
public static bool UnhideFile(string strFile)
{
#if (KeePassLibSD || KeePassRT)
#if KeePassLibSD
return false;
#else
if(strFile == null) throw new ArgumentNullException("strFile");
if (strFile == null) throw new ArgumentNullException("strFile");
try
{
FileAttributes fa = File.GetAttributes(strFile);
if((long)(fa & FileAttributes.Hidden) == 0) return false;
if ((long)(fa & FileAttributes.Hidden) == 0) return false;
return HideFile(strFile, false);
}
catch(Exception) { }
catch (Exception) { }
return false;
#endif
@@ -272,26 +318,26 @@ namespace KeePassLib.Utility
public static bool HideFile(string strFile, bool bHide)
{
#if (KeePassLibSD || KeePassRT)
#if KeePassLibSD
return false;
#else
if(strFile == null) throw new ArgumentNullException("strFile");
if (strFile == null) throw new ArgumentNullException("strFile");
try
{
FileAttributes fa = File.GetAttributes(strFile);
if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden);
if (bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden);
else // Unhide
{
fa &= ~FileAttributes.Hidden;
if((long)fa == 0) fa = FileAttributes.Normal;
if ((long)fa == 0) fa = FileAttributes.Normal;
}
File.SetAttributes(strFile, fa);
return true;
}
catch(Exception) { }
catch (Exception) { }
return false;
#endif
@@ -299,48 +345,47 @@ namespace KeePassLib.Utility
public static string MakeRelativePath(string strBaseFile, string strTargetFile)
{
if(strBaseFile == null) throw new ArgumentNullException("strBasePath");
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if(strBaseFile.Length == 0) return strTargetFile;
if(strTargetFile.Length == 0) return string.Empty;
if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if (strBaseFile.Length == 0) return strTargetFile;
if (strTargetFile.Length == 0) return string.Empty;
// Test whether on different Windows drives
if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3))
if ((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3))
{
if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') &&
if ((strBaseFile[1] == ':') && (strTargetFile[1] == ':') &&
(strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') &&
(strBaseFile[0] != strTargetFile[0]))
return strTargetFile;
}
#if (!KeePassLibSD && !KeePassUAP)
if(NativeLib.IsUnix())
#endif
if (NativeLib.IsUnix())
{
#endif
bool bBaseUnc = IsUncPath(strBaseFile);
bool bTargetUnc = IsUncPath(strTargetFile);
if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
if ((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
return strTargetFile;
string strBase = GetShortestAbsolutePath(strBaseFile);
string strTarget = GetShortestAbsolutePath(strTargetFile);
string[] vBase = strBase.Split(m_vDirSeps);
string[] vTarget = strTarget.Split(m_vDirSeps);
string[] vBase = strBase.Split(UrlUtil.DirSepChars);
string[] vTarget = strTarget.Split(UrlUtil.DirSepChars);
int i = 0;
while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
while ((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
(vBase[i] == vTarget[i])) { ++i; }
StringBuilder sbRel = new StringBuilder();
for(int j = i; j < (vBase.Length - 1); ++j)
for (int j = i; j < (vBase.Length - 1); ++j)
{
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
sbRel.Append("..");
}
for(int k = i; k < vTarget.Length; ++k)
for (int k = i; k < vTarget.Length; ++k)
{
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
sbRel.Append(vTarget[k]);
}
@@ -352,28 +397,28 @@ namespace KeePassLib.Utility
{
const int nMaxPath = NativeMethods.MAX_PATH * 2;
StringBuilder sb = new StringBuilder(nMaxPath + 2);
if(NativeMethods.PathRelativePathTo(sb, strBaseFile, 0,
strTargetFile, 0) == false)
if (!NativeMethods.PathRelativePathTo(sb, strBaseFile, 0,
strTargetFile, 0))
return strTargetFile;
string str = sb.ToString();
while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2);
while (str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2);
return str;
}
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
return strTargetFile;
#endif
}
public static string MakeAbsolutePath(string strBaseFile, string strTargetFile)
{
if(strBaseFile == null) throw new ArgumentNullException("strBasePath");
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if(strBaseFile.Length == 0) return strTargetFile;
if(strTargetFile.Length == 0) return string.Empty;
if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if (strBaseFile.Length == 0) return strTargetFile;
if (strTargetFile.Length == 0) return string.Empty;
if(IsAbsolutePath(strTargetFile)) return strTargetFile;
if (IsAbsolutePath(strTargetFile)) return strTargetFile;
string strBaseDir = GetFileDirectory(strBaseFile, true, false);
return GetShortestAbsolutePath(strBaseDir + strTargetFile);
@@ -381,55 +426,56 @@ namespace KeePassLib.Utility
public static bool IsAbsolutePath(string strPath)
{
if(strPath == null) throw new ArgumentNullException("strPath");
if(strPath.Length == 0) return false;
if (strPath == null) throw new ArgumentNullException("strPath");
if (strPath.Length == 0) return false;
if(IsUncPath(strPath)) return true;
if (IsUncPath(strPath)) return true;
try { return Path.IsPathRooted(strPath); }
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
return true;
}
public static string GetShortestAbsolutePath(string strPath)
{
if(strPath == null) throw new ArgumentNullException("strPath");
if(strPath.Length == 0) return string.Empty;
if (strPath == null) throw new ArgumentNullException("strPath");
if (strPath.Length == 0) return string.Empty;
// Path.GetFullPath is incompatible with UNC paths traversing over
// different server shares (which are created by PathRelativePathTo);
// we need to build the absolute path on our own...
if(IsUncPath(strPath))
if (IsUncPath(strPath))
{
char chSep = strPath[0];
Debug.Assert(Array.IndexOf<char>(m_vDirSeps, chSep) >= 0);
char[] vSep = ((chSep == '/') ? (new char[] { '/' }) :
(new char[] { '\\', '/' }));
List<string> l = new List<string>();
#if !KeePassLibSD
string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None);
string[] v = strPath.Split(vSep, StringSplitOptions.None);
#else
string[] v = strPath.Split(m_vDirSeps);
string[] v = strPath.Split(vSep);
#endif
Debug.Assert((v.Length >= 3) && (v[0].Length == 0) &&
(v[1].Length == 0));
foreach(string strPart in v)
foreach (string strPart in v)
{
if(strPart.Equals(".")) continue;
else if(strPart.Equals(".."))
if (strPart.Equals(".")) continue;
else if (strPart.Equals(".."))
{
if(l.Count > 0) l.RemoveAt(l.Count - 1);
if (l.Count > 0) l.RemoveAt(l.Count - 1);
else { Debug.Assert(false); }
}
else l.Add(strPart); // Do not ignore zero length parts
}
StringBuilder sb = new StringBuilder();
for(int i = 0; i < l.Count; ++i)
for (int i = 0; i < l.Count; ++i)
{
// Don't test length of sb, might be 0 due to initial UNC seps
if(i > 0) sb.Append(chSep);
if (i > 0) sb.Append(chSep);
sb.Append(l[i]);
}
@@ -438,20 +484,11 @@ namespace KeePassLib.Utility
}
string str;
try
{
#if KeePassRT
var dirT = Windows.Storage.StorageFolder.GetFolderFromPathAsync(
strPath).AwaitEx();
str = dirT.Path;
#else
str = Path.GetFullPath(strPath);
#endif
}
catch(Exception) { Debug.Assert(false); return strPath; }
try { str = Path.GetFullPath(strPath); }
catch (Exception) { Debug.Assert(false); return strPath; }
Debug.Assert(str.IndexOf("\\..\\") < 0);
foreach(char ch in m_vDirSeps)
Debug.Assert((str.IndexOf("\\..\\") < 0) || NativeLib.IsUnix());
foreach (char ch in UrlUtil.DirSepChars)
{
string strSep = new string(ch, 1);
str = str.Replace(strSep + "." + strSep, strSep);
@@ -462,17 +499,17 @@ namespace KeePassLib.Utility
public static int GetUrlLength(string strText, int nOffset)
{
if(strText == null) throw new ArgumentNullException("strText");
if(nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len)
if (strText == null) throw new ArgumentNullException("strText");
if (nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len)
int iPosition = nOffset, nLength = 0, nStrLen = strText.Length;
while(iPosition < nStrLen)
while (iPosition < nStrLen)
{
char ch = strText[iPosition];
++iPosition;
if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
break;
++nLength;
@@ -481,24 +518,30 @@ namespace KeePassLib.Utility
return nLength;
}
internal static string GetScheme(string strUrl)
{
if (string.IsNullOrEmpty(strUrl)) return string.Empty;
int i = strUrl.IndexOf(':');
if (i > 0) return strUrl.Substring(0, i);
return string.Empty;
}
public static string RemoveScheme(string strUrl)
{
if(string.IsNullOrEmpty(strUrl)) return string.Empty;
if (string.IsNullOrEmpty(strUrl)) return string.Empty;
int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp);
int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp);
int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp);
int i = strUrl.IndexOf(':');
if (i < 0) return strUrl; // No scheme to remove
++i;
if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0))
return strUrl; // No scheme
// A single '/' indicates a path (absolute) and should not be removed
if (((i + 1) < strUrl.Length) && (strUrl[i] == '/') &&
(strUrl[i + 1] == '/'))
i += 2; // Skip authority prefix
int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue,
(nShScheme >= 0) ? nShScheme : int.MaxValue),
(nSmpScheme >= 0) ? nSmpScheme : int.MaxValue);
if(nMin == nNetScheme) return strUrl.Substring(nMin + 3);
if(nMin == nShScheme) return strUrl.Substring(nMin + 2);
return strUrl.Substring(nMin + 1);
return strUrl.Substring(i);
}
public static string ConvertSeparators(string strPath)
@@ -508,7 +551,7 @@ namespace KeePassLib.Utility
public static string ConvertSeparators(string strPath, char chSeparator)
{
if(string.IsNullOrEmpty(strPath)) return string.Empty;
if (string.IsNullOrEmpty(strPath)) return string.Empty;
strPath = strPath.Replace('/', chSeparator);
strPath = strPath.Replace('\\', chSeparator);
@@ -518,33 +561,61 @@ namespace KeePassLib.Utility
public static bool IsUncPath(string strPath)
{
if(strPath == null) throw new ArgumentNullException("strPath");
if (strPath == null) throw new ArgumentNullException("strPath");
return (strPath.StartsWith("\\\\") || strPath.StartsWith("//"));
}
public static string FilterFileName(string strName)
{
if(strName == null) { Debug.Assert(false); return string.Empty; }
if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; }
string str = strName;
// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
str = str.Replace('/', '-');
str = str.Replace('\\', '-');
str = str.Replace(":", string.Empty);
str = str.Replace("*", string.Empty);
str = str.Replace("?", string.Empty);
str = str.Replace("\"", string.Empty);
str = str.Replace(@"'", string.Empty);
str = str.Replace('<', '(');
str = str.Replace('>', ')');
str = str.Replace('|', '-');
StringBuilder sb = new StringBuilder(strName.Length);
foreach (char ch in strName)
{
if (ch < '\u0020') continue;
return str;
switch (ch)
{
case '\"':
case '*':
case ':':
case '?':
break;
case '/':
case '\\':
case '|':
sb.Append('-');
break;
case '<':
sb.Append('(');
break;
case '>':
sb.Append(')');
break;
default: sb.Append(ch); break;
}
}
// Trim trailing spaces and periods
for (int i = sb.Length - 1; i >= 0; --i)
{
char ch = sb[i];
if ((ch == ' ') || (ch == '.')) sb.Remove(i, 1);
else break;
}
return sb.ToString();
}
/// <summary>
/// Get the host component of an URL.
/// Get the host component of a URL.
/// This method is faster and more fault-tolerant than creating
/// an <code>Uri</code> object and querying its <code>Host</code>
/// property.
@@ -555,60 +626,60 @@ namespace KeePassLib.Utility
/// </example>
public static string GetHost(string strUrl)
{
if(strUrl == null) { Debug.Assert(false); return string.Empty; }
if (strUrl == null) { Debug.Assert(false); return string.Empty; }
StringBuilder sb = new StringBuilder();
bool bInExtHost = false;
for(int i = 0; i < strUrl.Length; ++i)
for (int i = 0; i < strUrl.Length; ++i)
{
char ch = strUrl[i];
if(bInExtHost)
if (bInExtHost)
{
if(ch == '/')
if (ch == '/')
{
if(sb.Length == 0) { } // Ignore leading '/'s
if (sb.Length == 0) { } // Ignore leading '/'s
else break;
}
else sb.Append(ch);
}
else // !bInExtHost
{
if(ch == ':') bInExtHost = true;
if (ch == ':') bInExtHost = true;
}
}
string str = sb.ToString();
if(str.Length == 0) str = strUrl;
if (str.Length == 0) str = strUrl;
// Remove the login part
int nLoginLen = str.IndexOf('@');
if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1);
if (nLoginLen >= 0) str = str.Substring(nLoginLen + 1);
// Remove the port
int iPort = str.LastIndexOf(':');
if(iPort >= 0) str = str.Substring(0, iPort);
if (iPort >= 0) str = str.Substring(0, iPort);
return str;
}
public static bool AssemblyEquals(string strExt, string strShort)
{
if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; }
if ((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; }
if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) ||
if (strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp))
return true;
if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp))
if (!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp))
{
if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) ||
if (strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp))
return true;
}
if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp))
if (!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp))
{
if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) ||
if (strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp))
return true;
}
@@ -619,7 +690,7 @@ namespace KeePassLib.Utility
public static string GetTempPath()
{
string strDir;
if(NativeLib.IsUnix())
if (NativeLib.IsUnix())
strDir = NativeMethods.GetUserRuntimeDir();
#if KeePassUAP
else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
@@ -629,9 +700,9 @@ namespace KeePassLib.Utility
try
{
if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir);
if (!Directory.Exists(strDir)) Directory.CreateDirectory(strDir);
}
catch(Exception) { Debug.Assert(false); }
catch (Exception) { Debug.Assert(false); }
return strDir;
}
@@ -642,31 +713,29 @@ namespace KeePassLib.Utility
SearchOption opt)
{
List<string> l = new List<string>();
if(strDir == null) { Debug.Assert(false); return l; }
if(strPattern == null) { Debug.Assert(false); return l; }
if (strDir == null) { Debug.Assert(false); return l; }
if (strPattern == null) { Debug.Assert(false); return l; }
string[] v = Directory.GetFiles(strDir, strPattern, opt);
if(v == null) { Debug.Assert(false); return l; }
if (v == null) { Debug.Assert(false); return l; }
// Only accept files with the correct extension; GetFiles may
// return additional files, see GetFiles documentation
string strExt = GetExtension(strPattern);
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
(strExt.IndexOf('?') < 0))
{
strExt = "." + strExt;
foreach(string strPathRaw in v)
foreach (string strPathRaw in v)
{
if(strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; }
if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if (strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw);
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
continue;
l.Add(strPathRaw);
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
l.Add(strPathRaw);
}
}
else l.AddRange(v);
@@ -679,33 +748,31 @@ namespace KeePassLib.Utility
SearchOption opt)
{
List<FileInfo> l = new List<FileInfo>();
if(di == null) { Debug.Assert(false); return l; }
if(strPattern == null) { Debug.Assert(false); return l; }
if (di == null) { Debug.Assert(false); return l; }
if (strPattern == null) { Debug.Assert(false); return l; }
FileInfo[] v = di.GetFiles(strPattern, opt);
if(v == null) { Debug.Assert(false); return l; }
if (v == null) { Debug.Assert(false); return l; }
// Only accept files with the correct extension; GetFiles may
// return additional files, see GetFiles documentation
string strExt = GetExtension(strPattern);
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
(strExt.IndexOf('?') < 0))
{
strExt = "." + strExt;
foreach(FileInfo fi in v)
foreach (FileInfo fi in v)
{
if(fi == null) { Debug.Assert(false); continue; }
if (fi == null) { Debug.Assert(false); continue; }
string strPathRaw = fi.FullName;
if(strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; }
if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if (strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw);
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
continue;
l.Add(fi);
if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
l.Add(fi);
}
}
else l.AddRange(v);
@@ -713,5 +780,82 @@ namespace KeePassLib.Utility
return l;
}
#endif
public static char GetDriveLetter(string strPath)
{
if (strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(default(char) == '\0');
if (strPath.Length < 3) return '\0';
if ((strPath[1] != ':') || (strPath[2] != '\\')) return '\0';
char ch = char.ToUpperInvariant(strPath[0]);
return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0');
}
internal static string GetSafeFileName(string strName)
{
Debug.Assert(!string.IsNullOrEmpty(strName));
string str = FilterFileName(GetFileName(strName ?? string.Empty));
if (string.IsNullOrEmpty(str))
{
Debug.Assert(false);
return "File.dat";
}
return str;
}
internal static string GetCanonicalUri(string strUri)
{
if (string.IsNullOrEmpty(strUri)) { Debug.Assert(false); return strUri; }
try
{
Uri uri = new Uri(strUri);
if (uri.IsAbsoluteUri) return uri.AbsoluteUri;
else { Debug.Assert(false); }
}
catch (Exception) { Debug.Assert(false); }
return strUri;
}
/* internal static Dictionary<string, string> ParseQuery(string strQuery)
{
Dictionary<string, string> d = new Dictionary<string, string>();
if(string.IsNullOrEmpty(strQuery)) return d;
string[] vKvp = strQuery.Split(new char[] { '?', '&' });
if(vKvp == null) { Debug.Assert(false); return d; }
foreach(string strKvp in vKvp)
{
if(string.IsNullOrEmpty(strKvp)) continue;
string strKey, strValue;
int iSep = strKvp.IndexOf('=');
if(iSep < 0)
{
strKey = strKvp;
strValue = string.Empty;
}
else
{
strKey = strKvp.Substring(0, iSep);
strValue = strKvp.Substring(iSep + 1);
}
strKey = Uri.UnescapeDataString(strKey);
strValue = Uri.UnescapeDataString(strValue);
d[strKey] = strValue;
}
return d;
} */
}
}

View File

@@ -0,0 +1,290 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program 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.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;
using KeePassLib.Delegates;
using KeePassLib.Interfaces;
using KeePassLib.Serialization;
namespace KeePassLib.Utility
{
public static class XmlUtilEx
{
public static XmlDocument CreateXmlDocument()
{
XmlDocument d = new XmlDocument();
// .NET 4.5.2 and newer do not resolve external XML resources
// by default; for older .NET versions, we explicitly
// prevent resolving
d.XmlResolver = null; // Default in old .NET: XmlUrlResolver object
return d;
}
public static XmlReaderSettings CreateXmlReaderSettings()
{
XmlReaderSettings xrs = new XmlReaderSettings();
xrs.CloseInput = false;
xrs.IgnoreComments = true;
xrs.IgnoreProcessingInstructions = true;
xrs.IgnoreWhitespace = true;
#if KeePassUAP
xrs.DtdProcessing = DtdProcessing.Prohibit;
#else
// Also see PrepMonoDev.sh script
xrs.ProhibitDtd = true; // Obsolete in .NET 4, but still there
// xrs.DtdProcessing = DtdProcessing.Prohibit; // .NET 4 only
#endif
xrs.ValidationType = ValidationType.None;
xrs.XmlResolver = null;
return xrs;
}
public static XmlReader CreateXmlReader(Stream s)
{
if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); }
return XmlReader.Create(s, CreateXmlReaderSettings());
}
public static XmlWriterSettings CreateXmlWriterSettings()
{
XmlWriterSettings xws = new XmlWriterSettings();
xws.CloseOutput = false;
xws.Encoding = StrUtil.Utf8;
xws.Indent = true;
xws.IndentChars = "\t";
xws.NewLineOnAttributes = false;
return xws;
}
public static XmlWriter CreateXmlWriter(Stream s)
{
if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); }
return XmlWriter.Create(s, CreateXmlWriterSettings());
}
public static T Deserialize<T>(Stream s)
{
if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); }
XmlSerializer xs = new XmlSerializer(typeof(T));
T t = default(T);
using(XmlReader xr = CreateXmlReader(s))
{
t = (T)xs.Deserialize(xr);
}
return t;
}
public static void Serialize<T>(Stream s, T t)
{
if(s == null) { Debug.Assert(false); throw new ArgumentNullException("s"); }
XmlSerializer xs = new XmlSerializer(typeof(T));
using(XmlWriter xw = CreateXmlWriter(s))
{
xs.Serialize(xw, t);
}
}
internal static void Serialize<T>(Stream s, T t, bool bRemoveXsdXsi)
{
// One way to remove the "xsd" and "xsi" namespace declarations
// is to use an XmlSerializerNamespaces object containing only
// a ""/"" pair; this seems to work, but Microsoft's
// documentation explicitly states that it isn't supported:
// https://docs.microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializernamespaces
// There are other, more complex ways, but these either rely on
// undocumented details or require the type T to be modified.
string str;
using(MemoryStream ms = new MemoryStream())
{
Serialize<T>(ms, t);
str = StrUtil.Utf8.GetString(ms.ToArray());
}
Func<string, string, bool> fFindPfx = delegate(string strText, string strSub)
{
int i = strText.IndexOf(strSub, StringComparison.Ordinal);
if(i < 0) return false;
if(i == 0) return true;
return char.IsWhiteSpace(strText[i - 1]);
};
if(bRemoveXsdXsi)
{
if(!fFindPfx(str, "xsd:") && !fFindPfx(str, "xsi:"))
{
Debug.Assert(str.IndexOf("xmlns:xsd") > 0);
str = str.Replace(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"", string.Empty);
Debug.Assert(str.IndexOf("xmlns:xsd") < 0);
Debug.Assert(str.IndexOf("xmlns:xsi") > 0);
str = str.Replace(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", string.Empty);
Debug.Assert(str.IndexOf("xmlns:xsi") < 0);
}
else { Debug.Assert(false); } // "xsd"/"xsi" decl. may be required
}
MemUtil.Write(s, StrUtil.Utf8.GetBytes(str));
}
#if DEBUG
internal static void ValidateXml(string strXml, bool bReplaceStdEntities)
{
if(strXml == null) throw new ArgumentNullException("strXml");
if(strXml.Length == 0) { Debug.Assert(false); return; }
string str = strXml;
if(bReplaceStdEntities)
str = str.Replace("&nbsp;", "&#160;");
XmlDocument d = new XmlDocument();
d.LoadXml(str);
}
#endif
internal static XPathNodeIterator FindNodes(PwDatabase pd, string strXPath,
IStatusLogger sl, out XmlDocument xd)
{
if(pd == null) throw new ArgumentNullException("pd");
if(strXPath == null) { Debug.Assert(false); strXPath = string.Empty; }
KdbxFile kdbx = new KdbxFile(pd);
byte[] pbXml;
using(MemoryStream ms = new MemoryStream())
{
kdbx.Save(ms, null, KdbxFormat.PlainXml, sl);
pbXml = ms.ToArray();
}
string strXml = StrUtil.Utf8.GetString(pbXml);
xd = CreateXmlDocument();
xd.LoadXml(strXml);
XPathNavigator xpNav = xd.CreateNavigator();
return xpNav.Select(strXPath);
// XPathExpression xpExpr = xpNav.Compile(strXPath);
// xpExpr.SetContext(new XuXsltContext());
// return xpNav.Select(xpExpr);
}
/* private sealed class XuFnMatches : IXsltContextFunction
{
private readonly XPathResultType[] m_vArgTypes = new XPathResultType[] {
XPathResultType.String, XPathResultType.String, XPathResultType.String
};
public XPathResultType[] ArgTypes { get { return m_vArgTypes; } }
public int Maxargs { get { return 3; } }
public int Minargs { get { return 2; } }
public XPathResultType ReturnType { get { return XPathResultType.Boolean; } }
private static string GetArgString(object[] args, int i, string strDefault)
{
if(args == null) { Debug.Assert(false); return strDefault; }
if(i >= args.Length) return strDefault;
object o = args[i];
if(o == null) return strDefault;
XPathNodeIterator it = (o as XPathNodeIterator);
if(it != null) o = it.Current.Value;
return (o.ToString() ?? strDefault);
}
public object Invoke(XsltContext xsltContext, object[] args,
XPathNavigator docContext)
{
string strInput = GetArgString(args, 0, string.Empty);
string strPattern = GetArgString(args, 1, string.Empty);
string strFlags = GetArgString(args, 2, null);
RegexOptions ro = RegexOptions.None;
if(!string.IsNullOrEmpty(strFlags))
{
if(strFlags.IndexOf('s') >= 0) ro |= RegexOptions.Singleline;
if(strFlags.IndexOf('m') >= 0) ro |= RegexOptions.Multiline;
if(strFlags.IndexOf('i') >= 0) ro |= RegexOptions.IgnoreCase;
if(strFlags.IndexOf('x') >= 0) ro |= RegexOptions.IgnorePatternWhitespace;
}
return Regex.IsMatch(strInput, strPattern, ro);
}
}
private sealed class XuXsltContext : XsltContext
{
public override bool Whitespace { get { return false; } }
public override int CompareDocument(string baseUri, string nextbaseUri)
{
return string.CompareOrdinal(baseUri, nextbaseUri);
}
public override bool PreserveWhitespace(XPathNavigator node)
{
return false;
}
public override IXsltContextFunction ResolveFunction(string prefix,
string name, XPathResultType[] ArgTypes)
{
if(prefix != "kp") { Debug.Assert(false); return null; }
if(name == "matches") return new XuFnMatches();
Debug.Assert(false);
return null;
}
public override IXsltContextVariable ResolveVariable(string prefix,
string name)
{
Debug.Assert(false);
return null;
}
} */
}
}

View File

@@ -119,7 +119,7 @@ namespace keepass2android
/// </summary>
IFileStorage GetFileStorage(IOConnectionInfo iocInfo, bool allowCache);
void TriggerReload(Context context);
void TriggerReload(Context context, Action<bool> actionOnResult /*if not null, called when the user selected yes (true) or no (false)*/);
bool CheckForDuplicateUuids { get; }

View File

@@ -249,28 +249,11 @@ namespace keepass2android.Io
reason.Result = UiStringKey.ReadOnlyReason_PreKitKat;
return true;
}
//KitKat or later...
var uri = Android.Net.Uri.Parse(ioc.Path);
cursor = _ctx.ContentResolver.Query(uri, null, null, null, null, null);
if (cursor != null && cursor.MoveToFirst())
{
int column = cursor.GetColumnIndex(DocumentsContract.Document.ColumnFlags);
if (column < 0)
return false; //seems like this is not supported. See below for reasoning to return false.
int flags = cursor.GetInt(column);
Kp2aLog.Log("File flags: " + flags);
if ((flags & (long) DocumentContractFlags.SupportsWrite) == 0)
{
if (reason != null)
reason.Result = UiStringKey.ReadOnlyReason_ReadOnlyFlag;
return true;
}
else return false;
}
else return false;
//in previous implementations, we were checking for FLAG_SUPPORTS_WRITE in the document flags,
//but it seems like this is very poorly supported, e.g. Dropbox and OneDrive return !FLAG_SUPPORTS_WRITE
//even though writing work.
return false;
}
catch (Exception e)
{

View File

@@ -246,7 +246,7 @@ namespace keepass2android.Io
else
{
Intent intent = new Intent();
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId+"://"});
activity.IocToIntent(intent, new IOConnectionInfo() { Path = protocolId+"://", });
activity.OnImmediateResult(requestCode, (int) FileStorageResults.FileChooserPrepared, intent);
}
}

View File

@@ -120,10 +120,14 @@ namespace keepass2android.Io
public bool IsCached(IOConnectionInfo ioc)
{
return File.Exists(CachedFilePath(ioc))
bool result = File.Exists(CachedFilePath(ioc))
&& File.Exists(VersionFilePath(ioc))
&& File.Exists(BaseVersionFilePath(ioc));
}
Kp2aLog.Log(ioc.GetDisplayName() + " isCached = " + result);
return result;
}
public void Delete(IOConnectionInfo ioc)
{
@@ -593,11 +597,15 @@ namespace keepass2android.Io
public string GetBaseVersionHash(IOConnectionInfo ioc)
{
return File.ReadAllText(BaseVersionFilePath(ioc));
}
string hash = File.ReadAllText(BaseVersionFilePath(ioc));
Kp2aLog.Log(ioc.GetDisplayName() + " baseVersionHash = " + hash);
return hash;
}
public string GetLocalVersionHash(IOConnectionInfo ioc)
{
return File.ReadAllText(VersionFilePath(ioc));
string hash = File.ReadAllText(VersionFilePath(ioc));
Kp2aLog.Log(ioc.GetDisplayName() + " localVersionHash = " + hash);
return hash;
}
public bool HasLocalChanges(IOConnectionInfo ioc)
{

View File

@@ -17,7 +17,7 @@ namespace keepass2android.Io
public class GoogleDriveFileStorage : JavaFileStorage
{
public GoogleDriveFileStorage(Context ctx, IKp2aApp app) :
base(new Keepass2android.Javafilestorage.GoogleDriveFileStorage(), app)
base(new Keepass2android.Javafilestorage.GoogleDriveFullFileStorage(), app)
{
}
@@ -27,5 +27,19 @@ namespace keepass2android.Io
get { return false; }
}
}
public class GoogleDriveAppDataFileStorage : JavaFileStorage
{
public GoogleDriveAppDataFileStorage(Context ctx, IKp2aApp app) :
base(new Keepass2android.Javafilestorage.GoogleDriveAppDataFileStorage(), app)
{
}
public override bool UserShouldBackup
{
get { return false; }
}
}
}
#endif

View File

@@ -0,0 +1,506 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Android.Content;
using Android.OS;
using Android.Preferences;
using Android.Util;
using CG.Web.MegaApiClient;
using Group.Pals.Android.Lib.UI.Filechooser.Utils;
using KeePassLib.Cryptography.Cipher;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace keepass2android.Io
{
public class MegaFileStorage : IFileStorage
{
private readonly Context _appContext;
public const string ProtocolId = "mega";
private const string PreferenceKey = "KP2A-Mega-Accounts";
public MegaFileStorage(Context appContext)
{
_appContext = appContext;
}
//we don't want to store passwords in plain text, encrypt them with this key at least:
public static readonly byte[] EncryptionKey = new byte[] { 86,239,128,218,160,22,245,114,193,92,151,10,134,104,121,170,
183,110,60,38,179,181,24,206,169,43,125,193,142,156,47,45};
public class AccountSettings
{
public Dictionary<string, string> PasswordByUsername { get; set; } = new Dictionary<string, string>();
public static byte[] exclusiveOR(byte[] arr1, byte[] arr2)
{
byte[] result = new byte[arr1.Length];
for (int i = 0; i < arr1.Length; ++i)
result[i] = (byte)(arr1[i] ^ arr2[i % arr2.Length]);
return result;
}
static string Encrypt(string s)
{
var plainTextBytes = exclusiveOR(System.Text.Encoding.UTF8.GetBytes(s), EncryptionKey);
return System.Convert.ToBase64String(plainTextBytes);
}
static string Decrypt(string s)
{
var base64EncodedBytes = System.Convert.FromBase64String(s);
return System.Text.Encoding.UTF8.GetString(exclusiveOR(base64EncodedBytes, EncryptionKey));
}
public string Serialize()
{
Dictionary<string, string> encryptedPasswordByUsername = PasswordByUsername
.Select(kvp => new KeyValuePair<string, string>(kvp.Key, Encrypt(kvp.Value)))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
return Newtonsoft.Json.JsonConvert.SerializeObject(encryptedPasswordByUsername);
}
public void Deserialize(string data)
{
if (string.IsNullOrEmpty(data))
{
PasswordByUsername = new Dictionary<string, string>();
return;
}
Dictionary<string, string> encryptedPasswordByUsername =
Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
PasswordByUsername = encryptedPasswordByUsername
.Select(kvp => new KeyValuePair<string, string>(kvp.Key, Decrypt(kvp.Value)))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
public IEnumerable<string> SupportedProtocols
{
get { yield return ProtocolId; }
}
public bool UserShouldBackup
{
get { return false; }
}
class MegaFileStorageWriteTransaction : IWriteTransaction
{
public bool UseFileTransaction { get; }
private readonly string _path;
private readonly MegaFileStorage _filestorage;
private MemoryStream _memoryStream;
public MegaFileStorageWriteTransaction(string path, MegaFileStorage filestorage, bool useFileTransaction)
{
UseFileTransaction = useFileTransaction;
_path = path;
_filestorage = filestorage;
}
public void Dispose()
{
_memoryStream.Dispose();
}
public Stream OpenFile()
{
_memoryStream = new MemoryStream();
return _memoryStream;
}
public void CommitWrite()
{
_filestorage.UploadFile(_path, new MemoryStream(_memoryStream.ToArray()), UseFileTransaction);
}
}
private void UploadFile(string path, MemoryStream memoryStream, bool useTransaction)
{
var accountData = GetAccountData(path);
if (accountData.TryGetNode(path, out var node))
{
if (useTransaction)
{
string temporaryName = node.Name + "." + new Guid().ToString() + ".tmp";
var newNode = accountData.Client.Upload(memoryStream, temporaryName, accountData.GetParentNode(node));
accountData.Client.Delete(node);
newNode = accountData.Client.Rename(newNode, node.Name);
accountData._nodes.Remove(node);
accountData._nodes.Add(newNode);
}
else
{
var newNode = accountData.Client.Upload(memoryStream, node.Name, accountData.GetParentNode(node));
//we now have two nodes with the same name. Delete the old one:
accountData.Client.Delete(node);
accountData._nodes.Remove(node);
accountData._nodes.Add(newNode);
}
}
else
{
//file did not exist yet
string parentPath = GetParentPath(new IOConnectionInfo() { Path = path }).Path;
string name = path.Substring(parentPath.Length + 1);
var newNode = accountData.Client.Upload(memoryStream, name, accountData.GetNode(parentPath));
accountData._nodes.Add(newNode);
}
}
public void StartSelectFile(IFileStorageSetupInitiatorActivity activity, bool isForSave, int requestCode,
string protocolId)
{
activity.PerformManualFileSelect(isForSave, requestCode, protocolId);
}
public void OnCreate(IFileStorageSetupActivity activity, Bundle savedInstanceState)
{
}
public void OnResume(IFileStorageSetupActivity activity)
{
}
public void OnStart(IFileStorageSetupActivity activity)
{
}
public void OnActivityResult(IFileStorageSetupActivity activity, int requestCode, int resultCode, Intent data)
{
}
public IWriteTransaction OpenWriteTransaction(IOConnectionInfo ioc, bool useFileTransaction)
{
return new MegaFileStorageWriteTransaction(ioc.Path, this, useFileTransaction);
}
public string GetFilenameWithoutPathAndExt(IOConnectionInfo ioc)
{
return UrlUtil.StripExtension(
UrlUtil.GetFileName(ioc.Path));
}
public string GetFileExtension(IOConnectionInfo ioc)
{
return UrlUtil.GetExtension(ioc.Path);
}
public string CreateFilePath(string parent, string newFilename)
{
if (!parent.EndsWith("/"))
parent += "/";
return parent + newFilename;
}
public bool IsReadOnly(IOConnectionInfo ioc, OptionalOut<UiStringKey> reason = null)
{
return false;
}
public bool IsPermanentLocation(IOConnectionInfo ioc)
{
return true;
}
public IOConnectionInfo GetFilePath(IOConnectionInfo folderPath, string filename)
{
IOConnectionInfo res = folderPath.CloneDeep();
if (!res.Path.EndsWith("/"))
res.Path += "/";
res.Path += filename;
return res;
}
public IOConnectionInfo GetParentPath(IOConnectionInfo ioc)
{
return IoUtil.GetParentPath(ioc);
}
public string GetDisplayName(IOConnectionInfo ioc)
{
return ioc.GetDisplayName();
}
public void PrepareFileUsage(Context ctx, IOConnectionInfo ioc)
{
//nothing to do
}
public void PrepareFileUsage(IFileStorageSetupInitiatorActivity activity, IOConnectionInfo ioc, int requestCode,
bool alwaysReturnSuccess)
{
Intent intent = new Intent();
activity.IocToIntent(intent, ioc);
activity.OnImmediateResult(requestCode, (int)FileStorageResults.FileUsagePrepared, intent);
}
public string IocToPath(IOConnectionInfo ioc)
{
return ioc.Path;
}
public bool RequiresSetup(IOConnectionInfo ioConnection)
{
return false;
}
public FileDescription GetFileDescription(IOConnectionInfo ioc)
{
var accountData = GetAccountData(ioc);
return MakeFileDescription(accountData, accountData.GetNode(ioc));
}
class AccountData
{
public string Account { get; set; }
public IMegaApiClient Client { get; set; }
public void RefreshMetadata()
{
//make sure we refresh meta data after one minute:
if (DateTime.Now.Subtract(_nodesLoadingTime).TotalMinutes > 1.0)
{
_nodes.Clear();
EnsureMetadataLoaded();
}
}
public List<INode> _nodes = new List<INode>();
private DateTime _nodesLoadingTime;
private INode _rootNode;
public INode GetNode(IOConnectionInfo ioc)
{
return GetNode(ioc.Path);
}
public bool TryGetNode(string path, out INode node)
{
try
{
node = GetNode(path);
return true;
}
catch (Exception e)
{
node = null;
return false;
}
}
public INode GetNode(string path)
{
EnsureMetadataLoaded();
if (!path.StartsWith(ProtocolId + "://"))
throw new Exception("Invalid Mega URL: " + path);
path = path.Substring(ProtocolId.Length + 3);
var parts = path.Split('/');
if (parts.Length < 1 || parts[0] == "")
throw new Exception("Invalid Mega URL: " + path);
INode node = _rootNode;
for (int i = 1; i < parts.Length; i++)
{
if (parts[i] == "")
continue;
var matchingChildren = _nodes.Where(n => n.ParentId == node.Id && n.Name == parts[i]).ToList();
if (matchingChildren.Count == 0)
throw new FileNotFoundException("Did not find " + path);
if (matchingChildren.Count > 1)
throw new Java.IO.FileNotFoundException(
$"Found more than one child with name {parts[i]} while trying to get node for {path}");
node = matchingChildren.Single();
}
return node;
}
private void EnsureMetadataLoaded()
{
if (_nodes.Any() == false)
{
_nodes = Client.GetNodes().ToList();
_rootNode = _nodes.Single(n => n.Type == NodeType.Root);
_nodesLoadingTime = DateTime.Now;
}
}
public INode GetParentNode(INode node)
{
return _nodes.Single(n => n.Id == node.ParentId);
}
internal void InvalidateMetaData()
{
_nodes.Clear();
}
public IEnumerable<INode> GetChildNodes(INode node)
{
EnsureMetadataLoaded();
return _nodes.Where(n => n.ParentId == node.Id);
}
public string GetPath(INode node)
{
if (node.Type == NodeType.Root)
return ProtocolId + "://" + this.Account;
var parent = _nodes.Single(n => n.Id == node.ParentId);
return GetPath(parent) + "/" + node.Name;
}
}
readonly Dictionary<string /*account*/, AccountData> _allAccountData = new Dictionary<string, AccountData>();
public string GetAccount(IOConnectionInfo ioc)
{
return GetAccount(ioc.Path);
}
public static string GetAccount(string path)
{
if (!path.StartsWith(ProtocolId + "://"))
throw new Exception("Invalid Mega URL: " + path);
path = path.Substring(ProtocolId.Length + 3);
var parts = path.Split('/');
if (parts.Length < 1 || parts[0] == "")
throw new Exception("Invalid Mega URL: " + path);
return parts[0];
}
private AccountData GetAccountData(IOConnectionInfo ioc)
{
return GetAccountData(ioc.Path);
}
public static AccountSettings GetAccountSettings(Context ctx)
{
string accountSettingsString = PreferenceManager.GetDefaultSharedPreferences(ctx).GetString(PreferenceKey, null);
AccountSettings settings = new AccountSettings();
settings.Deserialize(accountSettingsString);
return settings;
}
public static void UpdateAccountSettings(AccountSettings settings, Context ctx)
{
PreferenceManager.GetDefaultSharedPreferences(ctx).Edit().PutString(PreferenceKey, settings.Serialize())
.Commit();
}
private AccountData GetAccountData(string path)
{
string account = GetAccount(path);
if (_allAccountData.TryGetValue(account, out var accountData))
{
return accountData;
}
AccountData newAccountData = new AccountData()
{
Account = account,
Client = new MegaApiClient()
};
var settings = GetAccountSettings(_appContext);
if (!settings.PasswordByUsername.TryGetValue(account, out string password))
{
throw new Exception("No account configured with username = " + account);
}
try
{
newAccountData.Client.Login(account, password);
}
catch (CG.Web.MegaApiClient.ApiException e)
{
if (e.ApiResultCode == CG.Web.MegaApiClient.ApiResultCode.ResourceNotExists)
{
throw new Exception("Failed to login to MEGA account. Please check username and password!");
}
}
_allAccountData[account] = newAccountData;
return newAccountData;
}
public IEnumerable<FileDescription> ListContents(IOConnectionInfo ioc)
{
AccountData accountData = GetAccountData(ioc);
accountData.RefreshMetadata();
return accountData.GetChildNodes(accountData.GetNode(ioc)).Select(n => MakeFileDescription(accountData, n));
}
private FileDescription MakeFileDescription(AccountData account, INode n)
{
return new FileDescription()
{
CanRead = true,
CanWrite = true,
DisplayName = n.Name ?? (n.Type == NodeType.Root ? "root" : ""),
IsDirectory = n.Type != NodeType.File,
LastModified = n.ModificationDate ?? n.CreationDate ?? DateTime.MinValue,
Path = account.GetPath(n),
SizeInBytes = n.Size
};
}
public void CreateDirectory(IOConnectionInfo ioc, string newDirName)
{
var accountData = GetAccountData(ioc);
var newNode = accountData.Client.CreateFolder(newDirName, accountData.GetNode(ioc));
accountData._nodes.Add(newNode);
}
public bool RequiresCredentials(IOConnectionInfo ioc)
{
return false;
}
public Stream OpenFileForRead(IOConnectionInfo ioc)
{
var accountData = GetAccountData(ioc);
return accountData.Client.Download(accountData.GetNode(ioc));
}
public string GetCurrentFileVersionFast(IOConnectionInfo ioc)
{
return null;
}
public bool CheckForFileChangeFast(IOConnectionInfo ioc, string previousFileVersion)
{
return false;
}
public void Delete(IOConnectionInfo ioc)
{
var accountData = GetAccountData(ioc);
accountData.Client.Delete(accountData.GetNode(ioc));
}
}
}

View File

@@ -322,20 +322,37 @@ namespace keepass2android.Io
private async Task<IGraphServiceClient> TryGetMsGraphClient(String path, bool tryConnect)
{
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(path).User.Id;
logDebug("TryGetMsGraphClient for " + userId);
if (mClientByUser.ContainsKey(userId))
{
logDebug("TryGetMsGraphClient found user " + userId);
GraphServiceClientWithState clientWithState = mClientByUser[userId];
if (!(clientWithState.RequiresUserInteraction || (clientWithState.TokenExpiryDate < DateTime.Now) || (clientWithState.Client == null)))
if (!(clientWithState.RequiresUserInteraction || (clientWithState.TokenExpiryDate < DateTime.Now) ||
(clientWithState.Client == null)))
{
logDebug("TryGetMsGraphClient returning client");
return clientWithState.Client;
}
else
{
logDebug("not returning client because " + clientWithState.RequiresUserInteraction + " " +
(clientWithState.TokenExpiryDate < DateTime.Now) + " " + (clientWithState.Client == null));
}
}
if (tryConnect)
{
logDebug("trying to connect...");
if (await TryLoginSilent(path) != null)
{
logDebug("trying to connect ok");
return mClientByUser[userId].Client;
}
logDebug("trying to connect failed");
}
logDebug("TryGetMsGraphClient for " + userId + " returns null");
return null;
}
@@ -367,7 +384,7 @@ namespace keepass2android.Io
if (authenticationResult.Account == null)
throw new Exception("authenticationResult.Account == null!");
mClientByUser[authenticationResult.Account.HomeAccountId.Identifier] = clientWithState;
logDebug("buildClient ok.");
return clientWithState.Client;
}
@@ -375,7 +392,9 @@ namespace keepass2android.Io
private void logDebug(string str)
{
Log.Debug("KP2A", str);
#if DEBUG
Log.Debug("KP2A", "OneDrive2: " + str);
#endif
}
@@ -530,13 +549,50 @@ namespace keepass2android.Io
{
Task.Run(async () =>
{
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path);
PathItemBuilder pathItemBuilder = await GetPathItemBuilder(path);
//for small files <2MB use the direct upload:
if (stream.Length < 2* 1024 * 1024)
{
return await
pathItemBuilder
.getPathItem()
.Content
.Request()
.PutAsync<DriveItem>(stream);
}
//for larger files use an upload session. This is required for 4MB and beyond, but as the docs are not very clear about this
//limit, let's use it a bit more often to be safe.
var uploadProps = new DriveItemUploadableProperties
{
ODataType = null,
AdditionalData = new Dictionary<string, object>
{
{ "@microsoft.graph.conflictBehavior", "replace" }
}
};
var uploadSession = await pathItemBuilder
.getPathItem()
.CreateUploadSession(uploadProps)
.Request()
.PostAsync();
// Max slice size must be a multiple of 320 KiB
int maxSliceSize = 320 * 1024;
var fileUploadTask = new LargeFileUploadTask<DriveItem>(uploadSession, stream, maxSliceSize);
var uploadResult = await fileUploadTask.UploadAsync();
if (!uploadResult.UploadSucceeded)
{
throw new Exception("Failed to upload data!");
}
return uploadResult.ItemResponse;
}).Wait();
@@ -821,14 +877,17 @@ namespace keepass2android.Io
public async void OnStart(IFileStorageSetupActivity activity)
{
logDebug("OneDrive2.OnStart");
if (activity.ProcessName.Equals(FileStorageSetupDefs.ProcessNameFileUsageSetup))
activity.State.PutString(FileStorageSetupDefs.ExtraPath, activity.Ioc.Path);
string rootPathForUser = await TryLoginSilent(activity.Ioc.Path);
if (rootPathForUser != null)
{
logDebug("rootPathForUser not null");
FinishActivityWithSuccess(activity, rootPathForUser);
return;
}
logDebug("rootPathForUser null");
try
{
@@ -856,13 +915,14 @@ namespace keepass2android.Io
private async Task<string> TryLoginSilent(string iocPath)
{
logDebug("Login Silent for " + iocPath);
IAccount account = null;
try
{
if (IsConnected(iocPath))
{
logDebug("Login Silent ok, connected");
return iocPath;
}
String userId = OneDrive2ItemLocation<OneDrive2PrefixContainerType>.FromString(iocPath).User?.Id;
@@ -891,7 +951,9 @@ namespace keepass2android.Io
/*User me = await graphClient.Me.Request().WithForceRefresh(true).GetAsync();
logDebug("received name " + me.DisplayName);*/
return BuildRootPathForUser(authResult);
var rootFolder = BuildRootPathForUser(authResult);
logDebug("Found RootPath for user");
return rootFolder;
}
catch (MsalUiRequiredException ex)

View File

@@ -12,7 +12,7 @@
<FileAlignment>512</FileAlignment>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
@@ -34,7 +34,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<JavaMaximumHeapSize>4G</JavaMaximumHeapSize>
<JavaMaximumHeapSize>4G</JavaMaximumHeapSize>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ReleaseNoNet|AnyCPU'">
@@ -87,6 +87,7 @@
<Compile Include="Io\IFileStorage.cs" />
<Compile Include="Io\IoUtil.cs" />
<Compile Include="Io\JavaFileStorage.cs" />
<Compile Include="Io\MegaFileStorage.cs" />
<Compile Include="Io\NetFtpFileStorage.cs" />
<Compile Include="Io\OfflineSwitchableFileStorage.cs" />
<Compile Include="Io\OneDrive2FileStorage.cs" />
@@ -165,6 +166,9 @@
<PackageReference Include="FluentFTP">
<Version>31.3.1</Version>
</PackageReference>
<PackageReference Include="MegaApiClient">
<Version>1.10.2</Version>
</PackageReference>
<PackageReference Include="Microsoft.Graph">
<Version>1.21.0</Version>
</PackageReference>
@@ -276,6 +280,9 @@
<PackageReference Include="Xamarin.Android.Support.ViewPager">
<Version>28.0.0.3</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.Preference">
<Version>1.1.1.11</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

File diff suppressed because it is too large Load Diff

View File

@@ -51,7 +51,7 @@ namespace keepass2android
if (!MemUtil.ArraysEqual(_app.CurrentDb.KpDatabase.HashOfFileOnDisk, hashingRemoteStream.Hash))
{
_app.TriggerReload(_context);
_app.TriggerReload(_context, null);
Finish(true);
}
else

View File

@@ -43,17 +43,21 @@ namespace keepass2android
try
{
remoteData = cachingFileStorage.GetRemoteDataAndHash(ioc, out hash);
Kp2aLog.Log("Checking for file change. Current hash = " + hash);
}
catch (FileNotFoundException)
{
StatusLogger.UpdateSubMessage(_app.GetResourceString(UiStringKey.RestoringRemoteFile));
cachingFileStorage.UpdateRemoteFile(ioc, _app.GetBooleanPreference(PreferenceKey.UseFileTransactions));
Finish(true, _app.GetResourceString(UiStringKey.SynchronizedDatabaseSuccessfully));
Kp2aLog.Log("Checking for file change: file not found");
return;
}
//check if remote file was modified:
if (cachingFileStorage.GetBaseVersionHash(ioc) != hash)
var baseVersionHash = cachingFileStorage.GetBaseVersionHash(ioc);
Kp2aLog.Log("Checking for file change. baseVersionHash = " + baseVersionHash);
if (baseVersionHash != hash)
{
//remote file is modified
if (cachingFileStorage.HasLocalChanges(ioc))
@@ -81,8 +85,7 @@ namespace keepass2android
{
//only the remote file was modified -> reload database.
//note: it's best to lock the database and do a complete reload here (also better for UI consistency in case something goes wrong etc.)
_app.TriggerReload(_context);
Finish(true);
_app.TriggerReload(_context, (bool result) => Finish(result));
}
}
else

View File

@@ -21,6 +21,7 @@ using System.Security.Cryptography;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Preferences;
using Java.Lang;
using KeePassLib;
using KeePassLib.Serialization;
@@ -37,7 +38,7 @@ namespace keepass2android
private readonly Database _db;
private readonly bool _dontSave;
/// <summary>
/// <summary>
/// stream for reading the data from the original file. If this is set to a non-null value, we know we need to sync
/// </summary>
private readonly Stream _streamForOrigFile;
@@ -51,7 +52,7 @@ namespace keepass2android
_ctx = ctx;
_app = app;
_dontSave = dontSave;
}
}
/// <summary>
/// Constructor for sync
@@ -116,14 +117,29 @@ namespace keepass2android
return;
}
}
if (
(_streamForOrigFile != null)
|| fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion) //first try to use the fast change detection
|| (FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) == FileHashChange.Changed) //if that fails, hash the file and compare:
)
bool hasStreamForOrigFile = (_streamForOrigFile != null);
bool hasChangeFast = hasStreamForOrigFile ||
fileStorage.CheckForFileChangeFast(ioc, _db.LastFileVersion); //first try to use the fast change detection;
bool hasHashChanged = hasChangeFast ||
(FileHashChanged(ioc, _db.KpDatabase.HashOfFileOnDisk) ==
FileHashChange.Changed); //if that fails, hash the file and compare:
if (hasHashChanged)
{
Kp2aLog.Log("Conflict. " + hasStreamForOrigFile + " " + hasChangeFast + " " + hasHashChanged);
bool alwaysMerge = (PreferenceManager.GetDefaultSharedPreferences(Application.Context)
.GetBoolean("AlwaysMergeOnConflict", false));
if (alwaysMerge)
{
MergeAndFinish(fileStorage, ioc);
}
else
{
//ask user...
_app.AskYesNoCancel(UiStringKey.TitleSyncQuestion, UiStringKey.MessageSyncQuestion,
@@ -132,16 +148,7 @@ namespace keepass2android
//yes = sync
(sender, args) =>
{
Action runHandler = () =>
{
//note: when synced, the file might be downloaded once again from the server. Caching the data
//in the hashing function would solve this but increases complexity. I currently assume the files are
//small.
MergeIn(fileStorage, ioc);
PerformSaveWithoutCheck(fileStorage, ioc);
_db.UpdateGlobals();
Finish(true);
};
Action runHandler = () => { MergeAndFinish(fileStorage, ioc); };
RunInWorkerThread(runHandler);
},
//no = overwrite
@@ -160,6 +167,8 @@ namespace keepass2android
},
_ctx
);
}
}
else
{
@@ -189,7 +198,18 @@ namespace keepass2android
}
private void RunInWorkerThread(Action runHandler)
private void MergeAndFinish(IFileStorage fileStorage, IOConnectionInfo ioc)
{
//note: when synced, the file might be downloaded once again from the server. Caching the data
//in the hashing function would solve this but increases complexity. I currently assume the files are
//small.
MergeIn(fileStorage, ioc);
PerformSaveWithoutCheck(fileStorage, ioc);
_db.UpdateGlobals();
Finish(true);
}
private void RunInWorkerThread(Action runHandler)
{
try
{

Binary file not shown.

Binary file not shown.

View File

@@ -55,7 +55,7 @@
<ItemGroup>
<None Include="Jars\AboutJars.txt" />
<None Include="Additions\AboutAdditions.txt" />
<LibraryProjectZip Include="Jars\pcloud-sdk-android-1.1.0.aar" />
<LibraryProjectZip Include="Jars\pcloud-sdk-android-1.2.0.aar" />
</ItemGroup>
<ItemGroup>
<TransformFile Include="Transforms\Metadata.xml" />
@@ -71,6 +71,6 @@
</Target>
-->
<ItemGroup>
<EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.1.0.jar" />
<EmbeddedReferenceJar Include="Jars\pcloud-sdk-java-core-1.2.0.jar" />
</ItemGroup>
</Project>

View File

@@ -1,18 +1,20 @@
cd ..\java\JavaFileStorageTest-AS
./gradlew clean assemble
cd ..\..\build-scripts
cd ..\KP2ASoftkeyboard_AS
cd ..\java\KP2ASoftkeyboard_AS
./gradlew clean assemble
cd ..\..\build-scripts
cd ..\Keepass2AndroidPluginSDK2
cd ..\java\Keepass2AndroidPluginSDK2
./gradlew clean assemble
cd ..\..\build-scripts
cd ..\KP2AKdbLibrary
cd ..\java\KP2AKdbLibrary
./gradlew clean assemble
cd ..\..\build-scripts
cd ..\PluginQR
cd ..\java\PluginQR
./gradlew clean assemble
cd ..\..\build-scripts

View File

@@ -33,7 +33,7 @@ build-java.bat will call gradlew for several Java modules. build-xamarin.bat wil
- Install [Mono](https://www.mono-project.com/)
- Install Xamarin.Android
- Option 1: Use the mono-project [CI builds](https://jenkins.mono-project.com/view/Xamarin.Android/job/xamarin-android-linux/lastSuccessfulBuild/Azure/)
- Option 1: Use the mono-project [CI builds](https://dev.azure.com/xamarin/public/_build/latest?definitionId=48&branchName=main&stageName=Linux)
- Option 2: [Build it from source](https://github.com/xamarin/xamarin-android/blob/master/Documentation/README.md#building-from-source)
- Setup your environment:
- Add `xabuild` to your path: `export PATH=/path/to/xamarin.android-oss/bin/Release/bin/:$PATH`

View File

@@ -26,23 +26,27 @@ NOTE: If you change dependencies here, don't forget to update the jar files in J
*/
dependencies {
compile 'com.android.support:appcompat-v7:28.0.0'
compile 'com.squareup.okhttp3:okhttp:4.10.0-RC1'
compile 'com.burgstaller:okhttp-digest:2.5'
compile 'com.google.android.gms:play-services:4.0.30'
compile 'com.google.http-client:google-http-client-gson:1.20.0'
compile('com.google.api-client:google-api-client-android:1.16.0-rc') {
compile('com.google.api-client:google-api-client-android:1.30.5') {
exclude group: 'com.google.android.google-play-services'
}
compile 'com.google.apis:google-api-services-drive:v2-rev102-1.16.0-rc'
compile 'com.dropbox.core:dropbox-core-sdk:3.1.1'
compile 'com.dropbox.core:dropbox-core-sdk:4.0.0'
implementation 'com.google.api-client:google-api-client:1.30.5'
implementation 'com.google.api-client:google-api-client-android:1.30.5'
implementation 'com.google.android.gms:play-services-auth:19.2.0'
//onedrive:
compile('com.onedrive.sdk:onedrive-sdk-android:1.2.0') {
transitive = false
}
compile 'com.pcloud.sdk:java-core:1.1.0'
compile 'com.pcloud.sdk:android:1.1.0'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.pcloud.sdk:java-core:1.2.0'
compile 'com.pcloud.sdk:android:1.2.0'
compile 'com.google.code.gson:gson:2.8.6'
compile 'com.microsoft.services.msa:msa-auth:0.8.6'
compile 'com.microsoft.aad:adal:1.14.0'

View File

@@ -6,7 +6,6 @@
android:versionName="1.0">
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2006-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2006-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2005-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2005-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2013-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2013-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2002-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2002-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2015-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -1,6 +1,6 @@
/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
/*
Copyright (c) 2015-2016 ymnk, JCraft,Inc. All rights reserved.
Copyright (c) 2015-2018 ymnk, JCraft,Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

Some files were not shown because too many files have changed in this diff Show More