Fix unicode for socket.io 2.0
This commit is contained in:
parent
6ca554f4ad
commit
468d7b2453
@ -17,28 +17,28 @@ class SocketEngineTest: XCTestCase {
|
||||
super.setUp()
|
||||
client = SocketIOClient(socketURL: URL(string: "http://localhost")!)
|
||||
engine = SocketEngine(client: client, url: URL(string: "http://localhost")!, options: nil)
|
||||
|
||||
|
||||
client.setTestable()
|
||||
}
|
||||
|
||||
|
||||
func testBasicPollingMessage() {
|
||||
let expect = expectation(description: "Basic polling test")
|
||||
client.on("blankTest") {data, ack in
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
|
||||
engine.parsePollingMessage("15:42[\"blankTest\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
|
||||
func testTwoPacketsInOnePollTest() {
|
||||
let finalExpectation = expectation(description: "Final packet in poll test")
|
||||
var gotBlank = false
|
||||
|
||||
|
||||
client.on("blankTest") {data, ack in
|
||||
gotBlank = true
|
||||
}
|
||||
|
||||
|
||||
client.on("stringTest") {data, ack in
|
||||
if let str = data[0] as? String, gotBlank {
|
||||
if str == "hello" {
|
||||
@ -46,44 +46,46 @@ class SocketEngineTest: XCTestCase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
engine.parsePollingMessage("15:42[\"blankTest\"]24:42[\"stringTest\",\"hello\"]")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
|
||||
func testEngineDoesErrorOnUnknownTransport() {
|
||||
let finalExpectation = expectation(description: "Unknown Transport")
|
||||
|
||||
|
||||
client.on("error") {data, ack in
|
||||
if let error = data[0] as? String, error == "Unknown transport" {
|
||||
finalExpectation.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}", fromPolling: false)
|
||||
|
||||
engine.parseEngineMessage("{\"code\": 0, \"message\": \"Unknown transport\"}")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
|
||||
func testEngineDoesErrorOnUnknownMessage() {
|
||||
let finalExpectation = expectation(description: "Engine Errors")
|
||||
|
||||
|
||||
client.on("error") {data, ack in
|
||||
finalExpectation.fulfill()
|
||||
}
|
||||
|
||||
engine.parseEngineMessage("afafafda", fromPolling: false)
|
||||
|
||||
engine.parseEngineMessage("afafafda")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
|
||||
func testEngineDecodesUTF8Properly() {
|
||||
let expect = expectation(description: "Engine Decodes utf8")
|
||||
|
||||
|
||||
client.on("stringTest") {data, ack in
|
||||
XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo", "Failed string test")
|
||||
XCTAssertEqual(data[0] as? String, "lïne one\nlīne \rtwo𦅙𦅛", "Failed string test")
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
engine.parsePollingMessage("41:42[\"stringTest\",\"lïne one\\nlīne \\rtwo\"]")
|
||||
let stringMessage = "42[\"stringTest\",\"lïne one\\nlīne \\rtwo𦅙𦅛\"]"
|
||||
|
||||
engine.parsePollingMessage("\(stringMessage.utf16.count):\(stringMessage)")
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
|
||||
@ -102,23 +104,23 @@ class SocketEngineTest: XCTestCase {
|
||||
XCTAssertEqual(engine.urlPolling.query, "transport=polling&b64=1&forbidden=%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23%5B%5D%22%20%7B%7D")
|
||||
XCTAssertEqual(engine.urlWebSocket.query, "transport=websocket&forbidden=%21%2A%27%28%29%3B%3A%40%26%3D%2B%24%2C%2F%3F%25%23%5B%5D%22%20%7B%7D")
|
||||
}
|
||||
|
||||
|
||||
func testBase64Data() {
|
||||
let expect = expectation(description: "Engine Decodes base64 data")
|
||||
let b64String = "b4aGVsbG8NCg=="
|
||||
let packetString = "451-[\"test\",{\"test\":{\"_placeholder\":true,\"num\":0}}]"
|
||||
|
||||
|
||||
client.on("test") {data, ack in
|
||||
if let data = data[0] as? Data, let string = String(data: data, encoding: .utf8) {
|
||||
XCTAssertEqual(string, "hello")
|
||||
}
|
||||
|
||||
|
||||
expect.fulfill()
|
||||
}
|
||||
|
||||
engine.parseEngineMessage(packetString, fromPolling: false)
|
||||
engine.parseEngineMessage(b64String, fromPolling: false)
|
||||
|
||||
|
||||
engine.parseEngineMessage(packetString)
|
||||
engine.parseEngineMessage(b64String)
|
||||
|
||||
waitForExpectations(timeout: 3, handler: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -511,11 +511,10 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
/// - parameter message: The message to parse.
|
||||
/// - parameter fromPolling: Whether this message is from long-polling.
|
||||
/// If `true` we might have to fix utf8 encoding.
|
||||
public func parseEngineMessage(_ message: String, fromPolling: Bool) {
|
||||
public func parseEngineMessage(_ message: String) {
|
||||
DefaultSocketLogger.Logger.log("Got message: %@", type: logType, args: message)
|
||||
|
||||
let reader = SocketStringReader(message: message)
|
||||
let fixedString: String
|
||||
|
||||
if message.hasPrefix("b4") {
|
||||
return handleBase64(message: message)
|
||||
@ -527,23 +526,17 @@ public final class SocketEngine : NSObject, URLSessionDelegate, SocketEnginePoll
|
||||
return
|
||||
}
|
||||
|
||||
if fromPolling && type != .noop && doubleEncodeUTF8 {
|
||||
fixedString = fixDoubleUTF8(message)
|
||||
} else {
|
||||
fixedString = message
|
||||
}
|
||||
|
||||
switch type {
|
||||
case .message:
|
||||
handleMessage(String(fixedString.characters.dropFirst()))
|
||||
handleMessage(String(message.characters.dropFirst()))
|
||||
case .noop:
|
||||
handleNOOP()
|
||||
case .pong:
|
||||
handlePong(with: fixedString)
|
||||
handlePong(with: message)
|
||||
case .open:
|
||||
handleOpen(openData: String(fixedString.characters.dropFirst()))
|
||||
handleOpen(openData: String(message.characters.dropFirst()))
|
||||
case .close:
|
||||
handleClose(fixedString)
|
||||
handleClose(message)
|
||||
default:
|
||||
DefaultSocketLogger.Logger.log("Got unknown packet type", type: logType)
|
||||
}
|
||||
|
||||
@ -90,9 +90,7 @@ extension SocketEnginePollable {
|
||||
var postStr = ""
|
||||
|
||||
for packet in postWait {
|
||||
let len = packet.characters.count
|
||||
|
||||
postStr += "\(len):\(packet)"
|
||||
postStr += "\(packet.utf16.count):\(packet)"
|
||||
}
|
||||
|
||||
DefaultSocketLogger.Logger.log("Created POST string: %@", type: "SocketEnginePolling", args: postStr)
|
||||
@ -202,13 +200,15 @@ extension SocketEnginePollable {
|
||||
func parsePollingMessage(_ str: String) {
|
||||
guard str.characters.count != 1 else { return }
|
||||
|
||||
DefaultSocketLogger.Logger.log("Got poll message: %@", type: "SocketEnginePolling", args: str)
|
||||
|
||||
var reader = SocketStringReader(message: str)
|
||||
|
||||
while reader.hasNext {
|
||||
if let n = Int(reader.readUntilOccurence(of: ":")) {
|
||||
parseEngineMessage(reader.read(count: n), fromPolling: true)
|
||||
parseEngineMessage(reader.read(count: n))
|
||||
} else {
|
||||
parseEngineMessage(str, fromPolling: true)
|
||||
parseEngineMessage(str)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -223,15 +223,8 @@ extension SocketEnginePollable {
|
||||
/// - parameter withData: The data associated with this message.
|
||||
public func sendPollMessage(_ message: String, withType type: SocketEnginePacketType, withData datas: [Data]) {
|
||||
DefaultSocketLogger.Logger.log("Sending poll: %@ as type: %@", type: "SocketEnginePolling", args: message, type.rawValue)
|
||||
let fixedMessage: String
|
||||
|
||||
if doubleEncodeUTF8 {
|
||||
fixedMessage = doubleEncodeUTF8(message)
|
||||
} else {
|
||||
fixedMessage = message
|
||||
}
|
||||
|
||||
postWait.append(String(type.rawValue) + fixedMessage)
|
||||
postWait.append(String(type.rawValue) + message)
|
||||
|
||||
for data in datas {
|
||||
if case let .right(bin) = createBinaryDataForSend(using: data) {
|
||||
|
||||
@ -125,7 +125,7 @@ import Foundation
|
||||
/// - parameter message: The message to parse.
|
||||
/// - parameter fromPolling: Whether this message is from long-polling.
|
||||
/// If `true` we might have to fix utf8 encoding.
|
||||
func parseEngineMessage(_ message: String, fromPolling: Bool)
|
||||
func parseEngineMessage(_ message: String)
|
||||
|
||||
/// Writes a message to engine.io, independent of transport.
|
||||
///
|
||||
@ -165,24 +165,6 @@ extension SocketEngineSpec {
|
||||
}
|
||||
}
|
||||
|
||||
func doubleEncodeUTF8(_ string: String) -> String {
|
||||
if let latin1 = string.data(using: String.Encoding.utf8),
|
||||
let utf8 = NSString(data: latin1, encoding: String.Encoding.isoLatin1.rawValue) {
|
||||
return utf8 as String
|
||||
} else {
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
func fixDoubleUTF8(_ string: String) -> String {
|
||||
if let utf8 = string.data(using: String.Encoding.isoLatin1),
|
||||
let latin1 = NSString(data: utf8, encoding: String.Encoding.utf8.rawValue) {
|
||||
return latin1 as String
|
||||
} else {
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
/// Send an engine message (4)
|
||||
func send(_ msg: String, withData datas: [Data]) {
|
||||
write(msg, withType: .message, withData: datas)
|
||||
|
||||
@ -68,7 +68,7 @@ extension SocketEngineWebsocket {
|
||||
|
||||
/// Delegate method for when a message is received.
|
||||
public func websocketDidReceiveMessage(socket: WebSocket, text: String) {
|
||||
parseEngineMessage(text, fromPolling: false)
|
||||
parseEngineMessage(text)
|
||||
}
|
||||
|
||||
/// Delegate method for when binary is received.
|
||||
|
||||
@ -88,39 +88,39 @@ extension NSDictionary {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func toSocketConfiguration() -> SocketIOClientConfiguration {
|
||||
var options = [] as SocketIOClientConfiguration
|
||||
|
||||
|
||||
for (rawKey, value) in self {
|
||||
if let key = rawKey as? String, let opt = NSDictionary.keyValueToSocketIOClientOption(key: key, value: value) {
|
||||
options.insert(opt)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func toArray() throws -> [Any] {
|
||||
guard let stringData = data(using: .utf8, allowLossyConversion: false) else { return [] }
|
||||
guard let stringData = data(using: .utf16, allowLossyConversion: false) else { return [] }
|
||||
guard let array = try JSONSerialization.jsonObject(with: stringData, options: .mutableContainers) as? [Any] else {
|
||||
throw JSONError.notArray
|
||||
}
|
||||
|
||||
|
||||
return array
|
||||
}
|
||||
|
||||
|
||||
func toNSDictionary() throws -> NSDictionary {
|
||||
guard let binData = data(using: .utf8, allowLossyConversion: false) else { return [:] }
|
||||
guard let binData = data(using: .utf16, allowLossyConversion: false) else { return [:] }
|
||||
guard let json = try JSONSerialization.jsonObject(with: binData, options: .allowFragments) as? NSDictionary else {
|
||||
throw JSONError.notNSDictionary
|
||||
}
|
||||
|
||||
|
||||
return json
|
||||
}
|
||||
|
||||
|
||||
func urlEncode() -> String? {
|
||||
return addingPercentEncoding(withAllowedCharacters: .allowedURLCharacterSet)
|
||||
}
|
||||
|
||||
@ -36,8 +36,8 @@ public enum SocketIOClientOption : ClientOption {
|
||||
/// An array of cookies that will be sent during the initial connection.
|
||||
case cookies([HTTPCookie])
|
||||
|
||||
/// The node.js socket.io currently does funky things to unicode when doing HTTP long-polling. Passing `true` in
|
||||
/// this option causes the client to try and fix any bad unicode that might be sent.
|
||||
/// Deprecated
|
||||
@available(*, deprecated, message: "No longer needed in socket.io 2.0+")
|
||||
case doubleEncodeUTF8(Bool)
|
||||
|
||||
/// Any extra HTTP headers that should be sent during the initial connection.
|
||||
|
||||
@ -107,7 +107,7 @@ extension SocketParsable where Self: SocketIOClientSpec {
|
||||
}
|
||||
}
|
||||
|
||||
var dataArray = message[message.characters.index(reader.currentIndex, offsetBy: 1)..<message.endIndex]
|
||||
var dataArray = String(message.utf16[message.utf16.index(reader.currentIndex, offsetBy: 1)..<message.utf16.endIndex])!
|
||||
|
||||
if type == .error && !dataArray.hasPrefix("[") && !dataArray.hasSuffix("]") {
|
||||
dataArray = "[" + dataArray + "]"
|
||||
|
||||
@ -24,50 +24,50 @@
|
||||
|
||||
struct SocketStringReader {
|
||||
let message: String
|
||||
var currentIndex: String.Index
|
||||
var currentIndex: String.UTF16View.Index
|
||||
var hasNext: Bool {
|
||||
return currentIndex != message.endIndex
|
||||
return currentIndex != message.utf16.endIndex
|
||||
}
|
||||
|
||||
|
||||
var currentCharacter: String {
|
||||
return String(message[currentIndex])
|
||||
return String(UnicodeScalar(message.utf16[currentIndex])!)
|
||||
}
|
||||
|
||||
|
||||
init(message: String) {
|
||||
self.message = message
|
||||
currentIndex = message.startIndex
|
||||
currentIndex = message.utf16.startIndex
|
||||
}
|
||||
|
||||
|
||||
@discardableResult
|
||||
mutating func advance(by: Int) -> String.Index {
|
||||
currentIndex = message.characters.index(currentIndex, offsetBy: by)
|
||||
|
||||
mutating func advance(by: Int) -> String.UTF16View.Index {
|
||||
currentIndex = message.utf16.index(currentIndex, offsetBy: by)
|
||||
|
||||
return currentIndex
|
||||
}
|
||||
|
||||
|
||||
mutating func read(count: Int) -> String {
|
||||
let readString = message[currentIndex..<message.characters.index(currentIndex, offsetBy: count)]
|
||||
|
||||
let readString = String(message.utf16[currentIndex..<message.utf16.index(currentIndex, offsetBy: count)])!
|
||||
|
||||
advance(by: count)
|
||||
|
||||
|
||||
return readString
|
||||
}
|
||||
|
||||
|
||||
mutating func readUntilOccurence(of string: String) -> String {
|
||||
let substring = message[currentIndex..<message.endIndex]
|
||||
|
||||
guard let foundRange = substring.range(of: string) else {
|
||||
currentIndex = message.endIndex
|
||||
|
||||
return substring
|
||||
let substring = message.utf16[currentIndex..<message.utf16.endIndex]
|
||||
|
||||
guard let foundIndex = substring.index(of: string.utf16.first!) else {
|
||||
currentIndex = message.utf16.endIndex
|
||||
|
||||
return String(substring)!
|
||||
}
|
||||
|
||||
advance(by: message.characters.distance(from: message.characters.startIndex, to: foundRange.lowerBound) + 1)
|
||||
|
||||
return substring.substring(to: foundRange.lowerBound)
|
||||
|
||||
advance(by: substring.distance(from: substring.startIndex, to: foundIndex) + 1)
|
||||
|
||||
return String(substring[substring.startIndex..<foundIndex])!
|
||||
}
|
||||
|
||||
|
||||
mutating func readUntilEnd() -> String {
|
||||
return read(count: message.characters.distance(from: currentIndex, to: message.endIndex))
|
||||
return read(count: message.utf16.distance(from: currentIndex, to: message.utf16.endIndex))
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user