diff --git a/ShadowsocksX-NG/ServerProfile.swift b/ShadowsocksX-NG/ServerProfile.swift index f5f01f5..2fde458 100644 --- a/ShadowsocksX-NG/ServerProfile.swift +++ b/ShadowsocksX-NG/ServerProfile.swift @@ -65,17 +65,40 @@ class ServerProfile: NSObject, NSCopying { return nil } guard let host = parsedUrl.host, let port = parsedUrl.port, - let method = parsedUrl.user, let password = parsedUrl.password else { + let user = parsedUrl.user else { return nil } self.serverHost = host self.serverPort = UInt16(port) - self.method = method.lowercased() - self.password = password + // This can be overriden by the fragment part of SIP002 URL remark = parsedUrl.queryItems? .filter({ $0.name == "Remark" }).first?.value ?? "" + + if let password = parsedUrl.password { + self.method = user.lowercased() + self.password = password + } else { + // SIP002 URL have no password section + guard let data = Data(base64Encoded: padBase64(string: user)), + let userInfo = String(data: data, encoding: .utf8) else { + return nil + } + + let parts = userInfo.characters.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false) + if parts.count != 2 { + return nil + } + self.method = String(parts[0]).lowercased() + self.password = String(parts[1]) + + // SIP002 defines where to put the profile name + if let profileName = parsedUrl.fragment { + self.remark = profileName + } + } + if let otaStr = parsedUrl.queryItems? .filter({ $0.name == "OTA" }).first?.value { ota = NSString(string: otaStr).boolValue diff --git a/ShadowsocksX-NGTests/ServerProfileTests.swift b/ShadowsocksX-NGTests/ServerProfileTests.swift index 8204f42..c3b4d42 100644 --- a/ShadowsocksX-NGTests/ServerProfileTests.swift +++ b/ShadowsocksX-NGTests/ServerProfileTests.swift @@ -135,6 +135,40 @@ class ServerProfileTests: XCTestCase { XCTAssertNil(profile) } + func testInitWithSIP002URL() { + // "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=true" + let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@example.com:8388/?Remark=Prism&OTA=true")! + + let profile = ServerProfile(url: url) + + XCTAssertNotNil(profile) + + XCTAssertEqual(profile?.serverHost, "example.com") + XCTAssertEqual(profile?.serverPort, 8388) + XCTAssertEqual(profile?.method, "aes-256-cfb") + XCTAssertEqual(profile?.password, "password") + XCTAssertEqual(profile?.remark, "Prism") + XCTAssertEqual(profile?.ota, true) + } + + func testInitWithSIP002URLProfileName() { + let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@example.com:8388/#Name")! + + let profile = ServerProfile(url: url) + + XCTAssertNotNil(profile) + XCTAssertEqual(profile?.remark, "Name") + } + + func testInitWithSIP002URLProfileNameOverride() { + let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmQ=@example.com:8388/?Remark=Name#Overriden")! + + let profile = ServerProfile(url: url) + + XCTAssertNotNil(profile) + XCTAssertEqual(profile?.remark, "Overriden") + } + func testPerformanceExample() { // This is an example of a performance test case. self.measure {