Compare commits

...

186 Commits

Author SHA1 Message Date
J-Jamet
b087733e37 Merge branch 'release/2.5beta27' 2020-03-07 14:41:29 +01:00
J-Jamet
675efe29ac Fix unused string 2020-03-07 14:31:08 +01:00
J-Jamet
9833af8225 Update french translations 2020-03-07 14:13:10 +01:00
J-Jamet
790c624571 Fix strong html 2020-03-07 14:06:50 +01:00
J-Jamet
67a70a8453 Merge branch 'translations' into develop 2020-03-07 13:57:47 +01:00
J-Jamet
64bb05e2dd Update CHANGELOG 2020-03-07 13:29:09 +01:00
J-Jamet
c111db6e73 Rename preference keys 2020-03-07 13:25:53 +01:00
J-Jamet
24c5915bd3 Setting to request a search when opening a database #220 2020-03-07 13:18:45 +01:00
J-Jamet
04c3717618 Remove screenshot from bug report 2020-03-07 12:41:57 +01:00
J-Jamet
78275a0984 Fix search empty list 2020-03-07 12:37:46 +01:00
J-Jamet
bd908ed10d Update CHANGELOG 2020-03-07 12:01:03 +01:00
J-Jamet
5473deec95 Show URL before username when title is empty #463 2020-03-07 11:58:47 +01:00
Ettore Atalan
653258afd2 Translated using Weblate (German)
Currently translated at 99.7% (418 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-03-07 03:33:06 +01:00
J-Jamet
00a2210eea Fix button menu color 2020-03-06 17:36:02 +01:00
J-Jamet
ec5688a013 Remove autofill setting for incompatible devices 2020-03-06 17:29:48 +01:00
J-Jamet
fc0c7b5708 Update CHANGELOG 2020-03-06 15:54:02 +01:00
J-Jamet
464f7ac486 Sorted list and comparator improvement 2020-03-06 15:46:08 +01:00
solokot
fb2457146c Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-29 19:33:06 +01:00
solokot
d90870838e Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-28 19:15:38 +01:00
Andrew
8c45f23291 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-28 19:15:37 +01:00
Kunzisoft
315a3efa52 Added translation using Weblate (Romanian) 2020-02-27 14:01:57 +01:00
J-Jamet
28abb5ae6f Delete password from starting event #461 2020-02-27 13:26:41 +01:00
J-Jamet
bb78d89682 Try to fix aliased text #414 2020-02-27 13:17:01 +01:00
J-Jamet
fd36e19168 Update CHANGELOG 2020-02-27 12:57:31 +01:00
J-Jamet
58db516e44 Fix refresh appearance settings #452 #451 2020-02-27 12:54:47 +01:00
J-Jamet
2b875e94dc Add GZip string to attachment view if database is compressed 2020-02-27 12:38:16 +01:00
J-Jamet
0ba2447f55 Change about page 2020-02-27 10:16:43 +01:00
solokot
8e684d0d3a Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-25 23:32:46 +01:00
MrEnderfall
ce3c1d4685 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-25 23:32:46 +01:00
J-Jamet
2cd77d47eb Move up remember keyfile locations setting 2020-02-23 13:43:09 +01:00
J-Jamet
ac9366e351 Update CHANGELOGS 2020-02-23 13:39:57 +01:00
J-Jamet
af356586f8 Merge branch 'feature/Check_Broken_Link' into develop 2020-02-23 13:33:56 +01:00
J-Jamet
7650db81a4 Hide broken database links 2020-02-23 13:15:09 +01:00
J-Jamet
8925c86afd Fix remember key file after press back 2020-02-23 12:49:40 +01:00
J-Jamet
8f1836009e Fix database and keyfile locations settings 2020-02-23 12:39:39 +01:00
J-Jamet
5fcf3f9b95 Fix erasing file 2020-02-23 10:47:28 +01:00
Kunzisoft
1c466d9e40 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-22 17:33:01 +01:00
WaldiS
b97c2d9cbc Translated using Weblate (Polish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-22 17:33:01 +01:00
zeritti
3d970e4967 Translated using Weblate (Czech)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-02-22 17:33:01 +01:00
J-Jamet
765c8f53dd Remove permission variable and rename preference 2020-02-20 19:53:49 +01:00
J-Jamet
4e81caeadf Remove unused external storage permission for Sdk > 18 2020-02-20 19:41:48 +01:00
J-Jamet
08b10e4d58 Update string 2020-02-20 19:35:52 +01:00
J-Jamet
f8594f72e8 Fix small elements 2020-02-20 19:31:44 +01:00
J-Jamet
24ebee07cd Fix small elements 2020-02-20 19:26:03 +01:00
J-Jamet
a06ebb0991 Force read only when file is not writable 2020-02-20 19:20:00 +01:00
J-Jamet
cd5d4498e7 Fix small element 2020-02-20 16:11:50 +01:00
J-Jamet
0bd62780c6 Check if uri is broken 2020-02-20 14:13:11 +01:00
J-Jamet
896f9327d6 Fix error messages #431 2020-02-20 11:58:00 +01:00
J-Jamet
cc83a99efe Try to fix #465 2020-02-20 09:06:11 +01:00
J Smith
b6da20fef7 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2020-02-19 23:53:44 +01:00
Kunzisoft
2912678559 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-19 23:53:38 +01:00
WaldiS
409e870ef0 Translated using Weblate (Polish)
Currently translated at 99.5% (417 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-19 23:53:38 +01:00
Kunzisoft
3d84074a0a Translated using Weblate (English)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-02-19 23:53:37 +01:00
Oğuz Ersen
a717fdfed4 Translated using Weblate (Turkish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-19 23:53:36 +01:00
J-Jamet
be1f68015b Merge branch 'master' into develop 2020-02-17 19:05:23 +01:00
WaldiS
2c29dcf1f6 Translated using Weblate (Polish)
Currently translated at 99.0% (415 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2020-02-17 00:50:57 +01:00
Filip Miletic
2b1173177f Translated using Weblate (Croatian)
Currently translated at 79.4% (333 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-17 00:50:55 +01:00
jan madsen
3c0f7dc79c Translated using Weblate (Danish)
Currently translated at 98.8% (414 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-02-17 00:50:42 +01:00
marzzzello
39d813bf3a Translated using Weblate (German)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-02-17 00:50:40 +01:00
Filip Miletic
5e2bc0d05b Translated using Weblate (Croatian)
Currently translated at 51.3% (215 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-14 04:18:25 +01:00
iLocIT!
ec751159ae Translated using Weblate (German)
Currently translated at 89.5% (375 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2020-02-14 04:18:02 +01:00
Filip Miletic
dda8b95f83 Translated using Weblate (Croatian)
Currently translated at 11.0% (46 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2020-02-13 19:31:33 +01:00
Filip Miletic
286012fe2a Added translation using Weblate (Croatian) 2020-02-13 11:04:02 +01:00
Jérémy JAMET
ab27299789 Update description for other KeePass app 2020-02-12 17:09:14 +01:00
Éfrit
dfcdd5aa88 Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-11 21:50:45 +01:00
Allan Nordhøy
a05ea52689 Translated using Weblate (Italian)
Currently translated at 72.8% (305 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2020-02-11 21:50:45 +01:00
zeritti
de12b5de5b Translated using Weblate (Czech)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2020-02-11 21:50:44 +01:00
solokot
57b03eaca4 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-11 21:50:43 +01:00
Retrial
36bd00b760 Translated using Weblate (Greek)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-02-11 21:50:42 +01:00
Oğuz Ersen
4578a9974a Translated using Weblate (Turkish)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-11 21:50:33 +01:00
J-Jamet
d0371f58c6 Capture exception to avoid crash after clear clipboard 2020-02-08 17:39:08 +01:00
solokot
9f80457351 Translated using Weblate (Russian)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-08 16:39:00 +01:00
J-Jamet
56daf6f676 Remove JVMOverload in ClipboardHelper 2020-02-08 12:09:12 +01:00
J-Jamet
7f7b8d423b Merge branch 'translations' into develop 2020-02-08 11:34:35 +01:00
J-Jamet
031afc80cb Fix contribution strings 2020-02-08 11:32:56 +01:00
J-Jamet
b77e28b04d Merge branch 'master' of https://hosted.weblate.org/projects/keepass-dx/strings into translations 2020-02-08 11:27:36 +01:00
Allan Nordhøy
2f8c3fdcfe Translated using Weblate (Norwegian Bokmål)
Currently translated at 81.4% (341 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-02-08 11:27:15 +01:00
solokot
f501a87099 Translated using Weblate (Russian)
Currently translated at 99.8% (418 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-02-08 11:27:14 +01:00
Kunzisoft
d0ec5f26dd Translated using Weblate (English)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2020-02-08 11:27:13 +01:00
Mesut Akcan
97776e9329 Translated using Weblate (Turkish)
Currently translated at 98.3% (412 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2020-02-08 11:27:11 +01:00
Kunzisoft
a19356c49e Translated using Weblate (French)
Currently translated at 100.0% (419 of 419 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2020-02-08 11:21:45 +01:00
J-Jamet
4f16918cf0 Remove unused strings 2020-02-08 11:19:45 +01:00
J-Jamet
1af9761144 Merge branch 'master' into develop 2020-02-07 22:14:20 +01:00
J-Jamet
d74e814c79 Fix fastlane data 2020-02-07 22:05:58 +01:00
J-Jamet
16d09bca6c Merge tag '2.5.0.0beta26' into develop
2.5.0.0beta26
2020-02-07 21:45:51 +01:00
J-Jamet
462c29b769 Merge branch 'release/2.5.0.0beta26' 2020-02-07 21:45:40 +01:00
J-Jamet
3939276d58 Fix translations 2020-02-07 19:44:21 +01:00
J-Jamet
ff85f18c4c Update ReadMe.md 2020-02-07 19:24:12 +01:00
J-Jamet
dd14fe4123 Fix exception after changing theme 2020-02-07 19:20:43 +01:00
J-Jamet
d804659ee2 Change blue color of classic dark style 2020-02-07 19:10:48 +01:00
J-Jamet
e3152cf901 Remove AMOLED string for Black theme 2020-02-07 18:57:07 +01:00
J-Jamet
de236f321f Upgrade CHANGELOG 2020-02-07 18:26:27 +01:00
J-Jamet
66f4611866 Restore and delete entry history #335 2020-02-07 18:22:37 +01:00
heta1121
6a6ef052af Translated using Weblate (Swedish)
Currently translated at 94.9% (388 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-02-06 19:07:41 +01:00
J-Jamet
4e4606dabd Fix strings #436 2020-01-31 20:14:53 +01:00
J-Jamet
0ce11103ab Update CHANGELOG 2020-01-31 19:33:24 +01:00
J-Jamet
899d0e0557 Fix expired entries views 2020-01-31 19:29:40 +01:00
J-Jamet
4aefeff41f Hide expired entries from quick search when setting is activated #453 2020-01-31 17:26:36 +01:00
J-Jamet
08d59e50e8 Add android:resizeableActivity="true" for DeX mode 2020-01-31 16:48:15 +01:00
J-Jamet
e6c06aba8c Merge branch 'qtwyeuritoiy-feature/amoled_black' into develop 2020-01-31 13:12:37 +01:00
J-Jamet
e5c2a04922 Merge branch 'feature/amoled_black' of git://github.com/qtwyeuritoiy/KeePassDX into qtwyeuritoiy-feature/amoled_black 2020-01-31 13:12:18 +01:00
J-Jamet
c134ccf8d9 Encapsulate filters 2020-01-29 20:20:39 +01:00
J-Jamet
4f959d1ff6 Merge branch 'feature/hide_expired_entries' of git://github.com/qtwyeuritoiy/KeePassDX into qtwyeuritoiy-feature/hide_expired_entries 2020-01-29 17:22:40 +01:00
J-Jamet
1c06d93951 Fix custom fields in Magikeyboard 2020-01-29 17:10:26 +01:00
J-Jamet
b2dff29baa Auto performed "Go" key in Magikeyboard 2020-01-29 14:13:13 +01:00
J-Jamet
72633e9472 Change KEYCODE_ENTER by IME_ACTION_GO #369 2020-01-29 13:34:32 +01:00
J-Jamet
898a88f7d8 Fix app description 2020-01-27 12:52:46 +01:00
J-Jamet
c2f09f6666 Change "KeePass DX" name to "KeePassDX" and fix small issues #411 2020-01-27 12:43:45 +01:00
J-Jamet
d45bcbc347 Fix empty space in entry content 2020-01-27 11:18:54 +01:00
Jihoon Kim
1f834567f8 [Feature/HideExpiredEntries] Move 'Hide Expired Entries' Switch to 'Other' Category
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-27 17:39:26 +08:00
Jihoon Kim
0b62c04867 [Feature/AmoledBlack] Add AMOLED Black Theme
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-27 17:00:24 +08:00
J-Jamet
c71bb2a570 Password display box multiline #225 2020-01-26 18:02:11 +01:00
J-Jamet
0a575b5bf8 Prevent capture for all screens 2020-01-26 15:47:50 +01:00
J-Jamet
6f2bf903c5 Fix storage permission 2020-01-26 13:26:09 +01:00
Rodrigo Saldaña
210ad4b8db Translated using Weblate (Spanish)
Currently translated at 88.5% (362 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2020-01-25 19:21:30 +01:00
Allan Nordhøy
b42cf0e204 Translated using Weblate (Norwegian Bokmål)
Currently translated at 87.5% (358 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-23 09:21:33 +01:00
solokot
2045d3aab8 Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-23 09:21:32 +01:00
Allan Nordhøy
e20cb9431d Translated using Weblate (Danish)
Currently translated at 95.6% (391 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2020-01-23 09:21:31 +01:00
J-Jamet
7161eaea8b Allow write permission for specific file managers 2020-01-22 16:37:38 +01:00
J-Jamet
b786da52f5 Add error snackbar for error in entry edit view #431 2020-01-22 13:01:15 +01:00
J-Jamet
ccbfec838d Fix disclaimer formal string 2020-01-22 12:18:19 +01:00
J-Jamet
f5c1872225 Fix show read only warning 2020-01-22 10:53:24 +01:00
heta1121
3001013bad Translated using Weblate (Swedish)
Currently translated at 85.6% (350 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-01-21 17:21:30 +01:00
Allan Nordhøy
1280803e5e Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.9% (343 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-19 15:22:00 +01:00
heta1121
f84fc07fe0 Translated using Weblate (Swedish)
Currently translated at 85.1% (348 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2020-01-19 15:22:00 +01:00
J-Jamet
d83e53f589 Fix read only for recycle bin 2020-01-19 12:17:27 +01:00
J-Jamet
52ba2c53f1 Fix progress for Android < 5 2020-01-18 15:23:51 +01:00
J-Jamet
f266e1de4c Add string to open the attachment downloaded 2020-01-18 12:24:21 +01:00
J-Jamet
5a0aafd3ce Update CHANGELOG 2020-01-18 12:19:56 +01:00
J-Jamet
c7ce07a43c Change file size format 2020-01-18 12:15:14 +01:00
J-Jamet
a73644c285 Merge branch 'feature/Stream_Binaries' into develop 2020-01-18 12:03:55 +01:00
J-Jamet
e28f1ffc99 Fix attachment orientation change 2020-01-18 11:33:17 +01:00
Deleted User
afc691b2f4 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.9% (343 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2020-01-16 06:27:50 +01:00
J-Jamet
00f6eb83c3 Fix close notification 2020-01-15 05:52:32 +01:00
J-Jamet
bbf51ebbec Multiple attachments download and add file compression 2020-01-14 19:44:10 +01:00
Jihoon Kim
7816c8f16e [Feature/HideExpiredEntries] Apply 'Hide Expired Entries' Setting And Adjust Item Count Accordingly
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-14 17:28:27 +08:00
Jihoon Kim
fdbcba2412 [Feature/HideExpiredEntries] Add Preference Entry for 'Hide Expired Entries'
Signed-off-by: Jihoon Kim <imsesaok@tuta.io>
2020-01-14 17:28:27 +08:00
J-Jamet
50dc6cb0aa Fix download attachment 2020-01-13 10:05:34 +01:00
J-Jamet
e413198d12 Download attachment dynamic string 2020-01-10 19:40:00 +01:00
solokot
72592772f9 Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-09 09:21:27 +01:00
solokot
f918206bcd Translated using Weblate (Russian)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2020-01-07 22:27:33 +01:00
J-Jamet
6b34d67da8 Remove WRITE_EXTERNAL_STORAGE for Android > 18
Download attachment as service
2020-01-07 19:43:41 +01:00
Retrial
04fb093d4d Translated using Weblate (Greek)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/el/
2020-01-04 18:21:28 +01:00
J-Jamet
7de0e0cc4a Add Attachment view 2019-12-31 14:34:51 +01:00
J-Jamet
5cf7688e38 Merge branch 'develop' into feature/Stream_Binaries 2019-12-31 12:05:48 +01:00
J-Jamet
97cd06b52b Merge branch 'feature/Duplicate_UUID_Database1' into develop 2019-12-31 11:23:44 +01:00
J-Jamet
6d522ead1d Fix illegal argument exception and refactor KDB parser code 2019-12-31 08:26:15 +01:00
J-Jamet
8e0fae62f3 Merge branch 'develop' into feature/Stream_Binaries 2019-12-30 14:09:05 +01:00
J-Jamet
af72098d60 Refactor bytes utility methods 2019-12-30 12:59:50 +01:00
Taeko Hasegawa
62da582f4e Translated using Weblate (Japanese)
Currently translated at 36.4% (149 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2019-12-23 14:21:24 +01:00
J-Jamet
f3efa6eddc Add parameter to fix duplicate UUID in DatabaseV1 2019-12-22 18:26:35 +01:00
J-Jamet
c85fb7bd0a Better element output KDB implementation 2019-12-22 18:13:26 +01:00
J-Jamet
fc7b183461 Update CHANGELOG 2019-12-22 17:51:48 +01:00
J-Jamet
a378810f88 Improvement code for ClipboardHelper 2019-12-21 21:36:33 +01:00
J-Jamet
2f5b322fad Fix crash during clean clipboard in Android 9 2019-12-21 21:31:12 +01:00
J-Jamet
efb9b50f85 Fix issue #127 KDBX binaries 2019-12-21 21:18:58 +01:00
J-Jamet
84fdef8eb6 Merge branch 'develop' into feature/Stream_Binaries 2019-12-21 19:46:05 +01:00
J-Jamet
76050a261b Update to version 2.5.0.0beta26 2019-12-21 19:16:30 +01:00
Éfrit
2667361450 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-19 17:18:38 +01:00
Éfrit
a2b3313cc0 Translated using Weblate (French)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-18 22:50:52 +01:00
J-Jamet
d343446235 Check file size to not corrupt a database v1 if too high 2019-12-18 18:58:26 +01:00
J-Jamet
cbce70f8a4 Save KDB binaries as file 2019-12-18 18:46:50 +01:00
WaldiS
4573d6b6fb Translated using Weblate (Polish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-12-18 14:21:21 +01:00
Chen Wang
973305d13c Translated using Weblate (Chinese (Simplified))
Currently translated at 99.3% (406 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2019-12-16 08:21:13 +01:00
solokot
4637016372 Translated using Weblate (Russian)
Currently translated at 99.8% (408 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ru/
2019-12-16 08:21:12 +01:00
Chen Wang
698ba4f7f1 Translated using Weblate (English)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-12-16 08:21:11 +01:00
Oğuz Ersen
502ebabf1f Translated using Weblate (Turkish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-12-16 08:21:10 +01:00
WaldiS
13f88626ca Translated using Weblate (Polish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2019-12-08 15:13:26 +01:00
zeritti
a39f58f7b5 Translated using Weblate (Czech)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2019-12-08 15:13:25 +01:00
nautilusx
aeb36468ce Translated using Weblate (German)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-08 15:13:23 +01:00
J-Jamet
fdd196526d Better InputStream / OutputStream close 2019-12-05 19:28:54 +01:00
Mattias Münster
ecca892b16 Translated using Weblate (Swedish)
Currently translated at 25.9% (106 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/sv/
2019-12-04 02:05:37 +01:00
Rudah Ximenes Alvarenga
03343d0301 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt_BR/
2019-12-04 02:05:36 +01:00
Allan Nordhøy
a61c8b0cb6 Translated using Weblate (Norwegian Bokmål)
Currently translated at 83.6% (342 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/nb_NO/
2019-12-02 03:33:26 +01:00
Kunzisoft
a92129da00 Translated using Weblate (French)
Currently translated at 99.5% (407 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/fr/
2019-12-02 03:33:24 +01:00
Allan Nordhøy
0b783d6390 Translated using Weblate (English)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/en/
2019-12-02 03:33:23 +01:00
jan madsen
9aa1a2e30e Translated using Weblate (Danish)
Currently translated at 95.8% (392 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2019-12-02 03:33:23 +01:00
C. Rüdinger
15b3c69514 Translated using Weblate (German)
Currently translated at 99.5% (407 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-02 03:33:21 +01:00
Oğuz Ersen
f2722e09ec Translated using Weblate (Turkish)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/tr/
2019-12-02 03:33:21 +01:00
Deleted User
b442859be0 Translated using Weblate (German)
Currently translated at 100.0% (409 of 409 strings)

Translation: KeePass DX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2019-12-01 17:45:31 +01:00
J-Jamet
1496a2efb1 Fix database decryption with attachment 2019-11-30 19:26:26 +01:00
J-Jamet
a0edb111f0 Merge branch 'develop' into feature/Stream_Binaries 2019-11-30 18:27:21 +01:00
J-Jamet
6bcf54fe29 Merge tag '2.5.0.0beta25' into develop
2.5.0.0beta25
2019-11-30 17:43:19 +01:00
J-Jamet
eb0e496cfd Try to decrypt DatabaseV1 by InputStream 2019-11-30 17:02:09 +01:00
J-Jamet
12c2a6e99c Refactor bytes utility methods 2019-11-29 14:19:13 +01:00
J-Jamet
714433b62d Refactor stream methods 2019-11-28 16:32:16 +01:00
439 changed files with 9424 additions and 5528 deletions

View File

@@ -20,9 +20,6 @@ Steps to reproduce the behavior:
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
** Keepass Database **
- Created with: [e.g Windows KeePass 2.42]
- Version: [e.g. 2]

View File

@@ -1,3 +1,25 @@
KeepassDX (2.5beta27)
* New setting to hide broken links
* Show URL when title is empty
* Setting to open search field at database opening
* Fix settings for database locations
* Fix error message when database file not writable
* Fix appearance refresh settings
* Sort optimization
KeepassDX (2.5.0.0beta26)
* Download attachments
* Change file size string format
* Prevent screenshot for all screen
* Auto performed "Go" key in Magikeyboard
* Restore and delete entry history
* Setting to hide expired entries
* New Black theme
* Fix crash when clearing clipboard
* Fix attachments compressions
* Fix dates
* Fix UUID message for Database v1
KeepassDX (2.5.0.0beta25)
* Setting for Recycle Bin
* Fix Recycle bin issues

View File

@@ -11,7 +11,7 @@
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)*
* **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* Material design with **themes**
* **AutoFill** and Integration
* Field filling **keyboard**
@@ -59,13 +59,15 @@ Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassD
## Other devices
- [KeePass XC](https://keepassxc.org/) (https://keepassxc.org/) works with **GNU/Linux**, **Mac** and **Windows**, is updated regularly and under the terms of the GNU General Public License. This is the recommended version for computers.
- [KeePass](https://keepass.info/) (https://keepass.info/) is the original and official project for desktop, with technical documentation for standardized database files. It is updated regularly with active maintenance (written in C#).
- [KeePass](https://keepass.info/) (https://keepass.info/) is the historical project, with good technical documentation for standardized database files but only running on **Windows**.
- [KeePassXC](https://keepassxc.org/) (https://keepassxc.org/) is an alternative integration to KeePass written in C++.
- [KeeWeb](https://keeweb.info/) (https://keeweb.info/) is a web version also compatible with KeePass files.
## License
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX.

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 14
targetSdkVersion 28
versionCode = 25
versionName = "2.5.0.0beta25"
versionCode = 27
versionName = "2.5beta27"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -1,38 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.junit.Assert
class PwDateTest : TestCase() {
fun testDate() {
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = DatabaseInputOutputUtils.readCDate(DatabaseInputOutputUtils.writeCDate(intermediate.date)!!, 0)
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
Assert.assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,19 +19,15 @@
*/
package com.kunzisoft.keepass.tests
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.Calendar
import java.util.Random
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
class DatabaseInputOutputUtilsTest : TestCase() {
class StringDatabaseKDBUtilsTest : TestCase() {
fun testReadWriteLongZero() {
testReadWriteLong(0.toByte())
@@ -55,15 +51,9 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8)
val dest = ByteArray(8)
setArray(orig, value, 0, 8)
val one = LEDataInputStream.readLong(orig, 0)
LEDataOutputStream.writeLong(one, dest, 0)
assertArrayEquals(orig, dest)
setArray(orig, value, 8)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
}
fun testReadWriteIntZero() {
@@ -80,24 +70,22 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4)
val dest = ByteArray(4)
for (i in 0..3) {
orig[i] = 0
}
setArray(orig, value, 0, 4)
setArray(orig, value, 4)
val one = LEDataInputStream.readInt(orig, 0)
LEDataOutputStream.writeInt(one, dest, 0)
val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
assertArrayEquals(orig, dest)
}
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) {
for (i in offset until offset + size) {
private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in 0 until size) {
buf[i] = value
}
}
@@ -108,11 +96,10 @@ class DatabaseInputOutputUtilsTest : TestCase() {
orig[0] = 0
orig[1] = 1
val one = LEDataInputStream.readUShort(orig, 0)
val dest = LEDataOutputStream.writeUShortBuf(one)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteShortMin() {
@@ -125,15 +112,12 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2)
val dest = ByteArray(2)
setArray(orig, value, 2)
setArray(orig, value, 0, 2)
val one = LEDataInputStream.readUShort(orig, 0)
LEDataOutputStream.writeUShort(one, dest, 0)
val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest)
}
fun testReadWriteByteZero() {
@@ -149,16 +133,8 @@ class DatabaseInputOutputUtilsTest : TestCase() {
}
private fun testReadWriteByte(value: Byte) {
val orig = ByteArray(1)
val dest = ByteArray(1)
setArray(orig, value, 0, 1)
val one = DatabaseInputOutputUtils.readUByte(orig, 0)
DatabaseInputOutputUtils.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
val dest: Byte = uIntToByte(byteToUInt(value))
assert(value == dest)
}
fun testDate() {
@@ -168,27 +144,33 @@ class DatabaseInputOutputUtilsTest : TestCase() {
expected.set(2008, 1, 2, 3, 4, 5)
val actual = Calendar.getInstance()
DatabaseInputOutputUtils.writeCDate(expected.time, cal)?.let { buf ->
actual.time = DatabaseInputOutputUtils.readCDate(buf, 0, cal).date
dateTo5Bytes(expected.time, cal)?.let { buf ->
actual.time = bytes5ToDate(buf, cal).date
}
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
assertTrue("jDate and intermediate not equal", jDate == intermediate)
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
}
fun testUUID() {
val bUUID = ByteArray(16)
Random().nextBytes(bUUID)
val uuid = DatabaseInputOutputUtils.bytesToUuid(bUUID)
val eUUID = DatabaseInputOutputUtils.uuidToBytes(uuid)
val uuid = bytes16ToUuid(bUUID)
val eUUID = uuidTo16Bytes(uuid)
val lUUID = LEDataInputStream.readUuid(bUUID, 0)
val leUUID = DatabaseInputOutputUtils.uuidToBytes(lUUID)
val lUUID = bytes16ToUuid(bUUID)
val leUUID = uuidTo16Bytes(lUUID)
assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID)
@@ -202,8 +184,8 @@ class DatabaseInputOutputUtilsTest : TestCase() {
}
val bos = ByteArrayOutputStream()
val leos = LEDataOutputStream(bos)
leos.writeLong(DatabaseInputOutputUtils.ULONG_MAX_VALUE)
val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(ULONG_MAX_VALUE)
leos.close()
val uLongMax = bos.toByteArray()

View File

@@ -39,9 +39,8 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() {
private val rand = Random()
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt)
val lis = LEDataInputStream(cis)
val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH)

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass"
android:installLocation="auto">
<supports-screens
@@ -7,10 +8,15 @@
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission
android:name="android.permission.USE_BIOMETRIC" />
<uses-permission
android:name="android.permission.VIBRATE"/>
<uses-permission
android:maxSdkVersion="18"
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:label="@string/app_name"
@@ -21,7 +27,9 @@
android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true"
android:theme="@style/KeepassDXStyle.Night">
android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
<!-- TODO backup API Key -->
<meta-data
android:name="com.google.android.backup.api_key"
@@ -125,7 +133,7 @@
<activity
android:name="com.kunzisoft.keepass.activities.AboutActivity"
android:launchMode="singleTask"
android:label="@string/menu_about" />
android:label="@string/about" />
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutofillLauncherActivity"
android:configChanges="keyboardHidden" />
@@ -150,6 +158,10 @@
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name=".notifications.AttachmentFileNotificationService"
android:enabled="true"
android:exported="false" />
<service
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
android:enabled="true"

View File

@@ -21,15 +21,15 @@ package com.kunzisoft.keepass.activities
import android.content.pm.PackageManager.NameNotFoundException
import android.os.Bundle
import androidx.appcompat.widget.Toolbar
import android.text.method.LinkMovementMethod
import android.util.Log
import android.view.MenuItem
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.core.text.HtmlCompat
import com.kunzisoft.keepass.BuildConfig
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.stylish.StylishActivity
import org.joda.time.DateTime
class AboutActivity : StylishActivity() {
@@ -40,7 +40,7 @@ class AboutActivity : StylishActivity() {
setContentView(R.layout.activity_about)
val toolbar = findViewById<Toolbar>(R.id.toolbar)
toolbar.title = getString(R.string.menu_about)
toolbar.title = getString(R.string.about)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
@@ -64,9 +64,17 @@ class AboutActivity : StylishActivity() {
val buildTextView = findViewById<TextView>(R.id.activity_about_build)
buildTextView.text = build
findViewById<TextView>(R.id.activity_about_licence_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_licence, DateTime().year),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
val disclaimerText = findViewById<TextView>(R.id.disclaimer)
disclaimerText.text = getString(R.string.disclaimer_formal, DateTime().year)
findViewById<TextView>(R.id.activity_about_contribution_text).apply {
movementMethod = LinkMovementMethod.getInstance()
text = HtmlCompat.fromHtml(getString(R.string.html_about_contribution),
HtmlCompat.FROM_HTML_MODE_LEGACY)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {

View File

@@ -22,6 +22,7 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
@@ -33,28 +34,40 @@ import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.createDocument
import com.kunzisoft.keepass.utils.onCreateDocumentResult
import com.kunzisoft.keepass.view.EntryContentsView
import com.kunzisoft.keepass.view.showActionError
import java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingHideActivity() {
class EntryActivity : LockingActivity() {
private var coordinatorLayout: CoordinatorLayout? = null
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null
private var historyView: View? = null
@@ -65,10 +78,16 @@ class EntryActivity : LockingHideActivity() {
private var mDatabase: Database? = null
private var mEntry: Entry? = null
private var mIsHistory: Boolean = false
private var mEntryLastVersion: Entry? = null
private var mEntryHistoryPosition: Int = -1
private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false
@@ -98,6 +117,7 @@ class EntryActivity : LockingHideActivity() {
invalidateOptionsMenu()
// Get views
coordinatorLayout = findViewById(R.id.toolbar_coordinator)
collapsingToolbarLayout = findViewById(R.id.toolbar_layout)
titleIconView = findViewById(R.id.entry_icon)
historyView = findViewById(R.id.history_container)
@@ -108,6 +128,21 @@ class EntryActivity : LockingHideActivity() {
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
firstLaunchOfActivity = true
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
}
coordinatorLayout?.showActionError(result)
}
}
override fun onResume() {
@@ -117,11 +152,13 @@ class EntryActivity : LockingHideActivity() {
try {
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry)
mEntryLastVersion = mEntry
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
}
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1)
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
mEntryHistoryPosition = historyPosition
if (historyPosition >= 0) {
mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition)
@@ -155,9 +192,24 @@ class EntryActivity : LockingHideActivity() {
}
}
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false
}
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance()
@@ -265,6 +317,27 @@ class EntryActivity : LockingHideActivity() {
}
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
// Manage attachments
val attachments = entry.getAttachments()
val showAttachmentsView = attachments.isNotEmpty()
entryContentsView?.showAttachments(showAttachmentsView)
if (showAttachmentsView) {
entryContentsView?.assignAttachments(attachments)
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
when (attachmentItem.downloadState) {
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
createDocument(this, attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
else -> {
// TODO Stop download
}
}
}
}
entryContentsView?.refreshAttachments()
// Assign dates
entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime)
@@ -276,9 +349,6 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) {
@@ -287,23 +357,26 @@ class EntryActivity : LockingHideActivity() {
taColorAccent.recycle()
}
val entryHistory = entry.getHistory()
// isMainEntry = not an history
// TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, position ->
launch(this, historyItem, true, position)
launch(this, historyItem, mReadOnly, position)
}
}
entryContentsView?.refreshHistory()
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
database.stopManageEntry(entry)
entryContentsView?.refreshHistory()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database
@@ -311,6 +384,15 @@ class EntryActivity : LockingHideActivity() {
fillEntryDataInContentsView(it)
}
}
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
}
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
@@ -330,7 +412,10 @@ class EntryActivity : LockingHideActivity() {
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu)
if (mReadOnly) {
if (mIsHistory && !mReadOnly) {
inflater.inflate(R.menu.entry_history, menu)
}
if (mIsHistory || mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false
}
@@ -423,6 +508,22 @@ class EntryActivity : LockingHideActivity() {
UriUtil.gotoUrl(this, url)
return true
}
R.id.menu_restore_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_delete_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_lock -> {
lockAndExit()
return true

View File

@@ -28,12 +28,16 @@ import android.view.MenuItem
import android.view.View
import android.widget.ScrollView
import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryEditActivityEducation
@@ -47,9 +51,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.showActionError
import java.util.*
class EntryEditActivity : LockingHideActivity(),
class EntryEditActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener {
@@ -64,6 +69,7 @@ class EntryEditActivity : LockingHideActivity(),
private var mIsNew: Boolean = false
// Views
private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: ScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null
private var saveView: View? = null
@@ -81,6 +87,8 @@ class EntryEditActivity : LockingHideActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
@@ -182,6 +190,7 @@ class EntryEditActivity : LockingHideActivity(),
finish()
}
}
coordinatorLayout?.showActionError(result)
}
}

View File

@@ -42,7 +42,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity
@@ -53,8 +52,8 @@ import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException
@@ -92,17 +91,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Create button
createButtonView = findViewById(R.id.create_database_button)
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
// There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE
}
else{
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
createButtonView?.setOnClickListener { createNewFile() }
@@ -182,18 +178,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/
@SuppressLint("InlinedApi")
private fun createNewFile() {
try {
startActivityForResult(Intent(
Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
},
CREATE_FILE_REQUEST_CODE)
} catch (e: Exception) {
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
}
createDocument(this, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default), "application/x-keepass")
}
private fun fileNoFoundAction(e: FileNotFoundException) {
@@ -289,13 +275,29 @@ class FileDatabaseSelectActivity : StylishActivity(),
updateExternalStorageWarning()
// Construct adapter with listeners
if (PreferencesUtil.showRecentFiles(this)) {
mFileDatabaseHistoryAction?.getAllFileDatabaseHistories { databaseFileHistoryList ->
databaseFileHistoryList?.let {
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(it)
updateFileListVisibility()
databaseFileHistoryList?.let { historyList ->
val hideBrokenLocations = PreferencesUtil.hideBrokenLocations(this@FileDatabaseSelectActivity)
mAdapterDatabaseHistory?.addDatabaseFileHistoryList(
// Show only uri accessible
historyList.filter {
if (hideBrokenLocations) {
UriUtil.parse(it.databaseUri)?.let { historyUri ->
UriUtil.isUriAccessible(contentResolver, historyUri)
} ?: false
} else
true
})
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
}
} else {
mAdapterDatabaseHistory?.clearDatabaseFileHistoryList()
mAdapterDatabaseHistory?.notifyDataSetChanged()
updateFileListVisibility()
}
// Register progress task
mProgressDialogThread?.registerProgressTask()
@@ -367,8 +369,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
}
// Retrieve the created URI from the file manager
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
mDatabaseFileUri = data?.data
onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog")
@@ -427,8 +429,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/*
* -------------------------
* No Standard Launch, pass by PasswordActivity

View File

@@ -42,14 +42,19 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.*
import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -71,6 +76,7 @@ import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.AddNodeButtonView
import com.kunzisoft.keepass.view.ToolbarAction
import com.kunzisoft.keepass.view.asError
import com.kunzisoft.keepass.view.showActionError
class GroupActivity : LockingActivity(),
GroupEditDialogFragment.EditGroupListener,
@@ -96,6 +102,7 @@ class GroupActivity : LockingActivity(),
private var mListNodesFragment: ListNodesFragment? = null
private var mCurrentGroupIsASearch: Boolean = false
private var mRequestStartupSearch = true
// Nodes
private var mRootGroup: Group? = null
@@ -136,6 +143,8 @@ class GroupActivity : LockingActivity(),
// Retrieve elements after an orientation change
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(REQUEST_STARTUP_SEARCH_KEY))
mRequestStartupSearch = savedInstanceState.getBoolean(REQUEST_STARTUP_SEARCH_KEY)
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
mOldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY)
}
@@ -246,15 +255,7 @@ class GroupActivity : LockingActivity(),
}
}
if (!result.isSuccess) {
coordinatorLayout?.let { coordinatorLayout ->
result.exception?.errorId?.let { errorId ->
Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show()
} ?: result.message?.let { message ->
Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show()
}
}
}
coordinatorLayout?.showActionError(result)
finishNodeAction()
@@ -334,6 +335,7 @@ class GroupActivity : LockingActivity(),
mOldGroupToUpdate?.let {
outState.putParcelable(OLD_GROUP_TO_UPDATE_KEY, it)
}
outState.putBoolean(REQUEST_STARTUP_SEARCH_KEY, mRequestStartupSearch)
super.onSaveInstanceState(outState)
}
@@ -430,12 +432,8 @@ class GroupActivity : LockingActivity(),
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
mCurrentGroup?.let {
val isRoot = it == mRootGroup
if (!it.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled
if (isRoot) {
showWarnings()
}
addEntryEnabled = it != mRootGroup && addEntryEnabled
}
enableAddGroup(addGroupEnabled)
enableAddEntry(addEntryEnabled)
@@ -448,7 +446,7 @@ class GroupActivity : LockingActivity(),
private fun refreshNumberOfChildren() {
numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: ""
text = mCurrentGroup?.getNumberOfChildEntries(*Group.ChildFilter.getDefaults(context))?.toString() ?: ""
visibility = View.VISIBLE
} else {
visibility = View.GONE
@@ -658,7 +656,8 @@ class GroupActivity : LockingActivity(),
}
// Menu for recycle bin
if (mDatabase?.isRecycleBinEnabled == true
if (!mReadOnly
&& mDatabase?.isRecycleBinEnabled == true
&& mDatabase?.recycleBin == mCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu)
}
@@ -688,6 +687,13 @@ class GroupActivity : LockingActivity(),
}
})
}
// Expand the search view if defined in settings
if (mRequestStartupSearch
&& PreferencesUtil.automaticallyFocusSearch(this@GroupActivity)) {
// To request search only one time
mRequestStartupSearch = false
it.expandActionView()
}
}
super.onCreateOptionsMenu(menu)
@@ -853,14 +859,6 @@ class GroupActivity : LockingActivity(),
.iconPicked(bundle)
}
private fun showWarnings() {
if (Database.getInstance().isReadOnly) {
if (PreferencesUtil.showReadOnlyWarning(this)) {
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
}
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
}
@@ -948,6 +946,7 @@ class GroupActivity : LockingActivity(),
private val TAG = GroupActivity::class.java.name
private const val REQUEST_STARTUP_SEARCH_KEY = "REQUEST_STARTUP_SEARCH_KEY"
private const val GROUP_ID_KEY = "GROUP_ID_KEY"
private const val LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG"
private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG"

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities
import android.content.Context
@@ -35,7 +54,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
private var nodeClickListener: NodeClickListener? = null
private var onScrollListener: OnScrollListener? = null
private var listView: RecyclerView? = null
private var mNodesRecyclerView: RecyclerView? = null
var mainGroup: Group? = null
private set
private var mAdapter: NodeAdapter? = null
@@ -150,11 +169,17 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
// To apply theme
val rootView = inflater.cloneInContext(contextThemed)
.inflate(R.layout.fragment_list_nodes, container, false)
listView = rootView.findViewById(R.id.nodes_list)
mNodesRecyclerView = rootView.findViewById(R.id.nodes_list)
notFoundView = rootView.findViewById(R.id.not_found_container)
mNodesRecyclerView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
onScrollListener?.let { onScrollListener ->
listView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
mNodesRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
onScrollListener.onScrolled(dy)
@@ -162,8 +187,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
})
}
rebuildList()
return rootView
}
@@ -175,14 +198,14 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Refresh data
mAdapter?.notifyDataSetChanged()
rebuildList()
if (isASearchResult && mAdapter!= null && mAdapter!!.isEmpty) {
// To show the " no search entry found "
listView?.visibility = View.GONE
mNodesRecyclerView?.visibility = View.GONE
notFoundView?.visibility = View.VISIBLE
} else {
listView?.visibility = View.VISIBLE
mNodesRecyclerView?.visibility = View.VISIBLE
notFoundView?.visibility = View.GONE
}
}
@@ -192,14 +215,12 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
listView?.apply {
scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
layoutManager = LinearLayoutManager(context)
adapter = mAdapter
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
override fun onSortSelected(sortNodeEnum: SortNodeEnum,
ascending: Boolean,
groupsBefore: Boolean,
recycleBinBottom: Boolean) {
// Toggle setting
prefs?.edit()?.apply {
putString(getString(R.string.sort_node_key), sortNodeEnum.name)
@@ -210,10 +231,9 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
}
// Tell the adapter to refresh it's list
mAdapter?.notifyChangeSort(sortNodeEnum, ascending, groupsBefore)
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
mAdapter?.notifyChangeSort(sortNodeEnum,
SortNodeEnum.SortNodeParameters(ascending, groupsBefore, recycleBinBottom))
rebuildList()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
@@ -359,9 +379,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
mAdapter?.addNode(newNode)
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mainGroup?.let { mainGroup ->
mAdapter?.rebuildList(mainGroup)
}
rebuildList()
}
} ?: Log.e(this.javaClass.name, "New node can be retrieve in Activity Result")
}

View File

@@ -32,10 +32,7 @@ import android.preference.PreferenceManager
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.view.*
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.Button
import android.widget.CompoundButton
@@ -75,11 +72,10 @@ import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_password.*
import java.io.FileNotFoundException
class PasswordActivity : StylishActivity() {
open class PasswordActivity : StylishActivity() {
// Views
private var toolbar: Toolbar? = null
private var containerView: View? = null
private var filenameView: TextView? = null
private var passwordView: EditText? = null
@@ -89,17 +85,28 @@ class PasswordActivity : StylishActivity() {
private var checkboxKeyFileView: CompoundButton? = null
private var checkboxDefaultDatabaseView: CompoundButton? = null
private var advancedUnlockInfoView: AdvancedUnlockInfoView? = null
private var infoContainerView: ViewGroup? = null
private var enableButtonOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
private var mDatabaseFileUri: Uri? = null
private var mDatabaseKeyFileUri: Uri? = null
private var prefs: SharedPreferences? = null
private var mSharedPreferences: SharedPreferences? = null
private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null
private var readOnly: Boolean = false
private var mForceReadOnly: Boolean = false
set(value) {
infoContainerView?.visibility = if (value) {
readOnly = true
View.VISIBLE
} else {
View.GONE
}
field = value
}
private var mProgressDialogThread: ProgressDialogThread? = null
@@ -108,9 +115,9 @@ class PasswordActivity : StylishActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = PreferenceManager.getDefaultSharedPreferences(this)
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
mRememberKeyFile = PreferencesUtil.rememberKeyFiles(this)
mRememberKeyFile = PreferencesUtil.rememberKeyFileLocations(this)
setContentView(R.layout.activity_password)
@@ -121,7 +128,7 @@ class PasswordActivity : StylishActivity() {
supportActionBar?.setDisplayShowHomeEnabled(true)
containerView = findViewById(R.id.container)
confirmButtonView = findViewById(R.id.pass_ok)
confirmButtonView = findViewById(R.id.activity_password_open_button)
filenameView = findViewById(R.id.filename)
passwordView = findViewById(R.id.password)
keyFileView = findViewById(R.id.pass_keyfile)
@@ -129,6 +136,7 @@ class PasswordActivity : StylishActivity() {
checkboxKeyFileView = findViewById(R.id.keyfile_checkox)
checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info)
infoContainerView = findViewById(R.id.activity_password_info_container)
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
@@ -179,6 +187,7 @@ class PasswordActivity : StylishActivity() {
removePassword()
if (result.isSuccess) {
setEmptyViews()
launchGroupActivity()
} else {
var resultError = ""
@@ -300,6 +309,8 @@ class PasswordActivity : StylishActivity() {
keyFileUri = intent.getParcelableExtra(KEY_KEYFILE)
}
mForceReadOnly = UriUtil.isUriNotWritable(contentResolver, databaseUri)
// Post init uri with KeyFile if needed
if (mRememberKeyFile && (keyFileUri == null || keyFileUri.toString().isEmpty())) {
// Retrieve KeyFile in a thread
@@ -338,7 +349,7 @@ class PasswordActivity : StylishActivity() {
newDefaultFileName = databaseFileUri ?: newDefaultFileName
}
prefs?.edit()?.apply {
mSharedPreferences?.edit()?.apply {
newDefaultFileName?.let {
putString(KEY_DEFAULT_DATABASE_PATH, newDefaultFileName.toString())
} ?: kotlin.run {
@@ -353,7 +364,7 @@ class PasswordActivity : StylishActivity() {
confirmButtonView?.setOnClickListener { verifyCheckboxesAndLoadDatabase() }
// Retrieve settings for default database
val defaultFilename = prefs?.getString(KEY_DEFAULT_DATABASE_PATH, "")
val defaultFilename = mSharedPreferences?.getString(KEY_DEFAULT_DATABASE_PATH, "")
if (databaseFileUri != null
&& databaseFileUri.path != null && databaseFileUri.path!!.isNotEmpty()
&& databaseFileUri == UriUtil.parse(defaultFilename)) {
@@ -363,6 +374,8 @@ class PasswordActivity : StylishActivity() {
// If Activity is launch with a password and want to open directly
val intent = intent
val password = intent.getStringExtra(KEY_PASSWORD)
// Consume the intent extra password
intent.removeExtra(KEY_PASSWORD)
val launchImmediately = intent.getBooleanExtra(KEY_LAUNCH_IMMEDIATELY, false)
if (password != null) {
populatePasswordTextView(password)
@@ -545,14 +558,16 @@ class PasswordActivity : StylishActivity() {
}.show(supportFragmentManager, "duplicateUUIDDialog")
}
// To fix multiple view education
private var performedEductionInProgress = false
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
// Read menu
inflater.inflate(R.menu.open_file, menu)
if (mForceReadOnly) {
menu.removeItem(R.id.menu_open_file_read_mode_key)
} else {
changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key))
}
MenuUtil.defaultMenuInflater(inflater, menu)
@@ -563,51 +578,65 @@ class PasswordActivity : StylishActivity() {
super.onCreateOptionsMenu(menu)
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
}
launchEducation(menu)
return true
}
// To fix multiple view education
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: (()-> Unit)? = null) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) {
val educationContainerView = containerView
val unlockEducationPerformed = educationContainerView != null
menu: Menu,
onEducationFinished: (()-> Unit)? = null) {
val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation(
educationContainerView,
educationToolbar,
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!unlockEducationPerformed) {
val educationToolbar = toolbar
val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu)
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
// EducationPerformed
val biometricEducationPerformed =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!)
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!biometricEducationPerformed) {
onEducationFinished?.invoke()
}
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class ReadOnlyDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
var warning = getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
}
builder.setMessage(warning)
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
edit.putBoolean(getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.annotation.SuppressLint

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.app.assist.AssistStructure

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers
import android.content.Context

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX 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 KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.lock
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
/**
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
*/
abstract class LockingHideActivity : LockingActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
}

View File

@@ -61,6 +61,7 @@ object Stylish {
return when (themeString) {
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red

View File

@@ -19,20 +19,45 @@
*/
package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity
import android.util.Log
import android.view.WindowManager
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
* appearing in the recent app preview
*/
abstract class StylishActivity : AppCompatActivity() {
@StyleRes
private var themeId: Int = 0
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this)
setTheme(themeId)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
override fun onResume() {

View File

@@ -0,0 +1,100 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
private val mDatabase = Database.getInstance()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
}
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
val entryAttachment = entryAttachmentsList[position]
holder.binaryFileTitle.text = entryAttachment.name
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachment.binaryAttachment.length())
holder.binaryFileCompression.apply {
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
|| entryAttachment.binaryAttachment.isCompressed == true) {
text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE
} else {
text = ""
visibility = View.GONE
}
}
holder.binaryFileProgress.apply {
visibility = when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
}
}
override fun getItemCount(): Int {
return entryAttachmentsList.size
}
fun updateProgress(entryAttachment: EntryAttachment) {
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) {
entryAttachmentsList[indexEntryAttachment] = entryAttachment
notifyItemChanged(indexEntryAttachment)
}
}
fun clear() {
entryAttachmentsList.clear()
}
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
@@ -15,7 +34,7 @@ import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var fields: MutableList<Field> = ArrayList()
private var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
@@ -33,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
return fields.size
}
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() {
fields.clear()
}

View File

@@ -20,6 +20,8 @@
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.graphics.PorterDuff
import androidx.annotation.ColorInt
import androidx.recyclerview.widget.RecyclerView
import android.util.TypedValue
@@ -82,15 +84,26 @@ class FileDatabaseHistoryAdapter(private val context: Context)
// File path
holder.filePath.text = UriUtil.decode(fileDatabaseInfo.fileUri?.toString())
holder.filePreciseInfoContainer.visibility = if (fileDatabaseInfo.found()) {
// Modification
holder.fileModification.text = fileDatabaseInfo.getModificationString()
// Size
holder.fileSize.text = fileDatabaseInfo.getSizeString()
if (fileDatabaseInfo.dataAccessible()) {
holder.fileInformation.clearColorFilter()
} else {
holder.fileInformation.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY)
}
View.VISIBLE
} else
View.GONE
// Modification
if (fileDatabaseInfo.lastModificationAccessible()) {
holder.fileModification.text = fileDatabaseInfo.getModificationString()
holder.fileModification.visibility = View.VISIBLE
} else {
holder.fileModification.visibility = View.GONE
}
// Size
if (fileDatabaseInfo.sizeAccessible()) {
holder.fileSize.text = fileDatabaseInfo.getSizeString()
holder.fileSize.visibility = View.VISIBLE
} else {
holder.fileSize.visibility = View.GONE
}
// Click on information
val isExpanded = position == mExpandedPosition
@@ -142,6 +155,10 @@ class FileDatabaseHistoryAdapter(private val context: Context)
return listDatabaseFiles.size
}
fun clearDatabaseFileHistoryList() {
listDatabaseFiles.clear()
}
fun addDatabaseFileHistoryList(listFileDatabaseHistoryToAdd: List<FileDatabaseHistoryEntity>) {
listDatabaseFiles.clear()
listDatabaseFiles.addAll(listFileDatabaseHistoryToAdd)
@@ -178,7 +195,6 @@ class FileDatabaseHistoryAdapter(private val context: Context)
var fileModifyButton: ImageView = itemView.findViewById(R.id.file_modify_button)
var fileDeleteButton: ImageView = itemView.findViewById(R.id.file_delete_button)
var filePath: TextView = itemView.findViewById(R.id.file_path)
var filePreciseInfoContainer: ViewGroup = itemView.findViewById(R.id.file_precise_info_container)
var fileModification: TextView = itemView.findViewById(R.id.file_modification)
var fileSize: TextView = itemView.findViewById(R.id.file_size)
}

View File

@@ -21,51 +21,52 @@ package com.kunzisoft.keepass.adapters
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.util.Log
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.setTextSize
import com.kunzisoft.keepass.view.strikeOut
import java.util.*
class NodeAdapter
/**
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
(private val context: Context)
class NodeAdapter (private val context: Context)
: RecyclerView.Adapter<NodeAdapter.NodeViewHolder>() {
private var nodeComparator: Comparator<NodeVersionedInterface<Group>>? = null
private val nodeSortedListCallback: NodeSortedListCallback
private val nodeSortedList: SortedList<Node>
private val inflater: LayoutInflater = LayoutInflater.from(context)
private var calculateViewTypeTextSize = Array(2) { true} // number of view type
private var textSizeUnit: Int = TypedValue.COMPLEX_UNIT_PX
private var prefTextSize: Float = 0F
private var subtextSize: Float = 0F
private var infoTextSize: Float = 0F
private var numberChildrenTextSize: Float = 0F
private var iconSize: Float = 0F
private var listSort: SortNodeEnum = SortNodeEnum.DB
private var ascendingSort: Boolean = true
private var groupsBeforeSort: Boolean = true
private var recycleBinBottomSort: Boolean = true
private var prefSizeMultiplier: Float = 0F
private var subtextDefaultDimension: Float = 0F
private var infoTextDefaultDimension: Float = 0F
private var numberChildrenTextDefaultDimension: Float = 0F
private var iconDefaultDimension: Float = 0F
private var showUserNames: Boolean = true
private var showNumberEntries: Boolean = true
private var entryFilters = arrayOf<Group.ChildFilter>()
private var actionNodesList = LinkedList<Node>()
private var nodeClickCallback: NodeClickCallback? = null
@@ -83,23 +84,15 @@ class NodeAdapter
get() = nodeSortedList.size() <= 0
init {
this.infoTextDefaultDimension = context.resources.getDimension(R.dimen.list_medium_size_default)
this.subtextDefaultDimension = context.resources.getDimension(R.dimen.list_small_size_default)
this.numberChildrenTextDefaultDimension = context.resources.getDimension(R.dimen.list_tiny_size_default)
this.iconDefaultDimension = context.resources.getDimension(R.dimen.list_icon_size_default)
assignPreferences()
this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2)
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
return item1 == item2
}
})
this.nodeSortedListCallback = NodeSortedListCallback()
this.nodeSortedList = SortedList(Node::class.java, nodeSortedListCallback)
// Database
this.mDatabase = Database.getInstance()
@@ -114,20 +107,23 @@ class NodeAdapter
taTextColor.recycle()
}
private fun assignPreferences() {
this.prefTextSize = PreferencesUtil.getListTextSize(context)
this.infoTextSize = context.resources.getDimension(R.dimen.list_medium_size_default) * prefTextSize
this.subtextSize = context.resources.getDimension(R.dimen.list_small_size_default) * prefTextSize
this.numberChildrenTextSize = context.resources.getDimension(R.dimen.list_tiny_size_default) * prefTextSize
this.iconSize = context.resources.getDimension(R.dimen.list_icon_size_default) * prefTextSize
fun assignPreferences() {
this.prefSizeMultiplier = PreferencesUtil.getListTextSize(context)
notifyChangeSort(
PreferencesUtil.getListSort(context),
SortNodeEnum.SortNodeParameters(
PreferencesUtil.getAscendingSort(context),
PreferencesUtil.getGroupsBeforeSort(context),
PreferencesUtil.getRecycleBinBottomSort(context)
)
)
this.listSort = PreferencesUtil.getListSort(context)
this.ascendingSort = PreferencesUtil.getAscendingSort(context)
this.groupsBeforeSort = PreferencesUtil.getGroupsBeforeSort(context)
this.recycleBinBottomSort = PreferencesUtil.getRecycleBinBottomSort(context)
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
this.entryFilters = Group.ChildFilter.getDefaults(context)
// Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
}
@@ -136,15 +132,25 @@ class NodeAdapter
* Rebuild the list by clear and build children from the group
*/
fun rebuildList(group: Group) {
this.nodeSortedList.clear()
assignPreferences()
try {
this.nodeSortedList.addAll(group.getChildren())
} catch (e: Exception) {
Log.e(TAG, "Can't add node elements to the list", e)
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
nodeSortedList.replaceAll(group.getFilteredChildren(*entryFilters)
)
}
private inner class NodeSortedListCallback: SortedListAdapterCallback<Node>(this) {
override fun compare(item1: Node, item2: Node): Int {
return nodeComparator!!.compare(item1, item2)
}
override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean {
return oldItem.type == newItem.type
&& oldItem.title == newItem.title
&& oldItem.icon == newItem.icon
}
override fun areItemsTheSame(item1: Node, item2: Node): Boolean {
return item1 == item2
}
notifyDataSetChanged()
}
fun contains(node: Node): Boolean {
@@ -255,10 +261,9 @@ class NodeAdapter
/**
* Notify a change sort of the list
*/
fun notifyChangeSort(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean) {
this.listSort = sortNodeEnum
this.ascendingSort = ascending
this.groupsBeforeSort = groupsBefore
fun notifyChangeSort(sortNodeEnum: SortNodeEnum,
sortNodeParameters: SortNodeEnum.SortNodeParameters) {
this.nodeComparator = sortNodeEnum.getNodeComparator(sortNodeParameters)
}
override fun getItemViewType(position: Int): Int {
@@ -285,19 +290,57 @@ class NodeAdapter
assignDatabaseIcon(mDatabase.drawFactory, subNode.icon, iconColor)
// Relative size of the icon
layoutParams?.apply {
height = iconSize.toInt()
width = iconSize.toInt()
height = (iconDefaultDimension * prefSizeMultiplier).toInt()
width = (iconDefaultDimension * prefSizeMultiplier).toInt()
}
}
// Assign text
holder.text.apply {
text = subNode.title
setTextSize(textSizeUnit, infoTextSize)
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
setTextSize(textSizeUnit, infoTextDefaultDimension, prefSizeMultiplier)
strikeOut(subNode.isCurrentlyExpires)
}
// Add subText with username
holder.subText.apply {
text = ""
strikeOut(subNode.isCurrentlyExpires)
visibility = View.GONE
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
holder.subText.apply {
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextDefaultDimension, prefSizeMultiplier)
}
}
mDatabase.stopManageEntry(entry)
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getNumberOfChildEntries(*entryFilters)
.toString()
setTextSize(textSizeUnit, numberChildrenTextDefaultDimension, prefSizeMultiplier)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
// Assign click
holder.container.setOnClickListener {
nodeClickCallback?.onNodeClick(subNode)
@@ -307,45 +350,6 @@ class NodeAdapter
}
holder.container.isSelected = actionNodesList.contains(subNode)
// Add subText with username
holder.subText.apply {
text = ""
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextSize)
}
mDatabase.stopManageEntry(entry)
}
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group).getChildEntries(true).size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
}
override fun getItemCount(): Int {

View File

@@ -28,15 +28,14 @@ import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
import com.kunzisoft.keepass.view.strikeOut
class SearchEntryCursorAdapter(context: Context, private val database: Database)
class SearchEntryCursorAdapter(private val context: Context,
private val database: Database)
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater = context.getSystemService(
@@ -71,34 +70,31 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
override fun bindView(view: View, context: Context, cursor: Cursor) {
// Retrieve elements from cursor
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
val iconFactory = database.iconFactory
var icon: IconImage = iconFactory.getIcon(
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
if (icon.isUnknown) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)))
if (icon.isUnknown)
icon = iconFactory.keyIcon
}
val title = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE))
val username = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME))
val url = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL))
database.getEntryFrom(cursor)?.let { currentEntry ->
val viewHolder = view.tag as ViewHolder
// Assign image
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor)
viewHolder.imageViewIcon?.assignDatabaseIcon(
database.drawFactory,
currentEntry.icon,
iconColor)
// Assign title
val showTitle = Entry.getVisualTitle(false, title, username, url, uuid.toString())
viewHolder.textViewTitle?.text = showTitle
if (displayUsername && username.isNotEmpty()) {
viewHolder.textViewSubTitle?.text = String.format("(%s)", username)
viewHolder.textViewTitle?.apply {
text = currentEntry.getVisualTitle()
strikeOut(currentEntry.isCurrentlyExpires)
}
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (displayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else {
viewHolder.textViewSubTitle?.text = ""
""
}
strikeOut(currentEntry.isCurrentlyExpires)
}
}
}
@@ -109,7 +105,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
}
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(constraint.toString())
return database.searchEntries(context, constraint.toString())
}
fun getEntryFromPosition(position: Int): Entry? {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.os.AsyncTask

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.Database

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.content.Context

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.*

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric
import android.content.Intent

View File

@@ -19,15 +19,13 @@
*/
package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException
import java.security.DigestOutputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.Arrays
import java.util.*
import javax.crypto.Mac
import kotlin.math.min
@@ -60,7 +58,7 @@ object CryptoUtil {
throw RuntimeException(e)
}
val pbR = LEDataOutputStream.writeLongBuf(r)
val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size)

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
@@ -47,7 +47,22 @@ class AesEngine : CipherEngine() {
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte()))
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
}
}

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
@@ -50,7 +50,22 @@ class ChaCha20Engine : CipherEngine() {
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte()))
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
}
}

View File

@@ -21,13 +21,11 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException
import java.util.UUID
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
@@ -53,7 +51,22 @@ class TwofishEngine : CipherEngine() {
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte()))
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
}
}

View File

@@ -23,7 +23,7 @@ import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(),
0xD9.toByte(),
0xF3.toByte(),

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException
import java.security.SecureRandom
import java.util.*
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid(
val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xEF.toByte(),
0x63.toByte(),
0x6D.toByte(),

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,20 +19,20 @@
*/
package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.util.UUID
import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() {
setByteArray(PARAM_UUID, DatabaseInputOutputUtils.uuidToBytes(uuid))
setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
}
companion object {
@@ -42,11 +42,11 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data)
val lis = LEDataInputStream(bis)
val lis = LittleEndianDataInputStream(bis)
val d = deserialize(lis) ?: return null
val uuid = DatabaseInputOutputUtils.bytesToUuid(d.getByteArray(PARAM_UUID))
val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid)
kdfP.copyTo(d)
@@ -56,7 +56,7 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream()
val los = LEDataOutputStream(bos)
val los = LittleEndianDataOutputStream(bos)
serialize(kdf, los)

View File

@@ -24,6 +24,7 @@ import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.settings.PreferencesUtil
class CreateDatabaseRunnable(context: Context,
private val mDatabase: Database,
@@ -57,8 +58,11 @@ class CreateDatabaseRunnable(context: Context,
if (result.isSuccess) {
// Add database to recent files
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context.applicationContext)
.addOrUpdateDatabaseUri(mDatabaseUri, mKeyFile)
.addOrUpdateDatabaseUri(mDatabaseUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKeyFile else null)
}
} else {
Log.e("CreateDatabaseRunnable", "Unable to create the database")
}

View File

@@ -74,14 +74,10 @@ class LoadDatabaseRunnable(private val context: Context,
override fun onFinishRun() {
if (result.isSuccess) {
// Save keyFile in app database
val rememberKeyFile = PreferencesUtil.rememberKeyFiles(context)
if (rememberKeyFile) {
var keyUri = mKey
if (!rememberKeyFile) {
keyUri = null
}
if (PreferencesUtil.rememberDatabaseLocations(context)) {
FileDatabaseHistoryAction.getInstance(context)
.addOrUpdateDatabaseUri(mUri, keyUri)
.addOrUpdateDatabaseUri(mUri,
if (PreferencesUtil.rememberKeyFileLocations(context)) mKey else null)
}
// Register the biometric

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action
import android.content.*
@@ -22,9 +41,11 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
@@ -178,7 +199,11 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
unBindService()
try {
activity.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) {
// If receiver not register, do nothing
}
}
@Synchronized
@@ -348,6 +373,34 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
}
/*
-----------------
Entry History Settings
-----------------
*/
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
}
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_DELETE_ENTRY_HISTORY)
}
/*
-----------------
Main Settings

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
class DeleteEntryHistoryDatabaseRunnable (
context: Context,
database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
try {
mainEntry.removeEntryFromHistory(entryHistoryPosition)
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2020 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable (
private val context: Context,
private val database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
private val saveDatabase: Boolean)
: ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null
override fun onStartRun() {
try {
val historyToRestore = Entry(mainEntry.getHistory()[entryHistoryPosition])
// Copy history of main entry in the restore entry
mainEntry.getHistory().forEach {
historyToRestore.addEntryToHistory(it)
}
// Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null)
updateEntryRunnable?.onStartRun()
} catch (e: Exception) {
setError(e)
}
}
override fun onActionRun() {
updateEntryRunnable?.onActionRun()
}
override fun onFinishRun() {
updateEntryRunnable?.onFinishRun()
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node
import android.content.Context

View File

@@ -1,10 +1,31 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor
import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.*
abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf(
_ID,
@@ -17,7 +38,9 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES
COLUMN_INDEX_NOTES,
COLUMN_INDEX_EXPIRY_TIME,
COLUMN_INDEX_EXPIRES
)) {
protected var entryId: Long = 0
@@ -37,6 +60,9 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))
pwEntry.url = getString(getColumnIndex(COLUMN_INDEX_URL))
pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES))
pwEntry.expiryTime = DateInstant(getString(getColumnIndex(COLUMN_INDEX_EXPIRY_TIME)))
pwEntry.expires = getString(getColumnIndex(COLUMN_INDEX_EXPIRES))
.toLowerCase(Locale.ENGLISH) != "false"
}
companion object {
@@ -51,5 +77,7 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
const val COLUMN_INDEX_PASSWORD = "password"
const val COLUMN_INDEX_URL = "URL"
const val COLUMN_INDEX_NOTES = "notes"
const val COLUMN_INDEX_EXPIRY_TIME = "expiry_time"
const val COLUMN_INDEX_EXPIRES = "expires"
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
@@ -17,7 +36,9 @@ class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
entry.username,
entry.password,
entry.url,
entry.notes
entry.notes,
entry.expiryTime,
entry.expires
))
entryId++
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -21,7 +40,9 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entry.username,
entry.password,
entry.url,
entry.notes
entry.notes,
entry.expiryTime,
entry.expires
))
for (element in entry.customFields.entries) {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryVersioned

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element
import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources
import android.database.Cursor
import android.net.Uri
@@ -36,7 +37,10 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
@@ -45,7 +49,7 @@ import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.readBytes4ToInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil
@@ -324,47 +328,37 @@ class Database {
isReadOnly = !file.canWrite()
}
// Pass Uris as InputStreams
val inputStream: InputStream?
try {
inputStream = UriUtil.getUriInputStream(contentResolver, uri)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
}
// Pass KeyFile Uri as InputStreams
var databaseInputStream: InputStream? = null
var keyFileInputStream: InputStream? = null
keyfile?.let {
try {
// Get keyFile inputStream
keyfile?.let {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
}
}
// Load Data
val bufferedInputStream = BufferedInputStream(inputStream)
if (!bufferedInputStream.markSupported()) {
// Load Data, pass Uris as InputStreams
databaseInputStream = BufferedInputStream(UriUtil.getUriInputStream(contentResolver, uri))
if (!databaseInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
// We'll end up reading 8 bytes to identify the header. Might as well use two extra.
bufferedInputStream.mark(10)
databaseInputStream.mark(10)
// Get the file directory to save the attachments
val sig1 = LEDataInputStream.readInt(bufferedInputStream)
val sig2 = LEDataInputStream.readInt(bufferedInputStream)
val sig1 = databaseInputStream.readBytes4ToInt()
val sig2 = databaseInputStream.readBytes4ToInt()
// Return to the start
bufferedInputStream.reset()
databaseInputStream.reset()
when {
// Header of database KDB
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB()
.openDatabase(bufferedInputStream,
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
cacheDirectory,
fixDuplicateUUID)
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
@@ -373,7 +367,7 @@ class Database {
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
cacheDirectory,
fixDuplicateUUID)
.openDatabase(bufferedInputStream,
.openDatabase(databaseInputStream,
password,
keyFileInputStream,
progressTaskUpdater))
@@ -384,6 +378,17 @@ class Database {
this.mSearchHelper = SearchHelper(omitBackup)
loaded = true
} catch (e: LoadDatabaseException) {
Log.e("KPD", "Database::loadData", e)
throw e
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
} finally {
keyFileInputStream?.close()
databaseInputStream?.close()
}
}
fun isGroupSearchable(group: Group, isOmitBackup: Boolean): Boolean {
@@ -397,7 +402,7 @@ class Database {
return mSearchHelper?.search(this, str, max)
}
fun searchEntries(query: String): Cursor? {
fun searchEntries(context: Context, query: String): Cursor? {
var cursorKDB: EntryCursorKDB? = null
var cursorKDBX: EntryCursorKDBX? = null
@@ -409,7 +414,8 @@ class Database {
val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY)
if (searchResult != null) {
for (entry in searchResult.getChildEntries(true)) {
// Search in hide entries but not meta-stream
for (entry in searchResult.getFilteredChildEntries(*Group.ChildFilter.getDefaults(context))) {
entry.entryKDB?.let {
cursorKDB?.addEntry(it)
}
@@ -424,25 +430,21 @@ class Database {
fun getEntryFrom(cursor: Cursor): Entry? {
val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory()
val entry = createEntry()
// TODO invert field reference manager
entry?.let { entryVersioned ->
startManageEntry(entryVersioned)
return createEntry()?.apply {
startManageEntry(this)
mDatabaseKDB?.let {
entryVersioned.entryKDB?.let { entryKDB ->
entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory)
}
}
mDatabaseKDBX?.let {
entryVersioned.entryKDBX?.let { entryKDBX ->
entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory)
}
}
stopManageEntry(entryVersioned)
stopManageEntry(this)
}
return entry
}
@Throws(DatabaseOutputException::class)

View File

@@ -23,6 +23,7 @@ import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import androidx.core.os.ConfigurationCompat
import java.text.SimpleDateFormat
import java.util.*
class DateInstant : Parcelable {
@@ -44,6 +45,10 @@ class DateInstant : Parcelable {
jDate = Date(millis)
}
constructor(string: String) {
jDate = dateFormat.parse(string)
}
constructor() {
jDate = Date()
}
@@ -84,12 +89,13 @@ class DateInstant : Parcelable {
}
override fun toString(): String {
return jDate.toString()
return dateFormat.format(jDate)
}
companion object {
val NEVER_EXPIRE = neverExpire
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
private val neverExpire: DateInstant
get() {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
@@ -13,7 +32,9 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement
@@ -139,6 +160,12 @@ class Entry : Node, EntryVersionedInterface<Group> {
return contained ?: false
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return entryKDB?.nodeIndexInParentForNaturalOrder()
?: entryKDBX?.nodeIndexInParentForNaturalOrder()
?: -1
}
override var creationTime: DateInstant
get() = entryKDB?.creationTime ?: entryKDBX?.creationTime ?: DateInstant()
set(value) {
@@ -209,12 +236,27 @@ class Entry : Node, EntryVersionedInterface<Group> {
return title == PMS_TAN_ENTRY && username.isNotEmpty()
}
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(): String {
return getVisualTitle(isTan(),
title,
username,
url,
nodeId.toString())
return if (isTan()) {
"$PMS_TAN_ENTRY $username"
} else {
if (title.isEmpty())
if (url.isEmpty())
if (username.isEmpty())
nodeId.toString()
else
username
else
url
else
title
}
}
/*
@@ -284,10 +326,29 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.stopToManageFieldReferences()
}
fun getAttachments(): ArrayList<EntryAttachment> {
val attachments = ArrayList<EntryAttachment>()
val binaryDescriptionKDB = entryKDB?.binaryDescription ?: ""
val binaryKDB = entryKDB?.binaryData
if (binaryKDB != null) {
attachments.add(EntryAttachment(binaryDescriptionKDB, binaryKDB))
}
val actionEach = object : (Map.Entry<String, BinaryAttachment>)->Unit {
override fun invoke(mapEntry: Map.Entry<String, BinaryAttachment>) {
attachments.add(EntryAttachment(mapEntry.key, mapEntry.value))
}
}
entryKDBX?.binaries?.forEach(actionEach)
return attachments
}
fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>()
val entryV4History = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryV4History) {
val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryKDBXHistory) {
history.add(Entry(entryHistory))
}
return history
@@ -299,6 +360,10 @@ class Entry : Node, EntryVersionedInterface<Group> {
}
}
fun removeEntryFromHistory(position: Int) {
entryKDBX?.removeEntryFromHistory(position)
}
fun removeAllHistory() {
entryKDBX?.removeAllHistory()
}
@@ -379,28 +444,5 @@ class Entry : Node, EntryVersionedInterface<Group> {
}
const val PMS_TAN_ENTRY = "<TAN>"
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(isTan: Boolean, title: String, userName: String, url: String, id: String): String {
return if (isTan) {
"$PMS_TAN_ENTRY $userName"
} else {
if (title.isEmpty())
if (userName.isEmpty())
if (url.isEmpty())
id
else
url
else
userName
else
title
}
}
}
}

View File

@@ -1,5 +1,25 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB
@@ -8,6 +28,7 @@ import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
import kotlin.collections.ArrayList
@@ -57,6 +78,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader)
}
enum class ChildFilter {
META_STREAM, EXPIRED;
companion object {
fun getDefaults(context: Context): Array<ChildFilter> {
return if (PreferencesUtil.showExpiredEntries(context)) {
arrayOf(META_STREAM)
} else {
arrayOf(META_STREAM, EXPIRED)
}
}
}
}
companion object CREATOR : Parcelable.Creator<Group> {
override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel)
@@ -152,6 +187,12 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
return contained ?: false
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return groupKDB?.nodeIndexInParentForNaturalOrder()
?: groupKDBX?.nodeIndexInParentForNaturalOrder()
?: -1
}
override var creationTime: DateInstant
get() = groupKDB?.creationTime ?: groupKDBX?.creationTime ?: DateInstant()
set(value) {
@@ -190,55 +231,58 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
override val isCurrentlyExpires: Boolean
get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false
override fun getChildGroups(): MutableList<Group> {
val children = ArrayList<Group>()
groupKDB?.getChildGroups()?.forEach {
children.add(Group(it))
}
groupKDBX?.getChildGroups()?.forEach {
children.add(Group(it))
override fun getChildGroups(): List<Group> {
return groupKDB?.getChildGroups()?.map {
Group(it)
} ?:
groupKDBX?.getChildGroups()?.map {
Group(it)
} ?:
ArrayList()
}
return children
override fun getChildEntries(): List<Entry> {
return groupKDB?.getChildEntries()?.map {
Entry(it)
} ?:
groupKDBX?.getChildEntries()?.map {
Entry(it)
} ?:
ArrayList()
}
override fun getChildEntries(): MutableList<Entry> {
return getChildEntries(false)
fun getFilteredChildEntries(vararg filter: ChildFilter): List<Entry> {
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
return groupKDB?.getChildEntries()?.filter {
(!withoutMetaStream || (withoutMetaStream && !it.isMetaStream))
&& (!it.isCurrentlyExpires or showExpiredEntries)
}?.map {
Entry(it)
} ?:
groupKDBX?.getChildEntries()?.filter {
!it.isCurrentlyExpires or showExpiredEntries
}?.map {
Entry(it)
} ?:
ArrayList()
}
fun getChildEntries(withoutMetaStream: Boolean): MutableList<Entry> {
val children = ArrayList<Entry>()
groupKDB?.getChildEntries()?.forEach {
val entryToAddAsChild = Entry(it)
if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
children.add(entryToAddAsChild)
}
groupKDBX?.getChildEntries()?.forEach {
children.add(Entry(it))
}
return children
fun getNumberOfChildEntries(vararg filter: ChildFilter): Int {
return getFilteredChildEntries(*filter).size
}
/**
* Filter MetaStream entries and return children
* Filter entries and return children
* @return List of direct children (one level below) as NodeVersioned
*/
fun getChildren(withoutMetaStream: Boolean = true): List<Node> {
val children = ArrayList<Node>()
children.addAll(getChildGroups())
groupKDB?.let {
children.addAll(getChildEntries(withoutMetaStream))
}
groupKDBX?.let {
// No MetasStream in V4
children.addAll(getChildEntries(withoutMetaStream))
fun getChildren(): List<Node> {
return getChildGroups() + getChildEntries()
}
return children
fun getFilteredChildren(vararg filter: ChildFilter): List<Node> {
return getChildGroups() + getFilteredChildEntries(*filter)
}
override fun addChildGroup(group: Group) {

View File

@@ -20,149 +20,198 @@
package com.kunzisoft.keepass.database.element
import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
import com.kunzisoft.keepass.database.element.node.Type
import java.util.*
enum class SortNodeEnum {
DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME;
fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator<Node> {
fun <G: GroupVersionedInterface<G, *>> getNodeComparator(sortNodeParameters: SortNodeParameters)
: Comparator<NodeVersionedInterface<G>> {
return when (this) {
DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom)
USERNAME -> NodeUsernameComparator(ascending, groupsBefore, recycleBinBottom)
CREATION_TIME -> NodeCreationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_MODIFY_TIME -> NodeLastModificationComparator(ascending, groupsBefore, recycleBinBottom)
LAST_ACCESS_TIME -> NodeLastAccessComparator(ascending, groupsBefore, recycleBinBottom)
DB -> NodeNaturalComparator(sortNodeParameters) // Force false because natural order contains recycle bin
TITLE -> NodeTitleComparator(sortNodeParameters)
USERNAME -> NodeUsernameComparator(sortNodeParameters)
CREATION_TIME -> NodeCreationComparator(sortNodeParameters)
LAST_MODIFY_TIME -> NodeLastModificationComparator(sortNodeParameters)
LAST_ACCESS_TIME -> NodeLastAccessComparator(sortNodeParameters)
}
}
abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator<Node> {
data class SortNodeParameters(var ascending: Boolean = true,
var groupsBefore: Boolean = true,
var recycleBinBottom: Boolean = true)
abstract fun compareBySpecificOrder(object1: Node, object2: Node): Int
abstract class NodeComparator
<
G: GroupVersionedInterface<*, *>,
T: NodeVersionedInterface<G>
>(var sortNodeParameters: SortNodeParameters)
: Comparator<T> {
private fun specificOrderOrHashIfEquals(object1: Node, object2: Node): Int {
val database = Database.getInstance()
abstract fun compareBySpecificOrder(object1: T, object2: T): Int
private fun specificOrderOrHashIfEquals(object1: T, object2: T): Int {
val specificOrderComp = compareBySpecificOrder(object1, object2)
return if (specificOrderComp == 0) {
object1.hashCode() - object2.hashCode()
} else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert
return when {
specificOrderComp == 0 -> object1.hashCode() - object2.hashCode()
sortNodeParameters.ascending -> specificOrderComp
else -> -specificOrderComp
}
}
override fun compare(object1: Node, object2: Node): Int {
override fun compare(object1: T, object2: T): Int {
if (object1 == object2)
return 0
if (object1.type == Type.GROUP) {
return if (object2.type == Type.GROUP) {
when (object1.type) {
Type.GROUP -> {
when (object2.type) {
Type.GROUP -> {
// RecycleBin at end of groups
val database = Database.getInstance()
if (database.isRecycleBinEnabled && recycleBinBottom) {
if (database.isRecycleBinEnabled && sortNodeParameters.recycleBinBottom) {
if (database.recycleBin == object1)
return 1
if (database.recycleBin == object2)
return -1
}
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.ENTRY) {
if (groupsBefore)
return specificOrderOrHashIfEquals(object1, object2)
}
Type.ENTRY -> {
return if (sortNodeParameters.groupsBefore)
-1
else
1
} else {
-1
}
} else if (object1.type == Type.ENTRY) {
return if (object2.type == Type.ENTRY) {
specificOrderOrHashIfEquals(object1, object2)
} else if (object2.type == Type.GROUP) {
if (groupsBefore)
}
}
Type.ENTRY -> {
return when (object2.type) {
Type.GROUP -> {
if (sortNodeParameters.groupsBefore)
1
else
-1
} else {
-1
}
Type.ENTRY -> {
specificOrderOrHashIfEquals(object1, object2)
}
}
}
}
// Type not known
return -1
}
}
/**
* Comparator of node by natural database placement
*/
class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeNaturalComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.nodePositionInParent.compareTo(object2.nodePositionInParent)
override fun compareBySpecificOrder(object1: T, object2: T): Int {
return object1.nodeIndexInParentForNaturalOrder()
.compareTo(object2.nodeIndexInParentForNaturalOrder())
}
}
/**
* Comparator of Node by Title
*/
class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeTitleComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.title.compareTo(object2.title, ignoreCase = true)
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val titleCompare = object1.title.compareTo(object2.title, ignoreCase = true)
return if (titleCompare == 0)
NodeNaturalComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
else
titleCompare
}
}
/**
* Comparator of Node by Username, Groups by title
*/
class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeUsernameComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
override fun compareBySpecificOrder(object1: T, object2: T): Int {
return if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) {
// To get username if it's a ref
return (object1 as Entry).getEntryInfo(Database.getInstance()).username
.compareTo((object2 as Entry).getEntryInfo(Database.getInstance()).username,
val usernameCompare = (object1 as Entry).getEntryInfo(database).username
.compareTo((object2 as Entry).getEntryInfo(database).username,
ignoreCase = true)
if (usernameCompare == 0)
NodeTitleComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
else
usernameCompare
} else {
NodeTitleComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
}
return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2)
}
}
/**
* Comparator of node by creation
*/
class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeCreationComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.creationTime.date
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val creationCompare = object1.creationTime.date
.compareTo(object2.creationTime.date)
return if (creationCompare == 0)
NodeNaturalComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
else
creationCompare
}
}
/**
* Comparator of node by last modification
*/
class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeLastModificationComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.lastModificationTime.date
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val lastModificationCompare = object1.lastModificationTime.date
.compareTo(object2.lastModificationTime.date)
return if (lastModificationCompare == 0)
NodeNaturalComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
else
lastModificationCompare
}
}
/**
* Comparator of node by last access
*/
class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean)
: NodeComparator(ascending, groupsBefore, recycleBinBottom) {
class NodeLastAccessComparator<G: GroupVersionedInterface<*, *>, T: NodeVersionedInterface<G>>(
sortNodeParameters: SortNodeParameters)
: NodeComparator<G, T>(sortNodeParameters) {
override fun compareBySpecificOrder(object1: Node, object2: Node): Int {
return object1.lastAccessTime.date
override fun compareBySpecificOrder(object1: T, object2: T): Int {
val lastAccessCompare = object1.lastAccessTime.date
.compareTo(object2.lastAccessTime.date)
return if (lastAccessCompare == 0)
NodeNaturalComparator<G, T>(sortNodeParameters)
.compare(object1, object2)
else
lastAccessCompare
}
}
}

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException
import java.io.InputStream
@@ -267,6 +267,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private const val DEFAULT_ENCRYPTION_ROUNDS = 300
const val BUFFER_SIZE_BYTES = 3 * 128
/**
* Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException

View File

@@ -181,7 +181,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// To compress, create a new binary with file
binary.compress()
binary.compress(BUFFER_SIZE_BYTES)
}
}
}
@@ -189,7 +189,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
when (newCompression) {
CompressionAlgorithm.None -> {
// To decompress, create a new binary with file
binary.decompress()
binary.decompress(BUFFER_SIZE_BYTES)
}
CompressionAlgorithm.GZip -> {
}
@@ -562,7 +562,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private const val KeyElementName = "Key"
private const val KeyDataElementName = "Data"
const val BASE_64_FLAG = Base64.DEFAULT
const val BASE_64_FLAG = Base64.NO_WRAP
const val BUFFER_SIZE_BYTES = 3 * 128
}

View File

@@ -26,6 +26,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.util.*
/**
@@ -51,19 +52,15 @@ import java.util.*
*/
class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
/** A string describing what is in pBinaryData */
var binaryDesc = ""
/**
* @return the actual binaryData byte array.
*/
var binaryData: ByteArray = ByteArray(0)
/** A string describing what is in binaryData */
var binaryDescription = ""
var binaryData: BinaryAttachment? = null
// Determine if this is a MetaStream entry
val isMetaStream: Boolean
get() {
if (binaryData.contentEquals(ByteArray(0))) return false
if (notes.isEmpty()) return false
if (binaryDesc != PMS_ID_BINDESC) return false
if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false
@@ -88,9 +85,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
password = parcel.readString() ?: password
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
binaryDesc = parcel.readString() ?: binaryDesc
binaryData = ByteArray(parcel.readInt())
parcel.readByteArray(binaryData)
binaryDescription = parcel.readString() ?: binaryDescription
binaryData = parcel.readParcelable(BinaryAttachment::class.java.classLoader)
}
override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -108,9 +104,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(password)
dest.writeString(url)
dest.writeString(notes)
dest.writeString(binaryDesc)
dest.writeInt(binaryData.size)
dest.writeByteArray(binaryData)
dest.writeString(binaryDescription)
dest.writeParcelable(binaryData, flags)
}
fun updateWith(source: EntryKDB) {
@@ -120,11 +115,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
password = source.password
url = source.url
notes = source.notes
binaryDesc = source.binaryDesc
val descLen = source.binaryData.size
binaryData = ByteArray(descLen)
System.arraycopy(source.binaryData, 0, binaryData, 0, descLen)
binaryDescription = source.binaryDescription
binaryData = source.binaryData
}
override var username = ""

View File

@@ -304,6 +304,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
history.add(entry)
}
fun removeEntryFromHistory(position: Int) {
history.removeAt(position)
}
fun removeAllHistory() {
history.clear()
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
@@ -17,4 +36,14 @@ abstract class EntryVersioned
constructor(parcel: Parcel) : super(parcel)
override fun nodeIndexInParentForNaturalOrder(): Int {
if (nodeIndexInParentForNaturalOrder == -1) {
val numberOfGroups = parent?.getChildGroups()?.size
val indexInEntries = parent?.getChildEntries()?.indexOf(this)
if (numberOfGroups != null && indexInEntries != null)
return numberOfGroups + indexInEntries
}
return nodeIndexInParentForNaturalOrder
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface

View File

@@ -1,8 +1,28 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import android.os.Parcel
import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.node.NodeVersioned
import java.util.*
abstract class GroupVersioned
<
@@ -15,9 +35,10 @@ abstract class GroupVersioned
private var titleGroup = ""
@Transient
private val childGroups = ArrayList<Group>()
private val childGroups = LinkedList<Group>()
@Transient
private val childEntries = ArrayList<Entry>()
private val childEntries = LinkedList<Entry>()
private var positionIndexChildren = 0
constructor() : super()
@@ -33,9 +54,8 @@ abstract class GroupVersioned
protected fun updateWith(source: GroupVersioned<GroupId, EntryId, Group, Entry>) {
super.updateWith(source)
titleGroup = source.titleGroup
childGroups.clear()
removeChildren()
childGroups.addAll(source.childGroups)
childEntries.clear()
childEntries.addAll(source.childEntries)
}
@@ -43,23 +63,27 @@ abstract class GroupVersioned
get() = titleGroup
set(value) { titleGroup = value }
override fun getChildGroups(): MutableList<Group> {
override fun getChildGroups(): List<Group> {
return childGroups
}
override fun getChildEntries(): MutableList<Entry> {
override fun getChildEntries(): List<Entry> {
return childEntries
}
override fun addChildGroup(group: Group) {
if (childGroups.contains(group))
removeChildGroup(group)
positionIndexChildren++
group.nodeIndexInParentForNaturalOrder = positionIndexChildren
this.childGroups.add(group)
}
override fun addChildEntry(entry: Entry) {
if (childEntries.contains(entry))
removeChildEntry(entry)
positionIndexChildren++
entry.nodeIndexInParentForNaturalOrder = positionIndexChildren
this.childEntries.add(entry)
}
@@ -76,6 +100,13 @@ abstract class GroupVersioned
this.childEntries.clear()
}
override fun nodeIndexInParentForNaturalOrder(): Int {
return if (nodeIndexInParentForNaturalOrder == -1)
childGroups.indexOf(this)
else
nodeIndexInParentForNaturalOrder
}
override fun toString(): String {
return titleGroup
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group
import com.kunzisoft.keepass.database.action.node.NodeHandler
@@ -5,9 +24,9 @@ import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface
interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>, Entry> : NodeVersionedInterface<Group> {
fun getChildGroups(): MutableList<Group>
fun getChildGroups(): List<Group>
fun getChildEntries(): MutableList<Entry>
fun getChildEntries(): List<Entry>
fun addChildGroup(group: Group)

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.Group
@@ -6,17 +25,6 @@ interface Node: NodeVersionedInterface<Group> {
val nodeId: NodeId<*>?
val nodePositionInParent: Int
get() {
parent?.getChildren(true)?.let { children ->
children.forEachIndexed { index, nodeVersioned ->
if (nodeVersioned.nodeId == this.nodeId)
return index
}
}
return -1
}
fun addParentFrom(node: Node) {
parent = node.parent
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant

View File

@@ -40,6 +40,8 @@ abstract class NodeVersioned<IdType, Parent : GroupVersionedInterface<Parent, En
val id: IdType
get() = nodeId.id
var nodeIndexInParentForNaturalOrder = -1
protected constructor()
protected constructor(parcel: Parcel) {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node
import android.os.Parcelable
@@ -28,5 +47,10 @@ interface NodeVersionedInterface<ParentGroup> : NodeTimeInterface, Parcelable {
fun isContainedIn(container: ParentGroup): Boolean
/**
* Groups are always before in natural order (DB order)
*/
fun nodeIndexInParentForNaturalOrder(): Int
fun touch(modified: Boolean, touchParents: Boolean)
}

View File

@@ -19,11 +19,11 @@
*/
package com.kunzisoft.keepass.database.element.security
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.readFromStream
import com.kunzisoft.keepass.stream.readBytes
import java.io.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
@@ -73,19 +73,22 @@ class BinaryAttachment : Parcelable {
}
@Throws(IOException::class)
fun compress() {
fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
if (dataFile != null) {
// To compress, create a new binary with file
if (isCompressed != true) {
val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
readFromStream(getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
var outputStream: GZIPOutputStream? = null
var inputStream: InputStream? = null
try {
outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
inputStream = getInputDataStream()
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
}
})
outputStream.close()
} finally {
inputStream?.close()
outputStream?.close()
// Remove unGzip file
if (dataFile!!.delete()) {
@@ -97,20 +100,24 @@ class BinaryAttachment : Parcelable {
}
}
}
}
@Throws(IOException::class)
fun decompress() {
fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
if (dataFile != null) {
if (isCompressed != false) {
val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = FileOutputStream(fileBinaryDecompress)
readFromStream(GZIPInputStream(getInputDataStream()), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
var outputStream: FileOutputStream? = null
var inputStream: GZIPInputStream? = null
try {
outputStream = FileOutputStream(fileBinaryDecompress)
inputStream = GZIPInputStream(getInputDataStream())
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer)
}
})
outputStream.close()
} finally {
inputStream?.close()
outputStream?.close()
// Remove gzip file
if (dataFile!!.delete()) {
@@ -122,6 +129,33 @@ class BinaryAttachment : Parcelable {
}
}
}
}
fun download(createdFileUri: Uri,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataDownloaded = 0
contentResolver.openOutputStream(createdFileUri).use { outputStream ->
outputStream?.let { fileOutputStream ->
if (isCompressed == true) {
GZIPInputStream(getInputDataStream())
} else {
getInputDataStream()
}.use { inputStream ->
inputStream.readBytes(bufferSize) { buffer ->
fileOutputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / length()).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {}
}
}
}
}
}
@Throws(IOException::class)
fun clear() {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
import android.content.res.Resources

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception
import java.io.IOException

View File

@@ -21,9 +21,11 @@
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.bytes4ToInt
import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToInt
import java.io.IOException
import java.io.InputStream
class DatabaseHeaderKDB : DatabaseHeader() {
@@ -47,30 +49,25 @@ class DatabaseHeaderKDB : DatabaseHeader() {
*/
var contentsHash = ByteArray(32)
// As UInt
var numKeyEncRounds: Int = 0
/**
* Parse given buf, as read from file.
* @param buf
* @throws IOException
*/
@Throws(IOException::class)
fun loadFromFile(buf: ByteArray, offset: Int) {
signature1 = LEDataInputStream.readInt(buf, offset)
signature2 = LEDataInputStream.readInt(buf, offset + 4)
flags = LEDataInputStream.readInt(buf, offset + 8)
version = LEDataInputStream.readInt(buf, offset + 12)
System.arraycopy(buf, offset + 16, masterSeed, 0, 16)
System.arraycopy(buf, offset + 32, encryptionIV, 0, 16)
numGroups = LEDataInputStream.readInt(buf, offset + 48)
numEntries = LEDataInputStream.readInt(buf, offset + 52)
System.arraycopy(buf, offset + 56, contentsHash, 0, 32)
System.arraycopy(buf, offset + 88, transformSeed, 0, 32)
numKeyEncRounds = LEDataInputStream.readInt(buf, offset + 120)
fun loadFromFile(inputStream: InputStream) {
signature1 = inputStream.readBytes4ToInt() // 4 bytes
signature2 = inputStream.readBytes4ToInt() // 4 bytes
flags = inputStream.readBytes4ToInt() // 4 bytes
version = inputStream.readBytes4ToInt() // 4 bytes
masterSeed = inputStream.readBytesLength(16) // 16 bytes
encryptionIV = inputStream.readBytesLength(16) // 16 bytes
numGroups = inputStream.readBytes4ToInt() // 4 bytes
numEntries = inputStream.readBytes4ToInt() // 4 bytes
contentsHash = inputStream.readBytesLength(32) // 32 bytes
transformSeed = inputStream.readBytesLength(32) // 32 bytes
numKeyEncRounds = inputStream.readBytes4ToInt()
if (numKeyEncRounds < 0) {
// TODO: Really treat this like an unsigned integer
throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.")

View File

@@ -30,10 +30,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.CopyInputStream
import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.*
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
@@ -148,7 +145,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val headerBOS = ByteArrayOutputStream()
val copyInputStream = CopyInputStream(inputStream, headerBOS)
val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LEDataInputStream(digestInputStream)
val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readInt()
val sig2 = littleEndianDataInputStream.readInt()
@@ -172,7 +169,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
}
@Throws(IOException::class)
private fun readHeaderField(dis: LEDataInputStream): Boolean {
private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
val fieldID = dis.read().toByte()
val fieldSize: Int = if (version < FILE_VERSION_32_4) {
@@ -243,12 +240,12 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid cipher ID.")
}
databaseV4.dataCipher = DatabaseInputOutputUtils.bytesToUuid(pbId)
databaseV4.dataCipher = bytes16ToUuid(pbId)
}
private fun setTransformRound(roundsByte: ByteArray?) {
private fun setTransformRound(roundsByte: ByteArray) {
assignAesKdfEngineIfNotExists()
val rounds = LEDataInputStream.readLong(roundsByte!!, 0)
val rounds = bytes64ToLong(roundsByte)
databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds)
databaseV4.numberKeyEncryptionRounds = rounds
}
@@ -259,7 +256,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid compression flags.")
}
val flag = LEDataInputStream.readInt(pbFlags, 0)
val flag = bytes4ToInt(pbFlags)
if (flag < 0 || flag >= CompressionAlgorithm.values().size) {
throw IOException("Unrecognized compression flag.")
}
@@ -275,7 +272,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid stream id.")
}
val id = LEDataInputStream.readInt(streamID, 0)
val id = bytes4ToInt(streamID)
if (id < 0 || id >= CrsAlgorithm.values().size) {
throw IOException("Invalid stream id.")
}
@@ -295,6 +292,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
}
companion object {
var ULONG_MAX_VALUE: Long = -1
const val DBSIG_PRE2 = -0x4ab4049a
const val DBSIG_2 = -0x4ab40499
@@ -323,7 +323,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
@Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.GetHmacKey64(key, DatabaseInputOutputUtils.ULONG_MAX_VALUE)
val blockKey = HmacBlockStream.getHmacKey64(key, ULONG_MAX_VALUE)
val hmac: Mac
try {

View File

@@ -22,9 +22,11 @@ package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.File
import java.io.InputStream
abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>> {
abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
(protected val cacheDirectory: File) {
/**
* Load a versioned database file, return contents in a new DatabaseVersioned.

View File

@@ -16,36 +16,10 @@
* You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*
Derived from
KeePass for J2ME
Copyright 2007 Naomaru Itoi <nao@phoneid.org>
This file was derived from
Java clone of KeePass - A KeePass file viewer for Java
Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
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; version 2
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
*/
package com.kunzisoft.keepass.database.file.input
import android.util.Log
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
@@ -53,28 +27,29 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import javax.crypto.*
import org.joda.time.Instant
import java.io.*
import java.security.*
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.security.*
import java.util.Arrays
/**
* Load a KDB database file.
*/
class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
class DatabaseInputKDB(cacheDirectory: File,
private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDB>(cacheDirectory) {
private lateinit var mDatabaseToOpen: DatabaseKDB
@@ -87,52 +62,63 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
try {
// Load entire file, most of it's encrypted.
val fileSize = databaseInputStream.available()
val filebuf = ByteArray(fileSize + 16) // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer
databaseInputStream.read(filebuf, 0, fileSize) // TODO remove
databaseInputStream.close()
// Parse header (unencrypted)
if (fileSize < DatabaseHeaderKDB.BUF_SIZE)
throw IOException("File too short for header")
val hdr = DatabaseHeaderKDB()
hdr.loadFromFile(filebuf, 0)
val header = DatabaseHeaderKDB()
header.loadFromFile(databaseInputStream)
if (hdr.signature1 != DatabaseHeader.PWM_DBSIG_1 || hdr.signature2 != DatabaseHeaderKDB.DBSIG_2) {
val contentSize = databaseInputStream.available()
if (fileSize != (contentSize + DatabaseHeaderKDB.BUF_SIZE))
throw IOException("Header corrupted")
if (header.signature1 != DatabaseHeader.PWM_DBSIG_1
|| header.signature2 != DatabaseHeaderKDB.DBSIG_2) {
throw SignatureDatabaseException()
}
if (!hdr.matchesVersion()) {
if (!header.matchesVersion()) {
throw VersionDatabaseException()
}
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabaseToOpen = DatabaseKDB()
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
// Select algorithm
when {
hdr.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
hdr.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
header.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
}
header.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
}
else -> throw InvalidAlgorithmDatabaseException()
}
mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong()
mDatabaseToOpen.numberKeyEncryptionRounds = header.numKeyEncRounds.toLong()
// Generate transformedMasterKey from masterKey
mDatabaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, mDatabaseToOpen.numberKeyEncryptionRounds)
mDatabaseToOpen.makeFinalKey(
header.masterSeed,
header.transformSeed,
mDatabaseToOpen.numberKeyEncryptionRounds)
progressTaskUpdater?.updateMessage(R.string.decrypting_db)
// Initialize Rijndael algorithm
val cipher: Cipher
try {
if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael) {
cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding")
} else if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.Twofish) {
cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
} else {
throw IOException("Encryption algorithm is not supported")
val cipher: Cipher = try {
when {
mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
CipherFactory.getInstance("AES/CBC/PKCS5Padding")
}
mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
}
else -> throw IOException("Encryption algorithm is not supported")
}
} catch (e1: NoSuchAlgorithmException) {
throw IOException("No such algorithm")
} catch (e1: NoSuchPaddingException) {
@@ -140,98 +126,231 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
}
try {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(mDatabaseToOpen.finalKey, "AES"), IvParameterSpec(hdr.encryptionIV))
cipher.init(Cipher.DECRYPT_MODE,
SecretKeySpec(mDatabaseToOpen.finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
} catch (e1: InvalidKeyException) {
throw IOException("Invalid key")
} catch (e1: InvalidAlgorithmParameterException) {
throw IOException("Invalid algorithm parameter.")
}
// Decrypt! The first bytes aren't encrypted (that's the header)
val encryptedPartSize: Int
val messageDigest: MessageDigest
try {
encryptedPartSize = cipher.doFinal(filebuf, DatabaseHeaderKDB.BUF_SIZE, fileSize - DatabaseHeaderKDB.BUF_SIZE, filebuf, DatabaseHeaderKDB.BUF_SIZE)
} catch (e1: ShortBufferException) {
throw IOException("Buffer too short")
} catch (e1: IllegalBlockSizeException) {
throw IOException("Invalid block size")
} catch (e1: BadPaddingException) {
throw InvalidCredentialsDatabaseException()
}
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA-256")
messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 algorithm")
}
val nos = NullOutputStream()
val dos = DigestOutputStream(nos, md)
dos.write(filebuf, DatabaseHeaderKDB.BUF_SIZE, encryptedPartSize)
dos.close()
val hash = md.digest()
// Decrypt content
val cipherInputStream = BufferedInputStream(
DigestInputStream(
BetterCipherInputStream(databaseInputStream, cipher),
messageDigest
)
)
if (!Arrays.equals(hash, hdr.contentsHash)) {
/* TODO checksum
// Add a mark to the content start
if (!cipherInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
cipherInputStream.mark(cipherInputStream.available() +1)
// Consume all data to get the digest
var numberRead = 0
while (numberRead > -1) {
numberRead = cipherInputStream.read(ByteArray(1024))
}
Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)")
// Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException()
}
// Back to the content start
cipherInputStream.reset()
*/
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabaseToOpen.createGroup()
newRoot.level = -1
mDatabaseToOpen.rootGroup = newRoot
// Import all groups
var pos = DatabaseHeaderKDB.BUF_SIZE
var newGrp = mDatabaseToOpen.createGroup()
run {
var i = 0
while (i < hdr.numGroups) {
val fieldType = LEDataInputStream.readUShort(filebuf, pos)
pos += 2
val fieldSize = LEDataInputStream.readInt(filebuf, pos)
pos += 4
// Import all nodes
var newGroup: GroupKDB? = null
var newEntry: EntryKDB? = null
var currentGroupNumber = 0
var currentEntryNumber = 0
while (currentGroupNumber < header.numGroups
|| currentEntryNumber < header.numEntries) {
if (fieldType == 0xFFFF) {
// End-Group record. Save group and count it.
mDatabaseToOpen.addGroupIndex(newGrp)
newGrp = mDatabaseToOpen.createGroup()
i++
} else {
readGroupField(mDatabaseToOpen, newGrp, fieldType, filebuf, pos)
}
pos += fieldSize
}
}
val fieldType = cipherInputStream.readBytes2ToUShort()
val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
// Import all entries
var newEnt = mDatabaseToOpen.createEntry()
var i = 0
while (i < hdr.numEntries) {
val fieldType = LEDataInputStream.readUShort(filebuf, pos)
val fieldSize = LEDataInputStream.readInt(filebuf, pos + 2)
if (fieldType == 0xFFFF) {
// End-Group record. Save group and count it.
mDatabaseToOpen.addEntryIndex(newEnt)
newEnt = mDatabaseToOpen.createEntry()
i++
} else {
readEntryField(mDatabaseToOpen, newEnt, filebuf, pos)
when (fieldType) {
0x0000 -> {
cipherInputStream.readBytesLength(fieldSize)
}
pos += 2 + 4 + fieldSize
0x0001 -> {
// Create new node depending on byte number
when (fieldSize) {
4 -> {
newGroup = mDatabaseToOpen.createGroup().apply {
setGroupId(cipherInputStream.readBytes4ToInt())
}
}
16 -> {
newEntry = mDatabaseToOpen.createEntry().apply {
nodeId = NodeIdUUID(cipherInputStream.readBytes16ToUuid())
}
}
else -> {
throw UnsupportedEncodingException("Field type $fieldType")
}
}
}
0x0002 -> {
newGroup?.let { group ->
group.title = cipherInputStream.readBytesToString(fieldSize)
} ?:
newEntry?.let { entry ->
val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToInt())
entry.parent = groupKDB
}
}
0x0003 -> {
newGroup?.let { group ->
group.creationTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToInt()
// Clean up after bug that set icon ids to -1
if (iconId == -1) {
iconId = 0
}
entry.icon = mDatabaseToOpen.iconFactory.getIcon(iconId)
}
}
0x0004 -> {
newGroup?.let { group ->
group.lastModificationTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.title = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0005 -> {
newGroup?.let { group ->
group.lastAccessTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.url = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0006 -> {
newGroup?.let { group ->
group.expiryTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.username = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0007 -> {
newGroup?.let { group ->
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToInt())
} ?:
newEntry?.let { entry ->
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
}
}
0x0008 -> {
newGroup?.let { group ->
group.level = cipherInputStream.readBytes2ToUShort()
} ?:
newEntry?.let { entry ->
entry.notes = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0009 -> {
newGroup?.let { group ->
group.flags = cipherInputStream.readBytes4ToInt()
} ?:
newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate()
}
}
0x000A -> {
newEntry?.let { entry ->
entry.lastModificationTime = cipherInputStream.readBytes5ToDate()
}
}
0x000B -> {
newEntry?.let { entry ->
entry.lastAccessTime = cipherInputStream.readBytes5ToDate()
}
}
0x000C -> {
newEntry?.let { entry ->
entry.expiryTime = cipherInputStream.readBytes5ToDate()
}
}
0x000D -> {
newEntry?.let { entry ->
entry.binaryDescription = cipherInputStream.readBytesToString(fieldSize)
}
}
0x000E -> {
newEntry?.let { entry ->
if (fieldSize > 0) {
// Generate an unique new file with timestamp
val binaryFile = File(cacheDirectory,
Instant.now().millis.toString())
entry.binaryData = BinaryAttachment(binaryFile)
BufferedOutputStream(FileOutputStream(binaryFile)).use { outputStream ->
cipherInputStream.readBytes(fieldSize,
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
outputStream.write(buffer)
}
}
}
}
}
0xFFFF -> {
// End record. Save node and count it.
newGroup?.let { group ->
mDatabaseToOpen.addGroupIndex(group)
currentGroupNumber++
newGroup = null
}
newEntry?.let { entry ->
mDatabaseToOpen.addEntryIndex(entry)
currentEntryNumber++
newEntry = null
}
cipherInputStream.readBytesLength(fieldSize)
}
else -> {
throw UnsupportedEncodingException("Field type $fieldType")
}
}
}
// Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException()
}
constructTreeFromIndex()
} catch (e: LoadDatabaseException) {
mDatabaseToOpen.clearCache()
throw e
} catch (e: IOException) {
mDatabaseToOpen.clearCache()
throw IODatabaseException(e)
} catch (e: OutOfMemoryError) {
mDatabaseToOpen.clearCache()
throw NoMemoryDatabaseException(e)
} catch (e: Exception) {
mDatabaseToOpen.clearCache()
throw LoadDatabaseException(e)
}
@@ -279,71 +398,6 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
}
}
/**
* Parse and save one record from binary file.
* @param buf
* @param offset
* @return If >0,
* @throws UnsupportedEncodingException
*/
@Throws(UnsupportedEncodingException::class)
private fun readGroupField(db: DatabaseKDB, grp: GroupKDB, fieldType: Int, buf: ByteArray, offset: Int) {
when (fieldType) {
0x0000 -> {
}
0x0001 -> grp.setGroupId(LEDataInputStream.readInt(buf, offset))
0x0002 -> grp.title = DatabaseInputOutputUtils.readCString(buf, offset)
0x0003 -> grp.creationTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0004 -> grp.lastModificationTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0005 -> grp.lastAccessTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0006 -> grp.expiryTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0007 -> grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset))
0x0008 -> grp.level = LEDataInputStream.readUShort(buf, offset)
0x0009 -> grp.flags = LEDataInputStream.readInt(buf, offset)
}// Ignore field
}
@Throws(UnsupportedEncodingException::class)
private fun readEntryField(db: DatabaseKDB, ent: EntryKDB, buf: ByteArray, offset: Int) {
var offsetMutable = offset
val fieldType = LEDataInputStream.readUShort(buf, offsetMutable)
offsetMutable += 2
val fieldSize = LEDataInputStream.readInt(buf, offsetMutable)
offsetMutable += 4
when (fieldType) {
0x0000 -> {
}
0x0001 -> ent.nodeId = NodeIdUUID(LEDataInputStream.readUuid(buf, offsetMutable))
0x0002 -> {
val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(LEDataInputStream.readInt(buf, offsetMutable))
ent.parent = groupKDB
}
0x0003 -> {
var iconId = LEDataInputStream.readInt(buf, offsetMutable)
// Clean up after bug that set icon ids to -1
if (iconId == -1) {
iconId = 0
}
ent.icon = db.iconFactory.getIcon(iconId)
}
0x0004 -> ent.title = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0005 -> ent.url = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0006 -> ent.username = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0007 -> ent.password = DatabaseInputOutputUtils.readPassword(buf, offsetMutable)
0x0008 -> ent.notes = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0009 -> ent.creationTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000A -> ent.lastModificationTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000B -> ent.lastAccessTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000C -> ent.expiryTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000D -> ent.binaryDesc = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x000E -> ent.binaryData = DatabaseInputOutputUtils.readBytes(buf, offsetMutable, fieldSize)
}// Ignore field
}
companion object {
private val TAG = DatabaseInputKDB::class.java.name
}

View File

@@ -24,7 +24,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -37,12 +38,11 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
@@ -54,10 +54,12 @@ import java.util.*
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import kotlin.math.min
class DatabaseInputKDBX(private val streamDir: File,
private val fixDuplicateUUID: Boolean = false) : DatabaseInput<DatabaseKDBX>() {
class DatabaseInputKDBX(cacheDirectory: File,
private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDBX>(cacheDirectory) {
private var randomStream: StreamCipher? = null
private lateinit var mDatabase: DatabaseKDBX
@@ -131,11 +133,11 @@ class DatabaseInputKDBX(private val streamDir: File,
if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LEDataInputStream(decrypted)
val dataDecrypted = LittleEndianDataInputStream(decrypted)
val storedStartBytes: ByteArray?
try {
storedStartBytes = dataDecrypted.readBytes(32)
if (storedStartBytes == null || storedStartBytes.size != 32) {
if (storedStartBytes.size != 32) {
throw InvalidCredentialsDatabaseException()
}
} catch (e: IOException) {
@@ -148,7 +150,7 @@ class DatabaseInputKDBX(private val streamDir: File,
isPlain = HashedBlockInputStream(dataDecrypted)
} else { // KDBX 4
val isData = LEDataInputStream(databaseInputStream)
val isData = LittleEndianDataInputStream(databaseInputStream)
val storedHash = isData.readBytes(32)
if (!Arrays.equals(storedHash, hashOfHeader)) {
throw InvalidCredentialsDatabaseException()
@@ -157,7 +159,7 @@ class DatabaseInputKDBX(private val streamDir: File,
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32)
if (storedHmac == null || storedHmac.size != 32) {
if (storedHmac.size != 32) {
throw InvalidCredentialsDatabaseException()
}
// Mac doesn't match
@@ -207,12 +209,12 @@ class DatabaseInputKDBX(private val streamDir: File,
}
private fun attachCipherStream(inputStream: InputStream, cipher: Cipher): InputStream {
return BetterCipherInputStream(inputStream, cipher, 50 * 1024)
return CipherInputStream(inputStream, cipher)
}
@Throws(IOException::class)
private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) {
val lis = LEDataInputStream(inputStream)
val lis = LittleEndianDataInputStream(inputStream)
while (true) {
if (!readInnerHeader(lis, header)) break
@@ -220,7 +222,8 @@ class DatabaseInputKDBX(private val streamDir: File,
}
@Throws(IOException::class)
private fun readInnerHeader(dataInputStream: LEDataInputStream, header: DatabaseHeaderKDBX): Boolean {
private fun readInnerHeader(dataInputStream: LittleEndianDataInputStream,
header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readInt()
@@ -243,13 +246,11 @@ class DatabaseInputKDBX(private val streamDir: File,
val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt()
val byteLength = size - 1
// Read in a file
val file = File(streamDir, unusedCacheFileName)
val file = File(cacheDirectory, unusedCacheFileName)
FileOutputStream(file).use { outputStream ->
dataInputStream.readBytes(byteLength, object : ReadBytes {
override fun read(buffer: ByteArray) {
dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer ->
outputStream.write(buffer)
}
})
}
val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary)
@@ -822,7 +823,7 @@ class DatabaseInputKDBX(private val streamDir: File,
buf = buf8
}
val seconds = LEDataInputStream.readLong(buf, 0)
val seconds = bytes64ToLong(buf)
utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
} else {
@@ -882,7 +883,7 @@ class DatabaseInputKDBX(private val streamDir: File,
}
val buf = Base64.decode(encoded, BASE_64_FLAG)
return DatabaseInputOutputUtils.bytesToUuid(buf)
return bytes16ToUuid(buf)
}
@Throws(IOException::class, XmlPullParserException::class)
@@ -960,7 +961,7 @@ class DatabaseInputKDBX(private val streamDir: File,
// New binary to retrieve
else {
var compressed: Boolean? = null
var compressed = false
var protected = false
if (xpp.attributeCount > 0) {
@@ -980,16 +981,16 @@ class DatabaseInputKDBX(private val streamDir: File,
return BinaryAttachment()
val data = Base64.decode(base64, BASE_64_FLAG)
val file = File(streamDir, unusedCacheFileName)
val file = File(cacheDirectory, unusedCacheFileName)
return FileOutputStream(file).use { outputStream ->
// Force compression in this specific case
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
&& compressed == false) {
&& !compressed) {
GZIPOutputStream(outputStream).write(data)
BinaryAttachment(file, protected, true)
} else {
outputStream.write(data)
BinaryAttachment(file, protected)
BinaryAttachment(file, protected, compressed)
}
}
}

View File

@@ -20,34 +20,34 @@
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.intTo4Bytes
import java.io.IOException
import java.io.OutputStream
class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB, private val mOS: OutputStream) {
class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
private val mOutputStream: OutputStream) {
@Throws(IOException::class)
fun outputStart() {
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature1))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature2))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.flags))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.version))
mOS.write(mHeader.masterSeed)
mOS.write(mHeader.encryptionIV)
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numGroups))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numEntries))
mOutputStream.write(intTo4Bytes(mHeader.signature1))
mOutputStream.write(intTo4Bytes(mHeader.signature2))
mOutputStream.write(intTo4Bytes(mHeader.flags))
mOutputStream.write(intTo4Bytes(mHeader.version))
mOutputStream.write(mHeader.masterSeed)
mOutputStream.write(mHeader.encryptionIV)
mOutputStream.write(intTo4Bytes(mHeader.numGroups))
mOutputStream.write(intTo4Bytes(mHeader.numEntries))
}
@Throws(IOException::class)
fun outputContentHash() {
mOS.write(mHeader.contentsHash)
mOutputStream.write(mHeader.contentsHash)
}
@Throws(IOException::class)
fun outputEnd() {
mOS.write(mHeader.transformSeed)
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numKeyEncRounds))
mOutputStream.write(mHeader.transformSeed)
mOutputStream.write(intTo4Bytes(mHeader.numKeyEncRounds))
}
@Throws(IOException::class)
@@ -59,6 +59,6 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB, private va
@Throws(IOException::class)
fun close() {
mOS.close()
mOutputStream.close()
}
}

View File

@@ -19,17 +19,14 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.MacOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.VariantDictionary
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
@@ -37,13 +34,15 @@ import java.security.DigestOutputStream
import java.security.InvalidKeyException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX, os: OutputStream) : DatabaseHeaderOutput() {
private val los: LEDataOutputStream
constructor(private val databaseKDBX: DatabaseKDBX,
private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) : DatabaseHeaderOutput() {
private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream
private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray
@@ -58,15 +57,16 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
}
try {
db.makeFinalKey(header.masterSeed)
databaseKDBX.makeFinalKey(header.masterSeed)
} catch (e: IOException) {
throw DatabaseOutputException(e)
}
val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined")
val hmac: Mac
try {
hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, DatabaseInputOutputUtils.ULONG_MAX_VALUE), "HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, ULONG_MAX_VALUE), "HmacSHA256")
hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e)
@@ -74,9 +74,9 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
throw DatabaseOutputException(e)
}
dos = DigestOutputStream(os, md)
dos = DigestOutputStream(outputStream, md)
mos = MacOutputStream(dos, hmac)
los = LEDataOutputStream(mos)
los = LittleEndianDataOutputStream(mos)
}
@Throws(IOException::class)
@@ -86,15 +86,15 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong())
los.writeUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, DatabaseInputOutputUtils.uuidToBytes(db.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(DatabaseHeaderKDBX.getFlagFromCompression(db.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, intTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numberKeyEncryptionRounds))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters!!))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(databaseKDBX.kdfParameters!!))
}
if (header.encryptionIV.isNotEmpty()) {
@@ -104,13 +104,13 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream!!.id))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, intTo4Bytes(header.innerRandomStream!!.id))
}
if (db.containsPublicCustomData()) {
if (databaseKDBX.containsPublicCustomData()) {
val bos = ByteArrayOutputStream()
val los = LEDataOutputStream(bos)
VariantDictionary.serialize(db.publicCustomData, los)
val los = LittleEndianDataOutputStream(bos)
VariantDictionary.serialize(databaseKDBX.publicCustomData, los)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray())
}

View File

@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.readFromStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.readBytes
import java.io.IOException
import java.io.OutputStream
import kotlin.experimental.or
@@ -33,7 +32,7 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) {
private val dataOutputStream: LEDataOutputStream = LEDataOutputStream(outputStream)
private val dataOutputStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
@Throws(IOException::class)
fun output() {
@@ -58,14 +57,10 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
dataOutputStream.write(flag.toInt())
readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
protectedBinary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
dataOutputStream.write(buffer)
}
}
)
}
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader.toInt())
dataOutputStream.writeInt(0)

View File

@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream
@@ -128,7 +128,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
header.version = DatabaseHeaderKDB.DBVER_DW
header.numGroups = mDatabaseKDB.numberOfGroups()
header.numEntries = mDatabaseKDB.numberOfEntries()
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt()
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() // TODO Signed Long - Unsigned Int
setIVs(header)
@@ -197,7 +197,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
@Suppress("CAST_NEVER_SUCCEEDS")
@Throws(DatabaseOutputException::class)
fun outputPlanGroupAndEntries(os: OutputStream) {
val los = LEDataOutputStream(os)
val los = LittleEndianDataOutputStream(os)
// useHeaderHash
if (headerHashBlock != null) {
@@ -261,7 +261,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
@Throws(IOException::class)
private fun writeExtData(headerDigest: ByteArray, os: OutputStream) {
val los = LEDataOutputStream(os)
val los = LittleEndianDataOutputStream(os)
writeExtDataField(los, 0x0001, headerDigest, headerDigest.size)
val headerRandom = ByteArray(32)
@@ -273,7 +273,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
}
@Throws(IOException::class)
private fun writeExtDataField(los: LEDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
private fun writeExtDataField(los: LittleEndianDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
los.writeUShort(fieldType)
los.writeInt(fieldSize)
if (data != null) {

View File

@@ -28,7 +28,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -47,7 +47,6 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlSerializer
@@ -95,7 +94,8 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
mOS.write(hashOfHeader!!)
mOS.write(headerHmac!!)
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey))
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!))
}
val osXml: OutputStream
@@ -230,7 +230,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta
// if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4)
if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4)
writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData)
@@ -390,7 +390,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} else {
val dt = DateTime(value)
val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt)
val buf = LEDataOutputStream.writeLongBuf(seconds)
val buf = longTo8Bytes(seconds)
val b64 = String(Base64.encode(buf, BASE_64_FLAG))
writeObject(name, b64)
}
@@ -414,26 +414,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeUuid(name: String, uuid: UUID) {
val data = DatabaseInputOutputUtils.uuidToBytes(uuid)
val data = uuidTo16Bytes(uuid)
writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
private fun writeBinary(binary : BinaryAttachment) {
val binaryLength = binary.length()
if (binaryLength > 0) {
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
// Force binary compression from database (compression was harmonized during import)
xml.attribute(null, DatabaseKDBXXML.AttrCompressed,
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
DatabaseKDBXXML.ValTrue
} else {
DatabaseKDBXXML.ValFalse
binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
} else {
// Force binary compression from database (compression was harmonized during import)
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
}
)
// Force decompression in this specific case
val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None
@@ -444,15 +447,22 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
// Write the XML
readFromStream(binaryInputStream, BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
binaryInputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
writeBinary(binary)
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
}
@@ -561,32 +571,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (ref != null) {
xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString())
} else {
val binaryLength = binary.length()
if (binaryLength > 0) {
if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
})
} else {
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
}
}
writeBinary(binary)
}
xml.endTag(null, DatabaseKDBXXML.ElemValue)

View File

@@ -19,12 +19,14 @@
*/
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import java.io.IOException
import java.io.OutputStream
import java.nio.charset.Charset
class EntryOutputKDB
/**
@@ -47,53 +49,76 @@ class EntryOutputKDB
// UUID
mOutputStream.write(UUID_FIELD_TYPE)
mOutputStream.write(UUID_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.uuidToBytes(mEntry.id))
mOutputStream.write(uuidTo16Bytes(mEntry.id))
// Group ID
mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(LONG_FOUR)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.parent!!.id))
mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.parent!!.id))
// Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(LONG_FOUR)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.icon.iconId))
mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(intTo4Bytes(mEntry.icon.iconId))
// Title
//byte[] title = mEntry.title.getBytes("UTF-8");
mOutputStream.write(TITLE_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.title, mOutputStream).toLong()
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.title, mOutputStream).toLong()
// URL
mOutputStream.write(URL_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.url, mOutputStream).toLong()
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.url, mOutputStream).toLong()
// Username
mOutputStream.write(USERNAME_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.username, mOutputStream).toLong()
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.username, mOutputStream).toLong()
// Password
mOutputStream.write(PASSWORD_FIELD_TYPE)
length += DatabaseInputOutputUtils.writePassword(mEntry.password, mOutputStream).toLong()
length += writePassword(mEntry.password, mOutputStream).toLong()
// Additional
mOutputStream.write(ADDITIONAL_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.notes, mOutputStream).toLong()
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.notes, mOutputStream).toLong()
// Create date
writeDate(CREATE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.creationTime.date))
writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date))
// Modification date
writeDate(MOD_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastModificationTime.date))
writeDate(MOD_FIELD_TYPE, dateTo5Bytes(mEntry.lastModificationTime.date))
// Access date
writeDate(ACCESS_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastAccessTime.date))
writeDate(ACCESS_FIELD_TYPE, dateTo5Bytes(mEntry.lastAccessTime.date))
// Expiration date
writeDate(EXPIRE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.expiryTime.date))
writeDate(EXPIRE_FIELD_TYPE, dateTo5Bytes(mEntry.expiryTime.date))
// Binary description
mOutputStream.write(BINARY_DESC_FIELD_TYPE)
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.binaryDescription, mOutputStream).toLong()
// Binary
writeBinary(mEntry.binaryData)
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.binaryData
val binaryDataLength = binaryData?.length() ?: 0
val binaryDataLengthRightSize = if (binaryDataLength <= Int.MAX_VALUE) {
binaryDataLength.toInt()
} else {
0 // TODO if length > UInt.maxvalue show exception
}
// Write data length
mOutputStream.write(intTo4Bytes(binaryDataLengthRightSize))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream().use { inputStream ->
inputStream?.readBytes(DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
length += buffer.size
mOutputStream.write(buffer)
}
inputStream?.close()
}
}
// End
mOutputStream.write(END_FIELD_TYPE)
@@ -112,39 +137,40 @@ class EntryOutputKDB
}
@Throws(IOException::class)
private fun writeBinary(data: ByteArray?) {
mOutputStream.write(BINARY_DESC_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.binaryDesc, mOutputStream).toLong()
val dataLen: Int = data?.size ?: 0
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeBytes(data, dataLen, mOutputStream)
private fun writePassword(str: String, os: OutputStream): Int {
val initial = str.toByteArray(Charset.forName("UTF-8"))
val length = initial.size + 1
os.write(intTo4Bytes(length))
os.write(initial)
os.write(0x00)
return length
}
companion object {
// Constants
val UUID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(1)
val GROUPID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2)
val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3)
val TITLE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4)
val URL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5)
val USERNAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6)
val PASSWORD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7)
val ADDITIONAL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8)
val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9)
val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(10)
val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(11)
val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(12)
val BINARY_DESC_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(13)
val BINARY_DATA_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(14)
val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF)
val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4)
val UUID_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(16)
val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5)
val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR
val LEVEL_FIELD_SIZE:ByteArray = LONG_FOUR
val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR
val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0)
val UUID_FIELD_TYPE:ByteArray = uShortTo2Bytes(1)
val GROUPID_FIELD_TYPE:ByteArray = uShortTo2Bytes(2)
val IMAGEID_FIELD_TYPE:ByteArray = uShortTo2Bytes(3)
val TITLE_FIELD_TYPE:ByteArray = uShortTo2Bytes(4)
val URL_FIELD_TYPE:ByteArray = uShortTo2Bytes(5)
val USERNAME_FIELD_TYPE:ByteArray = uShortTo2Bytes(6)
val PASSWORD_FIELD_TYPE:ByteArray = uShortTo2Bytes(7)
val ADDITIONAL_FIELD_TYPE:ByteArray = uShortTo2Bytes(8)
val CREATE_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val MOD_FIELD_TYPE:ByteArray = uShortTo2Bytes(10)
val ACCESS_FIELD_TYPE:ByteArray = uShortTo2Bytes(11)
val EXPIRE_FIELD_TYPE:ByteArray = uShortTo2Bytes(12)
val BINARY_DESC_FIELD_TYPE:ByteArray = uShortTo2Bytes(13)
val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val LONG_FOUR:ByteArray = intTo4Bytes(4)
val UUID_FIELD_SIZE:ByteArray = intTo4Bytes(16)
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
}
}

View File

@@ -20,9 +20,10 @@
package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.stream.intTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import java.io.IOException
import java.io.OutputStream
@@ -38,46 +39,46 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Group ID
mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.id))
mOutputStream.write(intTo4Bytes(mGroup.id))
// Name
mOutputStream.write(NAME_FIELD_TYPE)
DatabaseInputOutputUtils.writeCString(mGroup.title, mOutputStream)
StringDatabaseKDBUtils.writeStringToBytes(mGroup.title, mOutputStream)
// Create date
mOutputStream.write(CREATE_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.creationTime.date))
mOutputStream.write(dateTo5Bytes(mGroup.creationTime.date))
// Modification date
mOutputStream.write(MOD_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastModificationTime.date))
mOutputStream.write(dateTo5Bytes(mGroup.lastModificationTime.date))
// Access date
mOutputStream.write(ACCESS_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastAccessTime.date))
mOutputStream.write(dateTo5Bytes(mGroup.lastAccessTime.date))
// Expiration date
mOutputStream.write(EXPIRE_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.expiryTime.date))
mOutputStream.write(dateTo5Bytes(mGroup.expiryTime.date))
// Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.icon.iconId))
mOutputStream.write(intTo4Bytes(mGroup.icon.iconId))
// Level
mOutputStream.write(LEVEL_FIELD_TYPE)
mOutputStream.write(LEVEL_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeUShortBuf(mGroup.level))
mOutputStream.write(uShortTo2Bytes(mGroup.level))
// Flags
mOutputStream.write(FLAGS_FIELD_TYPE)
mOutputStream.write(FLAGS_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.flags))
mOutputStream.write(intTo4Bytes(mGroup.flags))
// End
mOutputStream.write(END_FIELD_TYPE)
@@ -86,23 +87,22 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
companion object {
// Constants
val GROUPID_FIELD_TYPE: ByteArray = LEDataOutputStream.writeUShortBuf(1)
val NAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2)
val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3)
val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4)
val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5)
val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6)
val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7)
val LEVEL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8)
val FLAGS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9)
val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF)
val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4)
val GROUPID_FIELD_SIZE:ByteArray = LONG_FOUR
val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5)
val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR
val LEVEL_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(2)
val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR
val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0)
}
val GROUPID_FIELD_TYPE: ByteArray = uShortTo2Bytes(1)
val NAME_FIELD_TYPE:ByteArray = uShortTo2Bytes(2)
val CREATE_FIELD_TYPE:ByteArray = uShortTo2Bytes(3)
val MOD_FIELD_TYPE:ByteArray = uShortTo2Bytes(4)
val ACCESS_FIELD_TYPE:ByteArray = uShortTo2Bytes(5)
val EXPIRE_FIELD_TYPE:ByteArray = uShortTo2Bytes(6)
val IMAGEID_FIELD_TYPE:ByteArray = uShortTo2Bytes(7)
val LEVEL_FIELD_TYPE:ByteArray = uShortTo2Bytes(8)
val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val GROUPID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(2)
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
@@ -19,16 +19,16 @@
*/
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils;
import java.util.UUID;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.uuidTo16Bytes;
public class UuidUtil {
public static String toHexString(UUID uuid) {
if (uuid == null) { return null; }
byte[] buf = DatabaseInputOutputUtils.INSTANCE.uuidToBytes(uuid);
byte[] buf = uuidTo16Bytes(uuid);
int len = buf.length;
if (len == 0) { return ""; }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education
import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.magikeyboard
import android.os.Bundle

View File

@@ -90,17 +90,10 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
keyboard = Keyboard(this, R.xml.keyboard_password)
keyboardEntry = Keyboard(this, R.xml.keyboard_password_entry)
if (!Database.getInstance().loaded)
removeEntryInfo()
assignKeyboardView()
keyboardView?.setOnKeyboardActionListener(this)
keyboardView?.isPreviewEnabled = false
val context = baseContext
val popupFieldsView = LayoutInflater.from(context)
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
dismissCustomKeys()
popupCustomKeys = PopupWindow(context).apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
@@ -114,6 +107,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
goNextAutomatically()
}
}
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
@@ -122,6 +116,12 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
closeView.setOnClickListener { popupCustomKeys?.dismiss() }
if (!Database.getInstance().loaded)
removeEntryInfo()
assignKeyboardView()
keyboardView?.setOnKeyboardActionListener(this)
keyboardView?.isPreviewEnabled = false
return rootKeyboardView
}
@@ -235,31 +235,41 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
}
KEY_USERNAME -> {
if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.username, 1)
currentInputConnection.commitText(entryInfoKey!!.username, 1)
}
goNextAutomatically()
}
KEY_PASSWORD -> {
if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.password, 1)
currentInputConnection.commitText(entryInfoKey!!.password, 1)
}
goNextAutomatically()
}
KEY_URL -> {
if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.url, 1)
currentInputConnection.commitText(entryInfoKey!!.url, 1)
}
goNextAutomatically()
}
KEY_FIELDS -> {
if (entryInfoKey != null) {
fieldsAdapter?.fields = entryInfoKey!!.customFields
fieldsAdapter?.notifyDataSetChanged()
fieldsAdapter?.apply {
setFields(entryInfoKey!!.customFields)
notifyDataSetChanged()
}
}
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
}
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
Keyboard.KEYCODE_DONE -> inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER))
Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}// TODO Unlock key
}
private fun goNextAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}
override fun onPress(primaryCode: Int) {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
if (audioManager != null)

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
data class EntryAttachment(var name: String,
var binaryAttachment: BinaryAttachment,
var downloadState: AttachmentState = AttachmentState.NULL,
var downloadProgression: Int = 0) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment(),
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(binaryAttachment, flags)
parcel.writeEnum(downloadState)
parcel.writeInt(downloadProgression)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<EntryAttachment> {
override fun createFromParcel(parcel: Parcel): EntryAttachment {
return EntryAttachment(parcel)
}
override fun newArray(size: Int): Array<EntryAttachment?> {
return arrayOfNulls(size)
}
}
}
enum class AttachmentState {
NULL, START, IN_PROGRESS, COMPLETE, ERROR
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model
import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model
import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model
import android.os.Parcel

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2019 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePassDX.
*
* KeePassDX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePassDX 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 KeePassDX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.tasks.AttachmentFileAsyncTask
import java.util.*
import kotlin.collections.HashMap
class AttachmentFileNotificationService: LockNotificationService() {
override val notificationId: Int = 10000
private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
inner class ActionTaskBinder: Binder() {
fun getService(): AttachmentFileNotificationService = this@AttachmentFileNotificationService
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.add(actionTaskListener)
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(entry.key, attachment)
}
}
}
})
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = null
}
})
mActionTaskListeners.remove(actionTaskListener)
}
}
interface ActionTaskListener {
fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment)
}
override fun onBind(intent: Intent): IBinder? {
return mActionTaskBinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) {
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY)
} else null
when(intent?.action) {
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
if (downloadFileUri != null
&& intent.hasExtra(ATTACHMENT_KEY)) {
val nextNotificationId = (downloadFileUris.values.maxBy { it.notificationId }
?.notificationId ?: notificationId) + 1
val entryAttachment: EntryAttachment = intent.getParcelableExtra(ATTACHMENT_KEY)
val attachmentNotification = AttachmentNotification(nextNotificationId, entryAttachment)
downloadFileUris[downloadFileUri] = attachmentNotification
try {
AttachmentFileAsyncTask(downloadFileUri,
attachmentNotification,
contentResolver).apply {
onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(downloadFileUri, attachment)
}
}
}.execute()
} catch (e: Exception) {
Log.e(TAG, "Unable to download $downloadFileUri", e)
}
}
}
else -> {
if (downloadFileUri != null) {
downloadFileUris[downloadFileUri]?.notificationId?.let {
notificationManager?.cancel(it)
downloadFileUris.remove(downloadFileUri)
}
}
if (downloadFileUris.isEmpty()) {
stopSelf()
}
}
}
return START_REDELIVER_INTENT
}
fun checkCurrentAttachmentProgress() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(entry.key, entry.value.entryAttachment)
}
}
})
}
private fun newNotification(downloadFileUri: Uri,
entryAttachment: EntryAttachment,
notificationIdAttachment: Int) {
val pendingContentIntent = PendingIntent.getActivity(this,
0,
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(downloadFileUri, contentResolver.getType(downloadFileUri))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, PendingIntent.FLAG_CANCEL_CURRENT)
val pendingDeleteIntent = PendingIntent.getService(this,
0,
Intent(this, AttachmentFileNotificationService::class.java).apply {
// No action to delete the service
putExtra(DOWNLOAD_FILE_URI_KEY, downloadFileUri)
}, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, downloadFileUri)?.name ?: ""
val builder = buildNewNotification().apply {
setSmallIcon(R.drawable.ic_file_download_white_24dp)
setContentTitle(getString(R.string.download_attachment, fileName))
setAutoCancel(false)
when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.START -> {
setContentText(getString(R.string.download_initialization))
setOngoing(true)
}
AttachmentState.IN_PROGRESS -> {
if (entryAttachment.downloadProgression > 100) {
setContentText(getString(R.string.download_finalization))
} else {
setProgress(100, entryAttachment.downloadProgression, false)
setContentText(getString(R.string.download_progression, entryAttachment.downloadProgression))
}
setOngoing(true)
}
AttachmentState.COMPLETE, AttachmentState.ERROR -> {
setContentText(getString(R.string.download_complete))
setContentIntent(pendingContentIntent)
setDeleteIntent(pendingDeleteIntent)
setOngoing(false)
}
}
}
notificationManager?.notify(notificationIdAttachment, builder.build())
}
override fun onDestroy() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = null
notificationManager?.cancel(entry.value.notificationId)
}
})
super.onDestroy()
}
data class AttachmentNotification(var notificationId: Int,
var entryAttachment: EntryAttachment,
var attachmentTask: AttachmentFileAsyncTask? = null) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttachmentNotification
if (notificationId != other.notificationId) return false
return true
}
override fun hashCode(): Int {
return notificationId
}
}
companion object {
private val TAG = AttachmentFileNotificationService::javaClass.name
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
const val DOWNLOAD_FILE_URI_KEY = "DOWNLOAD_FILE_URI_KEY"
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
private val downloadFileUris = HashMap<Uri, AttachmentNotification>()
}
}

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