From dbc6ee92d5cb884c282ba57dd18af287fe1d1e86 Mon Sep 17 00:00:00 2001 From: Rainux Luo Date: Mon, 9 Jan 2017 21:49:09 +0800 Subject: [PATCH] Refactor ParseSSURL as ServerProfile initializer --- ShadowsocksX-NG/AppDelegate.swift | 4 +- ShadowsocksX-NG/ServerProfile.swift | 90 ++++++++++++++----- ShadowsocksX-NG/Utils.swift | 50 ----------- ShadowsocksX-NGTests/ServerProfileTests.swift | 13 ++- .../ShadowsocksX_NGTests.swift | 2 +- ShadowsocksX-NGTests/UtilsTests.swift | 16 ++-- 6 files changed, 91 insertions(+), 84 deletions(-) diff --git a/ShadowsocksX-NG/AppDelegate.swift b/ShadowsocksX-NG/AppDelegate.swift index 0e98f02..dc1d6be 100755 --- a/ShadowsocksX-NG/AppDelegate.swift +++ b/ShadowsocksX-NG/AppDelegate.swift @@ -121,9 +121,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele var isChanged = false for url in urls { - let profielDict = ParseSSURL(url) - if let profielDict = profielDict { - let profile = ServerProfile.fromDictionary(profielDict) + if let profile = ServerProfile(url: url) { mgr.profiles.append(profile) isChanged = true diff --git a/ShadowsocksX-NG/ServerProfile.swift b/ShadowsocksX-NG/ServerProfile.swift index 92865c3..57f7ffb 100644 --- a/ShadowsocksX-NG/ServerProfile.swift +++ b/ShadowsocksX-NG/ServerProfile.swift @@ -8,26 +8,74 @@ import Cocoa - - class ServerProfile: NSObject { var uuid: String - + var serverHost: String = "" var serverPort: uint16 = 8379 var method:String = "aes-128-cfb" var password:String = "" var remark:String = "" var ota: Bool = false // onetime authentication - + override init() { uuid = UUID().uuidString } - + init(uuid: String) { self.uuid = uuid } - + + convenience init?(url: URL?) { + self.init() + + func padBase64(string: String) -> String { + var length = string.characters.count + if length % 4 == 0 { + return string + } else { + length = 4 - length % 4 + length + return string.padding(toLength: length, withPad: "=", startingAt: 0) + } + } + + func decodeUrl(url: URL?) -> String? { + guard let encodedStr = url?.host else { + return nil + } + guard let data = Data(base64Encoded: padBase64(string: encodedStr)) else { + return url?.absoluteString + } + guard let decoded = String(data: data, encoding: String.Encoding.utf8) else { + return nil + } + return "ss://\(decoded)" + } + + guard let decodedUrl = decodeUrl(url: url) else { + return nil + } + guard var parsedUrl = URLComponents(string: decodedUrl) else { + return nil + } + guard let host = parsedUrl.host, let port = parsedUrl.port, + let method = parsedUrl.user, let password = parsedUrl.password else { + return nil + } + + self.serverHost = host + self.serverPort = UInt16(port) + self.method = method + self.password = password + + remark = parsedUrl.queryItems? + .filter({ $0.name == "Remark" }).first?.value ?? "" + if let otaStr = parsedUrl.queryItems? + .filter({ $0.name == "OTA" }).first?.value { + ota = NSString(string: otaStr).boolValue + } + } + static func fromDictionary(_ data:[String: Any?]) -> ServerProfile { let cp = { (profile: ServerProfile) in @@ -42,7 +90,7 @@ class ServerProfile: NSObject { profile.ota = ota as! Bool } } - + if let id = data["Id"] as? String { let profile = ServerProfile(uuid: id) cp(profile) @@ -53,7 +101,7 @@ class ServerProfile: NSObject { return profile } } - + func toDictionary() -> [String:AnyObject] { var d = [String:AnyObject]() d["Id"] = uuid as AnyObject? @@ -65,28 +113,28 @@ class ServerProfile: NSObject { d["OTA"] = ota as AnyObject? return d } - + func toJsonConfig() -> [String: AnyObject] { var conf: [String: AnyObject] = ["server": serverHost as AnyObject, "server_port": NSNumber(value: serverPort as UInt16), "password": password as AnyObject, "method": method as AnyObject,] - + let defaults = UserDefaults.standard conf["local_port"] = NSNumber(value: UInt16(defaults.integer(forKey: "LocalSocks5.ListenPort")) as UInt16) conf["local_address"] = defaults.string(forKey: "LocalSocks5.ListenAddress") as AnyObject? conf["timeout"] = NSNumber(value: UInt32(defaults.integer(forKey: "LocalSocks5.Timeout")) as UInt32) conf["auth"] = NSNumber(value: ota as Bool) - + return conf } - + func isValid() -> Bool { func validateIpAddress(_ ipToValidate: String) -> Bool { - + var sin = sockaddr_in() var sin6 = sockaddr_in6() - + if ipToValidate.withCString({ cstring in inet_pton(AF_INET6, cstring, &sin6.sin6_addr) }) == 1 { // IPv6 peer. return true @@ -95,31 +143,31 @@ class ServerProfile: NSObject { // IPv4 peer. return true } - + return false; } - + func validateDomainName(_ value: String) -> Bool { let validHostnameRegex = "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$" - + if (value.range(of: validHostnameRegex, options: .regularExpression) != nil) { return true } else { return false } } - + if !(validateIpAddress(serverHost) || validateDomainName(serverHost)){ return false } - + if password.isEmpty { return false } - + return true } - + func URL() -> Foundation.URL? { var url = URLComponents() diff --git a/ShadowsocksX-NG/Utils.swift b/ShadowsocksX-NG/Utils.swift index 618d638..e0b8781 100644 --- a/ShadowsocksX-NG/Utils.swift +++ b/ShadowsocksX-NG/Utils.swift @@ -23,53 +23,3 @@ extension Data { return hexBytes.joined(separator: "") } } - -func ParseSSURL(_ url: URL?) -> [String: Any?]? { - - func padBase64(string: String) -> String { - var length = string.characters.count - if length % 4 == 0 { - return string - } else { - length = 4 - length % 4 + length - return string.padding(toLength: length, withPad: "=", startingAt: 0) - } - } - - if url?.host == nil { - return nil - } - - var plainUrl: URLComponents! = URLComponents(url: url!, - resolvingAgainstBaseURL: false) - - let data = Data(base64Encoded: padBase64(string: url!.host!), - options: Data.Base64DecodingOptions()) - - if data != nil { - let decoded = String(data: data!, encoding: String.Encoding.utf8) - plainUrl = URLComponents(string: "ss://\(decoded!)") - - if plainUrl == nil { - return nil - } - } - - let remark = plainUrl.queryItems? - .filter({ $0.name == "Remark" }).first?.value - let otaStr = plainUrl.queryItems? - .filter({ $0.name == "OTA" }).first?.value - - var ota: Bool? = nil - if otaStr != nil { - ota = NSString(string: otaStr!).boolValue - } - - return ["ServerHost": plainUrl.host, - "ServerPort": UInt16(plainUrl.port!), - "Method": plainUrl.user, - "Password": plainUrl.password, - "Remark": remark, - "OTA": ota, - ] -} diff --git a/ShadowsocksX-NGTests/ServerProfileTests.swift b/ShadowsocksX-NGTests/ServerProfileTests.swift index dd58578..63b9a50 100644 --- a/ShadowsocksX-NGTests/ServerProfileTests.swift +++ b/ShadowsocksX-NGTests/ServerProfileTests.swift @@ -37,7 +37,7 @@ class ServerProfileTests: XCTestCase { } func testServerProfileURL() { - let parsed = ParseSSURL(profile.URL()) + let parsed = ServerProfile(url: profile.URL())?.toDictionary() XCTAssertNotNil(parsed) @@ -49,6 +49,17 @@ class ServerProfileTests: XCTestCase { XCTAssertEqual(parsed?["OTA"] as? Bool, profile.ota) } + func testServerProfileInitFromURL() { + let newProfile = ServerProfile.init(url: profile.URL()) + + XCTAssertEqual(newProfile?.serverHost, profile.serverHost) + XCTAssertEqual(newProfile?.serverPort, profile.serverPort) + XCTAssertEqual(newProfile?.method, profile.method) + XCTAssertEqual(newProfile?.password, profile.password) + XCTAssertEqual(newProfile?.remark, profile.remark) + XCTAssertEqual(newProfile?.ota, profile.ota) + } + func testPerformanceExample() { // This is an example of a performance test case. self.measure { diff --git a/ShadowsocksX-NGTests/ShadowsocksX_NGTests.swift b/ShadowsocksX-NGTests/ShadowsocksX_NGTests.swift index 4bab66f..58c8fa6 100644 --- a/ShadowsocksX-NGTests/ShadowsocksX_NGTests.swift +++ b/ShadowsocksX-NGTests/ShadowsocksX_NGTests.swift @@ -32,5 +32,5 @@ class ShadowsocksX_NGTests: XCTestCase { // Put the code you want to measure the time of here. } } - + } diff --git a/ShadowsocksX-NGTests/UtilsTests.swift b/ShadowsocksX-NGTests/UtilsTests.swift index 321e158..36297ad 100644 --- a/ShadowsocksX-NGTests/UtilsTests.swift +++ b/ShadowsocksX-NGTests/UtilsTests.swift @@ -24,7 +24,7 @@ class UtilsTests: XCTestCase { func testParseSSURLwithPlainURL() { let url = URL(string: "ss://aes-256-cfb:password@example.com:8388") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url)?.toDictionary() XCTAssertNotNil(profile) @@ -37,7 +37,7 @@ class UtilsTests: XCTestCase { func testParseSSURLwithPlainURLandQuery() { let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url)?.toDictionary() XCTAssertNotNil(profile) @@ -52,7 +52,7 @@ class UtilsTests: XCTestCase { func testParseSSURLwithPlainURLandAnotherQuery() { let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=0") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url)?.toDictionary() XCTAssertNotNil(profile) @@ -68,7 +68,7 @@ class UtilsTests: XCTestCase { // "ss://aes-256-cfb:password@example.com:8388" let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url)?.toDictionary() XCTAssertNotNil(profile) @@ -82,7 +82,7 @@ class UtilsTests: XCTestCase { // "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true" let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OD9SZW1hcms9UHJpc20mT1RBPXRydWU") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url)?.toDictionary() XCTAssertNotNil(profile) @@ -97,7 +97,7 @@ class UtilsTests: XCTestCase { func testParseSSURLwithEmptyURL() { let url = URL(string: "ss://") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url) XCTAssertNil(profile) } @@ -105,7 +105,7 @@ class UtilsTests: XCTestCase { func testParseSSURLwithInvalidURL() { let url = URL(string: "ss://invalid url") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url) XCTAssertNil(profile) } @@ -114,7 +114,7 @@ class UtilsTests: XCTestCase { // "ss://invalid url" let url = URL(string: "ss://aW52YWxpZCB1cmw") - let profile = ParseSSURL(url) + let profile = ServerProfile(url: url) XCTAssertNil(profile) }