diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift index 67717c0..b105db3 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/CustomData2.swift @@ -11,7 +11,7 @@ public class CustomData2: Eraseable { public var value: String public var lastModificationTime: Date? - init(value: String, lastModificationTime: Date?) { + public init(value: String, lastModificationTime: Date?) { self.value = value self.lastModificationTime = lastModificationTime } @@ -96,7 +96,7 @@ public class CustomData2: Eraseable { case Xml2.value: value = tag.value ?? "" case Xml2.lastModificationTime: - optionalTimestamp = timeParser.xmlStringToDate(tag.value) + optionalTimestamp = (xmlParentName != "Group" && xmlParentName != "Entry") ? timeParser.xmlStringToDate(tag.value) : nil default: Logger.mainLog.error("Unexpected XML tag in CustomData/Item", metadata: ["name": "\(tag.name)"]) throw Xml2.ParsingError.unexpectedTag( diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Entry2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Entry2.swift index 55b76b8..aa54ef9 100644 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Entry2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Entry2.swift @@ -258,7 +258,7 @@ public class Entry2: Entry { return Bool(string: property.value) } set { - let dataItem = CustomData2.Item(value: String(describing: newValue), lastModificationTime: .now) + let dataItem = CustomData2.Item(value: String(describing: newValue), lastModificationTime: nil) customData[Xml2.ThirdParty.browserHideEntry] = dataItem } } diff --git a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Group2.swift b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Group2.swift index bb188c1..8ba4e6f 100755 --- a/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Group2.swift +++ b/ios/KdbxSwift/Sources/KdbxSwift/db/kp2/Group2.swift @@ -83,7 +83,7 @@ public class Group2: Group { targetGroup2.customData = customData.clone() } - override public func createEntry(detached: Bool = false) -> Entry { + override public func createEntry(detached: Bool = false) -> Entry2 { let newEntry = Entry2(database: database) newEntry.uuid = UUID() newEntry.isDeleted = self.isDeleted diff --git a/ios/KeeVaultAutofill/KeeVaultViewController.swift b/ios/KeeVaultAutofill/KeeVaultViewController.swift index d5d7702..604ab98 100644 --- a/ios/KeeVaultAutofill/KeeVaultViewController.swift +++ b/ios/KeeVaultAutofill/KeeVaultViewController.swift @@ -49,7 +49,7 @@ class KeeVaultViewController: UIViewController, AddOrEditEntryDelegate { if let urlString = self.searchDomains?.first { if let url = urlFromString(urlString) { - addUrlToEntry(entry, url.absoluteString) + addUrlToEntry(entry as! Entry2, url.absoluteString) } } @@ -73,7 +73,7 @@ class KeeVaultViewController: UIViewController, AddOrEditEntryDelegate { if (newUrl) { if let urlString = self.searchDomains?.first { if let url = urlFromString(urlString) { - addUrlToEntry(entry, url.absoluteString) + addUrlToEntry(entry as! Entry2, url.absoluteString) } } } @@ -222,17 +222,44 @@ class KeeVaultViewController: UIViewController, AddOrEditEntryDelegate { return (0, firstHostname) } - fileprivate func addUrlToEntry(_ entry: Entry, _ url: String) { + fileprivate func mashNewUrlIntoJSON(_ entryJson: String, _ propertyName: String, _ url: String) -> String { + let range = entryJson.range(of: #""\#(propertyName)"\:\[([^\]]*)\]"#, options: .regularExpression) + var newJson = entryJson.replacingOccurrences(of: "[]", with: "[\"\(url)\"]", options: .init(), range: range) + if (newJson.count == entryJson.count) { + // may already have an altURL + newJson = entryJson.replacingOccurrences(of: "]", with: ",\"\(url)\"]", options: .init(), range: range) + } + if (newJson.count == entryJson.count) { + // may be a null property + let rangeNull = entryJson.range(of: #""\#(propertyName)"\:null"#, options: .regularExpression) + newJson = entryJson.replacingOccurrences(of: "null", with: "[\"\(url)\"]", options: .init(), range: rangeNull) + } + if (newJson.count == entryJson.count) { + // no altURLs at all + let idx = newJson.index(newJson.endIndex, offsetBy: -1) + newJson.insert(contentsOf: ",\"\(propertyName)\":[\"\(url)\"]", at: idx) + } + return newJson + } + + fileprivate func addUrlToEntry(_ entry: Entry2, _ url: String) { if (entry.rawURL.isEmpty) { entry.rawURL = url } else { - let entryJson = entry.getField("KPRPC JSON")?.value ?? "{\"version\":1,\"priority\":0,\"hide\":false,\"hTTPRealm\":\"\",\"formFieldList\":[],\"alwaysAutoFill\":false,\"alwaysAutoSubmit\":false,\"neverAutoFill\":false,\"neverAutoSubmit\":false,\"blockDomainOnlyMatch\":false,\"blockHostnameOnlyMatch\":false,\"altURLs\":[],\"regExURLs\":[],\"blockedURLs\":[],\"regExBlockedURLs\":[]}" - let range = entryJson.range(of: #""altURLs"\:\[([^\]]*)\]"#, options: .regularExpression) - var newJson = entryJson.replacingOccurrences(of: "[]", with: "[\"\(url)\"]", options: .init(), range: range) - if (newJson.count == entryJson.count) { - newJson = entryJson.replacingOccurrences(of: "]", with: ",\"\(url)\"]", options: .init(), range: range) + // We can write new entry configuration to V2 location but can't assume that every entry + // has already been migrated from V1 and don't want to handle the migration in the + // AutoFill extension so we also edit V1 if we find it there. + + if let entryJson = entry.getField("KPRPC JSON")?.value { + let newJson = mashNewUrlIntoJSON(entryJson, "altURLs", url) + entry.setField(name: "KPRPC JSON", value: newJson, isProtected: true) } - entry.setField(name: "KPRPC JSON", value: newJson, isProtected: true) + + let entryJsonV2 = entry.customData["KPRPC JSON"]?.value ?? #"{"version":2,"authenticationMethods":["password"],"matcherConfigs":[{"matcherType":"Url"}],"fields":[{"page":1,"valuePath":"UserName","uuid":"\#(UUID())","type":"Text","matcherConfigs":[{"matcherType":"UsernameDefaultHeuristic"}]},{"page":1,"valuePath":"Password","uuid":"\#(UUID())","type":"Password","matcherConfigs":[{"matcherType":"PasswordDefaultHeuristic"}]}]}"# + + let newJson = mashNewUrlIntoJSON(entryJsonV2, "altUrls", url) + let dataItem = CustomData2.Item(value: String(describing: newJson), lastModificationTime: nil) + entry.customData["KPRPC JSON"] = dataItem } } @@ -240,7 +267,7 @@ class KeeVaultViewController: UIViewController, AddOrEditEntryDelegate { guard let db = entry.database else { fatalError("Invalid entry found while saving new URL") } - addUrlToEntry(entry, url) + addUrlToEntry(entry as! Entry2, url) entry.setModified() dbFileManager.saveToFile(db: db) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ff775a4..0cc37c1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -56,9 +56,9 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.1.1): + - permission_handler_apple (9.3.0): - Flutter - - rate_my_app (1.1.4): + - rate_my_app (2.0.0): - Flutter - SDWebImage (5.16.0): - SDWebImage/Core (= 5.16.0) @@ -153,16 +153,16 @@ SPEC CHECKSUMS: flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 - path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 - permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - rate_my_app: e249ec0751c276811746ff89558c23dc0da39f0c + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + permission_handler_apple: 036b856153a2b1f61f21030ff725f3e6fece2b78 + rate_my_app: 80fba94c4885cd31738231b7b1f51c1ad726f5c7 SDWebImage: 2aea163b50bfcb569a2726b6a754c54a4506fcf6 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 SwiftProtobuf: 40bd808372cb8706108f22d28f8ab4a6b9bc6989 SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f - url_launcher_ios: 68d46cc9766d0c41dbdc884310529557e3cd7a86 + url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812 PODFILE CHECKSUM: 0658f7bb36458779f6da1691e33f9d925242f612 diff --git a/lib/local_vault_repository.dart b/lib/local_vault_repository.dart index 1cf23a1..87992f7 100644 --- a/lib/local_vault_repository.dart +++ b/lib/local_vault_repository.dart @@ -332,6 +332,16 @@ class LocalVaultRepository { //something that a background service might be able to achieve if Apple //and Google are able to offer a suitable API. final kdbxToMergeInto = (await vault.files.pending) ?? vault.files.current; + + // may have just downloaded an old version or an old autofill kdbx might have been + // left over from an autofill operation in progress before user updated to this version of Kee Vault 2 + if (kdbxToMergeInto.header.version < KdbxVersion.V4_1) { + //TODO: date formats need to be left as is if already in v4 format + //TODO: upgrade as part of file load operations + //TODO: ios ignore entries in trash + //TODO: ios find out why edited bbc entry fails to open for editing... corrupt settings? + kdbxToMergeInto.upgrade(4, 1); + } kdbxToMergeInto.merge(autofill.files.current); final kdbxData = await kdbxFormat().save(kdbxToMergeInto); diff --git a/pubspec.lock b/pubspec.lock index aa0d2ea..30f6436 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -596,8 +596,8 @@ packages: description: path: "." ref: main - resolved-ref: "1cf19f0ed3fc1f0e3b88da341a0ef82ab26de16b" - url: "git@github.com:Floating-Dartists/matomo-tracker.git" + resolved-ref: "32e4e8ade8a4f3f9a0deb7cf2a27387919b77eb9" + url: "https://github.com/Floating-Dartists/matomo-tracker.git" source: git version: "4.1.1" meta: diff --git a/pubspec.yaml b/pubspec.yaml index 8cbd936..904a665 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -88,7 +88,7 @@ dependencies: sdk: flutter matomo_tracker: git: - url: git@github.com:Floating-Dartists/matomo-tracker.git + url: https://github.com/Floating-Dartists/matomo-tracker.git ref: main file_picker: ^6.0.0 zxcvbn: ^1.0.0