2
0

Compare commits

..

64 Commits

Author SHA1 Message Date
Maarten Billemont
2886e040a1 Warning fix. 2020-09-03 11:04:59 -04:00
Maarten Billemont
01cea659ca Bump Pearl for nullability fixes. 2020-09-03 10:52:56 -04:00
Maarten Billemont
3a18e02a87 Revert "Xcode 12 update of xcdatamodel."
This reverts commit 2de57984b2.

NSSecureUnarchiveFromDataTransformer is not compatible with iOS 9-11
2020-09-03 10:41:05 -04:00
Maarten Billemont
2de57984b2 Xcode 12 update of xcdatamodel. 2020-09-03 10:39:23 -04:00
Maarten Billemont
c7201c7d90 Update for Xcode 12 & build fixes. 2020-09-03 09:53:08 -04:00
Maarten Billemont
d62c6b4594 Sites no longer load with batch requests & load improvements. 2020-09-03 09:52:08 -04:00
Maarten Billemont
57f275c471 Update for Xcode 12 & add device identifier to UI. 2020-09-02 16:40:41 -04:00
Maarten Billemont
b1d8296396 Add nonstandard output type tests for i,r + fix indentation. 2020-08-29 09:48:14 -04:00
Maarten Billemont
6d25463de0 Rename next-gen to Spectre. 2020-07-21 21:21:52 -04:00
Maarten Billemont
029041dcf7 Expand the maximum length of query searches. 2020-07-11 21:43:34 -04:00
Maarten Billemont
cfbf1f5cac Use visibility instead of gone so constraints are managed by stack view. 2020-07-11 10:48:19 -04:00
Maarten Billemont
acbd2dc2cc Include purchased features in export file. 2020-07-06 22:28:15 -04:00
Maarten Billemont
8fcac65fd5 Additional documentation for parameter contracts. 2020-07-06 22:27:47 -04:00
Maarten Billemont
9904f4c715 Try to detect if cipherText is plainText.
In some situations, the cipherText that was passed in is actually
plainText.  Old mpsites files used to store the login name as plain text
even though the file was redacted.  Newer versions of the file store the
login name as ciphertext.  There is no clear way to distinguish between
the two cases.
2020-07-06 14:18:47 -04:00
Maarten Billemont
b51a3de32c Check pasteboard when app enters foreground, not activation. 2020-07-05 20:24:59 -04:00
Maarten Billemont
9e91f0a9d6 More reliable monitoring of changes using NSFetchedResultsController. 2020-07-05 20:24:18 -04:00
Maarten Billemont
7368b1be90 Source is button item, not a view. 2020-05-24 10:54:25 -04:00
Maarten Billemont
5db294bdb3 Show purchase transaction failures to the user. 2020-05-23 19:58:47 -04:00
Maarten Billemont
fee7bc7401 Resolve site cell sizing issues across window sizes. 2020-05-23 19:08:43 -04:00
Maarten Billemont
21968f4ba6 Fix messages for password reset. 2020-05-23 12:35:55 -04:00
Maarten Billemont
8582c934c2 Limit fuzzy searching to a depth of 10.
Avoids choking when query string becomes long and there are excessively
long site name entries.
2020-05-23 12:14:22 -04:00
Maarten Billemont
7091e2ee1b Disable automatic font scaling.
It's causing issues with pop-up alerts.
2020-05-23 10:12:07 -04:00
Maarten Billemont
d5d455ee57 Fix issues with content insets for sites across OS versions. 2020-05-22 23:04:36 -04:00
Maarten Billemont
e6ae06798b Handle store opening errors more gracefully.
Store opening can fail for example when hard-locking the device while
it's opening up.
2020-05-22 22:26:43 -04:00
Maarten Billemont
1cae4c754b Group MPErrors together, ignoring the actual inline values. 2020-05-22 22:26:18 -04:00
Maarten Billemont
93ad86e63c Remove PearlAppDelegate. 2020-05-22 17:34:04 -04:00
Maarten Billemont
cf74dc5cc2 Updated NSMenu API. 2020-05-19 13:28:23 -04:00
Maarten Billemont
981bdb3ab4 Fix isDescendantOfView bug & Sentry script error on failure. 2020-05-19 09:21:06 -04:00
Maarten Billemont
9bea8bcbdf Sheets need a source view on iPad. 2020-05-19 08:21:44 -04:00
Maarten Billemont
363d6f6639 Test configuration was removed for Release. 2020-05-18 13:01:16 -04:00
Maarten Billemont
eb1632cb62 Install cocoapods dependencies & gradle works on JDK 11 now. 2020-05-18 12:43:37 -04:00
Maarten Billemont
73fadaef7f iOS uses Xcode 11 now. 2020-05-18 12:15:36 -04:00
Maarten Billemont
60200f6302 Fix all versions advertising themselves as V0. 2020-05-18 12:14:44 -04:00
Maarten Billemont
cce8db5c48 Purge unused and deprecated UISearchDisplayController. 2020-05-18 11:10:23 -04:00
Maarten Billemont
6f3da5ccf0 Harmonize consent features. 2020-05-16 22:34:49 -04:00
Maarten Billemont
52c87eaeca Keep sites sorted by name on export to ensure consistency. 2020-05-16 16:03:42 -04:00
Maarten Billemont
1dccdd0a3c Improvements to algorithm and counter steppers. 2020-05-16 15:40:59 -04:00
Maarten Billemont
eb8d10ed05 Use new UN notification API. 2020-05-14 10:32:04 -04:00
Maarten Billemont
d9e5f77bee Resolve export state generation. 2020-05-14 00:04:29 -04:00
Maarten Billemont
60f60d087e Embed views using embed segue instead of MPRootSegue. 2020-05-12 23:27:56 -04:00
Maarten Billemont
df97dec2fe Sentry user login & MPError arguments. 2020-05-12 10:54:15 -04:00
Maarten Billemont
3bac8d9e0a iPad fixes & log initial start-up crashes. 2020-05-11 21:28:27 -04:00
Maarten Billemont
3fa7e1e8a1 Ensure Countly does not use the IDFA. 2020-05-02 12:31:54 -04:00
Maarten Billemont
d1104e4028 Need icon 1x icon for iPad. 2020-04-30 09:32:17 -04:00
Maarten Billemont
e9f2a25c9c Project icons update. 2020-04-28 13:49:34 -04:00
Maarten Billemont
171a3f0978 Icon update. 2020-04-28 12:24:41 -04:00
Maarten Billemont
8cfb9a83c5 mpjson support on iOS. 2020-04-27 16:33:10 -04:00
Maarten Billemont
5717375e75 Improve language. 2020-04-27 16:33:01 -04:00
Maarten Billemont
cc2dca3bd0 Report missing support when trying to parse unknown format. 2020-04-27 16:32:30 -04:00
Maarten Billemont
7575924d80 Bump site links to macOS app. 2020-04-26 00:39:08 -04:00
Maarten Billemont
8bedcedfaf Enable support for internal actions from URLs. 2020-04-25 17:13:48 -04:00
Maarten Billemont
10b205c541 Open URLs in external browser. 2020-04-25 11:44:27 -04:00
Maarten Billemont
774f183ac0 Improved web support & trigger notification URLs. 2020-04-25 10:44:59 -04:00
Maarten Billemont
2279aacb5a Change the user's default type from the Mac UI. 2020-04-21 13:27:06 -04:00
Maarten Billemont
1bd654621c Fix persistence of sendInfoDecided.
Don't log it as an event, event logging is probably disabled at this
point anyway.
2020-04-21 13:26:12 -04:00
Maarten Billemont
c4f60e325d Harmonize consent flow on both platforms. 2020-04-21 11:33:31 -04:00
Maarten Billemont
d4de3afb72 Show internal reason for why import fails. 2020-04-20 17:09:38 -04:00
Maarten Billemont
694b5ea227 Make marshal error messages owned by the file.
Error message lifecycle was limited to the static mpw_str buffer, which
is far too limited and also dangerous.  Own the message by the
MPMarshalFile object, freed in mpw_marshal_file_free.
2020-04-20 17:07:35 -04:00
Maarten Billemont
66dd78797b We use bounds-checked interfaces, so need ext1. 2020-04-20 17:05:37 -04:00
Maarten Billemont
61d1660560 Consent tweaks.
Don't conditional Countly events on sendInfo, it already turns Countly
on/off.

Keep Sentry enabled for longer while turning on/off for sendInfo to
cache more errors.

Use sendInfoDecided event only for initial sendInfo prompt, can use
Countly's censent tools to view overall opt-in/opt-outs.
2020-04-19 17:40:28 -04:00
Maarten Billemont
c3568e4744 Enable minor & build numbers > 9. 2020-04-18 22:13:36 -04:00
Maarten Billemont
0c921d4318 Update fonts to stay consistent with system UIs. 2020-04-18 21:50:06 -04:00
Maarten Billemont
0178efaaf7 Ask for consent to sendInfo. 2020-04-18 21:48:55 -04:00
Maarten Billemont
14f919584b Project update. 2020-04-16 17:46:39 -04:00
87 changed files with 1946 additions and 1454 deletions

View File

@@ -9,10 +9,12 @@ build_project:
- "( ./lib/bin/build_libsodium-macos clean && ./lib/bin/build_libsodium-macos )"
- "( ./lib/bin/build_libjson-c-macos clean && ./lib/bin/build_libjson-c-macos )"
- "( cd ./platform-independent/c/cli && ./clean && targets=all ./build && ./mpw-tests && ./mpw-cli-tests )"
- "( export JAVA_HOME=$(java_home -Fv 10 || java_home -Fv 9* ) && ./gradlew --stacktrace clean test )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Test' -scheme 'MasterPassword macOS' clean build )"
- "( ./gradlew --stacktrace --info clean test )"
- "( cd platform-darwin && pod install )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Release' -scheme 'MasterPassword iOS' -sdk iphonesimulator clean build )"
- "( xcodebuild -workspace platform-darwin/MasterPassword.xcworkspace -configuration 'Release' -scheme 'MasterPassword macOS' clean build )"
tags:
- brew
- java_9
- xcode_9
- java
- cocoapods
- xcode

3
.gitmodules vendored
View File

@@ -15,7 +15,6 @@
url = https://github.com/json-c/json-c.git
[submodule "public/site"]
path = public/site
url = https://github.com/Lyndir/MasterPassword.git
url = https://gitlab.com/MasterPassword/MasterPassword.git
branch = gh-pages
shallow = true
update = none

View File

@@ -49,7 +49,6 @@
93D39A5FF670957C0AF8298D /* MPSiteCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39DEA995041A13DC9CAF7 /* MPSiteCell.m */; };
93D39A8EA1C49CE43B63F47B /* PearlUICollectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D39D8A953779B35403AF6E /* PearlUICollectionView.m */; };
93D39AA4A0BE66A872CCC02E /* NSPersistentStore+PearlMigration.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D397F4BAFFF7CF3F1B21A4 /* NSPersistentStore+PearlMigration.h */; };
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */; };
93D39B76DD5AB108BA8928E8 /* UIScrollView+PearlAdjustInsets.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D39DE2CB351D4E3789462B /* UIScrollView+PearlAdjustInsets.h */; };
93D39B842AB9A5D072810D76 /* NSError+PearlFullDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = 93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */; };
93D39B8F90F58A5D158DDBA3 /* MPSitesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 93D3924EE15017F8A12CB436 /* MPSitesViewController.m */; };
@@ -77,11 +76,6 @@
DA0CC5951EB6B030009A8ED9 /* MPSiteEntity+CoreDataProperties.m in Sources */ = {isa = PBXBuildFile; fileRef = DA0CC58B1EB6B030009A8ED9 /* MPSiteEntity+CoreDataProperties.m */; };
DA24EBAE19DAD08900FF010B /* tip_basic_black_top.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38941711E29700CF925C /* tip_basic_black_top.png */; };
DA24EBAF19DAD08C00FF010B /* tip_basic_black_top@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD38951711E29700CF925C /* tip_basic_black_top@2x.png */; };
DA24EBE819DAD6DE00FF010B /* Icon-320.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBE619DAD6DE00FF010B /* Icon-320.png */; };
DA24EBE919DAD6DE00FF010B /* Icon-64.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBE719DAD6DE00FF010B /* Icon-64.png */; };
DA24EBEA19DAD6EE00FF010B /* Icon-Small.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBBA19DAD4D000FF010B /* Icon-Small.png */; };
DA24EBEB19DAD6EE00FF010B /* Icon-Small@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBBB19DAD4D000FF010B /* Icon-Small@2x.png */; };
DA24EBEC19DAD6EE00FF010B /* Icon-Small@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA24EBBC19DAD4D000FF010B /* Icon-Small@3x.png */; };
DA250A17195665A100AC23F1 /* UITableView+PearlReloadItems.m in Sources */ = {isa = PBXBuildFile; fileRef = DA250A13195665A100AC23F1 /* UITableView+PearlReloadItems.m */; };
DA250A18195665A100AC23F1 /* UITableView+PearlReloadItems.h in Headers */ = {isa = PBXBuildFile; fileRef = DA250A14195665A100AC23F1 /* UITableView+PearlReloadItems.h */; };
DA250A19195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m in Sources */ = {isa = PBXBuildFile; fileRef = DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */; };
@@ -155,6 +149,21 @@
DA5BFA4B147E415C00F98B1E /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4A147E415C00F98B1E /* Foundation.framework */; };
DA5BFA4D147E415C00F98B1E /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */; };
DA5BFA4F147E415C00F98B1E /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA5BFA4E147E415C00F98B1E /* CoreData.framework */; };
DA5E0E5524589C9B0007FBA7 /* Icon-Small@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4624589C9A0007FBA7 /* Icon-Small@3x.png */; };
DA5E0E5624589C9B0007FBA7 /* Icon-Small-40.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4724589C9A0007FBA7 /* Icon-Small-40.png */; };
DA5E0E5724589C9B0007FBA7 /* Icon-Small-20.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4824589C9A0007FBA7 /* Icon-Small-20.png */; };
DA5E0E5824589C9B0007FBA7 /* Icon-76.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4924589C9A0007FBA7 /* Icon-76.png */; };
DA5E0E5924589C9B0007FBA7 /* Icon-Small@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4A24589C9A0007FBA7 /* Icon-Small@2x.png */; };
DA5E0E5A24589C9B0007FBA7 /* Icon-Small-40@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4B24589C9A0007FBA7 /* Icon-Small-40@2x.png */; };
DA5E0E5B24589C9B0007FBA7 /* Icon-76@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4C24589C9A0007FBA7 /* Icon-76@2x.png */; };
DA5E0E5C24589C9B0007FBA7 /* Icon-Small-20@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4D24589C9A0007FBA7 /* Icon-Small-20@3x.png */; };
DA5E0E5D24589C9B0007FBA7 /* Icon-Small.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4E24589C9A0007FBA7 /* Icon-Small.png */; };
DA5E0E5E24589C9B0007FBA7 /* Icon-83@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E4F24589C9A0007FBA7 /* Icon-83@2x.png */; };
DA5E0E5F24589C9B0007FBA7 /* Icon-Small-40@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E5024589C9A0007FBA7 /* Icon-Small-40@3x.png */; };
DA5E0E6024589C9B0007FBA7 /* Icon-60@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E5124589C9A0007FBA7 /* Icon-60@3x.png */; };
DA5E0E6124589C9B0007FBA7 /* iTunesArtwork@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E5224589C9B0007FBA7 /* iTunesArtwork@2x.png */; };
DA5E0E6224589C9B0007FBA7 /* Icon-Small-20@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E5324589C9B0007FBA7 /* Icon-Small-20@2x.png */; };
DA5E0E6324589C9B0007FBA7 /* Icon-60@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DA5E0E5424589C9B0007FBA7 /* Icon-60@2x.png */; };
DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701B716406A4100B61001 /* Accounts.framework */; settings = {ATTRIBUTES = (Required, ); }; };
DA6701DE16406B7300B61001 /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DD16406B7300B61001 /* Social.framework */; settings = {ATTRIBUTES = (Required, ); }; };
DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA6701DF16406BB400B61001 /* AdSupport.framework */; settings = {ATTRIBUTES = (Required, ); }; };
@@ -165,7 +174,7 @@
DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */; };
DA69540617D975D900BF294E /* icon_gears.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37841711E29500CF925C /* icon_gears.png */; };
DA69540717D975D900BF294E /* icon_gears@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD37851711E29500CF925C /* icon_gears@2x.png */; };
DA72BD7B19C1510C00E6ACFE /* UIView+FontScale.m in Sources */ = {isa = PBXBuildFile; fileRef = DACE2F6719BA6A2A0010F92E /* UIView+FontScale.m */; };
DA72E2302453B91700676D4F /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA72E22F2453B91700676D4F /* WebKit.framework */; };
DA73049D194E022700E72520 /* ui_spinner.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36511711E29400CF925C /* ui_spinner.png */; };
DA73049E194E022700E72520 /* ui_spinner@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD36521711E29400CF925C /* ui_spinner@2x.png */; };
DA73049F194E022B00E72520 /* ui_textfield.png in Resources */ = {isa = PBXBuildFile; fileRef = DABD365B1711E29400CF925C /* ui_textfield.png */; };
@@ -323,7 +332,6 @@
DABD3C1E1711E2DC00CF925C /* MPPreferencesViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */; };
DABD3C1F1711E2DC00CF925C /* MPTypeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = DABD3BED1711E2DC00CF925C /* MPTypeViewController.m */; };
DABD3C211711E2DC00CF925C /* MPiOSConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DABD3BF11711E2DC00CF925C /* MPiOSConfig.m */; };
DABD3C241711E2DC00CF925C /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DABD3BF81711E2DC00CF925C /* MasterPassword.entitlements */; };
DABD3C251711E2DC00CF925C /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DABD3BF91711E2DC00CF925C /* Settings.bundle */; };
DABD3C261711E2DC00CF925C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DABD3BFA1711E2DC00CF925C /* InfoPlist.strings */; };
DABD3C271711E2DC00CF925C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DABD3BFC1711E2DC00CF925C /* main.m */; };
@@ -465,7 +473,6 @@
93D391943675426839501BB8 /* MPLogsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPLogsViewController.h; sourceTree = "<group>"; };
93D391AA32F24290C424438E /* NSNotificationCenter+PearlEasyCleanup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSNotificationCenter+PearlEasyCleanup.h"; sourceTree = "<group>"; };
93D39246FC21C6E63E35D615 /* UICollectionView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UICollectionView+PearlReloadItems.h"; sourceTree = "<group>"; };
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPRootSegue.h; sourceTree = "<group>"; };
93D3924EE15017F8A12CB436 /* MPSitesViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPSitesViewController.m; sourceTree = "<group>"; };
93D392876BE5C011DE73B43F /* MPPopdownSegue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPPopdownSegue.h; sourceTree = "<group>"; };
93D392C5A6572DB0EB5B82C8 /* mpw-types.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = "mpw-types.c"; sourceTree = "<group>"; };
@@ -494,7 +501,6 @@
93D398C95847261903D781D3 /* NSError+PearlFullDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSError+PearlFullDescription.h"; sourceTree = "<group>"; };
93D3990D850D76A94C6B7A4D /* mpw-algorithm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mpw-algorithm.h"; sourceTree = "<group>"; };
93D3990E0CD1B5CF9FBB2C07 /* MPWebViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPWebViewController.m; sourceTree = "<group>"; };
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPRootSegue.m; sourceTree = "<group>"; };
93D3995B1D4DCE5A30D882BA /* MPCoachmarkViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPCoachmarkViewController.m; sourceTree = "<group>"; };
93D39975CE5AEC99E3F086C7 /* MPSiteCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPSiteCell.h; sourceTree = "<group>"; };
93D3999693660C89A7465F4E /* MPCoachmarkViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPCoachmarkViewController.h; sourceTree = "<group>"; };
@@ -573,19 +579,6 @@
DA0CC5891EB6B030009A8ED9 /* MPSiteEntity+CoreDataClass.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteEntity+CoreDataClass.m"; sourceTree = "<group>"; };
DA0CC58A1EB6B030009A8ED9 /* MPSiteEntity+CoreDataProperties.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MPSiteEntity+CoreDataProperties.h"; sourceTree = "<group>"; };
DA0CC58B1EB6B030009A8ED9 /* MPSiteEntity+CoreDataProperties.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MPSiteEntity+CoreDataProperties.m"; sourceTree = "<group>"; };
DA24EBB219DAD4D000FF010B /* Icon-60.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60.png"; sourceTree = "<group>"; };
DA24EBB319DAD4D000FF010B /* Icon-60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@2x.png"; sourceTree = "<group>"; };
DA24EBB419DAD4D000FF010B /* Icon-60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@3x.png"; sourceTree = "<group>"; };
DA24EBB519DAD4D000FF010B /* Icon-76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-76.png"; sourceTree = "<group>"; };
DA24EBB619DAD4D000FF010B /* Icon-76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-76@2x.png"; sourceTree = "<group>"; };
DA24EBB719DAD4D000FF010B /* Icon-Small-40.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40.png"; sourceTree = "<group>"; };
DA24EBB819DAD4D000FF010B /* Icon-Small-40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40@2x.png"; sourceTree = "<group>"; };
DA24EBB919DAD4D000FF010B /* Icon-Small-40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40@3x.png"; sourceTree = "<group>"; };
DA24EBBA19DAD4D000FF010B /* Icon-Small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small.png"; sourceTree = "<group>"; };
DA24EBBB19DAD4D000FF010B /* Icon-Small@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small@2x.png"; sourceTree = "<group>"; };
DA24EBBC19DAD4D000FF010B /* Icon-Small@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small@3x.png"; sourceTree = "<group>"; };
DA24EBBD19DAD4D000FF010B /* iTunesArtwork.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = iTunesArtwork.png; sourceTree = "<group>"; };
DA24EBBE19DAD4D000FF010B /* iTunesArtwork@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@2x.png"; sourceTree = "<group>"; };
DA24EBBF19DAD4D000FF010B /* icon.sketch */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = icon.sketch; sourceTree = "<group>"; };
DA24EBC119DAD4D000FF010B /* 32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = 32.png; sourceTree = "<group>"; };
DA24EBC219DAD4D000FF010B /* 320-480.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "320-480.png"; sourceTree = "<group>"; };
@@ -597,8 +590,6 @@
DA24EBC819DAD4D000FF010B /* background@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@2x.png"; sourceTree = "<group>"; };
DA24EBC919DAD4D000FF010B /* background@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "background@3x.png"; sourceTree = "<group>"; };
DA24EBCA19DAD4D000FF010B /* launch.sketch */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = launch.sketch; sourceTree = "<group>"; };
DA24EBE619DAD6DE00FF010B /* Icon-320.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-320.png"; sourceTree = "<group>"; };
DA24EBE719DAD6DE00FF010B /* Icon-64.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-64.png"; sourceTree = "<group>"; };
DA250A13195665A100AC23F1 /* UITableView+PearlReloadItems.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UITableView+PearlReloadItems.m"; sourceTree = "<group>"; };
DA250A14195665A100AC23F1 /* UITableView+PearlReloadItems.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UITableView+PearlReloadItems.h"; sourceTree = "<group>"; };
DA250A15195665A100AC23F1 /* UICollectionReusableView+PearlDequeue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UICollectionReusableView+PearlDequeue.m"; sourceTree = "<group>"; };
@@ -653,6 +644,21 @@
DA5BFA4A147E415C00F98B1E /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
DA5BFA4C147E415C00F98B1E /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
DA5BFA4E147E415C00F98B1E /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; };
DA5E0E4624589C9A0007FBA7 /* Icon-Small@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small@3x.png"; sourceTree = "<group>"; };
DA5E0E4724589C9A0007FBA7 /* Icon-Small-40.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40.png"; sourceTree = "<group>"; };
DA5E0E4824589C9A0007FBA7 /* Icon-Small-20.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-20.png"; sourceTree = "<group>"; };
DA5E0E4924589C9A0007FBA7 /* Icon-76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-76.png"; sourceTree = "<group>"; };
DA5E0E4A24589C9A0007FBA7 /* Icon-Small@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small@2x.png"; sourceTree = "<group>"; };
DA5E0E4B24589C9A0007FBA7 /* Icon-Small-40@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40@2x.png"; sourceTree = "<group>"; };
DA5E0E4C24589C9A0007FBA7 /* Icon-76@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-76@2x.png"; sourceTree = "<group>"; };
DA5E0E4D24589C9A0007FBA7 /* Icon-Small-20@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-20@3x.png"; sourceTree = "<group>"; };
DA5E0E4E24589C9A0007FBA7 /* Icon-Small.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small.png"; sourceTree = "<group>"; };
DA5E0E4F24589C9A0007FBA7 /* Icon-83@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-83@2x.png"; sourceTree = "<group>"; };
DA5E0E5024589C9A0007FBA7 /* Icon-Small-40@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-40@3x.png"; sourceTree = "<group>"; };
DA5E0E5124589C9A0007FBA7 /* Icon-60@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@3x.png"; sourceTree = "<group>"; };
DA5E0E5224589C9B0007FBA7 /* iTunesArtwork@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "iTunesArtwork@2x.png"; sourceTree = "<group>"; };
DA5E0E5324589C9B0007FBA7 /* Icon-Small-20@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-Small-20@2x.png"; sourceTree = "<group>"; };
DA5E0E5424589C9B0007FBA7 /* Icon-60@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Icon-60@2x.png"; sourceTree = "<group>"; };
DA5E5C3C1723681B003798D8 /* Square-bottom.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "Square-bottom.png"; path = "Dividers/Square-bottom.png"; sourceTree = "<group>"; };
DA6701B716406A4100B61001 /* Accounts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accounts.framework; path = System/Library/Frameworks/Accounts.framework; sourceTree = SDKROOT; };
DA6701DD16406B7300B61001 /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
@@ -662,6 +668,7 @@
DA67460B18DE7F0C00DFE240 /* Exo2.0-ExtraBold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-ExtraBold.otf"; sourceTree = "<group>"; };
DA67460C18DE7F0C00DFE240 /* Exo2.0-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Exo2.0-Bold.otf"; sourceTree = "<group>"; };
DA70EC7F1811B13C00F65DB2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
DA72E22F2453B91700676D4F /* WebKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; };
DA771FE41E6E1595004D7EDE /* MasterPassword-Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MasterPassword-Prefix.pch"; sourceTree = "<group>"; };
DA854C8118D4CFBF00106317 /* avatar-add@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add@2x.png"; sourceTree = "<group>"; };
DA854C8218D4CFBF00106317 /* avatar-add.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "avatar-add.png"; sourceTree = "<group>"; };
@@ -1597,6 +1604,7 @@
DAE2725A19C93B8E007C5262 /* StoreKit.framework in Frameworks */,
DA6701E016406BB400B61001 /* AdSupport.framework in Frameworks */,
DA6701DE16406B7300B61001 /* Social.framework in Frameworks */,
DA72E2302453B91700676D4F /* WebKit.framework in Frameworks */,
DA6701B816406A4100B61001 /* Accounts.framework in Frameworks */,
DABB981615100B4000B05417 /* SystemConfiguration.framework in Frameworks */,
DA672D3014F9413D004A189C /* libPearl.a in Frameworks */,
@@ -1685,21 +1693,21 @@
DA24EBB119DAD4D000FF010B /* icon */ = {
isa = PBXGroup;
children = (
DA24EBB219DAD4D000FF010B /* Icon-60.png */,
DA24EBB319DAD4D000FF010B /* Icon-60@2x.png */,
DA24EBB419DAD4D000FF010B /* Icon-60@3x.png */,
DA24EBE719DAD6DE00FF010B /* Icon-64.png */,
DA24EBB519DAD4D000FF010B /* Icon-76.png */,
DA24EBB619DAD4D000FF010B /* Icon-76@2x.png */,
DA24EBE619DAD6DE00FF010B /* Icon-320.png */,
DA24EBB719DAD4D000FF010B /* Icon-Small-40.png */,
DA24EBB819DAD4D000FF010B /* Icon-Small-40@2x.png */,
DA24EBB919DAD4D000FF010B /* Icon-Small-40@3x.png */,
DA24EBBA19DAD4D000FF010B /* Icon-Small.png */,
DA24EBBB19DAD4D000FF010B /* Icon-Small@2x.png */,
DA24EBBC19DAD4D000FF010B /* Icon-Small@3x.png */,
DA24EBBD19DAD4D000FF010B /* iTunesArtwork.png */,
DA24EBBE19DAD4D000FF010B /* iTunesArtwork@2x.png */,
DA5E0E5424589C9B0007FBA7 /* Icon-60@2x.png */,
DA5E0E5124589C9A0007FBA7 /* Icon-60@3x.png */,
DA5E0E4924589C9A0007FBA7 /* Icon-76.png */,
DA5E0E4C24589C9A0007FBA7 /* Icon-76@2x.png */,
DA5E0E4F24589C9A0007FBA7 /* Icon-83@2x.png */,
DA5E0E4824589C9A0007FBA7 /* Icon-Small-20.png */,
DA5E0E5324589C9B0007FBA7 /* Icon-Small-20@2x.png */,
DA5E0E4D24589C9A0007FBA7 /* Icon-Small-20@3x.png */,
DA5E0E4724589C9A0007FBA7 /* Icon-Small-40.png */,
DA5E0E4B24589C9A0007FBA7 /* Icon-Small-40@2x.png */,
DA5E0E5024589C9A0007FBA7 /* Icon-Small-40@3x.png */,
DA5E0E4E24589C9A0007FBA7 /* Icon-Small.png */,
DA5E0E4A24589C9A0007FBA7 /* Icon-Small@2x.png */,
DA5E0E4624589C9A0007FBA7 /* Icon-Small@3x.png */,
DA5E0E5224589C9B0007FBA7 /* iTunesArtwork@2x.png */,
);
path = icon;
sourceTree = "<group>";
@@ -1775,6 +1783,7 @@
DA5BFA47147E415C00F98B1E /* Frameworks */ = {
isa = PBXGroup;
children = (
DA72E22F2453B91700676D4F /* WebKit.framework */,
DAFC1F6F2439795000D1CD66 /* CoreServices.framework */,
DAB7AE5C1F3D752900C856B1 /* libjson-c.a */,
DA6701B716406A4100B61001 /* Accounts.framework */,
@@ -2770,8 +2779,6 @@
93D39B050DD5F55E9794EFD4 /* MPPopdownSegue.m */,
DABD3BEA1711E2DC00CF925C /* MPPreferencesViewController.h */,
DABD3BEB1711E2DC00CF925C /* MPPreferencesViewController.m */,
93D3924D6F77E6BF41AC32D3 /* MPRootSegue.h */,
93D399493FEDDE74DD1A0C15 /* MPRootSegue.m */,
93D39730673227EFF6DEFF19 /* MPSetupViewController.h */,
93D39A28369954D147E239BA /* MPSetupViewController.m */,
93D39975CE5AEC99E3F086C7 /* MPSiteCell.h */,
@@ -3152,7 +3159,7 @@
DA5BFA42147E415C00F98B1E /* Resources */,
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */,
4A87858EE3659604089E2F9F /* [CP] Embed Pods Frameworks */,
DA3C4EB32439438B00A6C4A8 /* Upload Sentry dSYM */,
DA3C4EB32439438B00A6C4A8 /* Sentry dSYM Upload */,
);
buildRules = (
);
@@ -3286,6 +3293,7 @@
DA69540617D975D900BF294E /* icon_gears.png in Resources */,
DA67460D18DE7F0C00DFE240 /* Exo2.0-Thin.otf in Resources */,
DA4522451902355C008F650A /* icon_book@2x.png in Resources */,
DA5E0E5F24589C9B0007FBA7 /* Icon-Small-40@3x.png in Resources */,
DA32D04919D2F417004F3F0E /* thumb_fuel@2x.png in Resources */,
DAFC1F73243989FB00D1CD66 /* Launch Screen.storyboard in Resources */,
DABD39371711E29700CF925C /* avatar-0.png in Resources */,
@@ -3305,11 +3313,9 @@
DA67461018DE7F0C00DFE240 /* Exo2.0-Bold.otf in Resources */,
DABD39401711E29700CF925C /* avatar-13.png in Resources */,
DA32D07C19D7D784004F3F0E /* background.png in Resources */,
DA24EBE919DAD6DE00FF010B /* Icon-64.png in Resources */,
DABD39411711E29700CF925C /* avatar-13@2x.png in Resources */,
DABD39421711E29700CF925C /* avatar-14.png in Resources */,
DABD39431711E29700CF925C /* avatar-14@2x.png in Resources */,
DA24EBE819DAD6DE00FF010B /* Icon-320.png in Resources */,
DAA1764419D8B82B0044227B /* personal_pw.png in Resources */,
DABD39441711E29700CF925C /* avatar-15.png in Resources */,
DABD39451711E29700CF925C /* avatar-15@2x.png in Resources */,
@@ -3363,11 +3369,14 @@
DA32D00919CF5C55004F3F0E /* icon_question.png in Resources */,
DABD39561711E29700CF925C /* avatar-6@2x.png in Resources */,
DA32D07B19D7D784004F3F0E /* background@2x.png in Resources */,
DA5E0E6224589C9B0007FBA7 /* Icon-Small-20@2x.png in Resources */,
DABD39571711E29700CF925C /* avatar-7.png in Resources */,
DABD39581711E29700CF925C /* avatar-7@2x.png in Resources */,
DA5E0E5C24589C9B0007FBA7 /* Icon-Small-20@3x.png in Resources */,
DABD39591711E29700CF925C /* avatar-8.png in Resources */,
DA32D00A19CF5C55004F3F0E /* icon_question@2x.png in Resources */,
DAA1764019D8B82B0044227B /* site_new.png in Resources */,
DA5E0E5924589C9B0007FBA7 /* Icon-Small@2x.png in Resources */,
DABD395A1711E29700CF925C /* avatar-8@2x.png in Resources */,
DABD395B1711E29700CF925C /* avatar-9.png in Resources */,
DAA1765419D8B82B0044227B /* choose_type.png in Resources */,
@@ -3377,23 +3386,30 @@
DA945C8717E3F3FD0053236B /* Images.xcassets in Resources */,
DA32D04E19D2F59B004F3F0E /* meter_fuel@3x.png in Resources */,
DA25C5FE197DBF200046CDCF /* icon_thumbs-up.png in Resources */,
DA5E0E5624589C9B0007FBA7 /* Icon-Small-40.png in Resources */,
DABD39871711E29700CF925C /* SourceCodePro-Black.otf in Resources */,
DABD39881711E29700CF925C /* SourceCodePro-ExtraLight.otf in Resources */,
DABD39A01711E29700CF925C /* icon_action.png in Resources */,
DA5E0E5524589C9B0007FBA7 /* Icon-Small@3x.png in Resources */,
DABD39A11711E29700CF925C /* icon_action@2x.png in Resources */,
DABD39F21711E29700CF925C /* icon_cancel.png in Resources */,
DA25C5FB197CCAE00046CDCF /* icon_delete@2x.png in Resources */,
DA5E0E5724589C9B0007FBA7 /* Icon-Small-20.png in Resources */,
DA29993219C9132F00AF7DF1 /* thumb_generated_login@3x.png in Resources */,
DA29992F19C86F5700AF7DF1 /* thumb_generated_login@2x.png in Resources */,
DA73049F194E022B00E72520 /* ui_textfield.png in Resources */,
DABD39F31711E29700CF925C /* icon_cancel@2x.png in Resources */,
DA5E0E5B24589C9B0007FBA7 /* Icon-76@2x.png in Resources */,
DABD3A261711E29700CF925C /* icon_edit.png in Resources */,
DABD3A271711E29700CF925C /* icon_edit@2x.png in Resources */,
DA24EBAF19DAD08C00FF010B /* tip_basic_black_top@2x.png in Resources */,
DABD3A3A1711E29700CF925C /* icon_find.png in Resources */,
DA5E0E6324589C9B0007FBA7 /* Icon-60@2x.png in Resources */,
DABD3A3B1711E29700CF925C /* icon_find@2x.png in Resources */,
DAA1765319D8B82B0044227B /* choose_type@2x.png in Resources */,
DA5E0E6124589C9B0007FBA7 /* iTunesArtwork@2x.png in Resources */,
DABD3AA01711E29800CF925C /* icon_pause.png in Resources */,
DA5E0E5A24589C9B0007FBA7 /* Icon-Small-40@2x.png in Resources */,
DABD3AA11711E29800CF925C /* icon_pause@2x.png in Resources */,
DAA1764219D8B82B0044227B /* settings.png in Resources */,
DABD3AAA1711E29800CF925C /* icon_person.png in Resources */,
@@ -3418,7 +3434,6 @@
DABD3B951711E29800CF925C /* pull-down.png in Resources */,
DABD3B961711E29800CF925C /* pull-down@2x.png in Resources */,
DABD3B971711E29800CF925C /* pull-up.png in Resources */,
DA24EBEA19DAD6EE00FF010B /* Icon-Small.png in Resources */,
DA8495321A93049300B3053D /* icon_down@2x.png in Resources */,
DABD3B981711E29800CF925C /* pull-up@2x.png in Resources */,
DA7304A0194E022B00E72520 /* ui_textfield@2x.png in Resources */,
@@ -3427,10 +3442,9 @@
DA452249190628A1008F650A /* icon_wrench.png in Resources */,
DAA1764519D8B82B0044227B /* name_new@2x.png in Resources */,
DA45224819062899008F650A /* icon_settings@2x.png in Resources */,
DA5E0E5E24589C9B0007FBA7 /* Icon-83@2x.png in Resources */,
DA854C8418D4CFBF00106317 /* avatar-add.png in Resources */,
DA24EBEB19DAD6EE00FF010B /* Icon-Small@2x.png in Resources */,
DAA1764B19D8B82B0044227B /* login_name@2x.png in Resources */,
DABD3C241711E2DC00CF925C /* MasterPassword.entitlements in Resources */,
DABD3C251711E2DC00CF925C /* Settings.bundle in Resources */,
DABD3C261711E2DC00CF925C /* InfoPlist.strings in Resources */,
DA32D05119D3D107004F3F0E /* icon_meter.png in Resources */,
@@ -3444,16 +3458,18 @@
DAA1764619D8B82B0044227B /* name_new.png in Resources */,
DA45224B190628B2008F650A /* icon_gear.png in Resources */,
DAAA1D4123CD145000F3DF56 /* Storyboard.storyboard in Resources */,
DA5E0E6024589C9B0007FBA7 /* Icon-60@3x.png in Resources */,
DA8495311A93049300B3053D /* icon_down.png in Resources */,
DA5E0E5D24589C9B0007FBA7 /* Icon-Small.png in Resources */,
DA25C5FF197DBF200046CDCF /* icon_thumbs-up@2x.png in Resources */,
DAE1EF2217E942DE00BC0086 /* Localizable.strings in Resources */,
DA5A09DF171A70E4005284AB /* play.png in Resources */,
DA24EBEC19DAD6EE00FF010B /* Icon-Small@3x.png in Resources */,
DA5A09E0171A70E4005284AB /* play@2x.png in Resources */,
DA5A09EA171BB0F7005284AB /* unlocked.png in Resources */,
DAA1762819D89B610044227B /* thumb_ios_integration.png in Resources */,
DA5A09EB171BB0F7005284AB /* unlocked@2x.png in Resources */,
DAA1764F19D8B82B0044227B /* counter@2x.png in Resources */,
DA5E0E5824589C9B0007FBA7 /* Icon-76.png in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -3499,7 +3515,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
DA3C4EB32439438B00A6C4A8 /* Upload Sentry dSYM */ = {
DA3C4EB32439438B00A6C4A8 /* Sentry dSYM Upload */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 8;
files = (
@@ -3507,15 +3523,16 @@
inputFileListPaths = (
);
inputPaths = (
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}",
);
name = "Upload Sentry dSYM";
name = "Sentry dSYM Upload";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = "/bin/sh -e";
shellScript = "if hash sentry-cli 2>/dev/null; then\n if ! ERROR=$(SENTRY_ORG=lyndir SENTRY_PROJECT=masterpassword-ios sentry-cli upload-dif --log-level info \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null); then\n echo >&2 \"warning: sentry-cli: $ERROR\"\n fi\nelse\n echo >&2 \"warning: sentry-cli not installed: try brew install getsentry/tools/sentry-cli\"\nfi\n";
shellScript = "if ! hash sentry-cli 2>/dev/null; then\n echo >&2 \"error: sentry-cli not installed. Try brew install getsentry/tools/sentry-cli\"\n exit 1\nfi\n\nSENTRY_ORG=lyndir SENTRY_PROJECT=masterpassword-ios sentry-cli upload-dif --log-level info \"$DWARF_DSYM_FOLDER_PATH\"\n";
showEnvVarsInLog = 0;
};
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */ = {
@@ -3618,7 +3635,6 @@
93D399D7E08A142776A74CB8 /* MPOverlayViewController.m in Sources */,
DA46021D23D5E30B00398FF4 /* MPSecrets.m in Sources */,
93D39A27F2506C6FEEF9C588 /* MPAlgorithmV2.m in Sources */,
93D39B429C67A62E29DC02DA /* MPRootSegue.m in Sources */,
93D392FD5E2052F7D7DB3774 /* NSString+MPMarkDown.m in Sources */,
93D395B715D15F2B56F2A2EE /* mpw-types.c in Sources */,
93D39943D01E70DAC3B0DF76 /* mpw-util.c in Sources */,
@@ -3664,7 +3680,6 @@
DAFE4A2E15039824003ABA7C /* PearlStrings.m in Sources */,
DAFE4A3015039824003ABA7C /* PearlStringUtils.m in Sources */,
DAFE4A3715039824003ABA7C /* PearlKeyChain.m in Sources */,
DA72BD7B19C1510C00E6ACFE /* UIView+FontScale.m in Sources */,
DA250A17195665A100AC23F1 /* UITableView+PearlReloadItems.m in Sources */,
DAFE4A4115039824003ABA7C /* PearlArrayTVC.m in Sources */,
DAFE4A4315039824003ABA7C /* PearlBoxView.m in Sources */,
@@ -3865,7 +3880,6 @@
SKIP_INSTALL = YES;
STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
TARGETED_DEVICE_FAMILY = "1,2";
WARNING_CFLAGS = "-Wno-float-conversion";
};
@@ -3994,10 +4008,6 @@
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
);
INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib\"",
@@ -4005,7 +4015,7 @@
);
OTHER_CFLAGS = (
"-DMPW_SODIUM=1",
"-DMPW_CPERCIVA=0",
"-DMPW_JSON=1",
);
PRODUCT_BUNDLE_IDENTIFIER = com.lyndir.lhunath.MasterPassword;
PRODUCT_NAME = MasterPassword;
@@ -4013,9 +4023,6 @@
SKIP_INSTALL = NO;
STRIP_INSTALLED_PRODUCT = YES;
STRIP_SWIFT_SYMBOLS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "Source/iOS/MasterPassword-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 4.0;
};
name = Debug;
};
@@ -4028,7 +4035,6 @@
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = NO;
CODE_SIGN_ENTITLEMENTS = Source/iOS/MasterPassword.entitlements;
CODE_SIGN_STYLE = Manual;
EXCLUDED_SOURCE_FILE_NAMES = libDCIntrospect.a;
GCC_C_LANGUAGE_STANDARD = c11;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
GCC_WARN_ABOUT_DEPRECATED_FUNCTIONS = NO;
@@ -4038,10 +4044,6 @@
"\"$(PROJECT_DIR)/../lib/libjson-c/build-ios~/out/include\"",
);
INFOPLIST_FILE = "Source/iOS/MasterPassword-Info.plist";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"\"$(PROJECT_DIR)/../lib/libsodium/build-ios~/out/lib\"",
@@ -4049,7 +4051,7 @@
);
OTHER_CFLAGS = (
"-DMPW_SODIUM=1",
"-DMPW_CPERCIVA=0",
"-DMPW_JSON=1",
);
PRODUCT_BUNDLE_IDENTIFIER = com.lyndir.lhunath.MasterPassword;
PRODUCT_NAME = MasterPassword;
@@ -4057,10 +4059,6 @@
SKIP_INSTALL = NO;
STRIP_INSTALLED_PRODUCT = YES;
STRIP_SWIFT_SYMBOLS = YES;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OBJC_BRIDGING_HEADER = "Source/iOS/MasterPassword-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 4.0;
};
name = Release;
};

View File

@@ -73,6 +73,13 @@
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
<EnvironmentVariables>
<EnvironmentVariable
key = "TERM"
value = "color"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Debug"

View File

@@ -101,7 +101,6 @@
DA5E5D011724A667003798D8 /* MPKey.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CAE1724A667003798D8 /* MPKey.m */; };
DA5E5D031724A667003798D8 /* MPMacAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB41724A667003798D8 /* MPMacAppDelegate.m */; };
DA5E5D041724A667003798D8 /* MPMacConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CB61724A667003798D8 /* MPMacConfig.m */; };
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CBF1724A667003798D8 /* MasterPassword.entitlements */; };
DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CC21724A667003798D8 /* InfoPlist.strings */; };
DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = DA5E5CC41724A667003798D8 /* MainMenu.xib */; };
DA5E5D0C1724A667003798D8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = DA5E5CC61724A667003798D8 /* main.m */; };
@@ -2329,7 +2328,7 @@
DAD9B5EE1762CA3A001835F9 /* Copy LoginHelper */,
DA6556E314D55F3000841C99 /* Run Script: GIT version -> Info.plist */,
43E5966C8C236E86824DDADE /* [CP] Embed Pods Frameworks */,
DA3C4EB2243941AE00A6C4A8 /* Upload Sentry dSYM */,
DA3C4EB2243941AE00A6C4A8 /* Sentry dSYM Upload */,
);
buildRules = (
);
@@ -2388,7 +2387,7 @@
CLASSPREFIX = MP;
LastSwiftUpdateCheck = 0720;
LastTestingUpgradeCheck = 0510;
LastUpgradeCheck = 1140;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = Lyndir;
TargetAttributes = {
DA1C7AA61F1A8F24009A3551 = {
@@ -2535,7 +2534,6 @@
DACA29671705DF81002C6C22 /* SourceCodePro-ExtraLight.otf in Resources */,
DACA29681705DF81002C6C22 /* SourceCodePro-Black.otf in Resources */,
DACA29741705E1A8002C6C22 /* dictionary.lst in Resources */,
DA5E5D081724A667003798D8 /* MasterPassword.entitlements in Resources */,
DA5E5D0A1724A667003798D8 /* InfoPlist.strings in Resources */,
DA5E5D0B1724A667003798D8 /* MainMenu.xib in Resources */,
DA0933CC1747AD2D00DE1CEF /* shot-laptop-leaning-iphone.png in Resources */,
@@ -2585,7 +2583,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-MasterPassword-macOS/Pods-MasterPassword-macOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
DA3C4EB2243941AE00A6C4A8 /* Upload Sentry dSYM */ = {
DA3C4EB2243941AE00A6C4A8 /* Sentry dSYM Upload */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 8;
files = (
@@ -2593,15 +2591,16 @@
inputFileListPaths = (
);
inputPaths = (
"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}",
);
name = "Upload Sentry dSYM";
name = "Sentry dSYM Upload";
outputFileListPaths = (
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 1;
shellPath = "/bin/sh -e";
shellScript = "if hash sentry-cli 2>/dev/null; then\n if ! ERROR=$(SENTRY_ORG=lyndir SENTRY_PROJECT=masterpassword-macos sentry-cli upload-dif --log-level info \"$DWARF_DSYM_FOLDER_PATH\" 2>&1 >/dev/null); then\n echo >&2 \"warning: sentry-cli: $ERROR\"\n fi\nelse\n echo >&2 \"warning: sentry-cli not installed: try brew install getsentry/tools/sentry-cli\"\nfi\n";
shellScript = "if ! hash sentry-cli 2>/dev/null; then\n echo >&2 \"error: sentry-cli not installed. Try brew install getsentry/tools/sentry-cli\"\n exit 1\nfi\n\nSENTRY_ORG=lyndir SENTRY_PROJECT=masterpassword-macos sentry-cli upload-dif --log-level info \"$DWARF_DSYM_FOLDER_PATH\"\n";
showEnvVarsInLog = 0;
};
DA4EF9CB19FD4B600032ECB5 /* Run Script: genassets */ = {
@@ -3025,6 +3024,7 @@
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -3126,6 +3126,7 @@
CLANG_WARN_OBJC_RECEIVER_WEAK = NO;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -3201,6 +3202,7 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = Source/Mac/MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";
@@ -3240,6 +3242,7 @@
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = Source/Mac/MasterPassword.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
ENABLE_HARDENED_RUNTIME = YES;
GCC_PREFIX_HEADER = "Source/MasterPassword-Prefix.pch";

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA5BFA43147E415C00F98B1E"
BuildableName = "Master Password.app"
BlueprintName = "MasterPassword-macOS"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
@@ -59,6 +50,17 @@
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "TERM"
value = "color"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Debug"

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AC61F1A8FD8009A3551"
BuildableName = "mpw-bench"
BlueprintName = "mpw-bench"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
@@ -59,6 +50,17 @@
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "TERM"
value = "color"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA1C7AA61F1A8F24009A3551"
BuildableName = "mpw-cli"
BlueprintName = "mpw-cli"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
@@ -81,6 +72,13 @@
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "TERM"
value = "color"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1140"
version = "1.3">
LastUpgradeVersion = "1200"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "DA67743A1A474A03004F356A"
BuildableName = "mpw-test"
BlueprintName = "mpw-test"
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
@@ -60,6 +51,17 @@
ReferencedContainer = "container:MasterPassword-macOS.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "TERM"
value = "color"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<LocationScenarioReference
identifier = "com.apple.dt.IDEFoundation.CurrentLocationScenarioIdentifier"
referenceType = "1">
</LocationScenarioReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Debug"

View File

@@ -28,3 +28,14 @@ target 'MasterPassword-macOS' do
pod 'KCOrderedAccessorFix'
pod 'JRSwizzle'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
if target.name == 'Countly-iOS' || target.name == 'Countly-macOS'
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ['$(inherited)']
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'COUNTLY_EXCLUDE_IDFA=1'
end
end
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 843 B

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 879 KiB

After

Width:  |  Height:  |  Size: 879 KiB

View File

@@ -34,7 +34,7 @@ icons=(
# 57@1@iphone@:Icon.png
# 29@1@iphone@:Icon-Small.png
# iPad
# 76@1@ipad@7.0:Icon-76.png
76@1@ipad@7.0:Icon-76.png
152@2@ipad@7.0:Icon-76@2x.png
167@2@ipad@9.0:Icon-83@2x.png
20@1@ipad@7.0:Icon-Small-20.png

View File

@@ -46,9 +46,10 @@ esac
description=$(git describe --always --dirty --long --match "*-$platform*")
version=${description%-g*} build=${version##*-} version=${version%-$build}
version=${version//-$platform/} version=${version//-/.} commit=${description##*-g}
IFS=. read major minor build <<< "$version"
addPlistWithKey GITDescription string "$description"
setPlistWithKey CFBundleVersion "$(hr "${version%%.*}" 14).${version#*.}"
setPlistWithKey CFBundleVersion "$(hr "$major" 14)$(printf '%02d' "$minor")$(printf '%02d' "$build")"
setPlistWithKey CFBundleShortVersionString "$version"
setSettingWithTitle "Build" "$commit"

View File

@@ -621,7 +621,7 @@ static NSOperationQueue *_mpwQueue = nil;
- (NSString *)exportLoginForSite:(MPSiteEntity *)site usingKey:(MPKey *)key {
if (!(site.type & MPSiteFeatureExportContent) || site.loginGenerated || ![site.loginName length])
if (site.loginGenerated || ![site.loginName length])
return nil;
__block NSData *state = nil;

View File

@@ -117,7 +117,7 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
[self performPurchaseProductWithIdentifier:productIdentifier quantity:quantity];
}]];
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:controller animated:YES completion:nil];
[self.window.rootViewController presentViewController:controller animated:YES completion:nil];
return;
}
#endif
@@ -158,16 +158,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
MPError( error, @"StoreKit request (%@) failed.", request );
MPError( error, @"StoreKit request failed." );
#if TARGET_OS_IPHONE
PearlMainQueue( ^{
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Purchase Failed" message:
strf( @"%@\n\n%@", error.localizedDescription,
@"Ensure you are online and try logging out and back into iTunes from your device's Settings." )
@"Could not reach Apple's iTunes Store. Make sure you're connected to the Internet and try again." )
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:controller animated:YES completion:nil];
[self.window.rootViewController presentViewController:controller animated:YES completion:nil];
} );
#endif
}
@@ -197,18 +197,16 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
forKey:transaction.payment.productIdentifier];
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
SKProduct *product = self.products[transaction.payment.productIdentifier];
[attributes addEntriesFromDictionary:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"success",
@"quantity": @(transaction.payment.quantity).description,
}];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:attributes];
}
SKProduct *product = self.products[transaction.payment.productIdentifier];
[attributes addEntriesFromDictionary:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"success",
@"quantity": @(transaction.payment.quantity).description,
}];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:attributes];
break;
}
case SKPaymentTransactionStateRestored: {
@@ -224,18 +222,27 @@ PearlAssociatedObjectProperty( NSMutableArray*, ProductObservers, productObserve
MPError( transaction.error, @"Transaction failed: %@.", transaction.payment.productIdentifier );
[queue finishTransaction:transaction];
if ([[MPConfig get].sendInfo boolValue]) {
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"failed",
@"quantity": @(transaction.payment.quantity).description,
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
}];
}
#if TARGET_OS_IPHONE
PearlMainQueue( ^{
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Purchase Failed" message:
strf( @"%@\n\n%@", transaction.error.localizedDescription,
@"Could not reach Apple's iTunes Store. Make sure you're connected to the Internet and try again." )
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self.window.rootViewController presentViewController:controller animated:YES completion:nil];
} );
#endif
SKProduct *product = self.products[transaction.payment.productIdentifier];
[Countly.sharedInstance recordEvent:@"purchase" segmentation:@{
@"id": product.productIdentifier,
@"name": product.localizedTitle,
@"price": product.price.description,
@"currency": [product.priceLocale objectForKey:NSLocaleCurrencyCode],
@"state" : @"failed",
@"quantity": @(transaction.payment.quantity).description,
@"reason" : [transaction.error localizedFailureReason]?: [transaction.error localizedDescription],
}];
break;
}
}

View File

@@ -173,13 +173,11 @@
else
dbg( @"Automatic login failed for user: %@", user.userID );
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"failed",
@"algorithm": @(user.algorithm.version).description,
}];
}
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"failed",
@"algorithm": @(user.algorithm.version).description,
}];
return NO;
}
@@ -203,15 +201,14 @@
}
@try {
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance userLoggedIn:user.userID];
[SentrySDK setUser:[[SentryUser alloc] initWithUserId:user.userID]];
[Countly.sharedInstance userLoggedIn:user.userID];
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"success",
@"algorithm": @(user.algorithm.version).description,
}];
}
[Countly.sharedInstance recordEvent:@"login" segmentation:@{
@"method" : password? @"Password": @"Automatic",
@"state" : @"success",
@"algorithm": @(user.algorithm.version).description,
}];
}
@catch (id exception) {
err( @"While setting username: %@", exception );
@@ -252,18 +249,18 @@
masterPassword = PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Enter Old Master Password" message:
strf( @"Your old master password is required to migrate the stored password for %@", site.name )
strf( @"Your old master password is required to unlock the stored password for: <%@>", site.name )
preferredStyle:UIAlertControllerStyleAlert];
[controller addTextFieldWithConfigurationHandler:nil];
[controller addAction:[UIAlertAction actionWithTitle:@"Migrate" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *_Nonnull action) {
setResult( controller.textFields.firstObject.text );
}]];
[controller addAction:[UIAlertAction actionWithTitle:@"Don't Migrate" style:UIAlertActionStyleCancel handler:
[controller addAction:[UIAlertAction actionWithTitle:@"Leave It" style:UIAlertActionStyleCancel handler:
^(UIAlertAction *_Nonnull action) {
setResult( nil );
}]];
[self.navigationController presentViewController:controller animated:YES completion:nil];
[self.window.rootViewController presentViewController:controller animated:YES completion:nil];
} );
} );
#endif

View File

@@ -20,7 +20,7 @@
#if TARGET_OS_IPHONE
@interface MPAppDelegate_Shared : PearlAppDelegate
@interface MPAppDelegate_Shared : UIResponder<UIApplicationDelegate, PearlConfigDelegate>
#else

View File

@@ -116,11 +116,9 @@ static MPAppDelegate_Shared *instance;
if (self.activeUserOID == activeUserOID || [self.activeUserOID isEqual:activeUserOID])
return;
if (self.key)
self.key = nil;
if ([[MPConfig get].sendInfo boolValue])
[Countly.sharedInstance userLoggedOut];
self.key = nil;
[SentrySDK setUser:nil];
[Countly.sharedInstance userLoggedOut];
self.activeUserOID = activeUserOID;

View File

@@ -30,6 +30,7 @@
- (id)managedObjectContextChanged:(void ( ^ )(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects))changedBlock;
- (MPFixableResult)findAndFixInconsistenciesSaveInContext:(NSManagedObjectContext *)context;
- (void)retryCorruptStore;
- (void)deleteAndResetStore;
/** @param completion The block to execute after adding the site, executed from the main thread with the new site in the main MOC. */
@@ -39,8 +40,9 @@
askImportPassword:(NSString *( ^ )(NSString *userName))importPassword
askUserPassword:(NSString *( ^ )(NSString *userName))userPassword
result:(void ( ^ )(NSError *error))resultBlock;
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock;
- (NSString *)exportSitesFor:(MPUserEntity *)user
revealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
error:(__autoreleasing NSError **)error;
@end

View File

@@ -19,6 +19,8 @@
#import "MPAppDelegate_Store.h"
#import "mpw-marshal.h"
#import "mpw-util.h"
#import "MPAppDelegate_InApp.h"
#import "MPSecrets.h"
#if TARGET_OS_IPHONE
#define STORE_OPTIONS NSPersistentStoreFileProtectionKey : NSFileProtectionComplete,
@@ -181,12 +183,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
} );
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
if (self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
[self.storeQueue addOperationWithBlock:^{
// Do nothing if already fully set up, otherwise (re-)load the store.
if (self.storeCoordinator && self.mainManagedObjectContext && self.privateManagedObjectContext)
if (self.mainManagedObjectContext && self.privateManagedObjectContext)
return;
// Unregister any existing observers and contexts.
@@ -199,6 +201,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
[self.privateManagedObjectContext reset];
self.privateManagedObjectContext = nil;
}];
NSError *error = nil;
for (NSPersistentStore *store in self.storeCoordinator.persistentStores)
if (![self.storeCoordinator removePersistentStore:store error:&error] || error) {
MPError( error, @"Couldn't remove persistence store from coordinator." );
return;
}
// Don't load when the store is corrupted.
if ([self.storeCorrupted boolValue])
@@ -207,12 +215,36 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Check if migration is necessary.
[self migrateStore];
// Create a new store coordinator.
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
MPError( error, @"Couldn't create our application support directory." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStoreURL
options:@{
NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
MPError( error, @"Failed to open store." );
PearlRemoveNotificationObserversFrom( self.mainManagedObjectContext );
self.mainManagedObjectContext = nil;
self.privateManagedObjectContext = nil;
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
// Install managed object contexts and observers.
self.privateManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[self.privateManagedObjectContext performBlockAndWait:^{
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
}];
self.privateManagedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
self.privateManagedObjectContext.persistentStoreCoordinator = self.storeCoordinator;
self.mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
self.mainManagedObjectContext.parentContext = self.privateManagedObjectContext;
@@ -232,28 +264,6 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
}];
} );
// Create a new store coordinator.
NSError *error = nil;
NSURL *localStoreURL = [self localStoreURL];
if (![[NSFileManager defaultManager] createDirectoryAtURL:[localStoreURL URLByDeletingLastPathComponent]
withIntermediateDirectories:YES attributes:nil error:&error]) {
MPError( error, @"Couldn't create our application support directory." );
return;
}
if (![self.storeCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self localStoreURL]
options:@{
NSMigratePersistentStoresAutomaticallyOption: @YES,
NSInferMappingModelAutomaticallyOption : @YES,
STORE_OPTIONS
} error:&error]) {
MPError( error, @"Failed to open store." );
self.storeCorrupted = @YES;
[self handleCoordinatorError:error];
return;
}
self.storeCorrupted = @NO;
#if TARGET_OS_IPHONE
PearlAddNotificationObserver( UIApplicationWillResignActiveNotification, UIApp, [NSOperationQueue mainQueue],
^(MPAppDelegate_Shared *self, NSNotification *note) {
@@ -274,6 +284,12 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
}];
}
- (void)retryCorruptStore {
self.storeCorrupted = @NO;
[self loadStore];
}
- (void)deleteAndResetStore {
@synchronized (self) {
@@ -563,50 +579,46 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
// Read metadata for the import file.
MPMarshalledFile *file = mpw_marshal_read( NULL, importData.UTF8String );
if (!file)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @"Could not process Master Password import data.",
}]), @"While importing sites." );
if (file->error.type != MPMarshalSuccess) {
MPMarshalErrorType type = file->error.type;
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(type),
NSLocalizedDescriptionKey: @"Could not parse Master Password import data.",
}]), @"While importing sites." );
}
if (file->info->format == MPMarshalFormatNone) {
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
}
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(file->info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
mpw_marshal_file_free( &file );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
MPMarshalledUser *importUser = nil;
@try {
if (!file)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @"Could not process Master Password import data.",
}]), @"While importing sites." );
if (file->error.type != MPMarshalSuccess) {
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(file->error.type),
NSLocalizedDescriptionKey: strf( @"Could not parse Master Password import data:\n%@", @(file->error.message) ),
}]), @"While importing sites." );
}
if (file->info->format == MPMarshalFormatNone) {
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(MPMarshalErrorFormat),
NSLocalizedDescriptionKey: @"This is not a Master Password import file.",
}]), @"While importing sites." );
}
importKey = [[MPKey alloc] initForFullName:@(file->info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( file->info->algorithm )] encodeHex]
caseInsensitiveCompare:@(file->info->keyID)] != NSOrderedSame);
// Get master password for import file.
MPKey *importKey;
NSString *importMasterPassword;
do {
importMasterPassword = askImportPassword( @(file->info->fullName) );
if (!importMasterPassword) {
inf( @"Import cancelled." );
return MPError( ([NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]), @"" );
}
// Parse import data.
MPMarshalledUser *importUser = mpw_marshal_auth( file, mpw_masterKeyProvider_str( importMasterPassword.UTF8String ) );
importKey = [[MPKey alloc] initForFullName:@(file->info->fullName) withMasterPassword:importMasterPassword];
} while ([[[importKey keyIDForAlgorithm:MPAlgorithmForVersion( file->info->algorithm )] encodeHex]
caseInsensitiveCompare:@(file->info->keyID)] != NSOrderedSame);
@try {
// Parse import data.
importUser = mpw_marshal_auth( file, mpw_masterKeyProvider_str( importMasterPassword.UTF8String ) );
if (!importUser || file->error.type != MPMarshalSuccess)
return MPError( ([NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(file->error.type),
NSLocalizedDescriptionKey: @(file->error.message),
NSLocalizedDescriptionKey: strf( @"Could not authenticate Master Password import:\n%@", @(file->error.message) ),
}]), @"While importing sites." );
// Find an existing user to update.
@@ -708,31 +720,45 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
site.lastUsed = [NSDate dateWithTimeIntervalSince1970:importSite->lastUsed];
}
- (void)exportSitesRevealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askImportPassword
result:(void ( ^ )(NSString *exportedUser, NSError *error))resultBlock {
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
MPUserEntity *user = [self activeUserInContext:context];
- (NSString *)exportSitesFor:(MPUserEntity *)user
revealPasswords:(BOOL)revealPasswords
askExportPassword:(NSString *( ^ )(NSString *userName))askExportPassword
error:(__autoreleasing NSError **)error {
MPMarshalledUser *exportUser = NULL;
MPMarshalledFile *exportFile = mpw_marshal_file( NULL, NULL, mpw_marshal_data_new() );
@try {
inf( @"Exporting sites, %@, for user: %@", revealPasswords? @"revealing passwords": @"omitting passwords", user.userID );
MPMarshalledUser *exportUser = mpw_marshal_user( user.name.UTF8String,
mpw_masterKeyProvider_str( askImportPassword( user.name ).UTF8String ), user.algorithm.version );
NSString *masterPassword = askExportPassword( user.name );
if (!masterPassword) {
inf( @"Export cancelled." );
return nil;
}
for (NSString *feature in @[MPProductGenerateLogins, MPProductGenerateAnswers, MPProductOSIntegration, MPProductTouchID])
if ([[MPAppDelegate_Shared get] isFeatureUnlocked:feature])
mpw_marshal_data_set_str( digest( strf( @"%@/%@", user.name, feature )).UTF8String, exportFile->data,
"user", "_ext_mpw", feature.UTF8String, nil );
MPKey *key = [[MPKey alloc] initForFullName:user.name withMasterPassword:masterPassword];
exportUser = mpw_marshal_user( user.name.UTF8String,
mpw_masterKeyProvider_str( masterPassword.UTF8String ), user.algorithm.version );
exportUser->redacted = !revealPasswords;
exportUser->avatar = (unsigned int)user.avatar;
exportUser->keyID = mpw_strdup( [user.keyID encodeHex].UTF8String );
exportUser->defaultType = user.defaultType;
exportUser->lastUsed = (time_t)user.lastUsed.timeIntervalSince1970;
for (MPSiteEntity *site in user.sites) {
for (MPSiteEntity *site in [user.sites sortedArrayUsingDescriptors:@[
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]
]]) {
MPCounterValue counter = MPCounterValueInitial;
if ([site isKindOfClass:[MPGeneratedSiteEntity class]])
counter = ((MPGeneratedSiteEntity *)site).counter;
MPMarshalledSite *exportSite = mpw_marshal_site( exportUser,
site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->resultState = mpw_strdup( [site.algorithm exportPasswordForSite:site usingKey:self.key].UTF8String );
exportSite->loginState = mpw_strdup( [site.algorithm exportLoginForSite:site usingKey:self.key].UTF8String );
exportSite->loginType = site.loginGenerated? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
MPMarshalledSite *exportSite = mpw_marshal_site( exportUser, site.name.UTF8String, site.type, counter, site.algorithm.version );
exportSite->resultState = mpw_strdup( [site.algorithm exportPasswordForSite:site usingKey:key].UTF8String );
exportSite->loginState = mpw_strdup( [site.algorithm exportLoginForSite:site usingKey:key].UTF8String );
exportSite->loginType = site.loginGenerated || !exportSite->loginState? MPResultTypeTemplateName: MPResultTypeStatefulPersonal;
exportSite->url = mpw_strdup( site.url.UTF8String );
exportSite->uses = (unsigned int)site.uses;
exportSite->lastUsed = (time_t)site.lastUsed.timeIntervalSince1970;
@@ -741,22 +767,26 @@ PearlAssociatedObjectProperty( NSNumber*, StoreCorrupted, storeCorrupted );
mpw_marshal_question( exportSite, siteQuestion.keyword.UTF8String );
}
MPMarshalledFile *exportFile = NULL;
const char *export = mpw_marshal_write( MPMarshalFormatDefault, &exportFile, exportUser );
NSString *exportedUser = nil;
if (export && exportFile && exportFile->error.type == MPMarshalSuccess)
exportedUser = [NSString stringWithCString:export encoding:NSUTF8StringEncoding];
mpw_free_string( &export );
resultBlock( exportedUser, exportFile && exportFile->error.type == MPMarshalSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
}] );
if (error)
*error = exportFile && exportFile->error.type == MPMarshalSuccess? nil:
[NSError errorWithDomain:MPErrorDomain code:MPErrorMarshalCode userInfo:@{
@"type" : @(exportFile? exportFile->error.type: MPMarshalErrorInternal),
NSLocalizedDescriptionKey: @(exportFile? exportFile->error.message: nil),
}];
return exportedUser;
}
@finally {
mpw_marshal_file_free( &exportFile );
mpw_marshal_user_free( &exportUser );
mpw_masterKeyProvider_free();
}];
}
}
@end

View File

@@ -21,11 +21,13 @@
@interface MPConfig : PearlConfig
@property(nonatomic, retain) NSNumber *sendInfo;
@property(nonatomic, retain) NSNumber *sendInfoDecided;
@property(nonatomic, retain) NSNumber *notificationsDecided;
@property(nonatomic, retain) NSNumber *rememberLogin;
@property(nonatomic, retain) NSNumber *hidePasswords;
@property(nonatomic, strong) NSNumber *siteAttacker;
@property(nonatomic, retain) NSNumber *checkInconsistency;
@property(nonatomic, strong) NSNumber *siteAttacker;
@end

View File

@@ -21,7 +21,7 @@
@implementation MPConfig
@dynamic sendInfo, rememberLogin, checkInconsistency, hidePasswords, siteAttacker;
@dynamic sendInfo, sendInfoDecided, notificationsDecided, rememberLogin, hidePasswords, siteAttacker, checkInconsistency;
- (id)init {
@@ -29,13 +29,16 @@
return nil;
[self.defaults registerDefaults:@{
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
NSStringFromSelector( @selector( sendInfo ) ) : @NO,
NSStringFromSelector( @selector( sendInfoDecided ) ) : @NO,
NSStringFromSelector( @selector( notificationsDecided ) ): @NO,
NSStringFromSelector( @selector( sendInfo ) ) : @YES,
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( checkInconsistency ) ): @NO,
NSStringFromSelector( @selector( siteAttacker ) ) : @(MPAttacker1),
NSStringFromSelector( @selector( rememberLogin ) ) : @NO,
NSStringFromSelector( @selector( hidePasswords ) ) : @NO,
NSStringFromSelector( @selector( siteAttacker ) ) : @(MPAttacker1),
NSStringFromSelector( @selector( checkInconsistency ) ) : @NO,
NSStringFromSelector( @selector( askForReviews ) ) : @YES,
}];
self.delegate = [MPAppDelegate_Shared get];

View File

@@ -29,6 +29,8 @@
if ([self hasChanges])
[self performBlockAndWait:^{
@try {
[self processPendingChanges];
NSError *error = nil;
if (!(success = [self save:&error]))
MPError( error, @"While saving." );

View File

@@ -20,11 +20,19 @@
#import "base64.h"
// printf <secret> | openssl enc -[ed] -aes-128-cbc -a -A -K <appSecret> -iv 0
NSString *appSecret = @"";
NSString *appSalt = @"";
NSString *sentryDSN = @"";
NSString *countlyKey = @"";
NSString *countlySalt = @"";
#if TARGET_OS_IOS
NSString *appSecret = @"946a6b12e6e6e004cc35bad1ea11478c";
NSString *appSalt = @"uBcsbZeTB8TfSS7dDw4yUq6wMZD/2nREvR0mqzqsNXvv9guh+62hkt99ly6QcJ5n";
NSString *sentryDSN = @"tmVjdMN9DpZ+0EIrrvHi44hWfaBkwrlrxjBkdeau2rDk+zlvgSdAZkAvNj7m1V+5NUR7i8Y/NumNKOaYlWJvPynEMJ4ZBvPepSbivgVvmr8=";
NSString *countlyKey = @"mDnMZyxwoq4ENgYnGYTzW8wsyiJQlmNKxkRLj88/nrs0mzE+zVjs6Y5LAT3+AYBB";
NSString *countlySalt = @"2COFsZd+4FNAU6jvI/HUu297mkZALzRIyKv5mD3vs55BHXDowh62A7FursCYS+cG";
#elif TARGET_OS_MAC
NSString *appSecret = @"24fcbadccb5789b2a969c0c811f86702";
NSString *appSalt = @"0N1fzSanIOCb7OQ4hEshXSjwEPXAXMhPBKQJeEcYPor8FWz76IpdB8ZHa3Wyb7o9";
NSString *sentryDSN = @"2RbeS9wfzQEOKB9MG3EWLDe+N8iXYNtWc8tovMcBmhuMIeyAHYKqo5eclSEYyM6lA73Y7FFHqUyTLbEmOR6MAU2PtWAitLdxOZlq3VnbXjI=";
NSString *countlyKey = @"uiasXoQNtkPQHvpvNqEE5N/tw/F1Hnzm+4ViSJ38EMeoWGvDQPJ+Kt9zPhb8Qans";
NSString *countlySalt = @"/raQUNxKQdxXRR5VFmCDJdyyJE8f6SPrTO5Y4z0kJH+wCrjaZ1VvCq+JSmOsBkz2";
#endif
NSString *decrypt(NSString *secret) {

View File

@@ -16,7 +16,9 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
MP_LIBS_BEGIN
#import <Sentry/Sentry.h>
MP_LIBS_END
__BEGIN_DECLS
extern NSString *const MPErrorDomain;
@@ -41,8 +43,9 @@ __END_DECLS
\
if (__error && [[MPConfig get].sendInfo boolValue]) { \
SentryEvent *event = [[SentryEvent alloc] initWithLevel:kSentryLevelError]; \
event.message = strf(@"%@: %@", message_, [__error localizedDescription]); \
event.message = strf( message_ @": %@", ##__VA_ARGS__, [__error localizedDescription]); \
event.logger = @"MPError"; \
event.fingerprint = @[ message_, __error.domain, @(__error.code) ]; \
[SentrySDK captureEvent:event]; \
} \
__error; \

View File

@@ -12,9 +12,6 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<menu title="AMainMenu" systemMenu="main" id="29">
<point key="canvasLocation" x="139" y="155"/>
</menu>
<customObject id="494" customClass="MPMacAppDelegate">
<connections>
<outlet property="createUserItem" destination="757" id="763"/>
@@ -241,6 +238,9 @@
</connections>
</menuItem>
</items>
<connections>
<outlet property="delegate" destination="494" id="Slu-zT-yO4"/>
</connections>
<point key="canvasLocation" x="140" y="23"/>
</menu>
</objects>

View File

@@ -21,9 +21,9 @@
#import "MPSitesWindowController.h"
#import "MPInitialWindowController.h"
@interface MPMacAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate>
@interface MPMacAppDelegate : MPAppDelegate_Shared<NSApplicationDelegate, NSMenuDelegate>
@property(nonatomic, strong) NSStatusItem *statusView;
@property(nonatomic, strong) NSStatusItem *statusItem;
@property(nonatomic, strong) MPSitesWindowController *sitesWindowController;
@property(nonatomic, strong) MPInitialWindowController *initialWindowController;
@property(nonatomic, weak) IBOutlet NSMenuItem *lockItem;

View File

@@ -22,14 +22,15 @@
#import "MPSecrets.h"
#import "mpw-marshal.h"
MP_LIBS_BEGIN
#import <Carbon/Carbon.h>
#import <ServiceManagement/ServiceManagement.h>
#import <Sentry/Sentry.h>
#import <Countly/Countly.h>
MP_LIBS_END
#define LOGIN_HELPER_BUNDLE_ID @"com.lyndir.lhunath.MasterPassword.Mac.LoginHelper"
@implementation MPMacAppDelegate
#pragma clang diagnostic push
@@ -69,10 +70,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
@try {
// Sentry
[SentrySDK initWithOptions:@{
[SentrySDK startWithOptions:@{
@"dsn" : NilToNSNull( decrypt( sentryDSN ) ),
#ifdef DEBUG
@"debug" : @(YES),
@"debug" : @(NO),
@"environment" : @"Development",
#elif PUBLIC
@"debug" : @(NO),
@@ -81,12 +82,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
@"debug" : @(NO),
@"environment" : @"Private",
#endif
@"enabled" : [MPMacConfig get].sendInfo,
@"enabled" : @([[MPMacConfig get].sendInfo boolValue] || ![[MPMacConfig get].sendInfoDecided boolValue]),
@"enableAutoSessionTracking": @(YES),
}];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
PearlLogLevel level = PearlLogLevelWarn;
if ([[MPConfig get].sendInfo boolValue])
if ([[MPMacConfig get].sendInfo boolValue])
level = PearlLogLevelDebug;
if (message.level >= level) {
@@ -134,7 +135,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
#if DEBUG
countlyConfig.enableDebug = YES;
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
#elif ! PUBLIC
#elif !PUBLIC
countlyConfig.enableDebug = NO;
countlyConfig.pushTestMode = CLYPushTestModeTestFlightOrAdHoc;
#endif
@@ -145,7 +146,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
// Setup delegates and listeners.
[MPConfig get].delegate = self;
[MPMacConfig get].delegate = self;
__weak id weakSelf = self;
[self addObserverBlock:^(NSString *keyPath, id object, NSDictionary *change, void *context) {
dispatch_async( dispatch_get_main_queue(), ^{
@@ -159,12 +160,10 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
} forKeyPath:@"activeUser" options:0 context:nil];
// Status item.
self.statusView = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusView.image = [NSImage imageNamed:@"menu-icon"];
self.statusView.image.template = YES;
self.statusView.menu = self.statusMenu;
self.statusView.target = self;
self.statusView.action = @selector( showMenu );
self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength];
self.statusItem.menu = self.statusMenu;
self.statusItem.button.image = [NSImage imageNamed:@"menu-icon"];
self.statusItem.button.image.template = YES;
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, self.storeCoordinator, nil,
^(id self, NSNotification *note) {
@@ -205,12 +204,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[NSApp activateIgnoringOtherApps:YES];
}
[self enableNotifications];
[self tryNotifications];
}
- (void)applicationWillResignActive:(NSNotification *)notification {
if (![[MPConfig get].rememberLogin boolValue])
if (![[MPMacConfig get].rememberLogin boolValue])
[self lock:nil];
}
@@ -231,7 +230,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
return NSTerminateNow;
}
- (void)enableNotifications {
- (void)tryNotifications {
[Countly.sharedInstance giveConsentForFeature:CLYConsentPushNotifications];
if (@available( macOS 10.14, * )) {
@@ -250,18 +249,19 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (void)askNotifications {
if ([[MPMacConfig get].notificationsDecided boolValue])
return;
PearlMainQueue( ^{
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"notificationsDecided"]) {
if (@available( macOS 10.14, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"notificationsDecided"];
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"notificationsDecided"];
}
if (@available( macOS 10.14, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[MPMacConfig get].notificationsDecided = @(YES);
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[MPMacConfig get].notificationsDecided = @(YES);
}
} );
}
@@ -409,11 +409,11 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)togglePreference:(id)sender {
if (sender == self.diagnosticsItem)
[MPConfig get].sendInfo = @(self.diagnosticsItem.state != NSOnState);
[MPMacConfig get].sendInfo = @(self.diagnosticsItem.state != NSOnState);
if (sender == self.hidePasswordsItem)
[MPConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
[MPMacConfig get].hidePasswords = @(self.hidePasswordsItem.state != NSOnState);
if (sender == self.rememberPasswordItem)
[MPConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
[MPMacConfig get].rememberLogin = @(self.rememberPasswordItem.state != NSOnState);
if (sender == self.openAtLoginItem)
[self setLoginItemEnabled:self.openAtLoginItem.state != NSOnState];
if (sender == self.showFullScreenItem) {
@@ -440,8 +440,8 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
NSAlert *alert = [NSAlert new];
[alert setMessageText:@"New User"];
[alert setInformativeText:@"To begin, enter your full name.\n\n"
@"IMPORTANT: Enter your name correctly, including the right capitalization, "
@"as you would on an official document."];
@"IMPORTANT: Enter your name correctly, including the right capitalization, "
@"as you would on an official document."];
[alert addButtonWithTitle:@"Create User"];
[alert addButtonWithTitle:@"Cancel"];
NSTextField *nameField = [[NSTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
@@ -502,7 +502,7 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
- (IBAction)showPopup:(id)sender {
[self.statusView popUpStatusItemMenu:self.statusView.menu];
[[self.statusItem button] performClick:sender];
}
- (IBAction)showPasswordWindow:(id)sender {
@@ -546,15 +546,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
return;
}
if (!self.key) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"User Locked";
alert.informativeText = @"To export your sites, first unlock your user by opening Master Password.";
[alert runModal];
[self showPopup:nil];
return;
}
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
@@ -579,43 +570,46 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
if ([savePanel runModal] == NSFileHandlingPanelCancelButton)
return;
[self exportSitesRevealPasswords:revealPasswords
askExportPassword:^NSString *(NSString *userName) {
return PearlMainQueueAwait( ^id {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Import"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
return ((NSTextField *)alert.accessoryView).stringValue;
else
return nil;
} );
} result:^(NSString *mpsites, NSError *error) {
if (!mpsites || error) {
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( error, @"Failed to export mpsites." )] runModal];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSError *error = nil;
NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords askExportPassword:
^NSString *(NSString *userName) {
return PearlMainQueueAwait( ^id {
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Export"];
[alert addButtonWithTitle:@"Cancel"];
alert.messageText = strf( @"Master Password For\n%@", userName );
alert.informativeText = @"Enter the current master password for this user.";
alert.accessoryView = [[NSSecureTextField alloc] initWithFrame:NSMakeRect( 0, 0, 200, 22 )];
[alert layout];
if ([alert runModal] == NSAlertFirstButtonReturn)
return ((NSTextField *)alert.accessoryView).stringValue;
else
return nil;
} );
return;
}
} error:&error];
NSError *coordinateError = nil;
[[[NSFileCoordinator alloc] initWithFilePresenter:nil]
coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError byAccessor:^(NSURL *newURL) {
NSError *writeError = nil;
if (![mpsites writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( writeError, @"Could not write to the export file." )] runModal];
} );
}];
if (coordinateError)
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( coordinateError, @"Could not gain access to the export file." )] runModal];
} );
}];
if (error)
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( error, @"Failed to export mpsites." )] runModal];
} );
if (!exportedUser)
return;
NSError *coordinateError = nil;
[[[NSFileCoordinator alloc] initWithFilePresenter:nil]
coordinateWritingItemAtURL:savePanel.URL options:0 error:&coordinateError byAccessor:^(NSURL *newURL) {
NSError *writeError = nil;
if (![exportedUser writeToURL:newURL atomically:NO encoding:NSUTF8StringEncoding error:&writeError])
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( writeError, @"Could not write to the export file." )] runModal];
} );
}];
if (coordinateError)
PearlMainQueue( ^{
[[NSAlert alertWithError:MPError( coordinateError, @"Could not gain access to the export file." )] runModal];
} );
}];
}
- (void)updateUsers {
@@ -688,13 +682,6 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[self updateMenuItems];
}
- (void)showMenu {
[self updateMenuItems];
[self.statusView popUpStatusItemMenu:self.statusView.menu];
}
- (void)updateMenuItems {
MPUserEntity *activeUser = [self activeUserForMainThread];
@@ -749,6 +736,13 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
}
}
#pragma mark - NSMenuDelegate
- (void)menuNeedsUpdate:(NSMenu *)menu {
[self updateMenuItems];
}
#pragma mark - PearlConfigDelegate
- (void)didUpdateConfigForKey:(SEL)configKey fromValue:(id)oldValue {
@@ -760,26 +754,25 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
PearlMainQueue( ^{
if (!key || [key isEqualToString:NSStringFromSelector( @selector( sendInfo ) )])
self.diagnosticsItem.state = [[MPConfig get].sendInfo boolValue]? NSOnState: NSOffState;
self.diagnosticsItem.state = [[MPMacConfig get].sendInfo boolValue]? NSOnState: NSOffState;
if (!key || [key isEqualToString:NSStringFromSelector( @selector( hidePasswords ) )])
self.hidePasswordsItem.state = [[MPConfig get].hidePasswords boolValue]? NSOnState: NSOffState;
self.hidePasswordsItem.state = [[MPMacConfig get].hidePasswords boolValue]? NSOnState: NSOffState;
if (!key || [key isEqualToString:NSStringFromSelector( @selector( rememberLogin ) )])
self.rememberPasswordItem.state = [[MPConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
self.rememberPasswordItem.state = [[MPMacConfig get].rememberLogin boolValue]? NSOnState: NSOffState;
} );
// Send info
NSArray *countlyFeatures = @[
CLYConsentSessions, CLYConsentEvents, CLYConsentUserDetails, CLYConsentCrashReporting, CLYConsentViewTracking, CLYConsentStarRating
];
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
if ([[MPMacConfig get].sendInfo boolValue] || ![[MPMacConfig get].sendInfoDecided boolValue]) {
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
[SentrySDK.currentHub getClient].options.enabled = @YES;
[SentrySDK configureScope:^(SentryScope *scope) {
[scope setExtraValue:[MPConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPMacConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPMacConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPMacConfig get].fullScreen forKey:@"fullScreen"];
[scope setExtraValue:[PearlConfig get].firstRun forKey:@"firstRun"];
[scope setExtraValue:[PearlConfig get].launchCount forKey:@"launchCount"];
@@ -790,10 +783,12 @@ static OSStatus MPHotKeyHander(EventHandlerCallRef nextHandler, EventRef theEven
[scope setExtraValue:@([PearlDeviceUtils isAppEncrypted]) forKey:@"encrypted"];
[scope setExtraValue:[PearlDeviceUtils platform] forKey:@"platform"];
}];
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
}
else {
[SentrySDK.currentHub getClient].options.enabled = @NO;
[Countly.sharedInstance cancelConsentForFeatures:countlyFeatures];
[SentrySDK.currentHub getClient].options.enabled = @NO;
}
}

View File

@@ -45,7 +45,7 @@
@property(nonatomic, readonly) BOOL stored;
@property(nonatomic, readonly) BOOL transient;
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups;
- (instancetype)initWithEntity:(MPSiteEntity *)entity queryGroups:(NSArray *)queryGroups;
- (instancetype)initWithName:(NSString *)siteName forUser:(MPUserEntity *)user;
- (MPSiteEntity *)entityInContext:(NSManagedObjectContext *)moc;

View File

@@ -31,12 +31,12 @@
@implementation MPSiteModel
- (instancetype)initWithEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
- (instancetype)initWithEntity:(MPSiteEntity *)entity queryGroups:(NSArray *)queryGroups {
if (!(self = [super init]))
return nil;
[self setEntity:entity fuzzyGroups:fuzzyGroups];
[self setEntity:entity queryGroups:queryGroups];
self.initialized = YES;
return self;
@@ -53,23 +53,25 @@
return self;
}
- (void)setEntity:(MPSiteEntity *)entity fuzzyGroups:(NSArray *)fuzzyGroups {
- (void)setEntity:(MPSiteEntity *)entity queryGroups:(NSArray *)queryGroups {
if ([self.entityOID isEqual:entity.permanentObjectID])
return;
self.entityOID = entity.permanentObjectID;
NSString *siteName = entity.name;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName];
for (NSUInteger f = 0, s = (NSUInteger)-1; f < [fuzzyGroups count]; ++f) {
s = [siteName rangeOfString:fuzzyGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s + 1, [siteName length] - (s + 1) )].location;
if (s == NSNotFound)
break;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName?: @""];
if ([attributedSiteName length])
for (NSUInteger f = 0, s = 0; f < [queryGroups count]; ++f, ++s) {
s = [siteName rangeOfString:queryGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s, [siteName length] - s )].location;
if (s == NSNotFound)
break;
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[NSColor alternateSelectedControlColor]
range:NSMakeRange( s, [queryGroups[f] length] )];
}
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[NSColor alternateSelectedControlColor]
range:NSMakeRange( s, [fuzzyGroups[f] length] )];
}
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.alignment = NSCenterTextAlignment;
[attributedSiteName addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange( 0, [siteName length] )];
@@ -179,7 +181,8 @@
if (algorithmVersion == self.algorithm.version)
return;
[self willChangeValueForKey:@"outdated"];
self.algorithm = MPAlgorithmForVersion( algorithmVersion )?: self.algorithm;
self.algorithm =
MPAlgorithmForVersion( MIN( MPAlgorithmVersionLast, MAX( MPAlgorithmVersionFirst, algorithmVersion ) ) )?: self.algorithm;
[self didChangeValueForKey:@"outdated"];
if (self.entityOID)

View File

@@ -17,6 +17,8 @@
//==============================================================================
#import <QuartzCore/QuartzCore.h>
#import <Countly/Countly.h>
#import <UserNotifications/UserNotifications.h>
#import "MPSitesWindowController.h"
#import "MPMacAppDelegate.h"
#import "MPAppDelegate_Store.h"
@@ -42,13 +44,27 @@
prof_rewind( @"replaceFonts" );
PearlAddNotificationObserver( NSWindowDidBecomeKeyNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
(^(id host, NSNotification *note) {
prof_new( @"didBecomeKey" );
[self.window makeKeyAndOrderFront:nil];
prof_rewind( @"fadeIn" );
[self updateUser];
prof_finish( @"updateUser" );
} );
prof_rewind( @"updateUser" );
if (![[MPMacConfig get].sendInfoDecided boolValue]) {
NSAlert *alert = [NSAlert new];
alert.messageText = @"Diagnostics";
alert.informativeText = @"We look for bugs, sudden crashes, runtime issues & statistics.\n\n"
@"Diagnostics are scrubbed and personal details will never leave your device.";
[alert addButtonWithTitle:@"Engage"];
[alert addButtonWithTitle:@"Disable"];
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
[MPMacConfig get].sendInfo = @(returnCode != NSAlertSecondButtonReturn);
[MPMacConfig get].sendInfoDecided = @(YES);
}];
}
prof_finish( @"sendInfoDecided" );
}) );
PearlAddNotificationObserver( NSWindowWillCloseNotification, self.window, [NSOperationQueue mainQueue],
^(id host, NSNotification *note) {
NSWindow *sheet = [self.window attachedSheet];
@@ -81,7 +97,16 @@
self.siteTable.superview.superview.layer.mask = self.siteGradient;
self.siteTable.controller = self;
prof_finish( @"ui" );
prof_rewind( @"ui" );
if (@available( macOS 10.14, * )) {
[[UNUserNotificationCenter currentNotificationCenter]
requestAuthorizationWithOptions:UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError *error) {
if (!granted)
err( @"Couldn't obtain notification authorization: %@", error );
}];
}
prof_finish( @"notifications" );
}
- (void)dealloc {
@@ -376,7 +401,52 @@
}];
}
- (IBAction)changeType:(id)sender {
- (IBAction)changeDefaultType:(id)sender {
MPSiteModel *site = self.selectedSite;
MPUserEntity *user = [MPMacAppDelegate get].activeUserForMainThread;
NSArray *types = [user.algorithm allTypes];
[self.passwordTypesMatrix renewRows:(NSInteger)[types count] columns:1];
for (NSUInteger t = 0; t < [types count]; ++t) {
MPResultType type = (MPResultType)[types[t] unsignedIntegerValue];
NSString *title = [user.algorithm nameOfType:type];
if (type & MPResultTypeClassTemplate)
title = strf( @"%@ %@", [user.algorithm mpwTemplateForSiteNamed:site.name?: @"masterpassword.app" ofType:type
withCounter:site.counter?: MPCounterValueDefault
usingKey:[MPMacAppDelegate get].key], title );
NSButtonCell *cell = [self.passwordTypesMatrix cellAtRow:(NSInteger)t column:0];
cell.tag = type;
cell.state = type == site.type? NSOnState: NSOffState;
cell.title = title;
}
self.passwordTypesBox.title = strf( @"Choose a password type for new sites of %@:", user.name );
NSAlert *alert = [NSAlert new];
[alert addButtonWithTitle:@"Save"];
[alert addButtonWithTitle:@"Cancel"];
[alert setMessageText:@"Change Default Type"];
[alert setAccessoryView:self.passwordTypesBox];
[alert layout];
[alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) {
switch (returnCode) {
case NSAlertFirstButtonReturn: {
// "Save" button.
MPResultType type = (MPResultType)[self.passwordTypesMatrix.selectedCell tag];
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
[[MPMacAppDelegate get] activeUserInContext:context].defaultType = type;
[context saveToStore];
}];
break;
}
default:
break;
}
}];
}
- (IBAction)changeSiteType:(id)sender {
MPSiteModel *site = self.selectedSite;
NSArray *types = [site.algorithm allTypes];
@@ -487,13 +557,19 @@
[self copyContent:self.shiftPressed? selectedSite.loginName: selectedSite.content];
[NSApp hide:nil];
NSUserNotification *notification = [NSUserNotification new];
notification.title = @"Password Copied";
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else
notification.subtitle = selectedSite.name;
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification];
if (@available( macOS 10.14, * )) {
UNMutableNotificationContent *notification = [UNMutableNotificationContent new];
notification.title = self.shiftPressed? @"Login Copied": @"Password Copied";
if (selectedSite.loginName.length)
notification.subtitle = strf( @"%@ at %@", selectedSite.loginName, selectedSite.name );
else
notification.subtitle = selectedSite.name;
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:
[UNNotificationRequest requestWithIdentifier:selectedSite.name content:notification trigger:nil]
withCompletionHandler:^(NSError *error) {
dbg( @"notification: %@, completed w/errror: %@", notification, error );
}];
}
}
- (void)updateUser {
@@ -535,22 +611,20 @@
return;
}
static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
prof_new( @"updateSites" );
NSString *queryString = self.siteField.stringValue;
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"] stringByAppendingString:@"*"];
NSMutableArray *queryGroups = [NSMutableArray new];
NSMutableString *queryPattern = [NSMutableString new];
[queryString enumerateSubstringsInRange: NSMakeRange(0, [queryString length]) options: NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (substringRange.location < 20) {
[queryGroups addObject:substring];
[queryPattern appendString:@"*"];
}
[queryPattern appendString:substring];
}];
[queryPattern appendString:@"*"];
prof_rewind( @"queryPattern" );
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
prof_rewind( @"fuzzyRE" );
[MPMacAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
prof_rewind( @"moc" );
@@ -572,7 +646,7 @@
BOOL exact = NO;
NSMutableArray *newSites = [NSMutableArray arrayWithCapacity:[siteResults count]];
for (MPSiteEntity *site in siteResults) {
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site fuzzyGroups:fuzzyGroups]];
[newSites addObject:[[MPSiteModel alloc] initWithEntity:site queryGroups:queryGroups]];
exact |= [site.name isEqualToString:queryString];
}
prof_rewind( @"newSites: %u, exact: %d", (uint)[siteResults count], exact );

View File

@@ -31,26 +31,26 @@
<rect key="contentRect" x="0.0" y="0.0" width="640" height="577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1680" height="1027"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" userLabel="Root">
<rect key="frame" x="0.0" y="0.0" width="640" height="557"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="553"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView blendingMode="behindWindow" material="popover" state="followsWindowActiveState" translatesAutoresizingMaskIntoConstraints="NO" id="eRe-Ef-AZx">
<rect key="frame" x="0.0" y="0.0" width="640" height="557"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="553"/>
</visualEffectView>
<progressIndicator hidden="YES" wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" translatesAutoresizingMaskIntoConstraints="NO" id="oSh-Ec-8Nf" userLabel="Progress Spinner">
<rect key="frame" x="312" y="521" width="16" height="16"/>
<rect key="frame" x="361" y="517" width="16" height="16"/>
</progressIndicator>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="NGk-Io-Buc" userLabel="Top Box">
<rect key="frame" x="20" y="381" width="600" height="132"/>
<rect key="frame" x="20" y="377" width="698" height="132"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ond-dT-x5d" userLabel="Site Password Label">
<rect key="frame" x="157" y="98" width="287" height="14"/>
<rect key="frame" x="206" y="98" width="287" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Your password for apple.com (hit &lt;⏎ enter&gt; to copy):" id="CgJ-XZ-6Hy">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -72,13 +72,13 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ia6-7b-dFr">
<rect key="frame" x="127" y="59" width="347" height="14"/>
<rect key="frame" x="176" y="59" width="347" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="No password set. Click &quot;Set Password&quot; on the bottom to set one." id="eDQ-iz-97a">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -108,13 +108,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OaQ-of-zmb" userLabel="Initial Help">
<rect key="frame" x="39" y="109" width="522" height="18"/>
<rect key="frame" x="113" y="109" width="473" height="17"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Master Password generates passwords for your sites (and other things)." id="YyD-hd-wi3">
<font key="font" size="16" name="HelveticaNeue"/>
<font key="font" metaFont="menu" size="14"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -136,18 +136,18 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sYt-eL-uwt" userLabel="Initial Tip">
<rect key="frame" x="-26" y="31" width="652" height="70"/>
<rect key="frame" x="-2" y="31" width="702" height="70"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="left" id="9c4-NI-NM0">
<font key="font" size="12" name="HelveticaNeue"/>
<string key="title"> When you create an account with a site, use Master Password to create a password for it.
For accounts you already have, change the password to that created by Master Password for it.
To get a password, enter the site's bare domain name in the "site name" field (eg. apple.com).
For your master password, think of a strong password (eg. a short sentence). Tell *nobody*.
It's OK to share your site passwords. They can be changed if necessary.</string>
<font key="font" metaFont="message" size="11"/>
<string key="title"> When you create an account on a site, open Master Password to create your account password.
⑵ Consider changing all your existing account passwords to the password Master Password creates for those sites.
To get the password for a site, just enter its domain name in the "site name" field (eg. "apple.com").
⑷ When chosing a master password, make it easy but long (eg. a short sentence).
⑸ Tell *nobody* your master password. It's OK to share your site passwords with people you trust: they can be changed if necessary.</string>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -169,7 +169,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XUV-zU-Y9c" userLabel="Site Password">
<rect key="frame" x="-2" y="26" width="604" height="80"/>
<rect key="frame" x="-2" y="26" width="702" height="80"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
@@ -202,6 +202,7 @@
<constraint firstItem="XUV-zU-Y9c" firstAttribute="leading" secondItem="NGk-Io-Buc" secondAttribute="leading" id="6Kl-7u-r90"/>
<constraint firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="6Qf-5O-Cvk"/>
<constraint firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="7sl-qi-HY9"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="width" secondItem="NGk-Io-Buc" secondAttribute="width" id="Beb-mW-KDP"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="top" secondItem="NGk-Io-Buc" secondAttribute="top" constant="20" symbolic="YES" id="IX4-cd-VkJ"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerY" secondItem="XUV-zU-Y9c" secondAttribute="centerY" id="KqM-uR-Obm"/>
<constraint firstItem="Ia6-7b-dFr" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="NFQ-aw-8tm"/>
@@ -209,14 +210,13 @@
<constraint firstAttribute="trailing" secondItem="XUV-zU-Y9c" secondAttribute="trailing" id="TdB-QV-9JK"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="bottom" secondItem="XUV-zU-Y9c" secondAttribute="top" constant="8" symbolic="YES" id="UgV-J6-B5T"/>
<constraint firstItem="Ond-dT-x5d" firstAttribute="centerX" secondItem="XUV-zU-Y9c" secondAttribute="centerX" id="UhT-LQ-aZ8"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="leading" secondItem="NGk-Io-Buc" secondAttribute="leading" constant="-24" id="eaC-ow-ren"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="top" secondItem="OaQ-of-zmb" secondAttribute="bottom" constant="8" symbolic="YES" id="hjJ-f1-mFv"/>
<constraint firstItem="sYt-eL-uwt" firstAttribute="centerX" secondItem="OaQ-of-zmb" secondAttribute="centerX" id="mu2-se-Mtn"/>
<constraint firstAttribute="centerY" secondItem="sYt-eL-uwt" secondAttribute="centerY" id="zLS-QG-MKS"/>
</constraints>
</customView>
<scrollView focusRingType="none" borderType="none" autohidesScrollers="YES" horizontalLineScroll="34" horizontalPageScroll="10" verticalLineScroll="34" verticalPageScroll="10" hasHorizontalScroller="NO" hasVerticalScroller="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="Bme-XK-MMc" userLabel="Sites Table">
<rect key="frame" x="64" y="40" width="512" height="177"/>
<rect key="frame" x="113" y="40" width="512" height="177"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="e11-59-xSS">
<rect key="frame" x="0.0" y="0.0" width="512" height="177"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
@@ -251,7 +251,7 @@
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" lineBreakMode="truncatingTail" alignment="center" title="apple.com" id="o0g-Zv-pH4">
<font key="font" size="24" name="HelveticaNeue-Thin"/>
<font key="font" metaFont="systemUltraLight" size="24"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -310,7 +310,7 @@
</connections>
</scrollView>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="nM8-O3-spM" customClass="MPGradientView">
<rect key="frame" x="0.0" y="0.0" width="640" height="212"/>
<rect key="frame" x="0.0" y="0.0" width="738" height="238"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="color" keyPath="startingColor">
<color key="value" red="0.11764705882352941" green="0.11764705882352941" blue="0.11764705882352941" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
@@ -324,7 +324,7 @@
</userDefinedRuntimeAttributes>
</customView>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Aue-Zx-6Mf" userLabel="Settings Gear">
<rect key="frame" x="585" y="493" width="35" height="44"/>
<rect key="frame" x="683" y="489" width="35" height="44"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
@@ -339,13 +339,13 @@
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gAU-xs-aae">
<rect key="frame" x="594" y="479" width="18" height="14"/>
<rect key="frame" x="692" y="475" width="18" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘," id="Xm1-qb-6EP">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -358,7 +358,7 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="OnR-s6-d4P" userLabel="Site Name Label">
<rect key="frame" x="209" y="308" width="223" height="16"/>
<rect key="frame" x="258" y="306" width="223" height="16"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -370,7 +370,7 @@
</textFieldCell>
</textField>
<secureTextField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="iGR-wo-ual" userLabel="Secure Master Password">
<rect key="frame" x="18" y="257" width="604" height="43"/>
<rect key="frame" x="18" y="255" width="702" height="43"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -405,7 +405,7 @@
</connections>
</secureTextField>
<textField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="v80-wd-hUR" userLabel="Revealed Master Password">
<rect key="frame" x="18" y="257" width="604" height="43"/>
<rect key="frame" x="18" y="255" width="702" height="43"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
@@ -444,13 +444,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lW3-2z-cEa" userLabel="Master Password Tip">
<rect key="frame" x="195" y="237" width="251" height="12"/>
<rect key="frame" x="248" y="236" width="243" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hold alt ⌥ to reveal tips and unmask ●●●●●●●●." id="4Ep-xX-Ky8">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -471,13 +471,13 @@
</connections>
</textField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Oy5-B7-hdN" userLabel="Revealed Master Password Tip">
<rect key="frame" x="195" y="237" width="250" height="12"/>
<rect key="frame" x="258" y="236" width="222" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Hold alt ⌥ to reveal tips and unmask passwords." id="bQ4-AN-S1A">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -499,7 +499,7 @@
</connections>
</textField>
<searchField focusRingType="none" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="CnS-iI-dhr" userLabel="Site Name">
<rect key="frame" x="62" y="257" width="516" height="43"/>
<rect key="frame" x="111" y="255" width="516" height="43"/>
<constraints>
<constraint firstAttribute="width" constant="512" id="rW7-Vq-4Xy"/>
</constraints>
@@ -508,7 +508,7 @@
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<searchFieldCell key="cell" selectable="YES" editable="YES" focusRingType="none" alignment="center" placeholderString="Site Name" sendsSearchStringImmediately="YES" id="ppl-2c-1E9">
<font key="font" size="36" name="HelveticaNeue-Thin"/>
<font key="font" metaFont="system" size="36"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</searchFieldCell>
@@ -519,13 +519,13 @@
</connections>
</searchField>
<textField hidden="YES" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="npC-Kk-gUM" userLabel="Site Name Tip">
<rect key="frame" x="101" y="237" width="438" height="12"/>
<rect key="frame" x="172" y="236" width="395" height="11"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" title="Type the name of your site (eg. apple.com), then hit enter ⏎ to create a password for it." id="QTI-cz-Onx">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -542,13 +542,13 @@
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rhm-sC-xFS" userLabel="Site Name Tip">
<rect key="frame" x="138" y="225" width="365" height="24"/>
<rect key="frame" x="206" y="225" width="326" height="22"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="center" id="n3W-XU-dya">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="miniSystem"/>
<string key="title">Hit enter ⏎ to copy the site's password, hold shift ⇧ for the login name.
Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
@@ -568,7 +568,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<stackView distribution="fill" orientation="horizontal" alignment="bottom" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pHt-gg-ZNX">
<rect key="frame" x="73" y="20" width="495" height="152"/>
<rect key="frame" x="122" y="20" width="495" height="178"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="1Qo-iG-CQt">
<rect key="frame" x="0.0" y="-1" width="85" height="19"/>
@@ -591,16 +591,16 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</button>
<stackView distribution="fill" orientation="vertical" alignment="centerX" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DT0-RU-3LT">
<rect key="frame" x="93" y="0.0" width="177" height="152"/>
<rect key="frame" x="93" y="0.0" width="177" height="178"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uol-dE-I8H">
<rect key="frame" x="77" y="138" width="22" height="14"/>
<rect key="frame" x="77" y="164" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘P" id="MyN-x6-dMk">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -631,7 +631,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="brI-fg-Kav">
<rect key="frame" x="40" y="111" width="96" height="19"/>
<rect key="frame" x="40" y="137" width="96" height="19"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
@@ -659,6 +659,35 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jl4-sS-xbm">
<rect key="frame" x="12" y="111" width="153" height="19"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<buttonCell key="cell" type="recessed" title="Default Password Type" bezelStyle="recessed" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Ah6-gK-Rm7" customClass="MPNoStateButtonCell">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES" changeBackground="YES" changeGray="YES"/>
<font key="font" metaFont="systemBold" size="12"/>
<string key="keyEquivalent">p</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="changeDefaultType:" target="-2" id="3Qg-xV-ewc"/>
<binding destination="-2" name="hidden2" keyPath="alternatePressed" previousBinding="2Mv-lM-iXB" id="6QO-NJ-Uyo">
<dictionary key="options">
<integer key="NSMultipleValuesPlaceholder" value="-1"/>
<integer key="NSNoSelectionPlaceholder" value="-1"/>
<integer key="NSNotApplicablePlaceholder" value="-1"/>
<integer key="NSNullPlaceholder" value="-1"/>
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="2Mv-lM-iXB">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
</dictionary>
</binding>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="R46-fx-n14">
<rect key="frame" x="0.0" y="85" width="177" height="19"/>
<shadow key="shadow">
@@ -686,26 +715,26 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</button>
<stackView distribution="fill" orientation="horizontal" alignment="top" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Bgn-Ne-fQ7" userLabel="Version Container">
<rect key="frame" x="70" y="56" width="36" height="22"/>
<rect key="frame" x="71" y="56" width="34" height="22"/>
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="mcq-qD-yte">
<rect key="frame" x="-3" y="-3" width="19" height="28"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<stepperCell key="cell" continuous="YES" alignment="left" minValue="1" maxValue="1000" doubleValue="1" id="73y-03-zHt"/>
<stepperCell key="cell" continuous="YES" alignment="left" maxValue="4294967295" doubleValue="1" valueWraps="YES" id="73y-03-zHt"/>
<connections>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.algorithmVersion" id="GyA-hK-6cD"/>
</connections>
</stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gyg-Fh-yn7">
<rect key="frame" x="19" y="7" width="19" height="15"/>
<rect key="frame" x="19" y="8" width="17" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="V1" id="Pjy-Fm-zwB">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="calibratedRGB"/>
</textFieldCell>
@@ -740,26 +769,26 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</stackView>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" detachesHiddenViews="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6II-KA-cNi" userLabel="Counter Container">
<rect key="frame" x="74" y="26" width="28" height="22"/>
<rect key="frame" x="75" y="26" width="27" height="22"/>
<subviews>
<stepper horizontalHuggingPriority="750" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="XgA-Vl-CKh" userLabel="Counter Stepper">
<rect key="frame" x="-3" y="-3" width="19" height="28"/>
<shadow key="shadow">
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<stepperCell key="cell" continuous="YES" alignment="left" minValue="1" maxValue="1000" doubleValue="1" id="ikF-n4-xiI"/>
<stepperCell key="cell" continuous="YES" alignment="left" minValue="1" maxValue="4294967295" doubleValue="1" valueWraps="YES" id="ikF-n4-xiI"/>
<connections>
<binding destination="mcS-ik-b0n" name="value" keyPath="selection.counter" id="qmm-6z-boy"/>
</connections>
</stepper>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NvO-kt-eZ2" userLabel="Counter Field">
<rect key="frame" x="19" y="4" width="11" height="15"/>
<rect key="frame" x="19" y="4" width="10" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="1" id="dhQ-bJ-rn3">
<font key="font" size="12" name="HelveticaNeue-Medium"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="calibratedRGB"/>
</textFieldCell>
@@ -805,7 +834,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</buttonCell>
<connections>
<action selector="changeType:" target="-2" id="6Jj-7p-da9"/>
<action selector="changeSiteType:" target="-2" id="6Jj-7p-da9"/>
<binding destination="mcS-ik-b0n" name="hidden" keyPath="canRemove" id="Hat-GU-hcQ">
<dictionary key="options">
<string key="NSValueTransformerName">NSNegateBoolean</string>
@@ -821,6 +850,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
<integer value="1000"/>
</visibilityPriorities>
<customSpacing>
<real value="3.4028234663852886e+38"/>
@@ -829,6 +859,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
<real value="3.4028234663852886e+38"/>
</customSpacing>
</stackView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="whJ-Bw-pr4">
@@ -886,13 +917,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</customSpacing>
</stackView>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="luC-0j-BeV">
<rect key="frame" x="135" y="50" width="103" height="14"/>
<rect key="frame" x="185" y="50" width="103" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Password Number:" id="weR-33-U49">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -923,13 +954,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gjx-bt-fKM">
<rect key="frame" x="134" y="80" width="100" height="14"/>
<rect key="frame" x="184" y="80" width="100" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Password Version:" id="E77-5t-EKJ">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -960,13 +991,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dbM-ja-dKO" userLabel="Version Tip">
<rect key="frame" x="88" y="106" width="332" height="14"/>
<rect key="frame" x="137" y="106" width="332" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Upgrade your site's algorithm version for maximum protection:" id="3ds-qG-YNd">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -988,13 +1019,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ido-NQ-3MY">
<rect key="frame" x="104" y="4" width="22" height="12"/>
<rect key="frame" x="154" y="2" width="21" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘L" id="fUB-rF-7x8">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1016,13 +1047,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="9b3-wy-KBb">
<rect key="frame" x="243" y="2" width="22" height="14"/>
<rect key="frame" x="292" y="2" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘T" id="HFM-Bk-akx">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1044,13 +1075,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qal-PP-YtO">
<rect key="frame" x="516" y="4" width="23" height="12"/>
<rect key="frame" x="565" y="2" width="23" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘D" id="PPC-be-w4E">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1072,13 +1103,13 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
</connections>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="qLF-A6-ThX">
<rect key="frame" x="404" y="4" width="23" height="12"/>
<rect key="frame" x="453" y="2" width="22" height="14"/>
<shadow key="shadow" blurRadius="0.5">
<size key="offset" width="0.0" height="1"/>
<color key="color" name="controlLightHighlightColor" catalog="System" colorSpace="catalog"/>
</shadow>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="⌘S" id="qh6-k2-MUr">
<font key="font" size="11" name="HelveticaNeue"/>
<font key="font" metaFont="message" size="11"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@@ -1178,7 +1209,7 @@ Use the arrows ⇅ to navigate the list or esc ⎋ to exit.</string>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kCO-1M-Wz1">
<rect key="frame" x="10" y="12" width="390" height="92"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="kl0-P1-6ZY">
<font key="font" metaFont="menu" size="11"/>
<font key="font" metaFont="message" size="11"/>
<string key="title">"Personal password" allows you to store your own password. It cannot be regenerated in the event of loss. "Device private password" is similar but will never leave your device: it cannot be synced, backed up or exported.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>

View File

@@ -102,7 +102,7 @@
attributes = {
BuildIndependentTargetsInParallel = YES;
CLASSPREFIX = MP;
LastUpgradeCheck = 1140;
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = "Maarten Billemont";
TargetAttributes = {
DAD9B5C0176299B9001835F9 = {
@@ -185,6 +185,7 @@
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
@@ -271,6 +272,7 @@
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = NO;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;

View File

@@ -1,34 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSBackgroundOnly</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2013 Maarten Billemont. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

View File

@@ -58,22 +58,6 @@ const long MPAvatarAdd = 10000;
self.avatarImageView.layer.masksToBounds = NO;
self.avatarImageView.backgroundColor = [UIColor clearColor];
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
self.contentView.frame = self.bounds;
}];
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
^(MPAvatarCell *self, NSNotification *note) {
CGRect keyboardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = CGRectGetHeight( self.window.screen.bounds ) - CGRectGetMinY( keyboardRect );
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
} );
CABasicAnimation *toShadowOpacityAnimation = [CABasicAnimation animationWithKeyPath:@"shadowOpacity"];
toShadowOpacityAnimation.toValue = @0.2f;
toShadowOpacityAnimation.duration = 0.5f;
@@ -91,6 +75,22 @@ const long MPAvatarAdd = 10000;
self.targetedShadowAnimation.duration = MAXFLOAT;
self.avatarImageView.layer.shadowColor = [UIColor whiteColor].CGColor;
self.avatarImageView.layer.shadowOffset = CGSizeZero;
[self observeKeyPath:@"bounds" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
self.contentView.frame = self.bounds;
}];
[self observeKeyPath:@"selected" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
[self observeKeyPath:@"highlighted" withBlock:^(id from, id to, NSKeyValueChange cause, MPAvatarCell *self) {
[self updateAnimated:self.superview != nil];
}];
PearlAddNotificationObserver( UIKeyboardWillShowNotification, nil, [NSOperationQueue mainQueue],
^(MPAvatarCell *self, NSNotification *note) {
CGRect keyboardRect = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGFloat keyboardHeight = CGRectGetHeight( self.window.screen.bounds ) - CGRectGetMinY( keyboardRect );
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
} );
}
- (void)prepareForReuse {

View File

@@ -31,7 +31,6 @@
[super viewDidLoad];
self.mode = MPCombinedModeUserSelection;
[self performSegueWithIdentifier:@"users" sender:self];
}
- (void)viewWillAppear:(BOOL)animated {

View File

@@ -30,8 +30,10 @@
@property(weak, nonatomic) IBOutlet UIActivityIndicatorView *activity;
@property(weak, nonatomic) IBOutlet UIButton *passwordButton;
@property(weak, nonatomic) IBOutlet UIView *tipContainer;
@property(weak, nonatomic) IBOutlet UIButton *deviceButton;
- (IBAction)controlChanged:(UIControl *)control;
- (IBAction)copyPassword:(UITapGestureRecognizer *)recognizer;
- (IBAction)copyDevice:(id)sender;
@end

View File

@@ -38,6 +38,8 @@
self.view.backgroundColor = [UIColor clearColor];
self.dialogView.layer.cornerRadius = 5;
[self.deviceButton setTitle:[PearlKeyChain deviceIdentifier] forState:UIControlStateNormal];
}
- (void)viewWillAppear:(BOOL)animated {
@@ -112,6 +114,11 @@
}];
}
- (IBAction)copyDevice:(id)sender {
[UIPasteboard generalPasteboard].string = [PearlKeyChain deviceIdentifier];
[PearlOverlay showTemporaryOverlayWithTitle:strl( @"Device Identifier Copied" ) dismissAfter:2];
}
#pragma mark - Private
- (void)updateKey {

View File

@@ -17,7 +17,6 @@
//==============================================================================
#import "MPGuideViewController.h"
#import "markdown_lib.h"
#import "NSString+MPMarkDown.h"
@interface MPGuideStep : NSObject

View File

@@ -18,6 +18,7 @@
#import "MPNavigationController.h"
#import "MPWebViewController.h"
#import "MPiOSAppDelegate.h"
@implementation MPNavigationController
@@ -29,6 +30,16 @@
[self performSegueWithIdentifier:@"setup" sender:self];
}
- (void)performSegueWithIdentifier:(NSString *)identifier sender:(id)sender {
if ([identifier isEqualToString:@"web"] && [[(NSURL *)sender scheme] isEqualToString:@"masterpassword"]) {
[[MPiOSAppDelegate get] openURL:sender];
return;
}
[super performSegueWithIdentifier:identifier sender:sender];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"web"])

View File

@@ -33,13 +33,6 @@
self.dismissSegueByButton = [NSMutableDictionary dictionary];
}
- (void)viewDidLoad {
[super viewDidLoad];
[self performSegueWithIdentifier:@"root" sender:self];
}
- (UIViewController *)childViewControllerForStatusBarStyle {
return [self.childViewControllers lastObject];

View File

@@ -1,23 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password 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.
//
// Master Password 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 can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import <Foundation/Foundation.h>
@interface MPRootSegue : UIStoryboardSegue
@end

View File

@@ -1,35 +0,0 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password 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.
//
// Master Password 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 can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
#import "MPRootSegue.h"
@implementation MPRootSegue
- (void)perform {
UIViewController *sourceViewController = self.sourceViewController;
UIViewController *destinationViewController = self.destinationViewController;
[sourceViewController addChildViewController:destinationViewController];
destinationViewController.view.frame = sourceViewController.view.bounds;
destinationViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[sourceViewController.view addSubview:destinationViewController.view];
[destinationViewController didMoveToParentViewController:sourceViewController];
[sourceViewController setNeedsStatusBarAppearanceUpdate];
}
@end

View File

@@ -27,7 +27,7 @@ typedef NS_ENUM ( NSUInteger, MPSiteCellMode ) {
@interface MPSiteCell : MPCell<UIScrollViewDelegate, UITextFieldDelegate>
@property(nonatomic) NSArray *fuzzyGroups;
@property(nonatomic) NSArray *queryGroups;
- (void)setSite:(MPSiteEntity *)site animated:(BOOL)animated;
- (void)setTransientSite:(NSString *)siteName animated:(BOOL)animated;

View File

@@ -135,7 +135,7 @@
[super prepareForReuse];
self.siteOID = nil;
self.fuzzyGroups = nil;
self.queryGroups = nil;
self.transientSite = nil;
self.mode = MPPasswordCellModePassword;
[self updateAnimated:NO];
@@ -150,11 +150,11 @@
#pragma mark - State
- (void)setFuzzyGroups:(NSArray *)fuzzyGroups {
- (void)setQueryGroups:(NSArray *)queryGroups {
if (self.fuzzyGroups == fuzzyGroups)
if (self.queryGroups == queryGroups)
return;
_fuzzyGroups = fuzzyGroups;
_queryGroups = queryGroups;
[self updateSiteName:[self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]]];
}
@@ -263,6 +263,7 @@
UIAlertController *controller = [UIAlertController alertControllerWithTitle:strf( @"Delete %@?", site.name ) message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[controller.popoverPresentationController setSourceView:sender];
[controller addAction:[UIAlertAction actionWithTitle:@"Delete Site" style:UIAlertActionStyleDestructive
handler:^(UIAlertAction *_Nonnull action) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
@@ -284,6 +285,7 @@
MPSiteEntity *mainSite = [self siteInContext:[MPiOSAppDelegate managedObjectContextForMainThreadIfReady]];
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Change Password Type" message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[controller.popoverPresentationController setSourceView:sender];
for (NSNumber *typeNumber in [mainSite.algorithm allTypes]) {
MPResultType type = (MPResultType)[typeNumber unsignedIntegerValue];
NSString *typeName = [mainSite.algorithm nameOfType:type];
@@ -425,7 +427,7 @@
if (!site || ![site isKindOfClass:[MPGeneratedSiteEntity class]])
return;
((MPGeneratedSiteEntity *)site).counter = 1;
((MPGeneratedSiteEntity *)site).counter = MPCounterValueInitial;
[context saveToStore];
[PearlOverlay showTemporaryOverlayWithTitle:@"Counter Reset" dismissAfter:2];
@@ -433,7 +435,7 @@
}];
}
- (IBAction)doContent:(id)sender {
- (IBAction)doContent:(UIButton *)sender {
[UIView animateWithDuration:.2f animations:^{
self.contentButton.selected = YES;
@@ -444,6 +446,7 @@
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Create Site" message:
strf( @"Remember site named:\n%@", self.transientSite )
preferredStyle:UIAlertControllerStyleActionSheet];
[controller.popoverPresentationController setSourceView:sender];
[controller addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *_Nonnull action) {
[[MPiOSAppDelegate get]
@@ -534,13 +537,13 @@
// UI
//self.backgroundColor = mainSite.url? [UIColor greenColor]: [UIColor redColor];
self.upgradeButton.gone = !mainSite.requiresExplicitMigration && ![[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.gone = ![[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
self.upgradeButton.visible = mainSite.requiresExplicitMigration || [[MPiOSConfig get].allowDowngrade boolValue];
self.answersButton.visible = [[MPiOSAppDelegate get] isFeatureUnlocked:MPProductGenerateAnswers];
BOOL settingsMode = self.mode == MPPasswordCellModeSettings;
self.loginNameContainer.visible = settingsMode || mainSite.loginGenerated || [mainSite.loginName length];
self.modeButton.visible = !self.transientSite;
self.modeButton.alpha = settingsMode? 0.5f: 0.1f;
self.counterLabel.visible = self.counterButton.visible = mainSite.type & MPResultTypeClassTemplate;
self.counterLabel.visible = self.counterButton.visible = (mainSite.type & MPResultTypeClassTemplate) == MPResultTypeClassTemplate;
self.modeButton.selected = settingsMode;
self.strengthLabel.gone = !settingsMode;
self.modeScrollView.scrollEnabled = !self.transientSite;
@@ -653,14 +656,14 @@
NSString *siteName = self.transientSite?: site.name;
NSMutableAttributedString *attributedSiteName = [[NSMutableAttributedString alloc] initWithString:siteName?: @""];
if ([attributedSiteName length])
for (NSUInteger f = 0, s = (NSUInteger)-1; f < [self.fuzzyGroups count]; ++f) {
s = [siteName rangeOfString:self.fuzzyGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s + 1, [siteName length] - (s + 1) )].location;
for (NSUInteger f = 0, s = 0; f < [self.queryGroups count]; ++f, ++s) {
s = [siteName rangeOfString:self.queryGroups[f] options:NSDiacriticInsensitiveSearch | NSCaseInsensitiveSearch
range:NSMakeRange( s, [siteName length] - s )].location;
if (s == NSNotFound)
break;
[attributedSiteName addAttribute:NSBackgroundColorAttributeName value:[UIColor redColor]
range:NSMakeRange( s, [self.fuzzyGroups[f] length] )];
range:NSMakeRange( s, [self.queryGroups[f] length] )];
}
if (self.transientSite)

View File

@@ -43,7 +43,7 @@
UIView *sitesView = sitesVC.view;
sitesView.frame = combinedVC.view.bounds;
sitesView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[combinedVC.view insertSubview:sitesView belowSubview:combinedVC.usersVC.view];
[combinedVC.view insertSubview:sitesView belowSubview:combinedVC.usersVC.view.superview];
[sitesVC setActive:YES animated:self.animated completion:^(BOOL finished) {
if (!finished)

View File

@@ -30,8 +30,8 @@
@property(nonatomic, strong) IBOutlet UIView *badNameTipContainer;
@property(nonatomic, strong) IBOutlet UIView *popdownView;
@property(nonatomic, strong) IBOutlet UIView *popdownContainer;
@property(nonatomic, strong) IBOutlet UIView *voltoInstallAlert;
@property(nonatomic, strong) IBOutlet UIView *voltoMigrateAlert;
@property(nonatomic, strong) IBOutlet UIView *spectreInstallAlert;
@property(nonatomic, strong) IBOutlet UIView *spectreMigrateAlert;
@property(assign, nonatomic) BOOL active;
@@ -39,6 +39,6 @@
- (void)reloadSites;
- (IBAction)dismissPopdown:(id)sender;
- (IBAction)upgradeVolto:(UIButton *)sender;
- (IBAction)upgradeSpectre:(UIButton *)sender;
@end

View File

@@ -16,7 +16,9 @@
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
MP_LIBS_BEGIN
#import <StoreKit/StoreKit.h>
MP_LIBS_END
#import "MPSitesViewController.h"
#import "MPiOSAppDelegate.h"
@@ -33,11 +35,10 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
MPPasswordsBadNameTip = 1 << 0,
};
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate, SKStoreProductViewControllerDelegate>
@interface MPSitesViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic, strong) SKStoreProductViewController *voltoViewController;
@property(nonatomic, strong) NSFetchedResultsController *fetchedResultsController;
@property(nonatomic, strong) NSArray *fuzzyGroups;
@property(nonatomic, strong) NSArray *queryGroups;
@property(nonatomic, strong) NSCharacterSet *siteNameAcceptableCharactersSet;
@property(nonatomic, strong) NSMutableArray<NSMutableArray *> *dataSource;
@property(nonatomic, weak) UIViewController *popdownVC;
@@ -73,7 +74,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
[self registerObservers];
[self updateConfigKey:nil];
[self updateVoltoAlerts];
[self updateSpectreAlerts];
static NSRegularExpression *bareHostRE = nil;
static dispatch_once_t once = 0;
@@ -121,12 +122,14 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlRemoveNotificationObservers();
}
- (void)viewWillLayoutSubviews {
- (void)viewDidLayoutSubviews {
self.collectionView.contentInset = [self.collectionView occludedInsets];
self.collectionView.scrollIndicatorInsets = self.collectionView.contentInset;
[super viewDidLayoutSubviews];
[super viewWillLayoutSubviews];
if (@available( iOS 11, * )) {
self.collectionView.layoutMargins =
UIEdgeInsetsMake( [self.collectionView occludedInsets].top - self.view.safeAreaInsets.top, 0, 0, 0 );
}
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
@@ -161,7 +164,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MPSiteCell *cell = [MPSiteCell dequeueFromCollectionView:collectionView indexPath:indexPath];
[cell setFuzzyGroups:self.fuzzyGroups];
[cell setQueryGroups:self.queryGroups];
id item = self.dataSource[(NSUInteger)indexPath.section][(NSUInteger)indexPath.item];
if ([item isKindOfClass:[MPSiteEntity class]])
[cell setSite:item animated:NO];
@@ -171,6 +174,19 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
return cell;
}
#pragma mark - UICollectionViewDelegateFlowLayout
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)collectionViewLayout;
CGFloat availableWidth = collectionView.bounds.size.width
- collectionView.layoutMargins.left - collectionView.layoutMargins.right
- layout.sectionInset.left - layout.sectionInset.right;
CGFloat cells = MAX( 1, (int)((availableWidth + layout.minimumInteritemSpacing) / (318 + layout.minimumInteritemSpacing)) );
return CGSizeMake( (availableWidth - layout.minimumInteritemSpacing * (cells - 1)) / cells, 100 );
}
#pragma mark - UIScrollDelegate
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
@@ -185,11 +201,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if (controller == self.fetchedResultsController)
PearlMainQueue( ^{
[self.collectionView updateDataSource:self.dataSource
toSections:[self createDataSource]
reloadItems:nil completion:nil];
} );
[self updateSites];
}
#pragma mark - UISearchBarDelegate
@@ -306,7 +318,7 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
PearlAddNotificationObserver( UIApplicationWillEnterForegroundNotification, nil, [NSOperationQueue mainQueue],
^(MPSitesViewController *self, NSNotification *note) {
[self viewWillAppear:YES];
[self updateVoltoAlerts];
[self updateSpectreAlerts];
} );
PearlAddNotificationObserver( MPSignedOutNotification, nil, nil,
^(MPSitesViewController *self, NSNotification *note) {
@@ -354,39 +366,42 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
- (void)reloadSites {
[self.fetchedResultsController.managedObjectContext performBlock:^{
static NSRegularExpression *fuzzyRE;
static dispatch_once_t once = 0;
dispatch_once( &once, ^{
fuzzyRE = [NSRegularExpression regularExpressionWithPattern:@"(.)" options:0 error:nil];
} );
NSString *queryString = self.query;
NSString *queryPattern = [[queryString stringByReplacingMatchesOfExpression:fuzzyRE withTemplate:@"*$1"]
stringByAppendingString:@"*"];
NSMutableArray *fuzzyGroups = [NSMutableArray new];
[fuzzyRE enumerateMatchesInString:queryString options:0 range:NSMakeRange( 0, queryString.length )
usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
[fuzzyGroups addObject:[queryString substringWithRange:result.range]];
}];
self.fuzzyGroups = fuzzyGroups;
NSMutableArray *queryGroups = [NSMutableArray new];
NSMutableString *queryPattern = [NSMutableString new];
[queryString enumerateSubstringsInRange: NSMakeRange(0, [queryString length]) options: NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (substringRange.location < 20) {
[queryGroups addObject:substring];
[queryPattern appendString:@"*"];
}
[queryPattern appendString:substring];
}];
[queryPattern appendString:@"*"];
self.queryGroups = queryGroups;
NSError *error = nil;
self.fetchedResultsController.fetchRequest.predicate =
[NSPredicate predicateWithFormat:@"name LIKE[cd] %@ AND user == %@", queryPattern, [MPiOSAppDelegate get].activeUserOID];
if (![self.fetchedResultsController performFetch:&error])
if (![self.fetchedResultsController performFetch:&error] || error)
MPError( error, @"Couldn't fetch sites." );
PearlMainQueue( ^{
[self.collectionView updateDataSource:self.dataSource
toSections:[self createDataSource]
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
for (MPSiteCell *cell in self.collectionView.visibleCells)
[cell setFuzzyGroups:self.fuzzyGroups];
}];
} );
[self updateSites];
}];
}
- (void)updateSites {
PearlMainQueue( ^{
[self.collectionView updateDataSource:self.dataSource
toSections:[self createDataSource]
reloadItems:@[ MPTransientPasswordItem ] completion:^(BOOL finished) {
for (MPSiteCell *cell in self.collectionView.visibleCells)
[cell setQueryGroups:self.queryGroups];
}];
} );
}
#pragma mark - Properties
- (NSString *)query {
@@ -404,14 +419,19 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
if (!_fetchedResultsController) {
[MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPSiteEntity class] )];
NSFetchRequest *fetchRequest = [MPSiteEntity fetchRequest];
fetchRequest.sortDescriptors = @[
[[NSSortDescriptor alloc] initWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
];
fetchRequest.fetchBatchSize = 10;
(self.fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:mainContext
sectionNameKeyPath:nil cacheName:nil]).delegate = self;
NSError *error = nil;
if (![self.fetchedResultsController performFetch:&error] || error)
MPError( error, @"Couldn't fetch sites." );
[self updateSites];
}];
[self registerObservers];
}
@@ -435,13 +455,6 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
} completion:completion];
}
#pragma mark - SKStoreProductViewControllerDelegate
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - Actions
- (IBAction)dismissPopdown:(id)sender {
@@ -452,76 +465,23 @@ typedef NS_OPTIONS( NSUInteger, MPPasswordsTips ) {
self.popdownToTopConstraint.priority = UILayoutPriorityDefaultHigh;
}
- (IBAction)upgradeVolto:(UIButton *)sender {
- (IBAction)upgradeSpectre:(UIButton *)sender {
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]]) {
[[MPiOSAppDelegate get] exportSitesRevealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
^(UIAlertAction *action) { setResult( nil ); }]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
} );
} result:^(NSString *exportedUser, NSError *error) {
if (!exportedUser || error) {
MPError( error, @"Failed to export user." );
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
return;
}
NSURLComponents *components = [NSURLComponents new];
components.scheme = @"volto";
components.path = @"import";
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
[UIApp openURL:components.URL];
}];
}
else if (self.voltoViewController)
[self presentViewController:self.voltoViewController animated:YES completion:nil];
[[MPiOSAppDelegate get] migrateFor:[MPiOSAppDelegate get].activeUserForMainThread];
}
#pragma mark - Private
- (void)updateVoltoAlerts {
- (void)updateSpectreAlerts {
BOOL voltoInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"volto:"]];
if (voltoInstalled) {
self.voltoInstallAlert.visible = NO;
self.voltoMigrateAlert.visible = YES;
BOOL spectreInstalled = [UIApp canOpenURL:[[NSURL alloc] initWithString:@"spectre:"]];
if (spectreInstalled) {
self.spectreInstallAlert.visible = NO;
self.spectreMigrateAlert.visible = YES;
}
else {
self.voltoInstallAlert.visible = NO;
self.voltoMigrateAlert.visible = NO;
self.voltoViewController = [SKStoreProductViewController new];
self.voltoViewController.delegate = self;
[self.voltoViewController loadProductWithParameters:@{
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
//SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Volto iOS */
} completionBlock:^(BOOL result, NSError *error) {
if (error)
err( @"Failed loading Volto product information: %@", error );
[UIView animateWithDuration:0.3f animations:^{
self.voltoInstallAlert.visible = result;
}];
}];
self.spectreInstallAlert.visible = [MPiOSAppDelegate get].spectreViewController != nil;
self.spectreMigrateAlert.visible = NO;
}
}

View File

@@ -18,10 +18,13 @@
#import "MPStoreViewController.h"
#import "MPiOSAppDelegate.h"
#import "UIColor+Expanded.h"
#import "MPAppDelegate_InApp.h"
#import "MPSitesViewController.h"
MP_LIBS_BEGIN
#import "UIColor+Expanded.h"
MP_LIBS_END
PearlEnum( MPDevelopmentFuelConsumption,
MPDevelopmentFuelConsumptionQuarterly, MPDevelopmentFuelConsumptionMonthly, MPDevelopmentFuelWeekly );

View File

@@ -48,7 +48,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
MPActiveUserStateMinimized,
};
@interface MPUsersViewController()
@interface MPUsersViewController()<NSFetchedResultsControllerDelegate>
@property(nonatomic) MPActiveUserState activeUserState;
@property(nonatomic, strong) NSArray *userIDs;
@@ -57,7 +57,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
@property(nonatomic) NSUInteger marqueeTipTextIndex;
@property(nonatomic, copy) NSString *masterPasswordChoice;
@property(nonatomic, strong) NSOperationQueue *afterUpdates;
@property(nonatomic, weak) id contextChangedObserver;
@property(nonatomic, strong) NSFetchedResultsController *userResultsController;
@end
@@ -91,6 +91,7 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
[super viewWillAppear:animated];
self.userSelectionContainer.visible = NO;
[self.storeLoadingActivity startAnimating];
}
- (void)viewDidAppear:(BOOL)animated {
@@ -231,12 +232,10 @@ typedef NS_ENUM( NSUInteger, MPActiveUserState ) {
user.avatar = newUserAvatar;
user.name = newUserName;
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance recordEvent:@"new-user" segmentation:@{
@"algorithm": @(user.algorithm.version).description,
@"avatar" : @(user.avatar).description,
}];
}
[Countly.sharedInstance recordEvent:@"new-user" segmentation:@{
@"algorithm": @(user.algorithm.version).description,
@"avatar" : @(user.avatar).description,
}];
}
BOOL signedIn = [[MPiOSAppDelegate get] signInAsUser:user saveInContext:context
@@ -434,6 +433,7 @@ referenceSizeForFooterInSection:(NSInteger)section {
NSManagedObjectID *userID = user.permanentObjectID;
UIAlertController *controller = [UIAlertController alertControllerWithTitle:user.name message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[controller.popoverPresentationController setSourceView:avatarCell];
[controller addAction:[UIAlertAction actionWithTitle:@"Delete User" style:UIAlertActionStyleDestructive handler:^(UIAlertAction * _Nonnull action) {
UIAlertController *controller_ = [UIAlertController alertControllerWithTitle:@"Deleting User" message:
@"The user and its sites will be deleted." preferredStyle:UIAlertControllerStyleAlert];
@@ -643,7 +643,6 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self removeKeyPathObservers];
PearlRemoveNotificationObservers();
[[NSNotificationCenter defaultCenter] removeObserver:self.contextChangedObserver];
}
- (void)registerObservers {
@@ -675,24 +674,6 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self.keyboardHeightConstraint updateConstant:keyboardHeight];
} );
if ((self.contextChangedObserver
= [[MPiOSAppDelegate get] managedObjectContextChanged:^(NSDictionary<NSManagedObjectID *, NSString *> *affectedObjects) {
if ([[[affectedObjects allKeys] filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock:^BOOL(NSManagedObjectID *objectID, NSDictionary *bindings) {
return [objectID.entity.name isEqualToString:NSStringFromClass( [MPUserEntity class] )];
}]] count])
[self reloadUsers];
}]))
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = YES;
[self.storeLoadingActivity stopAnimating];
}];
else
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = NO;
[self.storeLoadingActivity startAnimating];
}];
PearlAddNotificationObserver( NSPersistentStoreCoordinatorStoresWillChangeNotification, [MPiOSAppDelegate get].storeCoordinator, nil,
^(MPUsersViewController *self, NSNotification *note) {
self.userIDs = nil;
@@ -704,32 +685,54 @@ referenceSizeForFooterInSection:(NSInteger)section {
[self reloadUsers];
} );
} );
[UIView animateWithDuration:0.3f animations:^{
self.avatarCollectionView.visible = YES;
[self.storeLoadingActivity stopAnimating];
}];
}
- (void)reloadUsers {
[self afterUpdatesMainQueue:^{
if (![MPiOSAppDelegate managedObjectContextForMainThreadPerformBlockAndWait:^(NSManagedObjectContext *mainContext) {
NSError *error = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
fetchRequest.sortDescriptors = @[
[NSSortDescriptor sortDescriptorWithKey:NSStringFromSelector( @selector( lastUsed ) ) ascending:NO]
];
NSArray *users = [mainContext executeFetchRequest:fetchRequest error:&error];
if (!users) {
MPError( error, @"Failed to load users." );
self.userIDs = nil;
}
self.userResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:mainContext
sectionNameKeyPath:nil cacheName:nil];
self.userResultsController.delegate = self;
NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]];
for (MPUserEntity *user in users)
[userIDs addObject:user.permanentObjectID];
self.userIDs = userIDs;
NSError *error = nil;
if (![self.userResultsController performFetch:&error])
MPError( error, @"Failed to load users." );
[self updateUsers];
}])
self.userIDs = nil;
}];
}
- (void)updateUsers {
[self.userResultsController.managedObjectContext performBlock:^{
NSArray *users = self.userResultsController.fetchedObjects;
NSMutableArray *userIDs = [NSMutableArray arrayWithCapacity:[users count]];
for (MPUserEntity *user in users)
[userIDs addObject:user.permanentObjectID];
self.userIDs = userIDs;
}];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self updateUsers];
}
#pragma mark - Properties
- (void)setActive:(BOOL)active animated:(BOOL)animated {

View File

@@ -17,10 +17,12 @@
//==============================================================================
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
@interface MPWebViewController : UIViewController<UIWebViewDelegate>
@interface MPWebViewController : UIViewController<WKNavigationDelegate>
@property(nonatomic) IBOutlet UIWebView *webView;
@property(nonatomic) IBOutlet WKWebView *webView;
@property(nonatomic) IBOutlet UINavigationBar *webNavigationBar;
@property(nonatomic) IBOutlet UINavigationItem *webNavigationItem;
@property(nonatomic) NSURL *initialURL;

View File

@@ -17,6 +17,7 @@
//==============================================================================
#import "MPWebViewController.h"
#import "MPiOSAppDelegate.h"
@implementation MPWebViewController
@@ -24,20 +25,30 @@
[super viewDidLoad];
[self.webView.scrollView insetOcclusion];
if (!self.initialURL)
self.initialURL = [NSURL URLWithString:@"https://masterpassword.app"];
self.webNavigationItem.title = self.initialURL.host;
// WKWebView can't be on the storyboard for iOS pre 11 due to an NSCoding bug.
[self.view insertSubview:self.webView = [WKWebView new] atIndex:0];
[self.webView setNavigationDelegate:self];
[self.webView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.webView.topAnchor constraintEqualToAnchor:self.view.topAnchor].active = YES;
[self.webView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor].active = YES;
[self.webView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor].active = YES;
[self.webView.bottomAnchor constraintEqualToAnchor:self.view.bottomAnchor].active = YES;
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
if (!self.initialURL)
self.initialURL = [NSURL URLWithString:@"https://masterpassword.app"];
self.webNavigationItem.title = self.initialURL.host;
self.webView.visible = NO;
[self.webView loadRequest:[[NSURLRequest alloc] initWithURL:self.initialURL]];
- (void)viewDidLayoutSubviews {
[self.webView.scrollView insetOcclusion];
[super viewDidLayoutSubviews];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
@@ -45,26 +56,24 @@
return UIStatusBarStyleLightContent;
}
#pragma mark - UIWebViewDelegate
#pragma mark - WKNavigationDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType {
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void ( ^ )(WKNavigationActionPolicy))decisionHandler {
if ([[request.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize thanks tip." );
if ([navigationAction.request.mainDocumentURL.scheme isEqualToString:@"masterpassword"]) {
[[MPiOSAppDelegate get] openURL:navigationAction.request.mainDocumentURL];
decisionHandler( WKNavigationActionPolicyCancel );
return;
}
if ([request.URL isEqual:request.mainDocumentURL]) {
self.webNavigationItem.title = request.URL.host;
self.webNavigationItem.prompt = strl( @"Loading" );
}
return YES;
decisionHandler( WKNavigationActionPolicyAllow );
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation {
self.webNavigationItem.title = webView.URL.host;
self.webNavigationItem.prompt = strl( @"Loading" );
UIActivityIndicatorView *activityView =
[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
@@ -72,19 +81,57 @@
[activityView startAnimating];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[UIView animateWithDuration:0.3 animations:^{
self.webView.visible = YES;
if ([[webView.URL absoluteString] rangeOfString:@"thanks.lhunath.com"].location != NSNotFound) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"tipped.thanks"];
if (![[NSUserDefaults standardUserDefaults] synchronize])
wrn( @"Couldn't synchronize thanks tip." );
}
[self.webNavigationItem setLeftBarButtonItem:[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector( action: )]];
[webView evaluateJavaScript:@"document.title" completionHandler:^(id o, NSError *error) {
self.webNavigationItem.prompt = [o description];
}];
[self.webNavigationItem setLeftBarButtonItem:[webView canGoBack]? [[UIBarButtonItem alloc]
initWithTitle:@"⬅︎" style:UIBarButtonItemStylePlain target:webView action:@selector( goBack )]: nil];
self.webNavigationItem.prompt = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
}
#pragma mark - Actions
- (IBAction)action:(UIBarButtonItem *)sender {
UIAlertController *controller = [UIAlertController alertControllerWithTitle:self.webView.URL.host
message:self.webView.URL.absoluteString
preferredStyle:UIAlertControllerStyleActionSheet];
[controller.popoverPresentationController setBarButtonItem:sender];
[controller addAction:[UIAlertAction actionWithTitle:@"Safari" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:self.webView.URL];
}]];
if ([UIApp canOpenURL:[NSURL URLWithString:@"firefox:"]]) {
[controller addAction:[UIAlertAction actionWithTitle:@"Firefox" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:[NSURL URLWithString:strf( @"firefox://open-url?url=%@",
[self.webView.URL.absoluteString stringByAddingPercentEncodingWithAllowedCharacters:
[NSCharacterSet URLQueryAllowedCharacterSet]] )]];
}]];
}
if ([UIApp canOpenURL:[NSURL URLWithString:@"googlechrome:"]]) {
NSURL *url = [[NSURL alloc] initWithScheme:[self.webView.URL.scheme isEqualToString:@"http"]? @"googlechrome": @"googlechromes"
host:self.webView.URL.host path:self.webView.URL.path];
[controller addAction:[UIAlertAction actionWithTitle:@"Chrome" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:url];
}]];
}
if ([UIApp canOpenURL:[NSURL URLWithString:@"opera-http:"]]) {
NSURL *url = [[NSURL alloc] initWithScheme:[self.webView.URL.scheme isEqualToString:@"http"]? @"opera-http": @"opera-https"
host:self.webView.URL.host path:self.webView.URL.path];
[controller addAction:[UIAlertAction actionWithTitle:@"Opera" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[UIApp openURL:url];
}]];
}
[controller addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:controller animated:YES completion:nil];
}
- (IBAction)done:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];

View File

@@ -17,15 +17,23 @@
//==============================================================================
#import <UIKit/UIKit.h>
#import <StoreKit/StoreKit.h>
#import "MPAppDelegate_Shared.h"
@interface MPiOSAppDelegate : MPAppDelegate_Shared
@interface MPiOSAppDelegate : MPAppDelegate_Shared <SKStoreProductViewControllerDelegate>
@property(nonatomic, strong) UIWindow *window;
@property(nonatomic, strong) SKStoreProductViewController *spectreViewController;
- (void)openURL:(NSURL *)url;
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
- (void)openFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController;
- (void)showExportForVC:(UIViewController *)viewController;
- (void)migrateFor:(MPUserEntity *)user;
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset;
@end

View File

@@ -23,8 +23,24 @@
#import "mpw-marshal.h"
#import "MPSecrets.h"
MP_LIBS_BEGIN
#import <Sentry/Sentry.h>
#import <Countly/Countly.h>
MP_LIBS_END
@interface CountlyPushNotifications
@end
@interface CountlyPushNotifications(MPNotifications)
@end
@implementation CountlyPushNotifications(MPNotifications)
- (void)openURL:(NSString *)URLString {
[[MPiOSAppDelegate get].window.rootViewController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:URLString]];
}
@end
@interface MPiOSAppDelegate()<UIDocumentInteractionControllerDelegate>
@@ -34,6 +50,8 @@
@implementation MPiOSAppDelegate
@synthesize window;
+ (void)initialize {
[MPiOSConfig get];
@@ -43,10 +61,10 @@
@try {
// Sentry
[SentrySDK initWithOptions:@{
[SentrySDK startWithOptions:@{
@"dsn" : NilToNSNull( decrypt( sentryDSN ) ),
#ifdef DEBUG
@"debug" : @(YES),
@"debug" : @(NO), //@(YES),
@"environment" : @"Development",
#elif PUBLIC
@"debug" : @(NO),
@@ -55,7 +73,7 @@
@"debug" : @(NO),
@"environment" : @"Private",
#endif
@"enabled" : [MPiOSConfig get].sendInfo,
@"enabled" : @([[MPiOSConfig get].sendInfo boolValue] || ![[MPiOSConfig get].sendInfoDecided boolValue]),
@"enableAutoSessionTracking": @(YES),
}];
[[PearlLogger get] registerListener:^BOOL(PearlLogMessage *message) {
@@ -106,7 +124,7 @@
countlyConfig.deviceID = [PearlKeyChain deviceIdentifier];
countlyConfig.secretSalt = decrypt( countlySalt );
#if DEBUG
countlyConfig.enableDebug = YES;
//countlyConfig.enableDebug = YES;
countlyConfig.pushTestMode = CLYPushTestModeDevelopment;
#elif ! PUBLIC
countlyConfig.enableDebug = NO;
@@ -136,12 +154,6 @@
@catch (id exception) {
err( @"During Config Test: %@", exception );
}
@try {
[super application:application didFinishLaunchingWithOptions:launchOptions];
}
@catch (id exception) {
err( @"During Pearl Application Launch: %@", exception );
}
@try {
inf( @"Started up with device identifier: %@", [PearlKeyChain deviceIdentifier] );
@@ -172,11 +184,29 @@
}
} );
SKStoreProductViewController *migrateVC = [SKStoreProductViewController new];
[migrateVC loadProductWithParameters:@{
SKStoreProductParameterCampaignToken : @"app-masterpassword.ios", /* Campaign: From MasterPassword iOS */
SKStoreProductParameterProviderToken : @153897, /* Provider: Maarten Billemont */
// SKStoreProductParameterITunesItemIdentifier: @510296984, /* Application: MasterPassword iOS */
SKStoreProductParameterITunesItemIdentifier: @1500430196, /* Application: Spectre iOS */
} completionBlock:^(BOOL result, NSError *error) {
if (error)
err( @"Failed loading Spectre product information: %@", error );
if (result) {
self.spectreViewController = migrateVC;
self.spectreViewController.delegate = self;
} else {
self.spectreViewController = nil;
}
}];
PearlMainQueueOperation( ^{
if ([[MPiOSConfig get].showSetup boolValue])
[self.navigationController performSegueWithIdentifier:@"setup" sender:self];
[self.window.rootViewController performSegueWithIdentifier:@"setup" sender:self];
[self enableNotifications];
[self consentFeatures];
} );
}
@catch (id exception) {
@@ -193,6 +223,12 @@
if (!url)
return NO;
// masterpassword: URLs.
if ([url.scheme isEqualToString:@"masterpassword"]) {
[self openURL:url];
return YES;
}
// Arbitrary URL to mpsites data.
[[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:
^(NSData *importedSitesData, NSURLResponse *response, NSError *error) {
@@ -206,7 +242,7 @@
(id)[error localizedDescription]?: error )
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
return;
}
@@ -218,7 +254,7 @@
@"Master Password couldn't understand the import file."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
return;
}
@@ -229,7 +265,42 @@
return YES;
}
- (void)enableNotifications {
- (void)consentFeatures {
if ([self askDiagnostics])
return;
[self tryNotifications];
}
- (BOOL)askDiagnostics {
if ([[MPiOSConfig get].sendInfoDecided boolValue])
return NO;
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Diagnostics" message:
@"We look for bugs, sudden crashes, runtime issues & statistics.\n\n"
@"Diagnostics are scrubbed and personal details will never leave your device."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Disable" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
[MPiOSConfig get].sendInfo = @(NO);
[MPiOSConfig get].sendInfoDecided = @(YES);
[self consentFeatures];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Engage" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[MPiOSConfig get].sendInfo = @(YES);
[MPiOSConfig get].sendInfoDecided = @(YES);
[self consentFeatures];
}]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
return YES;
}
- (void)tryNotifications {
[Countly.sharedInstance giveConsentForFeature:CLYConsentPushNotifications];
if (@available( iOS 12, * )) {
@@ -240,35 +311,35 @@
[self askNotifications];
}];
return;
}
else {
[self askNotifications];
}
[self askNotifications];
}
- (void)askNotifications {
if ([[MPiOSConfig get].notificationsDecided boolValue])
return;
PearlMainQueue( ^{
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"notificationsDecided"]) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Coming Soon" message:
@"Master Password is rolling out a new modern personal security platform and we're excited to bring you along.\n\n"
@"When it's time, we'll send you a notification to help you make an effortless transition."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if (@available( iOS 12, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"notificationsDecided"];
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"notificationsDecided"];
}
}]];
[(self.navigationController.presentedViewController?: (UIViewController *)self.navigationController)
presentViewController:alert animated:YES completion:nil];
}
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Coming Soon" message:
@"Master Password is rolling out a brand new, updated version and we're excited to bring you along.\n\n"
@"When it's time, we'll send you a notification to help you make an effortless transition."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Thanks" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
if (@available( iOS 12, * )) {
[Countly.sharedInstance askForNotificationPermissionWithOptions:UNAuthorizationOptionAlert completionHandler:
^(BOOL granted, NSError *error) {
[MPiOSConfig get].notificationsDecided = @(YES);
}];
}
else {
[Countly.sharedInstance askForNotificationPermission];
[MPiOSConfig get].notificationsDecided = @(YES);
}
}]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
}
@@ -297,7 +368,7 @@
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
} );
} askUserPassword:^NSString *(NSString *userName) {
@@ -315,7 +386,7 @@
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
} );
} result:^(NSError *error) {
@@ -326,7 +397,7 @@
UIAlertController *controller = [UIAlertController alertControllerWithTitle:@"Error" message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[controller addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:controller animated:YES completion:nil];
[self.window.rootViewController presentViewController:controller animated:YES completion:nil];
}
} );
}];
@@ -336,15 +407,7 @@
inf( @"Will foreground" );
[super applicationWillEnterForeground:application];
[self.hangDetector start];
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
PearlNotMainQueue( ^{
NSString *importData = [UIPasteboard generalPasteboard].string;
@@ -360,20 +423,22 @@
[UIPasteboard generalPasteboard].string = @"";
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"No" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
}
mpw_marshal_file_free( &importFile );
} );
}
[super applicationDidBecomeActive:application];
- (void)applicationDidBecomeActive:(UIApplication *)application {
inf( @"Re-activated" );
[[NSNotificationCenter defaultCenter] postNotificationName:MPCheckConfigNotification object:nil];
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
inf( @"Received memory warning." );
[super applicationDidReceiveMemoryWarning:application];
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
@@ -386,12 +451,46 @@
}
[self.hangDetector stop];
[super applicationDidEnterBackground:application];
}
#pragma mark - Behavior
- (void)openURL:(NSURL *)url {
if ([url.scheme isEqualToString:@"masterpassword"]) {
if ([url.host isEqualToString:@"open-url"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"url"]) {
[UIApp openURL:[NSURL URLWithString:item.value]];
return;
}
}
else if ([url.host isEqualToString:@"show-url"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"url"]) {
[self.window.rootViewController performSegueWithIdentifier:@"web" sender:[NSURL URLWithString:item.value]];
return;
}
}
else if ([url.host isEqualToString:@"migrate"]) {
for (NSURLQueryItem *item in [NSURLComponents componentsWithString:[url absoluteString]].queryItems)
if ([item.name isEqualToString:@"fullName"]) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSFetchRequest
*fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"name == %@", item.value];
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
[self migrateFor:users.firstObject];
}];
return;
}
[self migrateFor:nil];
return;
}
} else
[UIApp openURL:url];
}
- (void)showFeedbackWithLogs:(BOOL)logs forVC:(UIViewController *)viewController {
if (![PearlEMail canSendMail]) {
@@ -401,7 +500,7 @@
@"help@masterpassword.app"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
else if (logs) {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Feedback" message:
@@ -415,7 +514,7 @@
[alert addAction:[UIAlertAction actionWithTitle:@"No Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self openFeedbackWithLogs:NO forVC:viewController];
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
else
[self openFeedbackWithLogs:NO forVC:viewController];
@@ -461,15 +560,16 @@
@"This may be due to corruption. You can either reset Master Password and "
@"recreate your user, or E-Mail us your logs and leave your corrupt store as-is for now."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"E-Mail Logs" style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[self openFeedbackWithLogs:YES forVC:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Try Again" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self retryCorruptStore];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Send Logs" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self openFeedbackWithLogs:YES forVC:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Reset" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self deleteAndResetStore];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Ignore" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
} );
}
@@ -486,7 +586,7 @@
@"Would you like to make all your passwords visible in the export file?\n\n"
@"A safe export will include all sites but make their passwords invisible.\n"
@"It is great as a backup and remains safe when fallen in the wrong hands."
preferredStyle:UIAlertControllerStyleActionSheet];
preferredStyle:UIAlertControllerStyleAlert];
[sheet addAction:[UIAlertAction actionWithTitle:@"Safe Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self showExportRevealPasswords:NO forVC:viewController];
}]];
@@ -494,10 +594,10 @@
[self showExportRevealPasswords:YES forVC:viewController];
}]];
[sheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:sheet animated:YES completion:nil];
[self.window.rootViewController presentViewController:sheet animated:YES completion:nil];
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
- (void)showExportRevealPasswords:(BOOL)revealPasswords forVC:(UIViewController *)viewController {
@@ -508,93 +608,172 @@
@"Close Master Password, go into Settings and add a Mail account."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
return;
}
[self exportSitesRevealPasswords:revealPasswords askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
setResult( alert.textFields.firstObject.text );
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
} );
} result:^(NSString *exportedUser, NSError *error) {
if (!exportedUser || error) {
MPError( error, @"Failed to export mpsites." );
PearlMainQueue( ^{
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSError *error = nil;
NSString *exportedUser = [self exportSitesFor:[self activeUserInContext:context] revealPasswords:revealPasswords askExportPassword:
^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf(
@"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
setResult( alert.textFields.firstObject.text );
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action) {
setResult( nil );
}]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
} );
} error:&error];
PearlMainQueue( ^{
if (error) {
MPError( error, @"Failed to export mpsites." );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error" message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
} );
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
if (!exportedUser)
return;
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *message;
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:exportFileName], nil];
return;
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *writeError = nil;
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
}];
}
- (void)migrateFor:(MPUserEntity *)user {
if ([UIApp canOpenURL:[[NSURL alloc] initWithString:@"spectre:"]]) {
if (!user) {
[MPiOSAppDelegate managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:NSStringFromClass( [MPUserEntity class] )];
NSArray *users = [context executeFetchRequest:fetchRequest error:nil];
if (![users count])
return;
UIAlertController *usersSheet = [UIAlertController alertControllerWithTitle:@"Migrate User"
message:@"Choose a user to migrate out to Spectre."
preferredStyle:UIAlertControllerStyleAlert];
[usersSheet addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
for (MPUserEntity *user_ in users)
[usersSheet addAction:[UIAlertAction actionWithTitle:user_.name style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { [self migrateFor:user_]; }]];
PearlMainQueue( ^{
[self.window.rootViewController presentViewController:usersSheet animated:YES completion:nil];
} );
}];
return;
}
NSDateFormatter *exportDateFormatter = [NSDateFormatter new];
[exportDateFormatter setDateFormat:@"yyyy'-'MM'-'dd"];
NSString *exportFileName = strf( @"%@ (%@).mpsites",
[self activeUserForMainThread].name, [exportDateFormatter stringFromDate:[NSDate date]] );
[MPAppDelegate_Shared managedObjectContextPerformBlock:^(NSManagedObjectContext *context) {
NSError *error = nil;
NSString *exportedUser = [[MPAppDelegate_Shared get] exportSitesFor:[MPUserEntity existingObjectWithID:user.objectID inContext:context]
revealPasswords:NO askExportPassword:^NSString *(NSString *userName) {
return PearlAwait( ^(void (^setResult)(id)) {
PearlMainQueue( ^{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:strf( @"Master Password For:\n%@", userName )
message:@"Enter your master password to export the user."
preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler:^(UITextField *textField) {
textField.secureTextEntry = YES;
}];
[alert addAction:[UIAlertAction actionWithTitle:@"Export" style:UIAlertActionStyleDefault handler:
^(UIAlertAction *action) { setResult( alert.textFields.firstObject.text ); }]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:
^(UIAlertAction *action) { setResult( nil ); }]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
} );
} error:&error];
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Destination" message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:@"Send As E-Mail" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSString *message;
if (revealPasswords)
message = strf( @"Export of Master Password sites with passwords included.\n\n"
@"REMINDER: Make sure nobody else sees this file! Passwords are visible!\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
else
message = strf( @"Backup of Master Password sites.\n\n\n"
@"--\n"
@"%@\n"
@"Master Password %@, build %@",
[self activeUserForMainThread].name,
[PearlInfoPlist get].CFBundleShortVersionString,
[PearlInfoPlist get].CFBundleVersion );
PearlMainQueue( ^{
if (error) {
MPError( error, @"Failed to export user." );
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Export Error"
message:[error localizedDescription]
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Okay" style:UIAlertActionStyleCancel handler:nil]];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
}
if (!exportedUser)
return;
[PearlEMail sendEMailTo:nil fromVC:viewController subject:@"Master Password Export" body:message
attachments:[[PearlEMailAttachment alloc] initWithContent:[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
mimeType:@"text/plain"
fileName:exportFileName], nil];
return;
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Share / Export" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
NSURL *applicationSupportURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *exportURL = [[applicationSupportURL
URLByAppendingPathComponent:[NSBundle mainBundle].bundleIdentifier isDirectory:YES]
URLByAppendingPathComponent:exportFileName isDirectory:NO];
NSError *writeError = nil;
if (![[exportedUser dataUsingEncoding:NSUTF8StringEncoding]
writeToURL:exportURL options:NSDataWritingFileProtectionComplete error:&writeError])
MPError( writeError, @"Failed to write export data to URL %@.", exportURL );
else {
self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:exportURL];
self.interactionController.UTI = @"com.lyndir.masterpassword.sites";
self.interactionController.delegate = self;
[self.interactionController presentOpenInMenuFromRect:CGRectZero inView:viewController.view animated:YES];
}
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
}];
NSURLComponents *components = [NSURLComponents new];
components.scheme = @"spectre";
components.path = @"import";
components.queryItems = @[ [[NSURLQueryItem alloc] initWithName:@"data" value:exportedUser] ];
[UIApp openURL:components.URL];
} );
}];
}
else if (self.spectreViewController)
[self.window.rootViewController presentViewController:self.spectreViewController animated:YES completion:nil];
}
- (void)changeMasterPasswordFor:(MPUserEntity *)user saveInContext:(NSManagedObjectContext *)moc didResetBlock:(void ( ^ )(void))didReset {
@@ -605,7 +784,7 @@
@"Changing your master password will cause all your generated passwords to change!\n"
@"Changing the master password back to the old one will cause your passwords to revert as well."
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[alert addAction:[UIAlertAction actionWithTitle:@"Continue" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[moc performBlockAndWait:^{
inf( @"Clearing keyID for user: %@.", user.userID );
user.keyID = nil;
@@ -618,10 +797,17 @@
didReset();
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Abort" style:UIAlertActionStyleCancel handler:nil]];
[self.navigationController presentViewController:alert animated:YES completion:nil];
[self.window.rootViewController presentViewController:alert animated:YES completion:nil];
} );
}
#pragma mark - SKStoreProductViewControllerDelegate
- (void)productViewControllerDidFinish:(SKStoreProductViewController *)viewController {
[viewController dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - UIDocumentInteractionControllerDelegate
- (void)documentInteractionController:(UIDocumentInteractionController *)controller didEndSendingToApplication:(NSString *)application {
@@ -645,15 +831,14 @@
NSArray *countlyFeatures = @[
CLYConsentSessions, CLYConsentEvents, CLYConsentUserDetails, CLYConsentCrashReporting, CLYConsentViewTracking, CLYConsentStarRating
];
if ([[MPConfig get].sendInfo boolValue]) {
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
if ([[MPiOSConfig get].sendInfo boolValue] || ![[MPiOSConfig get].sendInfoDecided boolValue]) {
if ([PearlLogger get].printLevel > PearlLogLevelInfo)
[PearlLogger get].printLevel = PearlLogLevelInfo;
[SentrySDK.currentHub getClient].options.enabled = @YES;
[SentrySDK configureScope:^(SentryScope *scope) {
[scope setExtraValue:[MPConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPiOSConfig get].rememberLogin forKey:@"rememberLogin"];
[scope setExtraValue:[MPiOSConfig get].sendInfo forKey:@"sendInfo"];
[scope setExtraValue:[MPiOSConfig get].helpHidden forKey:@"helpHidden"];
[scope setExtraValue:[MPiOSConfig get].showSetup forKey:@"showQuickStart"];
[scope setExtraValue:[PearlConfig get].firstRun forKey:@"firstRun"];
@@ -671,10 +856,11 @@
[scope setExtraValue:@(NO) forKey:@"reviewedVersion"];
#endif
}];
[Countly.sharedInstance giveConsentForFeatures:countlyFeatures];
}
else {
[SentrySDK.currentHub getClient].options.enabled = @NO;
[Countly.sharedInstance cancelConsentForFeatures:countlyFeatures];
[SentrySDK.currentHub getClient].options.enabled = @NO;
}
}

View File

@@ -33,8 +33,8 @@
NSStringFromSelector( @selector( siteInfoHidden ) ) : @YES,
NSStringFromSelector( @selector( showSetup ) ) : @YES,
NSStringFromSelector( @selector( appleID ) ) : @"510296984",
NSStringFromSelector( @selector( actionsTipShown ) ) : @(!self.firstRun),
NSStringFromSelector( @selector( typeTipShown ) ) : @(!self.firstRun),
NSStringFromSelector( @selector( actionsTipShown ) ) : @(![self.firstRun boolValue]),
NSStringFromSelector( @selector( typeTipShown ) ) : @(![self.firstRun boolValue]),
NSStringFromSelector( @selector( loginNameTipShown ) ): @NO,
NSStringFromSelector( @selector( traceMode ) ) : @NO,
NSStringFromSelector( @selector( dictationSearch ) ) : @NO,

View File

@@ -11,9 +11,9 @@
<dict>
<key>CFBundleTypeIconFiles</key>
<array>
<string>Icon-Small</string>
<string>Icon-64.png</string>
<string>Icon-320.png</string>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>CFBundleTypeName</key>
<string>Master Password sites</string>
@@ -21,6 +21,7 @@
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>com.lyndir.masterpassword.json</string>
<string>com.lyndir.masterpassword.sites</string>
</array>
</dict>
@@ -37,8 +38,30 @@
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>[auto]</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLIconFile</key>
<string>Icon-Small-40@3x</string>
<key>CFBundleURLName</key>
<string>com.lyndir.masterpassword</string>
<key>CFBundleURLSchemes</key>
<array>
<string>masterpassword</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>[auto]</string>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>firefox</string>
<string>googlechrome</string>
<string>opera-http</string>
<string>spectre</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
@@ -103,6 +126,34 @@
</array>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Master Password JSON export</string>
<key>UTTypeIconFiles</key>
<array>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.json</string>
<key>UTTypeReferenceURL</key>
<string>https://gitlab.com/MasterPassword/MasterPassword/-/wikis/File-Format</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>mpjson</string>
<string>mpsites.json</string>
</array>
<key>public.mime-type</key>
<string>text/plain</string>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
@@ -112,9 +163,9 @@
<string>Master Password sites</string>
<key>UTTypeIconFiles</key>
<array>
<string>Icon-Small.png</string>
<string>Icon-64.png</string>
<string>Icon-320.png</string>
<string>Icon-Small-40.png</string>
<string>Icon-Small-40@2x.png</string>
<string>Icon-Small-40@3x.png</string>
</array>
<key>UTTypeIdentifier</key>
<string>com.lyndir.masterpassword.sites</string>
@@ -123,6 +174,7 @@
<key>public.filename-extension</key>
<array>
<string>mpsites</string>
<string>mpsites.txt</string>
</array>
</dict>
</dict>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="Q1S-vU-GGO">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17154" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" colorMatched="YES" initialViewController="Q1S-vU-GGO">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16086"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17124"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<customFonts key="customFonts">
@@ -97,10 +97,10 @@
</constraints>
</imageView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0Sa-Vg-EEI" userLabel="Name Backdrop">
<rect key="frame" x="43.5" y="263" width="128.5" height="16"/>
<rect key="frame" x="43.5" y="263" width="128" height="16"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalCompressionResistancePriority="1000" text="Maarten Billemont" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cLT-s0-4SQ" userLabel="Name Field">
<rect key="frame" x="5" y="0.0" width="118.5" height="16"/>
<rect key="frame" x="5" y="0.0" width="118" height="16"/>
<fontDescription key="fontDescription" name="Exo2.0-ExtraBold" family="Exo 2.0" pointSize="13"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -164,7 +164,7 @@
<outlet property="delegate" destination="S8q-YF-Kt9" id="det-Eh-phM"/>
</connections>
</collectionView>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9u7-pu-Wtv" userLabel="Previous Avatar">
<rect key="frame" x="0.0" y="439.5" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="Ay6-Jg-c3T"/>
@@ -177,7 +177,7 @@
<action selector="changeAvatar:" destination="S8q-YF-Kt9" eventType="touchUpInside" id="lNu-mK-3zD"/>
</connections>
</button>
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<button opaque="NO" alpha="0.69999999999999996" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="fUK-gJ-NRE" userLabel="Next Avatar">
<rect key="frame" x="370" y="439.5" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" constant="44" id="oAm-YX-Fx5"/>
@@ -276,7 +276,7 @@
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XEP-O3-ayG" userLabel="Footer">
<rect key="frame" x="0.0" y="824" width="414" height="72"/>
<subviews>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4md-Gp-SLG">
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4md-Gp-SLG">
<rect key="frame" x="20" y="48" width="374" height="24"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="10"/>
<state key="normal" title="Thanks, lhunath ➚">
@@ -288,14 +288,14 @@
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="069-Pu-yXe" userLabel="Thanks Tip">
<rect key="frame" x="91" y="0.0" width="232.5" height="60"/>
<rect key="frame" x="90.5" y="0.0" width="233" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="Z8P-ZK-aS0">
<rect key="frame" x="0.0" y="0.0" width="232.5" height="60"/>
<rect key="frame" x="0.0" y="0.0" width="233" height="60"/>
<rect key="contentStretch" x="0.15000000000000002" y="0.0" width="0.69999999999999973" height="1"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Why is Master Password free?" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="BLV-3x-Q0z">
<rect key="frame" x="20" y="11.5" width="192.5" height="17"/>
<rect key="frame" x="20" y="11.5" width="193" height="17"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -442,21 +442,12 @@
<outlet property="nextAvatarButton" destination="fUK-gJ-NRE" id="5qo-lK-rSa"/>
<outlet property="preferencesTipContainer" destination="0Um-Ot-hI6" id="Cv8-Bp-ZZs"/>
<outlet property="previousAvatarButton" destination="9u7-pu-Wtv" id="Hgv-lN-S1n"/>
<outlet property="searchDisplayController" destination="h98-GT-FoS" id="VvS-JO-rqq"/>
<outlet property="storeLoadingActivity" destination="VDd-oM-ZOO" id="MJ7-2f-e8n"/>
<outlet property="thanksTipContainer" destination="069-Pu-yXe" id="wWf-2X-Ryw"/>
<outlet property="userSelectionContainer" destination="rWM-08-aab" id="Yme-hX-8P0"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="8hZ-Tb-wZw" userLabel="First Responder" sceneMemberID="firstResponder"/>
<searchDisplayController id="h98-GT-FoS">
<connections>
<outlet property="delegate" destination="S8q-YF-Kt9" id="hyY-rf-2x2"/>
<outlet property="searchContentsController" destination="S8q-YF-Kt9" id="2RA-rs-GhH"/>
<outlet property="searchResultsDataSource" destination="S8q-YF-Kt9" id="Cdu-go-UBQ"/>
<outlet property="searchResultsDelegate" destination="S8q-YF-Kt9" id="xxe-xE-sFM"/>
</connections>
</searchDisplayController>
</objects>
<point key="canvasLocation" x="2041" y="226"/>
</scene>
@@ -472,17 +463,11 @@
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<webView contentMode="scaleToFill" scalesPageToFit="YES" suppressesIncrementalRendering="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3aB-Hk-Fgd">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<outlet property="delegate" destination="Sd5-eW-Cx2" id="ePX-qz-XXR"/>
</connections>
</webView>
<navigationBar contentMode="scaleToFill" barStyle="black" translatesAutoresizingMaskIntoConstraints="NO" id="03x-KT-JQN">
<rect key="frame" x="0.0" y="0.0" width="414" height="90"/>
<items>
<navigationItem title="masterpassword.app" prompt="Loading" id="Wpf-6b-UJb">
<barButtonItem key="leftBarButtonItem" systemItem="action" id="d9P-Hd-rdw"/>
<barButtonItem key="rightBarButtonItem" systemItem="done" id="Tbg-c3-qOh">
<connections>
<action selector="done:" destination="Sd5-eW-Cx2" id="cuN-bV-cwl"/>
@@ -494,19 +479,14 @@
</subviews>
<color key="backgroundColor" red="0.12549020350000001" green="0.1411764771" blue="0.14901961389999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="3aB-Hk-Fgd" firstAttribute="top" secondItem="zXV-QC-CZm" secondAttribute="bottom" id="3Od-Xa-Ssc"/>
<constraint firstItem="03x-KT-JQN" firstAttribute="leading" secondItem="2Jz-8H-3nG" secondAttribute="leading" id="BAn-wU-k2A"/>
<constraint firstItem="03x-KT-JQN" firstAttribute="top" secondItem="zXV-QC-CZm" secondAttribute="bottom" id="JU6-uj-GA3"/>
<constraint firstAttribute="bottom" secondItem="3aB-Hk-Fgd" secondAttribute="bottom" id="S01-AA-E0a"/>
<constraint firstAttribute="trailing" secondItem="03x-KT-JQN" secondAttribute="trailing" id="sQs-u3-M3r"/>
<constraint firstItem="3aB-Hk-Fgd" firstAttribute="leading" secondItem="2Jz-8H-3nG" secondAttribute="leading" id="w1l-x3-qCi"/>
<constraint firstAttribute="trailing" secondItem="3aB-Hk-Fgd" secondAttribute="trailing" id="xdJ-WF-yw0"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="gmy-ym-z4I"/>
<connections>
<outlet property="webNavigationItem" destination="Wpf-6b-UJb" id="8gM-LU-pHk"/>
<outlet property="webView" destination="3aB-Hk-Fgd" id="Mie-rI-l4h"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="hNt-WS-DuE" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -542,12 +522,23 @@
<view key="view" contentMode="scaleToFill" id="c4P-nn-PjR">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="qca-aL-Un8">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<connections>
<segue destination="Ac5-na-hOV" kind="embed" identifier="root" id="x4h-Hl-KXD"/>
</connections>
</containerView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="qca-aL-Un8" firstAttribute="top" secondItem="c4P-nn-PjR" secondAttribute="top" id="7ix-Oo-ELN"/>
<constraint firstAttribute="bottom" secondItem="qca-aL-Un8" secondAttribute="bottom" id="JRU-fk-cPG"/>
<constraint firstItem="qca-aL-Un8" firstAttribute="leading" secondItem="c4P-nn-PjR" secondAttribute="leading" id="OAO-XC-DQ1"/>
<constraint firstAttribute="trailing" secondItem="qca-aL-Un8" secondAttribute="trailing" id="R9n-bd-GVl"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="V6W-ql-3TD"/>
<connections>
<segue destination="Ac5-na-hOV" kind="custom" identifier="root" customClass="MPRootSegue" id="UKS-gd-oD2"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="fQY-fV-sIe" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
@@ -568,12 +559,25 @@
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Lkg-xn-bce" userLabel="Background">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
</imageView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="91I-wN-JQb">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="boolean" keyPath="ignoreTouches" value="YES"/>
</userDefinedRuntimeAttributes>
<connections>
<segue destination="S8q-YF-Kt9" kind="embed" identifier="users" id="GzD-Zv-DzW"/>
</connections>
</containerView>
</subviews>
<constraints>
<constraint firstItem="91I-wN-JQb" firstAttribute="leading" secondItem="fkJ-D0-yue" secondAttribute="leading" id="E4w-Dw-Vip"/>
<constraint firstItem="Lkg-xn-bce" firstAttribute="top" secondItem="fkJ-D0-yue" secondAttribute="top" id="EIy-Cd-0vW"/>
<constraint firstItem="91I-wN-JQb" firstAttribute="top" secondItem="fkJ-D0-yue" secondAttribute="top" id="EeB-9X-PEK"/>
<constraint firstAttribute="trailing" secondItem="Lkg-xn-bce" secondAttribute="trailing" id="ROW-fK-z92"/>
<constraint firstItem="Lkg-xn-bce" firstAttribute="leading" secondItem="fkJ-D0-yue" secondAttribute="leading" id="UH5-Kk-taJ"/>
<constraint firstAttribute="bottom" secondItem="91I-wN-JQb" secondAttribute="bottom" id="mxv-9s-jxT"/>
<constraint firstAttribute="bottom" secondItem="Lkg-xn-bce" secondAttribute="bottom" id="txR-pf-v3l"/>
<constraint firstAttribute="trailing" secondItem="91I-wN-JQb" secondAttribute="trailing" id="uef-ig-RRl"/>
</constraints>
</view>
<navigationItem key="navigationItem" id="MPa-zX-Kaq"/>
@@ -581,7 +585,6 @@
<connections>
<segue destination="osn-5H-SWW" kind="custom" identifier="emergency" customClass="MPOverlaySegue" id="gtX-Cx-AA2"/>
<segue destination="nkY-z6-8jd" kind="custom" identifier="passwords" customClass="MPSitesSegue" id="Ozp-YT-Utx"/>
<segue destination="S8q-YF-Kt9" kind="custom" identifier="users" customClass="MPRootSegue" id="StK-nr-nps"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="F33-Fe-Tb6" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -795,7 +798,7 @@
<constraint firstAttribute="height" constant="110" id="zBf-EA-iDN"/>
</constraints>
</imageView>
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DzC-Ts-gew" userLabel="Previous Avatar">
<rect key="frame" x="20" y="148.5" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="1Wu-gS-flK"/>
@@ -809,7 +812,7 @@
<action selector="previousAvatar:" destination="JFc-sj-awD" eventType="touchUpInside" id="D92-6I-zFd"/>
</connections>
</button>
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
<button opaque="NO" alpha="0.69999998807907104" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yAf-fc-SKl" userLabel="Next Avatar">
<rect key="frame" x="350" y="148.5" width="44" height="53"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" constant="44" id="pEf-Vp-D7s"/>
@@ -1036,7 +1039,7 @@ Note that this feature requires you enable the Save Password option and have pur
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rl7-cr-FHf">
<rect key="frame" x="20" y="118" width="374" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Home Page">
@@ -1046,7 +1049,7 @@ Note that this feature requires you enable the Save Password option and have pur
<action selector="homePageButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="ptD-cv-NMr"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="epW-Rm-9St">
<rect key="frame" x="20" y="153" width="374" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Understanding Master Password's Security">
@@ -1056,7 +1059,7 @@ Note that this feature requires you enable the Save Password option and have pur
<action selector="securityButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Efv-cp-Xfh"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="LTN-ch-h8D">
<rect key="frame" x="20" y="188" width="374" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Get the Master Password source code">
@@ -1066,7 +1069,7 @@ Note that this feature requires you enable the Save Password option and have pur
<action selector="sourceButton:" destination="JFc-sj-awD" eventType="touchUpInside" id="Y3O-di-CZo"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="1000" verticalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Z60-lc-Nka">
<rect key="frame" x="20" y="223" width="374" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Send Thanks">
@@ -1146,7 +1149,8 @@ Note that this feature requires you enable the Save Password option and have pur
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" minimumZoomScale="0.0" maximumZoomScale="0.0" keyboardDismissMode="interactive" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="aXw-tn-8Sj" userLabel="Password Collection">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" sectionInsetReference="safeArea" id="Mv1-29-TWx">
<edgeInsets key="layoutMargins" top="100" left="0.0" bottom="0.0" right="0.0"/>
<collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" sectionInsetReference="layoutMargins" id="Mv1-29-TWx">
<size key="itemSize" width="355" height="100"/>
<size key="headerReferenceSize" width="0.0" height="0.0"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
@@ -1154,7 +1158,7 @@ Note that this feature requires you enable the Save Password option and have pur
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPSiteCell" id="W2g-yv-V3V" customClass="MPSiteCell">
<rect key="frame" x="29.5" y="10" width="355" height="100"/>
<rect key="frame" x="29.5" y="110" width="355" height="100"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO">
<rect key="frame" x="0.0" y="0.0" width="355" height="100"/>
@@ -1671,16 +1675,16 @@ eg. apple.com, rmitchell@twitter.com</string>
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3ve-eR-1IW">
<rect key="frame" x="0.0" y="740" width="414" height="156"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-I5-RSl">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="PWC-I5-RSl">
<rect key="frame" x="8" y="8" width="398" height="106"/>
<state key="normal" backgroundImage="tip_alert_black"/>
<connections>
<action selector="upgradeVolto:" destination="nkY-z6-8jd" eventType="touchUpInside" id="8KJ-vm-3D0"/>
<action selector="upgradeSpectre:" destination="nkY-z6-8jd" eventType="touchUpInside" id="8KJ-vm-3D0"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XgC-Ky-oVC">
<rect key="frame" x="80" y="15" width="315" height="84"/>
<string key="text">The next generation of password security is now called «Volto»:
<string key="text">The next generation of password security is now called «Spectre»:
Tap to install the upgrade.
This app is now out of maintenance.</string>
@@ -1703,17 +1707,17 @@ This app is now out of maintenance.</string>
<view opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="67c-5R-Foa">
<rect key="frame" x="0.0" y="740" width="414" height="156"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EZK-er-8ql">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EZK-er-8ql">
<rect key="frame" x="8" y="8" width="398" height="106"/>
<state key="normal" backgroundImage="tip_alert_black"/>
<connections>
<action selector="upgradeVolto:" destination="nkY-z6-8jd" eventType="touchUpInside" id="P4a-tL-9yX"/>
<action selector="upgradeSpectre:" destination="nkY-z6-8jd" eventType="touchUpInside" id="P4a-tL-9yX"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fLB-gA-32p">
<rect key="frame" x="80" y="15" width="315" height="84"/>
<string key="text">The next generation of password security is now called «Volto»:
Tap to copy this user into Volto.
<string key="text">The next generation of password security is now called «Spectre»:
Tap to copy this user into Spectre.
This app is now out of maintenance.</string>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
@@ -1812,8 +1816,8 @@ This app is now out of maintenance.</string>
<outlet property="popdownView" destination="XNM-XQ-rMe" id="FaW-4m-Fff"/>
<outlet property="searchBar" destination="aGs-1S-aC3" id="rTp-DP-rIz"/>
<outlet property="sitesToBottomConstraint" destination="dNt-uf-8BC" id="Ta6-eL-z7w"/>
<outlet property="voltoInstallAlert" destination="3ve-eR-1IW" id="Ah5-OX-XhQ"/>
<outlet property="voltoMigrateAlert" destination="67c-5R-Foa" id="aTi-fu-opO"/>
<outlet property="spectreInstallAlert" destination="3ve-eR-1IW" id="Ah5-OX-XhQ"/>
<outlet property="spectreMigrateAlert" destination="67c-5R-Foa" id="aTi-fu-opO"/>
<segue destination="z9O-w0-6oR" kind="modal" identifier="guide" id="Ql4-wf-T8u"/>
<segue destination="Foa-Er-RBr" kind="custom" identifier="message" customClass="MPOverlaySegue" id="Xne-Sm-HQt"/>
</connections>
@@ -1842,10 +1846,10 @@ This app is now out of maintenance.</string>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="1lc-e7-Qme" userLabel="Emergency Generator">
<rect key="frame" x="20" y="240.5" width="374" height="415.5"/>
<rect key="frame" x="20" y="240" width="374" height="416"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Emergency Generator" textAlignment="center" lineBreakMode="tailTruncation" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="4Lh-s0-Dbt">
<rect key="frame" x="20" y="20" width="334" height="21"/>
<rect key="frame" x="20" y="20" width="334" height="21.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -1853,7 +1857,7 @@ This app is now out of maintenance.</string>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Generate your password without logging in. Great for if you're borrowing a friend's device or are having trouble logging in." lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="vHS-3A-Tae">
<rect key="frame" x="20" y="49" width="334" height="51.5"/>
<rect key="frame" x="20" y="49.5" width="334" height="51.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -1861,7 +1865,7 @@ This app is now out of maintenance.</string>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Name" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="XAC-Da-lpf" userLabel="User Name">
<rect key="frame" x="20" y="108.5" width="334" height="34"/>
<rect key="frame" x="20" y="109" width="334" height="34"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="words" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES"/>
<connections>
@@ -1870,7 +1874,7 @@ This app is now out of maintenance.</string>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Your Master Password" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="J46-0E-no3" userLabel="Master Password">
<rect key="frame" x="20" y="150.5" width="334" height="34"/>
<rect key="frame" x="20" y="151" width="334" height="34"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardAppearance="alert" returnKeyType="next" enablesReturnKeyAutomatically="YES" secureTextEntry="YES"/>
<connections>
@@ -1879,7 +1883,7 @@ This app is now out of maintenance.</string>
</connections>
</textField>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Site Name" clearsOnBeginEditing="YES" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="56H-xR-09J" userLabel="Site Name">
<rect key="frame" x="20" y="192.5" width="334" height="34"/>
<rect key="frame" x="20" y="193" width="334" height="34"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="14"/>
<textInputTraits key="textInputTraits" autocorrectionType="no" keyboardType="URL" keyboardAppearance="alert" returnKeyType="done" enablesReturnKeyAutomatically="YES"/>
<connections>
@@ -1888,7 +1892,7 @@ This app is now out of maintenance.</string>
</connections>
</textField>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bar" selectedSegmentIndex="1" translatesAutoresizingMaskIntoConstraints="NO" id="e4b-Iv-Pk9" userLabel="Type">
<rect key="frame" x="20" y="234.5" width="334" height="32"/>
<rect key="frame" x="20" y="235" width="334" height="32"/>
<segments>
<segment title="Max"/>
<segment title="Long"/>
@@ -1903,7 +1907,7 @@ This app is now out of maintenance.</string>
</connections>
</segmentedControl>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Counter" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="cAo-K2-E23">
<rect key="frame" x="20" y="277.5" width="64" height="21.5"/>
<rect key="frame" x="20" y="278.5" width="64" height="21.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -1911,13 +1915,13 @@ This app is now out of maintenance.</string>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<stepper opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" maximumValue="100" translatesAutoresizingMaskIntoConstraints="NO" id="ZPT-EI-yuv" userLabel="Counter">
<rect key="frame" x="260" y="273.5" width="94" height="32"/>
<rect key="frame" x="260" y="274" width="94" height="32"/>
<connections>
<action selector="controlChanged:" destination="osn-5H-SWW" eventType="valueChanged" id="eQA-3X-uc9"/>
</connections>
</stepper>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="1" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="3Cd-XH-Wau">
<rect key="frame" x="245" y="278.5" width="7" height="20"/>
<rect key="frame" x="245" y="279" width="7" height="20.5"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="highlightedColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -1958,10 +1962,10 @@ This app is now out of maintenance.</string>
</state>
</button>
<activityIndicatorView hidden="YES" opaque="NO" contentMode="scaleToFill" hidesWhenStopped="YES" style="whiteLarge" translatesAutoresizingMaskIntoConstraints="NO" id="4sN-hm-xio">
<rect key="frame" x="168.5" y="352" width="37" height="37"/>
<rect key="frame" x="168.5" y="352.5" width="37" height="37"/>
</activityIndicatorView>
<button opaque="NO" clipsSubviews="YES" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="clip" translatesAutoresizingMaskIntoConstraints="NO" id="bHR-he-dnZ">
<rect key="frame" x="20" y="345.5" width="334" height="50"/>
<rect key="frame" x="20" y="346" width="334" height="50"/>
<gestureRecognizers/>
<fontDescription key="fontDescription" name="SourceCodePro-Black" family="Source Code Pro" pointSize="30"/>
<state key="normal" title="XapaNuwjFihn6$">
@@ -1973,7 +1977,7 @@ This app is now out of maintenance.</string>
</connections>
</button>
<view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="beo-cJ-jIn" userLabel="View - Content Tip">
<rect key="frame" x="82" y="310.5" width="210" height="60"/>
<rect key="frame" x="82" y="311" width="210" height="60"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" image="tip_basic_black.png" translatesAutoresizingMaskIntoConstraints="NO" id="nyL-cO-aPa">
<rect key="frame" x="0.0" y="0.0" width="210" height="60"/>
@@ -2052,14 +2056,41 @@ This app is now out of maintenance.</string>
<constraint firstItem="1lc-e7-Qme" firstAttribute="leading" secondItem="whU-l0-2bU" secondAttribute="leading" constant="20" id="ztv-2f-RAc"/>
</constraints>
</scrollView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="cbe-hc-9K5">
<rect key="frame" x="20" y="820" width="374" height="42"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="Anonymous Device Identifier" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" minimumFontSize="10" translatesAutoresizingMaskIntoConstraints="NO" id="VyZ-qP-VkG">
<rect key="frame" x="0.0" y="0.0" width="374" height="18"/>
<fontDescription key="fontDescription" name="Exo2.0-Thin" family="Exo 2.0" pointSize="14"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
<color key="shadowColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<size key="shadowOffset" width="0.0" height="1"/>
</label>
<button opaque="NO" alpha="0.5" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="DVF-Im-H2I">
<rect key="frame" x="0.0" y="18" width="374" height="24"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="10"/>
<state key="normal" title="0000000000000000000000000000000000000000">
<color key="titleColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</state>
<connections>
<action selector="copyDevice:" destination="osn-5H-SWW" eventType="touchUpInside" id="0dD-IS-Ktn"/>
</connections>
</button>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="whU-l0-2bU" firstAttribute="height" secondItem="GiS-3g-cDj" secondAttribute="height" multiplier="1:2" id="4oQ-JI-wQv"/>
<constraint firstAttribute="width" secondItem="whU-l0-2bU" secondAttribute="width" id="AiQ-LE-3bh"/>
<constraint firstItem="IV3-lc-Fnf" firstAttribute="top" secondItem="gRG-Ys-94p" secondAttribute="bottom" id="IMb-wl-Eeb"/>
<constraint firstItem="cbe-hc-9K5" firstAttribute="leading" secondItem="GiS-3g-cDj" secondAttribute="leadingMargin" id="KNZ-jb-npt"/>
<constraint firstAttribute="trailing" secondItem="gRG-Ys-94p" secondAttribute="trailing" id="Pb1-eY-0FG"/>
<constraint firstItem="IV3-lc-Fnf" firstAttribute="top" secondItem="cbe-hc-9K5" secondAttribute="bottom" id="YWv-H4-Ij3"/>
<constraint firstItem="gRG-Ys-94p" firstAttribute="top" secondItem="GiS-3g-cDj" secondAttribute="top" id="oyk-Xr-zTZ"/>
<constraint firstAttribute="trailingMargin" secondItem="cbe-hc-9K5" secondAttribute="trailing" id="wss-B8-PT6"/>
<constraint firstItem="gRG-Ys-94p" firstAttribute="leading" secondItem="GiS-3g-cDj" secondAttribute="leading" id="zhC-jf-5dY"/>
</constraints>
<userDefinedRuntimeAttributes>
@@ -2074,6 +2105,7 @@ This app is now out of maintenance.</string>
<outlet property="containerView" destination="GiS-3g-cDj" id="01o-PU-SNZ"/>
<outlet property="counterLabel" destination="3Cd-XH-Wau" id="wv7-jZ-6tX"/>
<outlet property="counterStepper" destination="ZPT-EI-yuv" id="w5c-hO-T51"/>
<outlet property="deviceButton" destination="DVF-Im-H2I" id="TMU-Vo-bDo"/>
<outlet property="dialogView" destination="1lc-e7-Qme" id="JYt-mv-XV2"/>
<outlet property="fullNameField" destination="XAC-Da-lpf" id="XCk-0H-IcI"/>
<outlet property="masterPasswordField" destination="J46-0E-no3" id="DfH-4n-cop"/>
@@ -2115,10 +2147,10 @@ This app is now out of maintenance.</string>
</items>
</navigationBar>
<pageControl opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" contentHorizontalAlignment="center" contentVerticalAlignment="center" numberOfPages="3" translatesAutoresizingMaskIntoConstraints="NO" id="8A2-ly-WTX">
<rect key="frame" x="187.5" y="771" width="39" height="37"/>
<rect key="frame" x="141" y="778.5" width="132" height="29.5"/>
</pageControl>
<collectionView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" pagingEnabled="YES" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" minimumZoomScale="0.0" maximumZoomScale="0.0" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="i2y-lo-HXR">
<rect key="frame" x="0.0" y="44" width="414" height="637"/>
<rect key="frame" x="0.0" y="44" width="414" height="644.5"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
<collectionViewFlowLayout key="collectionViewLayout" scrollDirection="horizontal" minimumLineSpacing="0.0" minimumInteritemSpacing="0.0" id="m8O-kY-22j">
<size key="itemSize" width="320" height="458"/>
@@ -2128,7 +2160,7 @@ This app is now out of maintenance.</string>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="MPGuideStepCell" id="oGu-pJ-3bQ" customClass="MPGuideStepCell">
<rect key="frame" x="0.0" y="89.5" width="320" height="458"/>
<rect key="frame" x="0.0" y="93.5" width="320" height="458"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="320" height="458"/>
@@ -2157,13 +2189,13 @@ This app is now out of maintenance.</string>
</connections>
</collectionView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" text="To begin, tap the &quot;New User&quot; icon and add yourself as a user to the application." textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ciw-56-nNy" userLabel="Caption">
<rect key="frame" x="8" y="689" width="398" height="82"/>
<rect key="frame" x="8" y="696.5" width="398" height="82"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="13"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label hidden="YES" opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" lineBreakMode="tailTruncation" numberOfLines="3" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Oop-Ff-gbz" userLabel="Caption Height Strut">
<rect key="frame" x="8" y="689" width="1" height="82"/>
<rect key="frame" x="8" y="696.5" width="1" height="82"/>
<string key="text" base64-UTF8="YES">
CgoKCgoKCgoKCgoKCg
</string>
@@ -2324,7 +2356,7 @@ Suspendisse potenti. Etiam ut nisi id augue tempor ultrices et sit amet sapien.
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="OsH-Mq-gpM">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="OsH-Mq-gpM">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ol8-3I-X99">
@@ -2412,7 +2444,7 @@ However, it means that anyone who finds your device unlocked can do the same.</s
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Hrg-vp-M2J">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="Hrg-vp-M2J">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fJc-xU-1xA">
@@ -2752,7 +2784,7 @@ Invested: 3.7 work hours</string>
<rect key="frame" x="188.5" y="20" width="37" height="37"/>
</activityIndicatorView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" horizontalCompressionResistancePriority="751" verticalCompressionResistancePriority="751" text="Loading products..." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="12" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NPg-it-juF">
<rect key="frame" x="134.5" y="97" width="145.5" height="25"/>
<rect key="frame" x="134" y="97" width="146" height="25"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="17"/>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -2781,7 +2813,7 @@ Invested: 3.7 work hours</string>
<color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lQ1-b9-Xp4">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" verticalCompressionResistancePriority="751" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="lQ1-b9-Xp4">
<rect key="frame" x="20" y="34" width="374" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Bold" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Restore Previous Purchases">
@@ -2791,7 +2823,7 @@ Invested: 3.7 work hours</string>
<action selector="restorePurchases:" destination="pdl-xv-zjX" eventType="touchUpInside" id="Q8M-ud-OHL"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eus-kQ-emn">
<button opaque="NO" contentMode="scaleToFill" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="eus-kQ-emn">
<rect key="frame" x="8" y="69" width="398" height="27"/>
<fontDescription key="fontDescription" name="Exo2.0-Regular" family="Exo 2.0" pointSize="12"/>
<state key="normal" title="Send Thanks">
@@ -3036,7 +3068,7 @@ Invested: 3.7 work hours</string>
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="center" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="rvb-7K-MHe">
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFill" image="background.png" translatesAutoresizingMaskIntoConstraints="NO" id="rvb-7K-MHe">
<rect key="frame" x="0.0" y="0.0" width="414" height="842"/>
</imageView>
<scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7A7-Yn-F6L">
@@ -3269,7 +3301,7 @@ Ut in geometria, prima si dederis, danda sunt omnia. Nonne igitur tibi videntur,
</scenes>
<inferredMetricsTieBreakers>
<segue reference="Ql4-wf-T8u"/>
<segue reference="GZk-I4-JyH"/>
<segue reference="gtb-zE-u9H"/>
</inferredMetricsTieBreakers>
<color key="tintColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<resources>

View File

@@ -28,7 +28,7 @@ mpw_expect() {
json) file=~/.mpw.d/"$user.mpjson" ;;
esac
fi
[[ $file ]] && (( ! keep )) && rm "$file"
[[ -e $file ]] && (( ! keep )) && rm "$file"
printf '.'
local result=$(./mpw -q "${args[@]}") err=$?
@@ -79,397 +79,429 @@ mpw_expect() {
esac
fi
[[ $file ]] && (( ! keep )) && rm "$file"
[[ -e $file ]] && (( ! keep )) && rm "$file"
}
# mpw_tests.xml
## V3
printf "\nV%d, none: " 3
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' 'test'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'KovxFipe5:Zatu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'ModoLalhRapo6#' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CudmTecuPune7:' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' '⛄'
mpw_expect 'yubfalago' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'identification' 'ẗesẗ'
mpw_expect 'yubfalago' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a3 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'jip nodwoqude dizo' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'recovery' 'ẗesẗ'
mpw_expect 'jip nodwoqude dizo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'dok sorkicoyu ruya' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'j5TJ%G0WWwSMvYb)hr4)' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'TinRaz2?' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'jad0IQA3' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tin0' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '1710' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'tinraziqu' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'tinr ziq taghuye zuj' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'HidiLonoFopt9&' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a3 -p 'authentication' 'ẗesẗ'
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' 'test'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tina0#NotaMahu' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'KovxFipe5:Zatu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'ModoLalhRapo6#' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CudmTecuPune7:' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' '⛄'
mpw_expect 'yubfalago' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'yubfalago' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a3 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'jip nodwoqude dizo' -Fnone \
-u 'tesẗ' -M 'ẗest' -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'jip nodwoqude dizo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'dok sorkicoyu ruya' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'j5TJ%G0WWwSMvYb)hr4)' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'TinRaz2?' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'jad0IQA3' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Tin0' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '1710' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'tinraziqu' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'tinr ziq taghuye zuj' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'HidiLonoFopt9&' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a3 -p 'authentication' -C '' 'ẗesẗ'
## V2
printf "\nV%d, none: " 2
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a2 -p 'authentication' -C '' 'test'
mpw_expect "HuczFina3'Qatf" -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'SicrJuwaWaql0#' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'LokaJayp1@Faba' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'DoqaHulu8:Funh' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' '⛄'
mpw_expect 'yiyguxoxe' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a2 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a2 -p 'authentication' -C '' 'test'
mpw_expect "HuczFina3'Qatf" -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'SicrJuwaWaql0#' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'LokaJayp1@Faba' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'DoqaHulu8:Funh' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' '⛄'
mpw_expect 'yiyguxoxe' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a2 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'vu yelyo bat kujavmu' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ka deqce xad vomacgi' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'wRF$LmB@umWGLWeVlB0-' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'HucZuk0!' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wb59VoB5' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Huc9' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '2959' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'huczukamo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'huc finmokozi fota' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Mixa1~BulgNijo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a2 -p 'authentication' 'ẗesẗ'
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ka deqce xad vomacgi' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'wRF$LmB@umWGLWeVlB0-' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'HucZuk0!' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wb59VoB5' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Huc9' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '2959' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'huczukamo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'huc finmokozi fota' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Mixa1~BulgNijo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a2 -p 'authentication' -C '' 'ẗesẗ'
## V1
printf "\nV%d, none: " 1
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a1 -p 'authentication' -C '' 'test'
mpw_expect 'SuxiHoteCuwe3/' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CupaTixu8:Hetu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'NaqmBanu9+Decs' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'XowaDokoGeyu2)' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' '⛄'
mpw_expect 'makmabivo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a1 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'CefoTiciJuba7@' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a1 -p 'authentication' -C '' 'test'
mpw_expect 'SuxiHoteCuwe3/' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CupaTixu8:Hetu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'NaqmBanu9+Decs' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'XowaDokoGeyu2)' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' '⛄'
mpw_expect 'makmabivo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a1 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'je mutbo buf puhiywo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ne hapfa dax qamayqo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'JlZo&eLhqgoxqtJ!NC5/' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'SuxHot2*' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Jly28Veh' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Sux2' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '4922' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'suxhotito' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'su hotte pav calewxo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Luxn2#JapiXopa' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a1 -p 'authentication' 'ẗesẗ'
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ne hapfa dax qamayqo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'JlZo&eLhqgoxqtJ!NC5/' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'SuxHot2*' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Jly28Veh' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Sux2' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '4922' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'suxhotito' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'su hotte pav calewxo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Luxn2#JapiXopa' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a1 -p 'authentication' -C '' 'ẗesẗ'
## V0
printf "\nV%d, none: " 0
mpw_expect 'GeqoBigiFubh2!' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a0 -p 'authentication' -C '' 'test'
mpw_expect 'WumiZobxGuhe8]' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'KuhaXimj8@Zebu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CajtFayv9_Pego' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'QohaPokgYevu2!' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' '⛄'
mpw_expect 'takxabico' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a0 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'GeqoBigiFubh2!' -Fnone \
-u 'test' -M 'test' -tlong -c1 -a0 -p 'authentication' -C '' 'test'
mpw_expect 'WumiZobxGuhe8]' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'KuhaXimj8@Zebu' -Fnone \
-u '⛄' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'CajtFayv9_Pego' -Fnone \
-u 'tesẗ' -M '⛄' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'QohaPokgYevu2!' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' '⛄'
mpw_expect 'takxabico' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a0 -p 'identification' -C '' 'ẗesẗ'
mpw_expect 'je tuxfo fut huzivlo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ye zahqa lam jatavmo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'g4@)4SlA#)cJ#ib)vvH3' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Wum7_Xix' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'gAo78ARD' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Wum7' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '9427' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wumdoxixo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wu doxbe hac kaselqo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Pumy7.JadjQoda' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a0 -p 'authentication' 'ẗesẗ'
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C '' 'ẗesẗ'
mpw_expect 'ye zahqa lam jatavmo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ'
mpw_expect 'g4@)4SlA#)cJ#ib)vvH3' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Wum7_Xix' -Fnone \
-u 'tesẗ' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'gAo78ARD' -Fnone \
-u 'tesẗ' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Wum7' -Fnone \
-u 'tesẗ' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect '9427' -Fnone \
-u 'tesẗ' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wumdoxixo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'wu doxbe hac kaselqo' -Fnone \
-u 'tesẗ' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ'
mpw_expect 'Pumy7.JadjQoda' -Fnone \
-u 'tesẗ' -M 'ẗest' -tlong -c4294967295 -a0 -p 'authentication' -C '' 'ẗesẗ'
## V3
printf "\nV%d, flat: " 3
mpw_expect 'IfHuAUUpqpKZDZlNvz8$' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.max'
mpw_expect 'FamiJirk1)Zehc' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.long'
mpw_expect 'NofhMusw8+Cebo' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.⛄'
mpw_expect 'Necx1$LagaRizu' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c4294967295 -a3 -p 'authentication' 'ẗesẗ.c+a3pa'
mpw_expect 'Poq2)Tey' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.med'
mpw_expect 'Wr07Okx0' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.basic'
mpw_expect 'Bug9' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.short'
mpw_expect '3560' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.pin'
mpw_expect 'jupxiqepi' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.name'
mpw_expect 'vuh buxtukewo puhe' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.phrase'
mpw_expect 'mophabiwe' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi'
mpw_expect 'mup wulbezaxa juca' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr'
mpw_expect 'IfHuAUUpqpKZDZlNvz8$' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.max'
mpw_expect 'FamiJirk1)Zehc' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.long'
mpw_expect 'NofhMusw8+Cebo' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.⛄'
mpw_expect 'Necx1$LagaRizu' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c4294967295 -a3 -p 'authentication' -C '' 'ẗesẗ.c+a3pa'
mpw_expect 'Poq2)Tey' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.med'
mpw_expect 'Wr07Okx0' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.basic'
mpw_expect 'Bug9' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.short'
mpw_expect '3560' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.pin'
mpw_expect 'jupxiqepi' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.name'
mpw_expect 'vuh buxtukewo puhe' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.phrase'
mpw_expect 'Cq5$TfH#OHmPS9yREp7)' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi.max'
mpw_expect 'mophabiwe' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi'
mpw_expect 'lA^ul!%9&TD%fj6icT1[' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr.max'
mpw_expect 'mup wulbezaxa juca' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr'
mpw_expect 'molg rux kaczuvi ror' -Fflat -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a3pr.quesẗion'
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a3pr.quesẗion'
## V2
printf "\nV%d, flat: " 2
mpw_expect 'i7@0M*DdP4DgD#jJIzyL' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.max'
mpw_expect 'Lifw5]DablSuga' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.long'
mpw_expect 'Leja5%RavoZapa' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.⛄'
mpw_expect 'NejnGazo8?Seqo' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c4294967295 -a2 -p 'authentication' 'ẗesẗ.c+a2pa'
mpw_expect 'XicSux2&' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.med'
mpw_expect 'uEY50hcZ' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.basic'
mpw_expect 'Jif6' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.short'
mpw_expect '4001' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.pin'
mpw_expect 'rexmibace' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.name'
mpw_expect 'cez fexlemozo yula' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.phrase'
mpw_expect 'camfibeye' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi'
mpw_expect 'ye vemcu keq xepewmi' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr'
mpw_expect 'i7@0M*DdP4DgD#jJIzyL' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.max'
mpw_expect 'Lifw5]DablSuga' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.long'
mpw_expect 'Leja5%RavoZapa' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.⛄'
mpw_expect 'NejnGazo8?Seqo' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c4294967295 -a2 -p 'authentication' -C '' 'ẗesẗ.c+a2pa'
mpw_expect 'XicSux2&' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.med'
mpw_expect 'uEY50hcZ' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.basic'
mpw_expect 'Jif6' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.short'
mpw_expect '4001' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.pin'
mpw_expect 'rexmibace' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.name'
mpw_expect 'cez fexlemozo yula' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.phrase'
mpw_expect 'T8+xi4NMd3HUGdV#GW*%' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi.max'
mpw_expect 'camfibeye' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi'
mpw_expect 'YLcoWeBwyiBf2*irFq1.' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr.max'
mpw_expect 'ye vemcu keq xepewmi' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr'
mpw_expect 'yi qazne tid najuvme' -Fflat -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a2pr.quesẗion'
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a2pr.quesẗion'
## V1
printf "\nV%d, flat: " 1
mpw_expect 'a3~AiGkHk)Pgjbb)mk6H' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.max'
mpw_expect 'Lojz6?VotaJall' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.long'
mpw_expect 'Yoqu7)NiziFito' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.⛄'
mpw_expect 'Foha4[TojmXanc' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c4294967295 -a1 -p 'authentication' 'ẗesẗ.c+a1pa'
mpw_expect 'Hiy3*Zag' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.med'
mpw_expect 'UJR7HpG0' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.basic'
mpw_expect 'Cij7' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.short'
mpw_expect '0020' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.pin'
mpw_expect 'vadxovezu' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.name'
mpw_expect 'sij jihloyenu kizi' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.phrase'
mpw_expect 'qipberize' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi'
mpw_expect 'sok torxibute reza' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr'
mpw_expect 'a3~AiGkHk)Pgjbb)mk6H' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.max'
mpw_expect 'Lojz6?VotaJall' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.long'
mpw_expect 'Yoqu7)NiziFito' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.⛄'
mpw_expect 'Foha4[TojmXanc' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c4294967295 -a1 -p 'authentication' -C '' 'ẗesẗ.c+a1pa'
mpw_expect 'Hiy3*Zag' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.med'
mpw_expect 'UJR7HpG0' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.basic'
mpw_expect 'Cij7' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.short'
mpw_expect '0020' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.pin'
mpw_expect 'vadxovezu' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.name'
mpw_expect 'sij jihloyenu kizi' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.phrase'
mpw_expect 'z2U9)(uQ78TXqtaus)8.' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi.max'
mpw_expect 'qipberize' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi'
mpw_expect 'QMciaKyi1&I*g%tHz99,' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr.max'
mpw_expect 'sok torxibute reza' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr'
mpw_expect 'xacp qaw qutbece gan' -Fflat -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a1pr.quesẗion'
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a1pr.quesẗion'
## V0
printf "\nV%d, flat: " 0
mpw_expect 'b5@ww@Jmb4cAioRbivb)' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.max'
mpw_expect 'ZuceHazwLojz8!' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.long'
mpw_expect 'Boxj2!YabePodp' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.⛄'
mpw_expect 'PeblLuqc6]Cala' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c4294967295 -a0 -p 'authentication' 'ẗesẗ.c+a0pa'
mpw_expect 'XelQac0@' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.med'
mpw_expect 'qS07SRc8' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.basic'
mpw_expect 'Fih8' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.short'
mpw_expect '6121' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.pin'
mpw_expect 'rivfutipe' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.name'
mpw_expect 'xir qebdohogo buno' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.phrase'
mpw_expect 'ragcoxudo' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi'
mpw_expect 'kokl hov lowmaya xaf' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr'
mpw_expect 'wi zanmu nug zuwidwe' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a0pr.quesẗion'
mpw_expect 'b5@ww@Jmb4cAioRbivb)' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.max'
mpw_expect 'ZuceHazwLojz8!' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.long'
mpw_expect 'Boxj2!YabePodp' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.⛄'
mpw_expect 'PeblLuqc6]Cala' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c4294967295 -a0 -p 'authentication' -C '' 'ẗesẗ.c+a0pa'
mpw_expect 'XelQac0@' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.med'
mpw_expect 'qS07SRc8' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.basic'
mpw_expect 'Fih8' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.short'
mpw_expect '6121' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.pin'
mpw_expect 'rivfutipe' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.name'
mpw_expect 'xir qebdohogo buno' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.phrase'
mpw_expect "RoAm3bJSvo@#loHSRA6\'" -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi.max'
mpw_expect 'ragcoxudo' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi'
mpw_expect 'm8]SiJHiAS@H@Rbw))34' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr.max'
mpw_expect 'kokl hov lowmaya xaf' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr'
mpw_expect 'wi zanmu nug zuwidwe' -Fflat -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a0pr.quesẗion'
## V3
printf "\nV%d, json: " 3
mpw_expect 'IfHuAUUpqpKZDZlNvz8$' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.max'
mpw_expect 'FamiJirk1)Zehc' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.long'
mpw_expect 'NofhMusw8+Cebo' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.⛄'
mpw_expect 'Necx1$LagaRizu' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c4294967295 -a3 -p 'authentication' 'ẗesẗ.c+a3pa'
mpw_expect 'Poq2)Tey' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.med'
mpw_expect 'Wr07Okx0' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.basic'
mpw_expect 'Bug9' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.short'
mpw_expect '3560' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.pin'
mpw_expect 'jupxiqepi' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.name'
mpw_expect 'vuh buxtukewo puhe' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.phrase'
mpw_expect 'mophabiwe' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi'
mpw_expect 'mup wulbezaxa juca' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr'
mpw_expect 'IfHuAUUpqpKZDZlNvz8$' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.max'
mpw_expect 'FamiJirk1)Zehc' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.long'
mpw_expect 'NofhMusw8+Cebo' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tlong -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.⛄'
mpw_expect 'Necx1$LagaRizu' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c4294967295 -a3 -p 'authentication' -C '' 'ẗesẗ.c+a3pa'
mpw_expect 'Poq2)Tey' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmed -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.med'
mpw_expect 'Wr07Okx0' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tbasic -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.basic'
mpw_expect 'Bug9' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tshort -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.short'
mpw_expect '3560' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tpin -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.pin'
mpw_expect 'jupxiqepi' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tname -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.name'
mpw_expect 'vuh buxtukewo puhe' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'authentication' -C '' 'ẗesẗ.c1a3pa.phrase'
mpw_expect 'Cq5$TfH#OHmPS9yREp7)' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi.max'
mpw_expect 'mophabiwe' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'identification' -C '' 'ẗesẗ.c1a3pi'
mpw_expect 'lA^ul!%9&TD%fj6icT1[' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tmax -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr.max'
mpw_expect 'mup wulbezaxa juca' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'recovery' -C '' 'ẗesẗ.c1a3pr'
mpw_expect 'molg rux kaczuvi ror' -Fjson -R0 \
-u 'tesẗ.v3' -M 'ẗest' -tphrase -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a3pr.quesẗion'
-u 'tesẗ.v3' -M 'ẗest' -c1 -a3 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a3pr.quesẗion'
## V2
printf "\nV%d, json: " 2
mpw_expect 'i7@0M*DdP4DgD#jJIzyL' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.max'
mpw_expect 'Lifw5]DablSuga' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.long'
mpw_expect 'Leja5%RavoZapa' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.⛄'
mpw_expect 'NejnGazo8?Seqo' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c4294967295 -a2 -p 'authentication' 'ẗesẗ.c+a2pa'
mpw_expect 'XicSux2&' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.med'
mpw_expect 'uEY50hcZ' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.basic'
mpw_expect 'Jif6' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.short'
mpw_expect '4001' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.pin'
mpw_expect 'rexmibace' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.name'
mpw_expect 'cez fexlemozo yula' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.phrase'
mpw_expect 'camfibeye' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi'
mpw_expect 'ye vemcu keq xepewmi' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr'
mpw_expect 'i7@0M*DdP4DgD#jJIzyL' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.max'
mpw_expect 'Lifw5]DablSuga' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.long'
mpw_expect 'Leja5%RavoZapa' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tlong -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.⛄'
mpw_expect 'NejnGazo8?Seqo' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c4294967295 -a2 -p 'authentication' -C '' 'ẗesẗ.c+a2pa'
mpw_expect 'XicSux2&' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmed -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.med'
mpw_expect 'uEY50hcZ' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tbasic -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.basic'
mpw_expect 'Jif6' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tshort -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.short'
mpw_expect '4001' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tpin -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.pin'
mpw_expect 'rexmibace' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tname -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.name'
mpw_expect 'cez fexlemozo yula' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'authentication' -C '' 'ẗesẗ.c1a2pa.phrase'
mpw_expect 'T8+xi4NMd3HUGdV#GW*%' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi.max'
mpw_expect 'camfibeye' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'identification' -C '' 'ẗesẗ.c1a2pi'
mpw_expect 'YLcoWeBwyiBf2*irFq1.' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tmax -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr.max'
mpw_expect 'ye vemcu keq xepewmi' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'recovery' -C '' 'ẗesẗ.c1a2pr'
mpw_expect 'yi qazne tid najuvme' -Fjson -R0 \
-u 'tesẗ.v2' -M 'ẗest' -tphrase -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a2pr.quesẗion'
-u 'tesẗ.v2' -M 'ẗest' -c1 -a2 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a2pr.quesẗion'
## V1
printf "\nV%d, json: " 1
mpw_expect 'a3~AiGkHk)Pgjbb)mk6H' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.max'
mpw_expect 'Lojz6?VotaJall' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.long'
mpw_expect 'Yoqu7)NiziFito' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.⛄'
mpw_expect 'Foha4[TojmXanc' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c4294967295 -a1 -p 'authentication' 'ẗesẗ.c+a1pa'
mpw_expect 'Hiy3*Zag' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.med'
mpw_expect 'UJR7HpG0' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.basic'
mpw_expect 'Cij7' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.short'
mpw_expect '0020' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.pin'
mpw_expect 'vadxovezu' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.name'
mpw_expect 'sij jihloyenu kizi' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.phrase'
mpw_expect 'qipberize' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi'
mpw_expect 'sok torxibute reza' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr'
mpw_expect 'a3~AiGkHk)Pgjbb)mk6H' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.max'
mpw_expect 'Lojz6?VotaJall' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.long'
mpw_expect 'Yoqu7)NiziFito' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tlong -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.⛄'
mpw_expect 'Foha4[TojmXanc' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c4294967295 -a1 -p 'authentication' -C '' 'ẗesẗ.c+a1pa'
mpw_expect 'Hiy3*Zag' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmed -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.med'
mpw_expect 'UJR7HpG0' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tbasic -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.basic'
mpw_expect 'Cij7' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tshort -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.short'
mpw_expect '0020' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tpin -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.pin'
mpw_expect 'vadxovezu' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tname -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.name'
mpw_expect 'sij jihloyenu kizi' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'authentication' -C '' 'ẗesẗ.c1a1pa.phrase'
mpw_expect 'z2U9)(uQ78TXqtaus)8.' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi.max'
mpw_expect 'qipberize' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'identification' -C '' 'ẗesẗ.c1a1pi'
mpw_expect 'QMciaKyi1&I*g%tHz99,' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tmax -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr.max'
mpw_expect 'sok torxibute reza' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'recovery' -C '' 'ẗesẗ.c1a1pr'
mpw_expect 'xacp qaw qutbece gan' -Fjson -R0 \
-u 'tesẗ.v1' -M 'ẗest' -tphrase -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a1pr.quesẗion'
-u 'tesẗ.v1' -M 'ẗest' -c1 -a1 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a1pr.quesẗion'
## V0
printf "\nV%d, json: " 0
mpw_expect 'b5@ww@Jmb4cAioRbivb)' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.max'
mpw_expect 'ZuceHazwLojz8!' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.long'
mpw_expect 'Boxj2!YabePodp' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.⛄'
mpw_expect 'PeblLuqc6]Cala' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c4294967295 -a0 -p 'authentication' 'ẗesẗ.c+a0pa'
mpw_expect 'XelQac0@' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.med'
mpw_expect 'qS07SRc8' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.basic'
mpw_expect 'Fih8' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.short'
mpw_expect '6121' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.pin'
mpw_expect 'rivfutipe' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.name'
mpw_expect 'xir qebdohogo buno' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.phrase'
mpw_expect 'ragcoxudo' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi'
mpw_expect 'kokl hov lowmaya xaf' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr'
mpw_expect 'wi zanmu nug zuwidwe' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a0pr.quesẗion'
mpw_expect 'b5@ww@Jmb4cAioRbivb)' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.max'
mpw_expect 'ZuceHazwLojz8!' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.long'
mpw_expect 'Boxj2!YabePodp' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tlong -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.⛄'
mpw_expect 'PeblLuqc6]Cala' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c4294967295 -a0 -p 'authentication' -C '' 'ẗesẗ.c+a0pa'
mpw_expect 'XelQac0@' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmed -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.med'
mpw_expect 'qS07SRc8' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tbasic -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.basic'
mpw_expect 'Fih8' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tshort -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.short'
mpw_expect '6121' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tpin -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.pin'
mpw_expect 'rivfutipe' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tname -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.name'
mpw_expect 'xir qebdohogo buno' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tphrase -c1 -a0 -p 'authentication' -C '' 'ẗesẗ.c1a0pa.phrase'
mpw_expect "RoAm3bJSvo@#loHSRA6\'" -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi.max'
mpw_expect 'ragcoxudo' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'identification' -C '' 'ẗesẗ.c1a0pi'
mpw_expect 'm8]SiJHiAS@H@Rbw))34' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -tmax -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr.max'
mpw_expect 'kokl hov lowmaya xaf' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'recovery' -C '' 'ẗesẗ.c1a0pr'
mpw_expect 'wi zanmu nug zuwidwe' -Fjson -R0 \
-u 'tesẗ.v0' -M 'ẗest' -c1 -a0 -p 'recovery' -C 'quesẗion' 'ẗesẗ.c1a0pr.quesẗion'
# Finish

View File

@@ -166,6 +166,11 @@ const char *mpw_site_crypted_password_v0(
err( "Missing encrypted state." );
return NULL;
}
if (strlen( cipherText ) % 4 != 0) {
wrn( "Malformed encrypted state, not base64." );
// This can happen if state was stored in a non-encrypted form, eg. login in old mpsites.
return strdup( cipherText );
}
// Base64-decode
uint8_t *cipherBuf = calloc( 1, mpw_base64_decode_max( cipherText ) );
@@ -184,8 +189,10 @@ const char *mpw_site_crypted_password_v0(
mpw_free( &plainBytes, bufSize );
if (!plainText)
err( "AES decryption error: %s", strerror( errno ) );
else if (!mpw_utf8_strchars( plainText ))
wrn( "decrypted -> plainText: %zu bytes = illegal UTF-8 = %s", strlen( plainText ), mpw_hex( plainText, bufSize ) );
else
trc( "decrypted -> plainText: %zu bytes = %s = %s", strlen( plainText ), plainText, mpw_hex( plainText, strlen( plainText ) ) );
trc( "decrypted -> plainText: %zu bytes = %s = %s", strlen( plainText ), plainText, mpw_hex( plainText, bufSize ) );
return plainText;
}

View File

@@ -176,6 +176,21 @@ MPMarshalledFile *mpw_marshal_file(
return file;
}
MPMarshalledFile *mpw_marshal_error(
MPMarshalledFile *file, MPMarshalErrorType type, const char *format, ...) {
file = mpw_marshal_file( file, NULL, NULL );
if (!file)
return NULL;
va_list args;
va_start( args, format );
file->error = (MPMarshalError){ type, mpw_strdup( mpw_vstr( format, args ) ) };
va_end( args );
return file;
}
void mpw_marshal_info_free(
MPMarshalledInfo **info) {
@@ -228,6 +243,7 @@ void mpw_marshal_file_free(
mpw_marshal_info_free( &(*file)->info );
mpw_marshal_data_free( &(*file)->data );
mpw_free_string( &(*file)->error.message );
mpw_free( file, sizeof( MPMarshalledFile ) );
}
@@ -539,7 +555,7 @@ static const char *mpw_marshal_write_flat(
const MPMarshalledData *data = file->data;
if (!data) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing data." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing data." );
return NULL;
}
@@ -583,9 +599,9 @@ static const char *mpw_marshal_write_flat(
}
if (!out)
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't encode JSON." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't encode JSON." );
else
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
return out;
}
@@ -637,7 +653,7 @@ static const char *mpw_marshal_write_json(
// Section: "export"
json_object *json_file = mpw_get_json_data( file->data );
if (!json_file) {
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't serialize export data." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't serialize export data." );
return NULL;
}
@@ -650,9 +666,9 @@ static const char *mpw_marshal_write_json(
json_object_put( json_file );
if (!out)
file->error = (MPMarshalError){ MPMarshalErrorFormat, "Couldn't encode JSON." };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't encode JSON." );
else
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
return out;
}
@@ -698,17 +714,17 @@ const char *mpw_marshal_write(
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return NULL;
}
if (!user->fullName || !strlen( user->fullName )) {
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing full name." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing full name." );
return NULL;
}
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
MPMasterKey masterKey = NULL;
if (user->masterKeyProvider)
@@ -749,7 +765,7 @@ const char *mpw_marshal_write(
if (!file_)
mpw_marshal_file_free( &file );
else
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
return NULL;
}
@@ -807,7 +823,7 @@ const char *mpw_marshal_write(
const char *out = NULL;
switch (outFormat) {
case MPMarshalFormatNone:
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
break;
case MPMarshalFormatFlat:
out = mpw_marshal_write_flat( file );
@@ -818,7 +834,7 @@ const char *mpw_marshal_write(
break;
#endif
default:
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unsupported output format: %u", outFormat ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Unsupported output format: %u", outFormat );
break;
}
if (out && file->error.type == MPMarshalSuccess)
@@ -839,7 +855,7 @@ static void mpw_marshal_read_flat(
mpw_marshal_file( file, NULL, mpw_marshal_data_new() );
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return;
}
@@ -891,10 +907,7 @@ static void mpw_marshal_read_flat(
const char *headerName = mpw_get_token( &positionInLine, endOfLine, ":\n" );
const char *headerValue = mpw_get_token( &positionInLine, endOfLine, "\n" );
if (!headerName || !headerValue) {
file->error = (MPMarshalError){
MPMarshalErrorStructure,
mpw_str( "Invalid header: %s", mpw_strndup( line, (size_t)(endOfLine - line) ) )
};
mpw_marshal_error( file, MPMarshalErrorStructure, "Invalid header: %s", mpw_strndup( line, (size_t)(endOfLine - line) ) );
mpw_free_strings( &headerName, &headerValue, NULL );
continue;
}
@@ -908,7 +921,7 @@ static void mpw_marshal_read_flat(
if (mpw_strcasecmp( headerName, "Algorithm" ) == OK) {
unsigned long value = strtoul( headerValue, NULL, 10 );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast)
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm version: %s", headerValue ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user algorithm version: %s", headerValue );
else
algorithm = (MPAlgorithmVersion)value;
}
@@ -923,7 +936,7 @@ static void mpw_marshal_read_flat(
if (mpw_strcasecmp( headerName, "Default Type" ) == OK) {
unsigned long value = strtoul( headerValue, NULL, 10 );
if (!mpw_type_short_name( (MPResultType)value ))
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %s", headerValue ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user default type: %s", headerValue );
else
defaultType = (MPResultType)value;
}
@@ -934,7 +947,7 @@ static void mpw_marshal_read_flat(
if (!headerEnded)
continue;
if (!fullName)
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing header: Full Name" };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing header: Full Name" );
if (positionInLine >= endOfLine)
continue;
@@ -967,13 +980,13 @@ static void mpw_marshal_read_flat(
str_counter = mpw_strdup( strtok( NULL, "" ) );
mpw_free_string( &typeAndVersionAndCounter );
}
siteLoginState = mpw_get_token( &positionInLine, endOfLine, "\t\n" ); // TODO: Needs to be encoded if redacted?
siteLoginState = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteName = mpw_get_token( &positionInLine, endOfLine, "\t\n" );
siteResultState = mpw_get_token( &positionInLine, endOfLine, "\n" );
break;
}
default: {
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Unexpected import format: %u", format ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Unexpected import format: %u", format );
continue;
}
}
@@ -981,28 +994,24 @@ static void mpw_marshal_read_flat(
if (siteName && str_type && str_counter && str_algorithm && str_uses && str_lastUsed) {
MPResultType siteType = (MPResultType)strtoul( str_type, NULL, 10 );
if (!mpw_type_short_name( siteType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %s", siteName, str_type ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site type: %s: %s", siteName, str_type );
continue;
}
long long int value = strtoll( str_counter, NULL, 10 );
if (value < MPCounterValueFirst || value > MPCounterValueLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %s", siteName, str_counter ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site counter: %s: %s", siteName, str_counter );
continue;
}
MPCounterValue siteCounter = (MPCounterValue)value;
value = strtoll( str_algorithm, NULL, 0 );
if (value < MPAlgorithmVersionFirst || value > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){
MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %s", siteName, str_algorithm )
};
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site algorithm: %s: %s", siteName, str_algorithm );
continue;
}
MPAlgorithmVersion siteAlgorithm = (MPAlgorithmVersion)value;
time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) {
file->error = (MPMarshalError){
MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed )
};
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site last used: %s: %s", siteName, str_lastUsed );
continue;
}
@@ -1010,21 +1019,17 @@ static void mpw_marshal_read_flat(
mpw_marshal_data_set_num( siteCounter, file->data, "sites", siteName, "counter", NULL );
mpw_marshal_data_set_num( siteAlgorithm, file->data, "sites", siteName, "algorithm", NULL );
mpw_marshal_data_set_num( siteType, file->data, "sites", siteName, "type", NULL );
mpw_marshal_data_set_str( siteResultState && strlen( siteResultState )? siteResultState: NULL, file->data, "sites", siteName,
"password", NULL );
mpw_marshal_data_set_str( siteResultState, file->data, "sites", siteName, "password", NULL );
mpw_marshal_data_set_num( MPResultTypeDefault, file->data, "sites", siteName, "login_type", NULL );
mpw_marshal_data_set_str( siteLoginState && strlen( siteLoginState )? siteLoginState: NULL, file->data, "sites", siteName,
"login_name", NULL );
mpw_marshal_data_set_str( siteLoginState, file->data, "sites", siteName, "login_name", NULL );
mpw_marshal_data_set_num( strtol( str_uses, NULL, 10 ), file->data, "sites", siteName, "uses", NULL );
if (strftime( dateString, sizeof( dateString ), "%FT%TZ", gmtime( &siteLastUsed ) ))
mpw_marshal_data_set_str( dateString, file->data, "sites", siteName, "last_used", NULL );
}
else {
file->error = (MPMarshalError){
MPMarshalErrorMissing,
mpw_str( "Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginState, siteName )
};
mpw_marshal_error( file, MPMarshalErrorMissing,
"Missing one of: lastUsed=%s, uses=%s, type=%s, version=%s, counter=%s, loginName=%s, siteName=%s",
str_lastUsed, str_uses, str_type, str_algorithm, str_counter, siteLoginState, siteName );
continue;
}
@@ -1044,7 +1049,7 @@ static void mpw_marshal_read_json(
mpw_marshal_file( file, NULL, mpw_marshal_data_new() );
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate data." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate data." );
return;
}
@@ -1052,7 +1057,7 @@ static void mpw_marshal_read_json(
enum json_tokener_error json_error = json_tokener_success;
json_object *json_file = json_tokener_parse_verbose( in, &json_error );
if (!json_file || json_error != json_tokener_success) {
file->error = (MPMarshalError){ MPMarshalErrorFormat, mpw_str( "Couldn't parse JSON: %s", json_tokener_error_desc( json_error ) ) };
mpw_marshal_error( file, MPMarshalErrorFormat, "Couldn't parse JSON: %s", json_tokener_error_desc( json_error ) );
return;
}
@@ -1074,9 +1079,9 @@ MPMarshalledFile *mpw_marshal_read(
if (!file)
return NULL;
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
if (!info) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate info." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate info." );
return file;
}
@@ -1090,6 +1095,8 @@ MPMarshalledFile *mpw_marshal_read(
info->format = MPMarshalFormatJSON;
#if MPW_JSON
mpw_marshal_read_json( file, in );
#else
mpw_marshal_error( file, MPMarshalErrorFormat, "JSON support is not enabled." );
#endif
}
}
@@ -1116,13 +1123,13 @@ MPMarshalledUser *mpw_marshal_auth(
if (!file)
return NULL;
file->error = (MPMarshalError){ MPMarshalSuccess, NULL };
mpw_marshal_error( file, MPMarshalSuccess, NULL );
if (!file->info) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "File wasn't parsed yet." };
mpw_marshal_error( file, MPMarshalErrorMissing, "File wasn't parsed yet." );
return NULL;
}
if (!file->data) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "No input data." };
mpw_marshal_error( file, MPMarshalErrorMissing, "No input data." );
return NULL;
}
@@ -1132,43 +1139,43 @@ MPMarshalledUser *mpw_marshal_auth(
MPAlgorithmVersion algorithm =
mpw_default_n( MPAlgorithmVersionCurrent, mpw_marshal_data_get_num( file->data, "user", "algorithm", NULL ) );
if (algorithm < MPAlgorithmVersionFirst || algorithm > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user algorithm: %u", algorithm ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user algorithm: %u", algorithm );
return NULL;
}
unsigned int avatar = mpw_default_n( 0U, mpw_marshal_data_get_num( file->data, "user", "avatar", NULL ) );
const char *fullName = mpw_marshal_data_get_str( file->data, "user", "full_name", NULL );
if (!fullName || !strlen( fullName )) {
file->error = (MPMarshalError){ MPMarshalErrorMissing, "Missing value for full name." };
mpw_marshal_error( file, MPMarshalErrorMissing, "Missing value for full name." );
return NULL;
}
MPIdenticon identicon = mpw_identicon_encoded( mpw_marshal_data_get_str( file->data, "user", "identicon", NULL ) );
const char *keyID = mpw_marshal_data_get_str( file->data, "user", "key_id", NULL );
MPResultType defaultType = mpw_default_n( MPResultTypeDefault, mpw_marshal_data_get_num( file->data, "user", "default_type", NULL ) );
if (!mpw_type_short_name( defaultType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user default type: %u", defaultType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user default type: %u", defaultType );
return NULL;
}
const char *str_lastUsed = mpw_marshal_data_get_str( file->data, "user", "last_used", NULL );
time_t lastUsed = mpw_timegm( str_lastUsed );
if (!lastUsed) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid user last used: %s", str_lastUsed ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid user last used: %s", str_lastUsed );
return NULL;
}
MPMasterKey masterKey = NULL;
if (masterKeyProvider && !(masterKey = masterKeyProvider( algorithm, fullName ))) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't derive master key." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
return NULL;
}
if (keyID && masterKey && !mpw_id_buf_equals( keyID, mpw_id_buf( masterKey, MPMasterKeySize ) )) {
file->error = (MPMarshalError){ MPMarshalErrorMasterPassword, "Master key doesn't match key ID." };
mpw_marshal_error( file, MPMarshalErrorMasterPassword, "Master key doesn't match key ID." );
mpw_free( &masterKey, MPMasterKeySize );
return NULL;
}
MPMarshalledUser *user = NULL;
if (!(user = mpw_marshal_user( fullName, masterKeyProvider, algorithm ))) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new user." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate a new user." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1189,21 +1196,21 @@ MPMarshalledUser *mpw_marshal_auth(
algorithm = mpw_default_n( user->algorithm, mpw_marshal_data_get_num( siteData, "algorithm", NULL ) );
if (algorithm < MPAlgorithmVersionFirst || algorithm > MPAlgorithmVersionLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site algorithm: %s: %u", siteName, algorithm ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site algorithm: %s: %u", siteName, algorithm );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
}
MPCounterValue siteCounter = mpw_default_n( MPCounterValueDefault, mpw_marshal_data_get_num( siteData, "counter", NULL ) );
if (siteCounter < MPCounterValueFirst || siteCounter > MPCounterValueLast) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site counter: %s: %d", siteName, siteCounter ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site counter: %s: %d", siteName, siteCounter );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
}
MPResultType siteType = mpw_default_n( user->defaultType, mpw_marshal_data_get_num( siteData, "type", NULL ) );
if (!mpw_type_short_name( siteType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site type: %s: %u", siteName, siteType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site type: %s: %u", siteName, siteType );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1211,7 +1218,7 @@ MPMarshalledUser *mpw_marshal_auth(
const char *siteResultState = mpw_marshal_data_get_str( siteData, "password", NULL );
MPResultType siteLoginType = mpw_default_n( MPResultTypeTemplateName, mpw_marshal_data_get_num( siteData, "login_type", NULL ) );
if (!mpw_type_short_name( siteLoginType )) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site login type: %s: %u", siteName, siteLoginType ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site login type: %s: %u", siteName, siteLoginType );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1221,7 +1228,7 @@ MPMarshalledUser *mpw_marshal_auth(
str_lastUsed = mpw_marshal_data_get_str( siteData, "last_used", NULL );
time_t siteLastUsed = mpw_timegm( str_lastUsed );
if (!siteLastUsed) {
file->error = (MPMarshalError){ MPMarshalErrorIllegal, mpw_str( "Invalid site last used: %s: %s", siteName, str_lastUsed ) };
mpw_marshal_error( file, MPMarshalErrorIllegal, "Invalid site last used: %s: %s", siteName, str_lastUsed );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1231,7 +1238,7 @@ MPMarshalledUser *mpw_marshal_auth(
MPMarshalledSite *site = mpw_marshal_site( user, siteName, siteType, siteCounter, algorithm );
if (!site) {
file->error = (MPMarshalError){ MPMarshalErrorInternal, "Couldn't allocate a new site." };
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't allocate a new site." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;
@@ -1245,10 +1252,7 @@ MPMarshalledUser *mpw_marshal_auth(
// Clear Text
mpw_free( &masterKey, MPMasterKeySize );
if (!masterKeyProvider || !(masterKey = masterKeyProvider( site->algorithm, user->fullName ))) {
file->error = (MPMarshalError){
MPMarshalErrorInternal,
"Couldn't derive master key."
};
mpw_marshal_error( file, MPMarshalErrorInternal, "Couldn't derive master key." );
mpw_free( &masterKey, MPMasterKeySize );
mpw_marshal_user_free( &user );
return NULL;

View File

@@ -223,6 +223,7 @@ const char *mpw_marshal_write(
MPMarshalledFile *mpw_marshal_read(
MPMarshalledFile *file, const char *in);
/** Authenticate as the user identified by the given marshalled file.
* @note This object stores a reference to the given key provider.
* @return A user object (allocated), or NULL if the file format provides no marshalling or a format error occurred. */
MPMarshalledUser *mpw_marshal_auth(
MPMarshalledFile *file, const MPMasterKeyProvider masterKeyProvider);
@@ -230,22 +231,32 @@ MPMarshalledUser *mpw_marshal_auth(
//// Creating.
/** Create a new user object ready for marshalling.
* @note This object stores copies of the strings assigned to it and manages their deallocation internally.
* @return A user object (allocated), or NULL if the fullName is missing or the marshalled user couldn't be allocated. */
MPMarshalledUser *mpw_marshal_user(
const char *fullName, const MPMasterKeyProvider masterKeyProvider, const MPAlgorithmVersion algorithmVersion);
/** Create a new site attached to the given user object, ready for marshalling.
* @note This object stores copies of the strings assigned to it and manages their deallocation internally.
* @return A site object (allocated), or NULL if the siteName is missing or the marshalled site couldn't be allocated. */
MPMarshalledSite *mpw_marshal_site(
MPMarshalledUser *user,
const char *siteName, const MPResultType resultType, const MPCounterValue siteCounter, const MPAlgorithmVersion algorithmVersion);
/** Create a new question attached to the given site object, ready for marshalling.
* @note This object stores copies of the strings assigned to it and manages their deallocation internally.
* @return A question object (allocated), or NULL if the marshalled question couldn't be allocated. */
MPMarshalledQuestion *mpw_marshal_question(
MPMarshalledSite *site, const char *keyword);
/** Create or update a marshal file descriptor.
* @param file If NULL, a new file will be allocated. Otherwise, the given file will be updated and the updated file returned.
* @param info If NULL, the file's info will be left as-is, otherwise it will be replaced by the given one. The file will manage the info's deallocation.
* @param data If NULL, the file's data will be left as-is, otherwise it will be replaced by the given one. The file will manage the data's deallocation.
* @return The given file or new (allocated) if file is NULL; or NULL if the user is missing or the file couldn't be allocated. */
MPMarshalledFile *mpw_marshal_file(
MPMarshalledFile *file, MPMarshalledInfo *info, MPMarshalledData *data);
/** Record a marshal error.
* @return The given file or new (allocated) if file is NULL; or NULL if the file couldn't be allocated. */
MPMarshalledFile *mpw_marshal_error(
MPMarshalledFile *file, MPMarshalErrorType type, const char *format, ...);
//// Disposing.

View File

@@ -25,6 +25,7 @@
#endif
MP_LIBS_BEGIN
#define __STDC_WANT_LIB_EXT1__ 1
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

View File

@@ -607,6 +607,9 @@ const uint8_t *mpw_unhex(const char *hex, size_t *length) {
size_t mpw_utf8_charlen(const char *utf8String) {
if (!utf8String)
return 0;
// Legal UTF-8 byte sequences: <http://www.unicode.org/unicode/uni2errata/UTF-8_Corrigendum.html>
unsigned char utf8Char = (unsigned char)*utf8String;
if (utf8Char >= 0x00 && utf8Char <= 0x7F)
@@ -624,8 +627,9 @@ size_t mpw_utf8_charlen(const char *utf8String) {
size_t mpw_utf8_strchars(const char *utf8String) {
size_t strchars = 0, charlen;
for (char *remaining = (char *)utf8String; (charlen = mpw_utf8_charlen( remaining )); remaining += charlen)
++strchars;
for (char *remaining = (char *)utf8String; remaining && *remaining; remaining += charlen, ++strchars)
if (!(charlen = mpw_utf8_charlen( remaining )))
return 0;
return strchars;
}

View File

@@ -256,9 +256,9 @@ bool mpw_id_buf_equals(MPKeyID id1, MPKeyID id2);
//// String utilities.
/** @return The byte length of the UTF-8 character at the start of the given string. */
/** @return The byte length of the UTF-8 character at the start of the given string or 0 if it is NULL, empty or not a legal UTF-8 character. */
size_t mpw_utf8_charlen(const char *utf8String);
/** @return The amount of UTF-8 characters in the given string. */
/** @return The amount of UTF-8 characters in the given string or 0 if it is NULL, empty, or contains bytes that are not legal in UTF-8. */
size_t mpw_utf8_strchars(const char *utf8String);
/** Drop-in for memdup(3).
* @return A buffer (allocated, len) with len bytes copied from src or NULL if src is missing or the buffer could not be allocated. */

View File

@@ -151,7 +151,7 @@ public interface MPAlgorithm {
/**
* The algorithm iterations.
*/
enum Version implements MPAlgorithm {
enum Version implements MPAlgorithm {
/**
* bugs:
@@ -346,7 +346,7 @@ public interface MPAlgorithm {
@Nonnull
@Override
public Version version() {
return MPAlgorithm.Version.V0;
return this;
}
@Nonnull

View File

@@ -20,7 +20,6 @@ package com.lyndir.masterpassword;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import java.util.EnumMap;
@@ -56,6 +55,7 @@ public class MPMasterKey {
}
@Override
@SuppressWarnings("deprecation")
protected void finalize()
throws Throwable {