Skip to content

Commit

Permalink
Allow users to import settings by pasting JSON blobs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jon Petersson committed Feb 2, 2024
1 parent 0e9adc3 commit 69f0518
Show file tree
Hide file tree
Showing 17 changed files with 796 additions and 29 deletions.
55 changes: 55 additions & 0 deletions ios/MullvadSettings/IPOverride.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// IPOverride.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-01-16.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Network

public struct RelayOverrides: Codable {
public let overrides: [IPOverride]

private enum CodingKeys: String, CodingKey {
case overrides = "relay_overrides"
}
}

public struct IPOverride: Codable, Equatable {
public let hostname: String
public var ipv4Address: IPv4Address?
public var ipv6Address: IPv6Address?

private enum CodingKeys: String, CodingKey {
case hostname
case ipv4Address = "ipv4_addr_in"
case ipv6Address = "ipv6_addr_in"
}

init(hostname: String, ipv4Address: IPv4Address?, ipv6Address: IPv6Address?) throws {
self.hostname = hostname
self.ipv4Address = ipv4Address
self.ipv6Address = ipv6Address

if self.ipv4Address.isNil && self.ipv6Address.isNil {
throw IPOverrideFormatError(errorDescription: "ipv4Address and ipv6Address cannot both be nil.")
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

self.hostname = try container.decode(String.self, forKey: .hostname)
self.ipv4Address = try container.decodeIfPresent(IPv4Address.self, forKey: .ipv4Address)
self.ipv6Address = try container.decodeIfPresent(IPv6Address.self, forKey: .ipv6Address)

if self.ipv4Address.isNil && self.ipv6Address.isNil {
throw IPOverrideFormatError(errorDescription: "ipv4Address and ipv6Address cannot both be nil.")
}
}
}

public struct IPOverrideFormatError: LocalizedError {
public let errorDescription: String?
}
93 changes: 93 additions & 0 deletions ios/MullvadSettings/IPOverrideRepository.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//
// IPOverrideRepository.swift
// MullvadVPN
//
// Created by Jon Petersson on 2024-01-16.
// Copyright © 2024 Mullvad VPN AB. All rights reserved.
//

import Foundation
import MullvadLogging

public protocol IPOverrideRepositoryProtocol {
func add(_ overrides: [IPOverride])
func fetchAll() -> [IPOverride]
func fetchByHostname(_ hostname: String) -> IPOverride?
func deleteAll()
func parse(data: Data) throws -> [IPOverride]
}

public class IPOverrideRepository: IPOverrideRepositoryProtocol {
private let logger = Logger(label: "IPOverrideRepository")

public init() {}

public func add(_ overrides: [IPOverride]) {
var storedOverrides = fetchAll()

overrides.forEach { override in
if let existingOverrideIndex = storedOverrides.firstIndex(where: { $0.hostname == override.hostname }) {
var existingOverride = storedOverrides[existingOverrideIndex]

if let ipv4Address = override.ipv4Address {
existingOverride.ipv4Address = ipv4Address
}

if let ipv6Address = override.ipv6Address {
existingOverride.ipv6Address = ipv6Address
}

storedOverrides[existingOverrideIndex] = existingOverride
} else {
storedOverrides.append(override)
}
}

do {
try writeIpOverrides(storedOverrides)
} catch {
logger.error("Could not add override(s): \(overrides) \nError: \(error)")
}
}

public func fetchAll() -> [IPOverride] {
return (try? readIpOverrides()) ?? []
}

public func fetchByHostname(_ hostname: String) -> IPOverride? {
return fetchAll().first { $0.hostname == hostname }
}

public func deleteAll() {
do {
try SettingsManager.store.delete(key: .ipOverrides)
} catch {
logger.error("Could not delete all overrides. \nError: \(error)")
}
}

public func parse(data: Data) throws -> [IPOverride] {
let decoder = JSONDecoder()
let jsonData = try decoder.decode(RelayOverrides.self, from: data)

return jsonData.overrides
}

private func readIpOverrides() throws -> [IPOverride] {
let parser = makeParser()
let data = try SettingsManager.store.read(key: .ipOverrides)

return try parser.parseUnversionedPayload(as: [IPOverride].self, from: data)
}

private func writeIpOverrides(_ overrides: [IPOverride]) throws {
let parser = makeParser()
let data = try parser.produceUnversionedPayload(overrides)

try SettingsManager.store.write(data, for: .ipOverrides)
}

private func makeParser() -> SettingsParser {
SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
}
}
1 change: 1 addition & 0 deletions ios/MullvadSettings/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum SettingsKey: String, CaseIterable {
case settings = "Settings"
case deviceState = "DeviceState"
case apiAccessMethods = "ApiAccessMethods"
case ipOverrides = "IPOverrides"
case lastUsedAccount = "LastUsedAccount"
case shouldWipeSettings = "ShouldWipeSettings"
}
Expand Down
Loading

0 comments on commit 69f0518

Please sign in to comment.