Full support the legacy URI which doesn't follow RFC3986.
It means the password here should be plain text, not percent-encoded.
This commit is contained in:
@ -50,20 +50,46 @@ class ServerProfile: NSObject, NSCopying {
|
|||||||
let base64End = urlStr.firstIndex(of: "#")
|
let base64End = urlStr.firstIndex(of: "#")
|
||||||
let encodedStr = String(urlStr[base64Begin..<(base64End ?? urlStr.endIndex)])
|
let encodedStr = String(urlStr[base64Begin..<(base64End ?? urlStr.endIndex)])
|
||||||
guard let data = Data(base64Encoded: padBase64(string: encodedStr)) else {
|
guard let data = Data(base64Encoded: padBase64(string: encodedStr)) else {
|
||||||
|
// Not legacy format URI
|
||||||
return (url.absoluteString, nil)
|
return (url.absoluteString, nil)
|
||||||
}
|
}
|
||||||
guard let decoded = String(data: data, encoding: String.Encoding.utf8) else {
|
guard let decoded = String(data: data, encoding: String.Encoding.utf8) else {
|
||||||
return (nil, nil)
|
return (nil, nil)
|
||||||
}
|
}
|
||||||
let s = decoded.trimmingCharacters(in: CharacterSet(charactersIn: "\n"))
|
var s = decoded.trimmingCharacters(in: CharacterSet(charactersIn: "\n"))
|
||||||
|
|
||||||
|
// May be legacy format URI
|
||||||
|
// Note that the legacy URI doesn't follow RFC3986. It means the password here
|
||||||
|
// should be plain text, not percent-encoded.
|
||||||
|
// Ref: https://shadowsocks.org/en/config/quick-guide.html
|
||||||
|
let parser = try? NSRegularExpression(
|
||||||
|
pattern: "(.+):(.+)@(.+)", options: .init())
|
||||||
|
if let match = parser?.firstMatch(in:s, options: [], range: NSRange(location: 0, length: s.utf16.count)) {
|
||||||
|
// Convert legacy format to SIP002 format
|
||||||
|
let r1 = Range(match.range(at: 1), in: s)!
|
||||||
|
let r2 = Range(match.range(at: 2), in: s)!
|
||||||
|
let r3 = Range(match.range(at: 3), in: s)!
|
||||||
|
let user = String(s[r1])
|
||||||
|
let password = String(s[r2])
|
||||||
|
let hostAndPort = String(s[r3])
|
||||||
|
|
||||||
|
let rawUserInfo = "\(user):\(password)".data(using: .utf8)!
|
||||||
|
let userInfo = rawUserInfo.base64EncodedString()
|
||||||
|
|
||||||
|
s = "ss://\(userInfo)@\(hostAndPort)"
|
||||||
|
}
|
||||||
|
|
||||||
if let index = base64End {
|
if let index = base64End {
|
||||||
let i = urlStr.index(index, offsetBy: 1)
|
let i = urlStr.index(index, offsetBy: 1)
|
||||||
let fragment = String(urlStr[i...])
|
let fragment = String(urlStr[i...])
|
||||||
return ("ss://\(s)", fragment)
|
return (s, fragment)
|
||||||
}
|
}
|
||||||
return ("ss://\(s)", nil)
|
return (s, nil)
|
||||||
}
|
}
|
||||||
|
func decodeLegacyFormat(url: String) -> (URL?,String?) {
|
||||||
|
return (nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
let (_decodedUrl, _tag) = decodeUrl(url: url)
|
let (_decodedUrl, _tag) = decodeUrl(url: url)
|
||||||
guard let decodedUrl = _decodedUrl else {
|
guard let decodedUrl = _decodedUrl else {
|
||||||
return nil
|
return nil
|
||||||
@ -82,31 +108,27 @@ class ServerProfile: NSObject, NSCopying {
|
|||||||
// This can be overriden by the fragment part of SIP002 URL
|
// This can be overriden by the fragment part of SIP002 URL
|
||||||
remark = parsedUrl.queryItems?
|
remark = parsedUrl.queryItems?
|
||||||
.filter({ $0.name == "Remark" }).first?.value ?? ""
|
.filter({ $0.name == "Remark" }).first?.value ?? ""
|
||||||
|
|
||||||
|
if let tag = _tag {
|
||||||
|
remark = tag
|
||||||
|
}
|
||||||
|
|
||||||
if let password = parsedUrl.password {
|
// SIP002 URL have no password section
|
||||||
self.method = user.lowercased()
|
guard let data = Data(base64Encoded: padBase64(string: user)),
|
||||||
self.password = password
|
let userInfo = String(data: data, encoding: .utf8) else {
|
||||||
if let tag = _tag {
|
return nil
|
||||||
remark = tag
|
}
|
||||||
}
|
|
||||||
} 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.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
|
let parts = userInfo.split(separator: ":", maxSplits: 1, omittingEmptySubsequences: false)
|
||||||
if parts.count != 2 {
|
if parts.count != 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.method = String(parts[0]).lowercased()
|
self.method = String(parts[0]).lowercased()
|
||||||
self.password = String(parts[1])
|
self.password = String(parts[1])
|
||||||
|
|
||||||
// SIP002 defines where to put the profile name
|
// SIP002 defines where to put the profile name
|
||||||
if let profileName = parsedUrl.fragment {
|
if let profileName = parsedUrl.fragment {
|
||||||
self.remark = profileName
|
self.remark = profileName
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let pluginStr = parsedUrl.queryItems?
|
if let pluginStr = parsedUrl.queryItems?
|
||||||
|
@ -40,48 +40,6 @@ class ServerProfileTests: XCTestCase {
|
|||||||
XCTAssertEqual(newProfile?.remark, profile.remark)
|
XCTAssertEqual(newProfile?.remark, profile.remark)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testInitWithPlainURL() {
|
|
||||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388")!
|
|
||||||
|
|
||||||
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, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInitWithPlainURLandQuery() {
|
|
||||||
let url = URL(string: "ss://aes-256-cfb:password@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")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInitWithPlainURLandAnotherQuery() {
|
|
||||||
let url = URL(string: "ss://aes-256-cfb:password@example.com:8388?Remark=Prism&OTA=0")!
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInitWithBase64EncodedURL() {
|
func testInitWithBase64EncodedURL() {
|
||||||
// "ss://aes-256-cfb:password@example.com:8388"
|
// "ss://aes-256-cfb:password@example.com:8388"
|
||||||
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA")!
|
let url = URL(string: "ss://YWVzLTI1Ni1jZmI6cGFzc3dvcmRAZXhhbXBsZS5jb206ODM4OA")!
|
||||||
@ -120,6 +78,19 @@ class ServerProfileTests: XCTestCase {
|
|||||||
XCTAssertNotNil(profile)
|
XCTAssertNotNil(profile)
|
||||||
XCTAssertEqual(profile?.remark, "example-server")
|
XCTAssertEqual(profile?.remark, "example-server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInitWithLegacyBase64EncodedURLWithSymboInPassword() {
|
||||||
|
// Note that the legacy URI doesn't follow RFC3986. It means the password here
|
||||||
|
// should be plain text, not percent-encoded.
|
||||||
|
// Ref: https://shadowsocks.org/en/config/quick-guide.html
|
||||||
|
// `ss://bf-cfb:test/!@#:@192.168.100.1:8888`
|
||||||
|
let url = URL(string: "ss://YmYtY2ZiOnRlc3QvIUAjOkAxOTIuMTY4LjEwMC4xOjg4ODg#example")!
|
||||||
|
|
||||||
|
let profile = ServerProfile(url: url)
|
||||||
|
|
||||||
|
XCTAssertNotNil(profile)
|
||||||
|
XCTAssertEqual(profile?.password, "test/!@#:")
|
||||||
|
}
|
||||||
|
|
||||||
func testInitWithEmptyURL() {
|
func testInitWithEmptyURL() {
|
||||||
let url = URL(string: "ss://")!
|
let url = URL(string: "ss://")!
|
||||||
|
Reference in New Issue
Block a user