diff --git a/Podfile b/Podfile index e208186..fe7b6d7 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,5 @@ +platform :ios, '10.2' + def generate_modulemap(name, path) f = File.new(File.join("#{path}/module.modulemap"), "w+") module_name = "#{name}" diff --git a/README.md b/README.md index 816c01c..d10c16b 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ Testflight, drop an email to `developer@passforios.mssun.me`. Thank you. ## Features - Try to be compatible with the Password Store command line tool -- Support to view, copy, add, and edit password entries +- View, copy, add, and edit password entries - Encrypt and decrypt password entries by PGP keys - Synchronize with your password Git repository - User-friendly interface: search, long press to copy, copy and open link, etc. -- Support one-time password (OTP) tokens (QR code and otpauth URI) +- Support one-time password tokens (two-factor authentication codes) +- Autofill in Safari/Chrome and [supported apps](https://github.com/agilebits/onepassword-app-extension) - Written in Swift - No need to jailbreak your devices @@ -38,8 +39,8 @@ Testflight, drop an email to `developer@passforios.mssun.me`. Thank you. ## Usages - Setup your password-store ([official `Pass` introduction](https://www.passwordstore.org/)) -- Get Pass for iOS from the App Store or build one by yourself -- Start to use Pass for iOS on your iPhone/iPad ([quick-start guide](https://github.com/mssun/passforios/wiki#quick-start-guide-for-pass-for-ios)) +- Get Pass for iOS from the App Store or [build by yourself](https://github.com/mssun/passforios/wiki/Building-Pass-for-iOS) +- Setup Pass for iOS ([quick-start guide](https://github.com/mssun/passforios/wiki#quick-start-guide-for-pass-for-ios)) For more, please read the [wiki page](https://github.com/mssun/pass-ios/wiki). diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f8f0654..607740b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -55,7 +55,7 @@ platform :ios do ) match( type: "appstore", - app_identifier: "me.mssun.passforios.extension", + app_identifier: "me.mssun.passforios.find-login-action-extension", keychain_name: ENV["MATCH_KEYCHAIN_NAME"], keychain_password: ENV["MATCH_KEYCHAIN_PASSWORD"], readonly: true diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index c780732..162e70e 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2C58F31EECC494C7A7F00A98 /* libPods-passKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB6C63FA1652F925B5C9F0B5 /* libPods-passKitTests.a */; }; 398A8F69C2230A8117820BB7 /* libPods-passKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BAA15189E80AA544EAF7AD /* libPods-passKit.a */; }; 6930A9D26085DE7CA1A7AACC /* libPods-passExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B240CA444AC9172F3053651 /* libPods-passExtension.a */; }; + A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */; }; A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; }; A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; }; A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */; }; @@ -80,6 +81,7 @@ DCC408C71E307DBB00F29B0E /* SVProgressHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */; }; DCC441521E8F6C06008A90C4 /* RawPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */; }; DCC441541E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */; }; + DCD3C65E1EFB9BB400CBE842 /* SettingsSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */; }; DCDDEAB01E4639F300F68193 /* LabelTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */; }; DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */; }; DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */; }; @@ -165,6 +167,7 @@ 7592A214C22CEBBEF4596CC1 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7E088A9255B6CB576EF757C1 /* Pods-passKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.debug.xcconfig"; sourceTree = ""; }; A02ACA4077630047EA669D05 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = ""; }; + A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnePasswordExtensionConstants.swift; sourceTree = ""; }; A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = ""; }; A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; A2227D4C1EEE5E25002A69A9 /* libObjectivePGP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libObjectivePGP.a; path = "Pods/../build/Debug-iphoneos/ObjectivePGP/libObjectivePGP.a"; sourceTree = ""; }; @@ -253,6 +256,7 @@ DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = ""; }; DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawPasswordViewController.swift; sourceTree = ""; }; DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitSSHKeyArmorSettingTableViewController.swift; sourceTree = ""; }; + DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSplitViewController.swift; sourceTree = ""; }; DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LabelTableViewCell.xib; sourceTree = ""; }; DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordDetailTitleTableViewCell.swift; sourceTree = ""; }; DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleTextFieldTableViewCell.swift; sourceTree = ""; }; @@ -324,6 +328,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A2168A801EFD431A005EA873 /* Controllers */ = { + isa = PBXGroup; + children = ( + A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */, + A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + A2168A811EFD4322005EA873 /* Helpers */ = { + isa = PBXGroup; + children = ( + A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */, + A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */, + ); + name = Helpers; + sourceTree = ""; + }; A26075791EEC6F34005DB03E /* passKit */ = { isa = PBXGroup; children = ( @@ -348,14 +370,13 @@ A26700251EEC466A00176B8A /* passExtension */ = { isa = PBXGroup; children = ( + A26700331EEC46C900176B8A /* passExtension.entitlements */, + A267002B1EEC466A00176B8A /* Info.plist */, + A2168A801EFD431A005EA873 /* Controllers */, + A2168A811EFD4322005EA873 /* Helpers */, A2367B9F1EF0387000C8FE8B /* Assets.xcassets */, A26700351EEC475600176B8A /* passProcessor.js */, - A26700331EEC46C900176B8A /* passExtension.entitlements */, A26700281EEC466A00176B8A /* MainInterface.storyboard */, - A267002B1EEC466A00176B8A /* Info.plist */, - A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */, - A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */, - A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */, ); path = passExtension; sourceTree = ""; @@ -415,6 +436,7 @@ DC19400C1E4B39400077E0A3 /* Controllers */ = { isa = PBXGroup; children = ( + DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */, DC3E64E51E656F11009A83DE /* CommitLogsTableViewController.swift */, DC5F385A1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift */, DC037CB11E4CAB1700609409 /* AboutRepositoryTableViewController.swift */, @@ -1007,6 +1029,7 @@ buildActionMask = 2147483647; files = ( A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */, + A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */, A28C66681EF10EC900A398A1 /* PasscodeExtensionDisplay.swift in Sources */, A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */, ); @@ -1042,6 +1065,7 @@ DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */, DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */, DC5734AE1E439AD400D09270 /* PasswordsViewController.swift in Sources */, + DCD3C65E1EFB9BB400CBE842 /* SettingsSplitViewController.swift in Sources */, DC3E64E61E656F11009A83DE /* CommitLogsTableViewController.swift in Sources */, DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */, DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */, @@ -1143,13 +1167,12 @@ ); INFOPLIST_FILE = passKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MODULEMAP_FILE = ""; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKit; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1188,13 +1211,12 @@ ); INFOPLIST_FILE = passKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MODULEMAP_FILE = ""; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKit; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1220,7 +1242,7 @@ INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKitTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1240,7 +1262,7 @@ INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKitTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1266,14 +1288,13 @@ "$(SRCROOT)/Carthage/Build/iOS/ObjectiveGit.framework/Headers/", ); INFOPLIST_FILE = passExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.extension; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9feb619f-73bb-429c-a2a8-856ddcbb9e33"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.extension"; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.find-login-action-extension"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; @@ -1298,14 +1319,13 @@ "$(SRCROOT)/Carthage/Build/iOS/ObjectiveGit.framework/Headers/", ); INFOPLIST_FILE = passExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.extension; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9b4419c3-a959-4af8-8810-8af134008ec5"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.extension"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.find-login-action-extension"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; @@ -1320,7 +1340,7 @@ INFOPLIST_FILE = passTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1336,7 +1356,7 @@ INFOPLIST_FILE = passTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1386,6 +1406,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1428,6 +1449,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; @@ -1459,7 +1481,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "${inherited}"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "893c10b3-79b1-46f7-914a-e625bf10d665"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios"; @@ -1495,7 +1517,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "${inherited}"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "aefeea85-1194-4db2-9ce4-fb9995e2fdff"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios"; diff --git a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme index 73fdf63..062dfaf 100644 --- a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme +++ b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme @@ -38,6 +38,16 @@ ReferencedContainer = "container:pass.xcodeproj"> + + + + UIInterfaceOrientationMask { + if let _ = window?.rootViewController as? PasscodeLockViewController { + window?.frame = UIScreen.main.bounds + } + return .all + } + func postSearchNotification() { NotificationCenter.default.post(name: .passwordSearch, object: nil) } diff --git a/pass/Base.lproj/Main.storyboard b/pass/Base.lproj/Main.storyboard index 60b3202..0cb0b3b 100644 --- a/pass/Base.lproj/Main.storyboard +++ b/pass/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -102,7 +102,7 @@ - + @@ -129,7 +129,7 @@ - + @@ -222,7 +222,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -267,8 +267,8 @@ - - + + @@ -288,7 +288,7 @@ - + - + @@ -1073,7 +1077,7 @@ Phone Support PIN #: 84719 - + @@ -1090,7 +1094,7 @@ Phone Support PIN #: 84719 - + @@ -1124,7 +1128,7 @@ Phone Support PIN #: 84719 - + @@ -1148,7 +1152,7 @@ Phone Support PIN #: 84719 - + @@ -1168,7 +1172,7 @@ Phone Support PIN #: 84719 - + @@ -1188,7 +1192,7 @@ Phone Support PIN #: 84719 - + @@ -1305,7 +1309,7 @@ Cgo - + @@ -1331,7 +1335,7 @@ Cgo - + @@ -1343,7 +1347,7 @@ Cgo - + @@ -1369,7 +1373,7 @@ Cgo - + @@ -1382,11 +1386,6 @@ Cgo - - - - - @@ -1503,7 +1502,7 @@ Cgo - + @@ -1526,7 +1525,7 @@ Cgo - + @@ -1570,7 +1569,7 @@ Cgo - + - - - - - @@ -1660,25 +1654,7 @@ Cgo - - - - - - - - - - - - - - - - - - - + @@ -1693,7 +1669,7 @@ Cgo @@ -1756,28 +1725,76 @@ Cgo - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pass/Controllers/AdvancedSettingsTableViewController.swift b/pass/Controllers/AdvancedSettingsTableViewController.swift index 5f9e0e5..ed85ce7 100644 --- a/pass/Controllers/AdvancedSettingsTableViewController.swift +++ b/pass/Controllers/AdvancedSettingsTableViewController.swift @@ -88,9 +88,6 @@ class AdvancedSettingsTableViewController: UITableViewController { SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn } - @IBAction func cancelGitConfigSetting(segue: UIStoryboardSegue) { - } - @IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) { if let controller = segue.source as? GitConfigSettingTableViewController { if let gitSignatureName = controller.nameTextField.text, diff --git a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift index 4aa883b..5e28b73 100644 --- a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift +++ b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift @@ -204,9 +204,4 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe } } } - - @IBAction private func cancelPGPScanner(segue: UIStoryboardSegue) { - - } - } diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 1bbe900..e402159 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -261,6 +261,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni if let username = password.getUsername() { section.item.append(TableCell(title: "username", content: username)) } + if let login = password.getLogin() { + section.item.append(TableCell(title: "login", content: login)) + } section.item.append(TableCell(title: "password", content: password.password)) tableData.append(section) @@ -278,17 +281,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } // show additional information - let filteredAdditionKeys = password.additionKeys.filter { - $0.lowercased() != "username" && - $0.lowercased() != "password" && - (!$0.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) && - (!Password.otpKeywords.contains($0) || !SharedDefaults[.isHideOTPOn]) } - + let filteredAdditionKeys = password.getFilteredAdditions() if filteredAdditionKeys.count > 0 { section = TableSection(type: .addition, header: "additions") - for additionKey in filteredAdditionKeys { - section.item.append(TableCell(title: additionKey, content: password.additions[additionKey]!)) - } + filteredAdditionKeys.forEach({ (key: String, value: String) in + section.item.append(TableCell(title: key, content: value)) + }) tableData.append(section) } diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index 28992a5..b320c14 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -88,6 +88,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl case .fillPasswordCell: fillPasswordCell = tableView.dequeueReusableCell(withIdentifier: "fillPasswordCell", for: indexPath) as? FillPasswordTableViewCell fillPasswordCell?.delegate = self + fillPasswordCell?.contentTextField.delegate = self fillPasswordCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String) if tableData[passwordSection].count == 1 { fillPasswordCell?.settingButton.isHidden = true @@ -97,10 +98,18 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell let lengthSetting = Globals.passwordDefaultLength[SharedDefaults[.passwordGeneratorFlavor]] ?? Globals.passwordDefaultLength["Random"] + let minimumLength = lengthSetting?.min ?? 0 + let maximumLength = lengthSetting?.max ?? 0 + var defaultLength = lengthSetting?.def ?? 0 + if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.characters.count, + currentPasswordLength >= minimumLength, + currentPasswordLength <= maximumLength { + defaultLength = currentPasswordLength + } passwordLengthCell?.reset(title: "Length", - minimumValue: lengthSetting?.min ?? 0, - maximumValue: lengthSetting?.max ?? 0, - defaultValue: lengthSetting?.def ?? 0) + minimumValue: minimumLength, + maximumValue: maximumLength, + defaultValue: defaultLength) passwordLengthCell?.delegate = self return passwordLengthCell! case .additionsCell: @@ -234,18 +243,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl } } - @IBAction private func cancelOTPScanner(segue: UIStoryboardSegue) { - - } - - // update the data table after editing + // update tableData so to make sure reloadData() works correctly func textFieldDidEndEditing(_ textField: UITextField) { if textField == nameCell?.contentTextField { tableData[nameSection][0][PasswordEditorCellKey.content] = nameCell?.getContent() + } else if textField == fillPasswordCell?.contentTextField { + tableData[passwordSection][0][PasswordEditorCellKey.content] = fillPasswordCell?.getContent() } } - // update the data table after editing + // update tableData so to make sure reloadData() works correctly func textViewDidEndEditing(_ textView: UITextView) { if textView == additionsCell?.contentTextView { tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent() diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index 2bae84b..5fa05b4 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -456,7 +456,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV switch scope { case "All": filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in - return entry.title.lowercased().contains(searchText.lowercased()) + let name = entry.passwordEntity?.nameWithCategory ?? entry.title + return name.localizedCaseInsensitiveContains(searchText) } if searchController.isActive && searchController.searchBar.text != "" { reloadTableView(data: filteredPasswordsTableEntries) diff --git a/pass/Controllers/SettingsSplitViewController.swift b/pass/Controllers/SettingsSplitViewController.swift new file mode 100644 index 0000000..baf70ae --- /dev/null +++ b/pass/Controllers/SettingsSplitViewController.swift @@ -0,0 +1,24 @@ +// +// SettingsSplitViewController.swift +// pass +// +// Created by Mingshen Sun on 6/21/17. +// Copyright © 2017 Bob Sun. All rights reserved. +// + +import UIKit + +class SettingsSplitViewController: UISplitViewController, UISplitViewControllerDelegate { + override func viewDidLoad() { + self.delegate = self + self.preferredDisplayMode = .allVisible + } + + func splitViewController( + _ splitViewController: UISplitViewController, + collapseSecondary secondaryViewController: UIViewController, + onto primaryViewController: UIViewController) -> Bool { + // Return true to prevent UIKit from applying its default behavior + return true + } +} diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index e2b812d..86c308a 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -28,9 +28,6 @@ class SettingsTableViewController: UITableViewController { @IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell! let passwordStore = PasswordStore.shared var passcodeLockConfig = PasscodeLockConfiguration.shared - - @IBAction func cancelPGPKey(segue: UIStoryboardSegue) { - } @IBAction func savePGPKey(segue: UIStoryboardSegue) { if let controller = segue.source as? PGPKeySettingTableViewController { @@ -117,9 +114,6 @@ class SettingsTableViewController: UITableViewController { } } - @IBAction func cancelGitServerSetting(segue: UIStoryboardSegue) { - } - @IBAction func saveGitServerSetting(segue: UIStoryboardSegue) { self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host } diff --git a/pass/Info.plist b/pass/Info.plist index d749b9b..01bea53 100644 --- a/pass/Info.plist +++ b/pass/Info.plist @@ -17,7 +17,16 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.2.7 + 0.2.8 + CFBundleURLTypes + + + CFBundleURLSchemes + + org-appextension-feature-password-management + + + CFBundleVersion 1 ITSAppUsesNonExemptEncryption diff --git a/passExtension/ExtensionViewController.swift b/passExtension/ExtensionViewController.swift index 17e55af..29f6f0b 100644 --- a/passExtension/ExtensionViewController.swift +++ b/passExtension/ExtensionViewController.swift @@ -26,13 +26,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV private let passwordStore = PasswordStore.shared private var searchActive = false - - // the URL passed to the extension - private var extensionURL: String? - private var passwordsTableEntries: [PasswordsTableEntry] = [] private var filteredPasswordsTableEntries: [PasswordsTableEntry] = [] + enum Action { + case findLogin, fillBrowser, unknown + } + + private var extensionAction = Action.unknown + private lazy var passcodelock: PasscodeExtensionDisplay = { let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext) return passcodelock @@ -64,24 +66,67 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV // initialize table entries initPasswordsTableEntries() - // search using the extensionContext inputs - let item = extensionContext?.inputItems.first as! NSExtensionItem - let provider = item.attachments?.first as! NSItemProvider - let propertyList = String(kUTTypePropertyList) - if provider.hasItemConformingToTypeIdentifier(propertyList) { - provider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in - let dictionary = item as! NSDictionary - let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary - let url = URL(string: (results["url"] as? String)!)?.host - DispatchQueue.main.async { [weak self] in - // force search (set text, set active, force search) - self?.searchBar.text = url - self?.searchBar.becomeFirstResponder() - self?.searchBarSearchButtonClicked((self?.searchBar)!) + // get the provider + guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else { + return + } + + for extensionItem in extensionItems { + if let itemProviders = extensionItem.attachments as? [NSItemProvider] { + for provider in itemProviders { + // search using the extensionContext inputs + if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) { + provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, error) -> Void in + let dictionary = item as! NSDictionary + var url: String? + if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String { + if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { + urlString = "http://" + urlString + } + url = URL(string: urlString)?.host + } + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .findLogin + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } + else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) { + provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, error) -> Void in + var url: String? + if let dictionary = item as? NSDictionary, + let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary, + var urlString = results[OnePasswordExtensionKey.URLStringKey] as? String { + if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { + urlString = "http://" + urlString + } + url = URL(string: urlString)?.host + } + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .fillBrowser + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { + provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, error) -> Void in + let url = (item as? NSURL)!.host + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .fillBrowser + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } } - }) - } else { - print("error") + } } } @@ -115,19 +160,29 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV var decryptedPassword: Password? do { decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) - DispatchQueue.main.async { - Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) - let title = "Password Copied" - let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)" - let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in - // return a dictionary for JavaScript for best-effor fill in + let username = decryptedPassword?.getUsername() ?? decryptedPassword?.getLogin() ?? "" + let password = decryptedPassword?.password ?? "" + DispatchQueue.main.async {// prepare a dictionary to return + switch self.extensionAction { + case .findLogin: let extensionItem = NSExtensionItem() - let returnDictionary = [ NSExtensionJavaScriptFinalizeArgumentKey : ["username": decryptedPassword?.getUsername() ?? "", "password": decryptedPassword?.password ?? ""]] + var returnDictionary = [OnePasswordExtensionKey.usernameKey: username, + OnePasswordExtensionKey.passwordKey: password] + if let totpPassword = decryptedPassword?.getOtp() { + returnDictionary[OnePasswordExtensionKey.totpKey] = totpPassword + } extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))] self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil) - })) - self.present(alert, animated: true, completion: nil) + case .fillBrowser: + Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) + // return a dictionary for JavaScript for best-effor fill in + let extensionItem = NSExtensionItem() + let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey : ["username": username, "password": password]] + extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))] + self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil) + default: + self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) + } } } catch { print(error) diff --git a/passExtension/Info.plist b/passExtension/Info.plist index 971000a..f0802bb 100644 --- a/passExtension/Info.plist +++ b/passExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.2.7 + 0.2.8 CFBundleVersion 1 NSExtension @@ -29,7 +29,9 @@ NSExtensionActivationSupportsText NSExtensionActivationSupportsWebPageWithMaxCount - 100 + 1 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 NSExtensionJavaScriptPreprocessingFile passProcessor diff --git a/passExtension/OnePasswordExtensionConstants.swift b/passExtension/OnePasswordExtensionConstants.swift new file mode 100644 index 0000000..894b921 --- /dev/null +++ b/passExtension/OnePasswordExtensionConstants.swift @@ -0,0 +1,51 @@ +// +// OnePasswordExtensionConstants.swift +// pass +// +// Created by Yishi Lin on 2017/6/23. +// Copyright © 2017 Bob Sun. All rights reserved. +// +// This file contains constants from https://github.com/agilebits/onepassword-app-extension/ + +class OnePasswordExtensionActions { + static let findLogin = "org.appextension.find-login-action" + static let saveLogin = "org.appextension.save-login-action" + static let changePassword = "org.appextension.change-password-action" + static let fillWebView = "org.appextension.fill-webview-action" + static let fillBrowser = "org.appextension.fill-browser-action" +} + +class OnePasswordExtensionKey { + // Login Dictionary keys - Used to get or set the properties of a 1Password Login + static let URLStringKey = "url_string" + static let usernameKey = "username" + static let passwordKey = "password" + static let totpKey = "totp" + static let titleKey = "login_title" + static let notesKey = "notes" + static let sectionTitleKey = "section_title" + static let fieldsKey = "fields" + static let returnedFieldsKey = "returned_fields" + static let oldPasswordKey = "old_password" + static let passwordGeneratorOptionsKey = "password_generator_options" + + // Password Generator options - Used to set the 1Password Password Generator options when saving a new Login or when changing the password for for an existing Login + static let generatedPasswordMinLengthKey = "password_min_length" + static let generatedPasswordMaxLengthKey = "password_max_length" + static let generatedPasswordRequireDigitsKey = "password_require_digits" + static let generatedPasswordRequireSymbolsKey = "password_require_symbols" + static let generatedPasswordForbiddenCharactersKey = "password_forbidden_characters" +} + +// Errors codes +class OnePasswordExtensionError { + static let errorDomain = "OnePasswordExtension" + static let errorCodeCancelledByUser = 0 + static let errorCodeAPINotAvailable = 1 + static let errorCodeFailedToContactExtension = 2 + static let errorCodeFailedToLoadItemProviderData = 3 + static let errorCodeCollectFieldsScriptFailed = 4 + static let errorCodeFillFieldsScriptFailed = 5 + static let errorCodeUnexpectedData = 6 + static let errorCodeFailedToObtainURLStringFromWebView = 7 +} diff --git a/passExtension/passextension.entitlements b/passExtension/passExtension.entitlements similarity index 100% rename from passExtension/passextension.entitlements rename to passExtension/passExtension.entitlements diff --git a/passExtension/passProcessor.js b/passExtension/passProcessor.js index 6db3d04..2cb114e 100644 --- a/passExtension/passProcessor.js +++ b/passExtension/passProcessor.js @@ -3,22 +3,32 @@ var PassProcessor = function() {}; PassProcessor.prototype = { run: function(arguments) { var url - var html var error try { - url = document.URL; - html = document.body.innerHTML + url = document.URL } catch (e) { error = e } finally { - arguments.completionFunction({"url": url, "html": html, "error": error}); + arguments.completionFunction({"url_string": url, "error": error}); } }, finalize: function(arguments) { - var str = "username: " + arguments["username"] + "\r\npassword: " + arguments["password"]; - // alert(str) - // document.body.innerHTML = arguments["content"]; + if (arguments["password"]) { + var passwordElement = document.querySelector("input[type=password]") + if (passwordElement) { + passwordElement.setAttribute('value', arguments["password"]) + passwordElement.value = arguments["password"] + } + } + + if (arguments["username"]) { + var usernameElement = document.querySelector("input[type=email], input[type=text]") + if (usernameElement) { + usernameElement.setAttribute('value', arguments["username"]) + usernameElement.value = arguments["username"] + } + } } }; diff --git a/passKit/Helpers/Utils.swift b/passKit/Helpers/Utils.swift index 785f892..9ce518e 100644 --- a/passKit/Helpers/Utils.swift +++ b/passKit/Helpers/Utils.swift @@ -111,11 +111,13 @@ public class Utils { // draw all digits in the password into red // draw all punctuation characters in the password into blue for (index, element) in plainPassword.unicodeScalars.enumerated() { + var charColor = UIColor.darkText if NSCharacterSet.decimalDigits.contains(element) { - attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1)) + charColor = Globals.red } else if !NSCharacterSet.letters.contains(element) { - attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1)) + charColor = Globals.blue } + attributedPassword.addAttribute(NSForegroundColorAttributeName, value: charColor, range: NSRange(location: index, length: 1)) } return attributedPassword } diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index 78d4826..52b6566 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -36,11 +36,10 @@ public class Password { } } public var password = "" - public var additions = [String: String]() - public var additionKeys = [String]() public var changed: Int = 0 public var plainText = "" + private var additions = [String: String]() private var firstLineIsOTPField = false private var otpToken: Token? @@ -82,16 +81,29 @@ public class Password { self.name = name self.url = url self.plainText = plainText - self.additions.removeAll() - self.additionKeys.removeAll() + additions.removeAll() - // get password and additional fields + // split the plain text let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init) - self.password = plainTextSplit.first ?? "" + + // get password + password = plainTextSplit.first ?? "" + + // get additonal fields if plainTextSplit.count == 2 { - (self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1]) + var unknownIndex = 0 + plainTextSplit[1].enumerateLines() { line, _ in + if !line.isEmpty { + var (key, value) = Password.getKeyValuePair(from: line) + if key == nil { + unknownIndex += 1 + key = "unknown \(unknownIndex)" + } + self.additions[key!] = value + } + } } // check whether the first line of the plainText looks like an otp entry @@ -99,7 +111,6 @@ public class Password { if Password.otpKeywords.contains(key ?? "") { firstLineIsOTPField = true self.additions[key!] = value - self.additionKeys.insert(key!, at: 0) } else { firstLineIsOTPField = false } @@ -108,12 +119,28 @@ public class Password { self.updateOtpToken() } + public func getFilteredAdditions() -> [String: String] { + var filteredAdditions = [String: String]() + additions.forEach { (key: String, value: String) in + if key.lowercased() != "username" && key.lowercased() != "login" && key.lowercased() != "password" && + (!key.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) && + (!Password.otpKeywords.contains(key) || !SharedDefaults[.isHideOTPOn]) { + filteredAdditions[key] = value + } + } + return filteredAdditions + } + public func getUsername() -> String? { - return getAdditionValue(withKey: "Username") ?? getAdditionValue(withKey: "username") + return getAdditionValue(withKey: "username", caseSensitive: false) + } + + public func getLogin() -> String? { + return getAdditionValue(withKey: "login", caseSensitive: false) } public func getURLString() -> String? { - return getAdditionValue(withKey: "URL") ?? getAdditionValue(withKey: "url") ?? getAdditionValue(withKey: "Url") + return getAdditionValue(withKey: "url", caseSensitive: false) } // return a key-value pair from the line @@ -138,27 +165,6 @@ public class Password { return (key, value) } - private static func getAdditionFields(from additionFieldsPlainText: String) -> ([String: String], [String]){ - var additions = [String: String]() - var additionKeys = [String]() - var unknownIndex = 0 - - additionFieldsPlainText.enumerateLines() { line, _ in - if line == "" { - return - } - var (key, value) = getKeyValuePair(from: line) - if key == nil { - unknownIndex += 1 - key = "unknown \(unknownIndex)" - } - additions[key!] = value - additionKeys.append(key!) - } - - return (additions, additionKeys) - } - public func getAdditionsPlainText() -> String { // lines starting from the second let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) { @@ -179,8 +185,18 @@ public class Password { return getPlainText().data(using: .utf8)! } - private func getAdditionValue(withKey key: String) -> String? { - return self.additions[key] + private func getAdditionValue(withKey key: String, caseSensitive: Bool = true) -> String? { + if caseSensitive { + return additions[key] + } else { + let searchKey = key.lowercased() + for k in additions.keys { + if searchKey == k.lowercased() { + return additions[k] + } + } + return nil + } } /*