diff --git a/CHANGELOG.md b/CHANGELOG.md index c934b93c..e80e4a2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. `Starscream` adheres to [Semantic Versioning](http://semver.org/). +#### [2.1.0](https://github.com/daltoniam/Starscream/tree/2.1.0) + +Adds WebSocket compression. Also adds advance WebSocket delegate for extra control. Bug Fixes. + +[#349](https://github.com/daltoniam/Starscream/pull/349) +[#344](https://github.com/daltoniam/Starscream/pull/344) +[#337](https://github.com/daltoniam/Starscream/pull/337) +[#334](https://github.com/daltoniam/Starscream/issues/334) +[#333](https://github.com/daltoniam/Starscream/pull/333) +[#319](https://github.com/daltoniam/Starscream/issues/319) +[#309](https://github.com/daltoniam/Starscream/issues/309) + #### [2.0.4](https://github.com/daltoniam/Starscream/tree/2.0.4) SSL Pinning fix by Giuliano Galea as reported by Lukas Futera of [Centralway](https://www.centralway.com/de/). diff --git a/README.md b/README.md index 2c23a5d1..4a7639f6 100644 --- a/README.md +++ b/README.md @@ -187,16 +187,11 @@ socket.delegate = self socket.connect() ``` -### Self Signed SSL and VOIP - -There are a couple of other properties that modify the stream: +### Self Signed SSL ```swift socket = WebSocket(url: URL(string: "ws://localhost:8080/")!, protocols: ["chat","superchat"]) -//set this if you are planning on using the socket in a VOIP background setting (using the background VOIP service). -socket.voipEnabled = true - //set this you want to ignore SSL cert validation, so a self signed SSL certificate can be used. socket.disableSSLCertValidation = true ``` diff --git a/Source/Info-tvOS.plist b/Source/Info-tvOS.plist index 24551b51..a664afcf 100644 --- a/Source/Info-tvOS.plist +++ b/Source/Info-tvOS.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.4 + 2.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/Source/Info.plist b/Source/Info.plist index 241ef869..dca568ec 100644 --- a/Source/Info.plist +++ b/Source/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.4 + 2.1.0 CFBundleSignature ???? CFBundleVersion diff --git a/Source/WebSocket.swift b/Source/WebSocket.swift index 24808cab..5efc1d5f 100644 --- a/Source/WebSocket.swift +++ b/Source/WebSocket.swift @@ -21,7 +21,7 @@ import Foundation import CoreFoundation -import Security +import CommonCrypto public let WebsocketDidConnectNotification = "WebsocketDidConnectNotification" public let WebsocketDidDisconnectNotification = "WebsocketDidDisconnectNotification" @@ -80,6 +80,8 @@ open class WebSocket : NSObject, StreamDelegate { // 0-999 WebSocket status codes not used case outputStreamWriteError = 1 case compressionError = 2 + case invalidSSLError = 3 + case writeTimeoutError = 4 } // Where the callback is executed. It defaults to the main UI thread queue. @@ -168,7 +170,6 @@ open class WebSocket : NSObject, StreamDelegate { public var httpMethod: HTTPMethod = .get public var headers = [String: String]() - public var voipEnabled = false public var disableSSLCertValidation = false public var enableCompression = true public var security: SSLTrustValidator? @@ -207,6 +208,7 @@ open class WebSocket : NSObject, StreamDelegate { private var certValidated = false private var didDisconnect = false private var readyToWrite = false + private var headerSecKey = "" private let mutex = NSLock() private let notificationCenter = NotificationCenter.default private var canDispatch: Bool { @@ -329,8 +331,9 @@ open class WebSocket : NSObject, StreamDelegate { if let protocols = optionalProtocols { addHeader(urlRequest, key: headerWSProtocolName, val: protocols.joined(separator: ",")) } + headerSecKey = generateWebSocketKey() addHeader(urlRequest, key: headerWSVersionName, val: headerWSVersionValue) - addHeader(urlRequest, key: headerWSKeyName, val: generateWebSocketKey()) + addHeader(urlRequest, key: headerWSKeyName, val: headerSecKey) if let origin = origin { addHeader(urlRequest, key: headerOriginName, val: origin) } @@ -419,10 +422,6 @@ open class WebSocket : NSObject, StreamDelegate { } else { certValidated = true //not a https session, so no need to check SSL pinning } - if voipEnabled { - inStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) - outStream.setProperty(StreamNetworkServiceTypeValue.voIP as AnyObject, forKey: Stream.PropertyKey.networkServiceType) - } CFReadStreamSetDispatchQueue(inStream, WebSocket.sharedWorkQueue) CFWriteStreamSetDispatchQueue(outStream, WebSocket.sharedWorkQueue) @@ -446,7 +445,8 @@ open class WebSocket : NSObject, StreamDelegate { WebSocket.sharedWorkQueue.async { self?.cleanupStream() } - self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2)) + let errCode = InternalErrorCode.writeTimeoutError.rawValue + self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: errCode)) return } else if outStream.streamError != nil { return // disconnectStream will be called. @@ -460,7 +460,8 @@ open class WebSocket : NSObject, StreamDelegate { s.certValidated = sec.isValid(trust, domain: domain) if !s.certValidated { WebSocket.sharedWorkQueue.async { - let error = s.errorWithDetail("Invalid SSL certificate", code: 1) + let errCode = InternalErrorCode.invalidSSLError.rawValue + let error = s.errorWithDetail("Invalid SSL certificate", code: errCode) s.disconnectStream(error) } return @@ -645,6 +646,12 @@ open class WebSocket : NSObject, StreamDelegate { if let acceptKey = headers[headerWSAcceptName as NSString] as? NSString { if acceptKey.length > 0 { + if headerSecKey.characters.count > 0 { + let sha = "\(headerSecKey)258EAFA5-E914-47DA-95CA-C5AB0DC85B11".sha1Base64() + if sha != acceptKey as String { + return -1 + } + } return 0 } } @@ -661,19 +668,19 @@ open class WebSocket : NSObject, StreamDelegate { let part = p.trimmingCharacters(in: .whitespaces) if part == "permessage-deflate" { compressionState.supportsCompression = true - } else if part.hasPrefix("server_max_window_bits="){ + } else if part.hasPrefix("server_max_window_bits=") { let valString = part.components(separatedBy: "=")[1] if let val = Int(valString.trimmingCharacters(in: .whitespaces)) { compressionState.serverMaxWindowBits = val } - } else if part.hasPrefix("client_max_window_bits="){ + } else if part.hasPrefix("client_max_window_bits=") { let valString = part.components(separatedBy: "=")[1] if let val = Int(valString.trimmingCharacters(in: .whitespaces)) { compressionState.clientMaxWindowBits = val } - } else if part == "client_no_context_takeover"{ + } else if part == "client_no_context_takeover" { compressionState.clientNoContextTakeover = true - } else if part == "server_no_context_takeover"{ + } else if part == "server_no_context_takeover" { compressionState.serverNoContextTakeover = true } } @@ -1082,6 +1089,15 @@ open class WebSocket : NSObject, StreamDelegate { } +private extension String { + func sha1Base64() -> String { + let data = self.data(using: String.Encoding.utf8)! + var digest = [UInt8](repeating: 0, count:Int(CC_SHA1_DIGEST_LENGTH)) + data.withUnsafeBytes { _ = CC_SHA1($0, CC_LONG(data.count), &digest) } + return Data(bytes: digest).base64EncodedString() + } +} + private extension Data { init(buffer: UnsafeBufferPointer) { diff --git a/Starscream.podspec b/Starscream.podspec index 03060df6..8aa31113 100644 --- a/Starscream.podspec +++ b/Starscream.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Starscream" - s.version = "2.0.4" + s.version = "2.1.0" s.summary = "A conforming WebSocket RFC 6455 client library in Swift for iOS and OSX." s.homepage = "https://github.com/daltoniam/Starscream" s.license = 'Apache License, Version 2.0' diff --git a/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate b/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate index 94951322..6405dd2d 100644 Binary files a/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate and b/examples/SimpleTest/SimpleTest.xcodeproj/project.xcworkspace/xcuserdata/dalton.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/zlib/include.h b/zlib/include.h index bb92a98f..cb3747f2 100644 --- a/zlib/include.h +++ b/zlib/include.h @@ -1,2 +1,2 @@ - #include +#include diff --git a/zlib/module.modulemap b/zlib/module.modulemap index 0fdb2181..7172f843 100644 --- a/zlib/module.modulemap +++ b/zlib/module.modulemap @@ -2,3 +2,7 @@ module zlib [system] { header "include.h" link "z" } +module CommonCrypto [system] { + header "include.h" + export * +}