Compare commits

...

23 Commits

Author SHA1 Message Date
daltoniam 85070aab91 few warning fixes 2017-03-28 20:09:07 -05:00
daltoniam dbeb1190b8 some cleanup and SSL pinning fix 2017-03-14 13:12:39 -05:00
daltoniam 13859364e3 updated changelog 2017-02-03 14:25:31 -06:00
daltoniam 41c0c5e08b version bump 2017-02-03 14:20:21 -06:00
daltoniam 60b27a4388 possible fix for #301 2017-01-11 22:55:39 -06:00
daltoniam a68bf7c35b connect delegate before firing message delegates. Fixed disconnect getting called before connect 2017-01-11 12:10:33 -06:00
Dalton 33a4551f3b Merge pull request #302 from Elethier/operation-cancellation
Fixed issues with not cancelling block operations properly
2017-01-10 22:25:12 -06:00
Dalton b22fae407e Merge pull request #300 from Elethier/multiple-disconnects
Added guard against multiple disconnects
2017-01-10 21:41:02 -06:00
Dalton e9160df255 Merge pull request #296 from nuclearace/fix-disconnect
Fix #295
2017-01-06 21:32:13 -06:00
Praneet Sahgal c63e173021 Fixed issues with not cancelling block operations properly 2017-01-04 09:15:56 -06:00
Praneet Sahgal ddcc30d200 Added guard against multiple disconnects 2017-01-04 09:03:42 -06:00
Erik 6bacb6f972 Fix #295
This sets didDisconnect to false if we receive a connect.
2016-12-31 18:05:11 -05:00
Dalton aca2ad5332 Merge pull request #292 from euskadi31/master
Change public to open
2016-12-30 16:32:51 -06:00
Dalton 3ba1963c65 Merge pull request #294 from wordpress-mobile/prevent-reconnection-crash
Prevent reconnection crash
2016-12-30 15:56:12 -06:00
Dalton 3cc4a60e05 Merge pull request #289 from robinmj/master
Fix for excessive memory use caused by the receipt of an unusually large message
2016-12-30 15:54:37 -06:00
Jorge Bernal 14313df360 Disconnect and clean up streams before connecting.
Since `disconnect` without a timeout only sends a close message to the server,
if we tried to `connect` the socket again before the connection was closed it
would not clean up the streams properly, which could result in a crash.

See #272
2016-12-27 16:23:39 +01:00
Jorge Bernal 162c1d53b8 Keep isConnecting true while connecting.
The existing code only prevented a second connection to be created while
createHTTPRequest was being called (setting up the HTTP request and the
streams). However, between the time when the socket opens the streams until the
TCP handshake ends, it would be both `!conneted` and `!isConnecting`, which
wouldn't prevent a second `connect`.
2016-12-27 15:56:01 +01:00
Axel Etcheverry b404f98244 Change public to open 2016-12-19 15:31:10 +01:00
daltoniam 8d0c0c1afa simple test update 2016-12-12 13:26:41 -06:00
Dalton 816fe413a4 Merge pull request #288 from dancasimiro/host-only-origin
Remove the path components from the URL sent as origin
2016-12-12 13:22:27 -06:00
Daniel C. Casimiro 5cc0d860ba Add path components to the test server URL
This demonstrates the path stripping from the origin URL.
2016-11-22 08:53:45 -05:00
Daniel C. Casimiro 4d2f8dbfee Remove the path components from the URL sent as origin
This fixes issue #287. The origin is a URI indicating the server from
which the request initiated. It does not include any path information,
but only the server name.
2016-11-22 08:53:45 -05:00
Robin Johnson 126c021e61 fixed malloc failure by draining autorelease pool with every dequeuing operation 2016-11-21 19:14:20 -07:00
8 changed files with 113 additions and 74 deletions
+16
View File
@@ -2,6 +2,22 @@
All notable changes to this project will be documented in this file.
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
#### [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/).
Warning fixes for Swift 3.1
#### [2.0.3](https://github.com/daltoniam/Starscream/tree/2.0.3)
[#302](https://github.com/daltoniam/Starscream/issues/302)
[#301](https://github.com/daltoniam/Starscream/issues/301)
[#300](https://github.com/daltoniam/Starscream/issues/300)
[#296](https://github.com/daltoniam/Starscream/issues/296)
[#294](https://github.com/daltoniam/Starscream/issues/294)
[#292](https://github.com/daltoniam/Starscream/issues/292)
[#289](https://github.com/daltoniam/Starscream/issues/289)
[#288](https://github.com/daltoniam/Starscream/issues/288)
#### [2.0.2](https://github.com/daltoniam/Starscream/tree/2.0.2)
Fix for the Swift Package Manager.
+2 -6
View File
@@ -11,10 +11,6 @@ It's Objective-C counter part can be found here: [Jetfire](https://github.com/ac
- TLS/WSS support.
- Simple concise codebase at just a few hundred LOC.
## Swift 2.3
See release/tag 1.1.4 for Swift 2.3 support.
## Example
First thing is to import the framework. See the Installation instructions on how to add the framework to your project.
@@ -231,7 +227,7 @@ To use Starscream in your project add the following 'Podfile' to your project
platform :ios, '9.0'
use_frameworks!
pod 'Starscream', '~> 2.0.0'
pod 'Starscream', '~> 2.0.3'
Then run:
@@ -253,7 +249,7 @@ $ brew install carthage
To integrate Starscream into your Xcode project using Carthage, specify it in your `Cartfile`:
```
github "daltoniam/Starscream" >= 2.0.0
github "daltoniam/Starscream" >= 2.0.3
```
### Rogue
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.2</string>
<string>2.0.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+1 -1
View File
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>2.0.2</string>
<string>2.0.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
+91 -64
View File
@@ -167,6 +167,11 @@ open class WebSocket : NSObject, StreamDelegate {
public init(url: URL, protocols: [String]? = nil) {
self.url = url
self.origin = url.absoluteString
if let hostUrl = URL (string: "/", relativeTo: url) {
var origin = hostUrl.absoluteString
origin.remove(at: origin.index(before: origin.endIndex))
self.origin = origin
}
writeQueue.maxConcurrentOperationCount = 1
optionalProtocols = protocols
}
@@ -180,12 +185,11 @@ open class WebSocket : NSObject, StreamDelegate {
/**
Connect to the WebSocket server on a background thread.
*/
public func connect() {
open func connect() {
guard !isConnecting else { return }
didDisconnect = false
isConnecting = true
createHTTPRequest()
isConnecting = false
}
/**
@@ -198,7 +202,8 @@ open class WebSocket : NSObject, StreamDelegate {
- Parameter forceTimeout: Maximum time to wait for the server to close the socket.
- Parameter closeCode: The code to send on disconnect. The default is the normal close code for cleanly disconnecting a webSocket.
*/
public func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) {
open func disconnect(forceTimeout: TimeInterval? = nil, closeCode: UInt16 = CloseCode.normal.rawValue) {
guard isConnected else { return }
switch forceTimeout {
case .some(let seconds) where seconds > 0:
let milliseconds = Int(seconds * 1_000)
@@ -222,7 +227,7 @@ open class WebSocket : NSObject, StreamDelegate {
- parameter string: The string to write.
- parameter completion: The (optional) completion handler.
*/
public func write(string: String, completion: (() -> ())? = nil) {
open func write(string: String, completion: (() -> ())? = nil) {
guard isConnected else { return }
dequeueWrite(string.data(using: String.Encoding.utf8)!, code: .textFrame, writeCompletion: completion)
}
@@ -235,7 +240,7 @@ open class WebSocket : NSObject, StreamDelegate {
- parameter data: The data to write.
- parameter completion: The (optional) completion handler.
*/
public func write(data: Data, completion: (() -> ())? = nil) {
open func write(data: Data, completion: (() -> ())? = nil) {
guard isConnected else { return }
dequeueWrite(data, code: .binaryFrame, writeCompletion: completion)
}
@@ -244,7 +249,7 @@ open class WebSocket : NSObject, StreamDelegate {
Write a ping to the websocket. This sends it as a control frame.
Yodel a sound to the planet. This sends it as an astroid. http://youtu.be/Eu5ZJELRiJ8?t=42s
*/
public func write(ping: Data, completion: (() -> ())? = nil) {
open func write(ping: Data, completion: (() -> ())? = nil) {
guard isConnected else { return }
dequeueWrite(ping, code: .ping, writeCompletion: completion)
}
@@ -253,7 +258,6 @@ open class WebSocket : NSObject, StreamDelegate {
Private method that starts the connection.
*/
private func createHTTPRequest() {
let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString,
url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
@@ -314,6 +318,9 @@ open class WebSocket : NSObject, StreamDelegate {
//higher level API we will cut over to at some point
//NSStream.getStreamsToHostWithName(url.host, port: url.port.integerValue, inputStream: &inputStream, outputStream: &outputStream)
// Disconnect and clean up any existing streams before setting up a new pair
disconnectStream(nil, runDelegate: false)
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let h = url.host! as NSString
@@ -324,6 +331,7 @@ open class WebSocket : NSObject, StreamDelegate {
inStream.delegate = self
outStream.delegate = self
if supportedSSLSchemes.contains(url.scheme!) {
certValidated = false
inStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
outStream.setProperty(StreamSocketSecurityLevel.negotiatedSSL as AnyObject, forKey: Stream.PropertyKey.socketSecurityLevelKey)
if disableSSLCertValidation {
@@ -367,37 +375,46 @@ open class WebSocket : NSObject, StreamDelegate {
let bytes = UnsafeRawPointer((data as NSData).bytes).assumingMemoryBound(to: UInt8.self)
var out = timeout * 1_000_000 // wait 5 seconds before giving up
writeQueue.addOperation { [weak self] in
while !outStream.hasSpaceAvailable {
let operation = BlockOperation()
operation.addExecutionBlock { [weak self, weak operation] in
guard let sOperation = operation else { return }
while !outStream.hasSpaceAvailable && !sOperation.isCancelled {
usleep(100) // wait until the socket is ready
guard !sOperation.isCancelled else { return }
out -= 100
if out < 0 {
self?.cleanupStream()
WebSocket.sharedWorkQueue.async {
self?.cleanupStream()
}
self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2))
return
} else if outStream.streamError != nil {
return // disconnectStream will be called.
}
}
guard !sOperation.isCancelled, let s = self else { return }
// Do the pinning now if needed
if let sec = s.security, !s.certValidated {
let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
s.certValidated = sec.isValid(trust, domain: domain)
if !s.certValidated {
WebSocket.sharedWorkQueue.async {
let error = s.errorWithDetail("Invalid SSL certificate", code: 1)
s.disconnectStream(error)
}
return
}
}
outStream.write(bytes, maxLength: data.count)
}
writeQueue.addOperation(operation)
}
/**
Delegate for the stream methods. Processes incoming bytes
*/
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
if sec.isValid(trust, domain: domain) {
certValidated = true
} else {
let error = errorWithDetail("Invalid SSL certificate", code: 1)
disconnectStream(error)
return
}
}
open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
if eventCode == .hasBytesAvailable {
if aStream == inputStream {
processInputStream()
@@ -412,14 +429,17 @@ open class WebSocket : NSObject, StreamDelegate {
/**
Disconnect the stream object and notifies the delegate.
*/
private func disconnectStream(_ error: NSError?) {
private func disconnectStream(_ error: NSError?, runDelegate: Bool = true) {
if error == nil {
writeQueue.waitUntilAllOperationsAreFinished()
} else {
writeQueue.cancelAllOperations()
}
cleanupStream()
doDisconnect(error)
connected = false
if runDelegate {
doDisconnect(error)
}
}
/**
@@ -438,6 +458,7 @@ open class WebSocket : NSObject, StreamDelegate {
}
outputStream = nil
inputStream = nil
fragBuffer = nil
}
/**
@@ -463,22 +484,24 @@ open class WebSocket : NSObject, StreamDelegate {
*/
private func dequeueInput() {
while !inputQueue.isEmpty {
let data = inputQueue[0]
var work = data
if let fragBuffer = fragBuffer {
var combine = NSData(data: fragBuffer) as Data
combine.append(data)
work = combine
self.fragBuffer = nil
autoreleasepool {
let data = inputQueue[0]
var work = data
if let buffer = fragBuffer {
var combine = NSData(data: buffer) as Data
combine.append(data)
work = combine
fragBuffer = nil
}
let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self)
let length = work.count
if !connected {
processTCPHandshake(buffer, bufferLen: length)
} else {
processRawMessagesInBuffer(buffer, bufferLen: length)
}
inputQueue = inputQueue.filter{ $0 != data }
}
let buffer = UnsafeRawPointer((work as NSData).bytes).assumingMemoryBound(to: UInt8.self)
let length = work.count
if !connected {
processTCPHandshake(buffer, bufferLen: length)
} else {
processRawMessagesInBuffer(buffer, bufferLen: length)
}
inputQueue = inputQueue.filter{ $0 != data }
}
}
@@ -489,17 +512,10 @@ open class WebSocket : NSObject, StreamDelegate {
let code = processHTTP(buffer, bufferLen: bufferLen)
switch code {
case 0:
connected = true
guard canDispatch else {return}
callbackQueue.async { [weak self] in
guard let s = self else { return }
s.onConnect?()
s.delegate?.websocketDidConnect(socket: s)
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
}
break
case -1:
fragBuffer = Data(bytes: buffer, count: bufferLen)
break // do nothing, we are going to collect more data
break // do nothing, we are going to collect more data
default:
doDisconnect(errorWithDetail("Invalid HTTP upgrade", code: UInt16(code)))
}
@@ -528,6 +544,17 @@ open class WebSocket : NSObject, StreamDelegate {
if code != 0 {
return code
}
isConnecting = false
connected = true
didDisconnect = false
if canDispatch {
callbackQueue.async { [weak self] in
guard let s = self else { return }
s.onConnect?()
s.delegate?.websocketDidConnect(socket: s)
s.notificationCenter.post(name: NSNotification.Name(WebsocketDidConnectNotification), object: self)
}
}
totalSize += 1 //skip the last \n
let restSize = bufferLen - totalSize
if restSize > 0 {
@@ -618,7 +645,8 @@ open class WebSocket : NSObject, StreamDelegate {
return buffer.fromOffset(bufferLen - extra)
} else {
let isFin = (FinMask & baseAddress[0])
let receivedOpcode = OpCode(rawValue: (OpCodeMask & baseAddress[0]))
let receivedOpcodeRawValue = (OpCodeMask & baseAddress[0])
let receivedOpcode = OpCode(rawValue: receivedOpcodeRawValue)
let isMasked = (MaskMask & baseAddress[1])
let payloadLen = (PayloadLenMask & baseAddress[1])
var offset = 2
@@ -632,7 +660,7 @@ open class WebSocket : NSObject, StreamDelegate {
if !isControlFrame && (receivedOpcode != .binaryFrame && receivedOpcode != .continueFrame &&
receivedOpcode != .textFrame && receivedOpcode != .pong) {
let errCode = CloseCode.protocolError.rawValue
doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcode)", code: errCode))
doDisconnect(errorWithDetail("unknown opcode: \(receivedOpcodeRawValue)", code: errCode))
writeError(errCode)
return emptyBuffer
}
@@ -677,18 +705,13 @@ open class WebSocket : NSObject, StreamDelegate {
if dataLength > UInt64(bufferLen) {
len = UInt64(bufferLen-offset)
}
let data: Data
if len < 0 {
len = 0
data = Data()
} else {
if receivedOpcode == .connectionClose && len > 0 {
let size = MemoryLayout<UInt16>.size
offset += size
len -= UInt64(size)
}
data = Data(bytes: baseAddress+offset, count: Int(len))
if receivedOpcode == .connectionClose && len > 0 {
let size = MemoryLayout<UInt16>.size
offset += size
len -= UInt64(size)
}
let data = Data(bytes: baseAddress+offset, count: Int(len))
if receivedOpcode == .connectionClose {
var closeReason = "connection closed by server"
if let customCloseReason = String(data: data, encoding: .utf8) {
@@ -835,9 +858,11 @@ open class WebSocket : NSObject, StreamDelegate {
Used to write things to the stream
*/
private func dequeueWrite(_ data: Data, code: OpCode, writeCompletion: (() -> ())? = nil) {
writeQueue.addOperation { [weak self] in
let operation = BlockOperation()
operation.addExecutionBlock { [weak self, weak operation] in
//stream isn't ready, let's wait
guard let s = self else { return }
guard let sOperation = operation else { return }
var offset = 2
let dataLength = data.count
let frame = NSMutableData(capacity: dataLength + s.MaxFrameSize)
@@ -864,7 +889,7 @@ open class WebSocket : NSObject, StreamDelegate {
offset += 1
}
var total = 0
while true {
while !sOperation.isCancelled {
guard let outStream = s.outputStream else { break }
let writeBuffer = UnsafeRawPointer(frame!.bytes+total).assumingMemoryBound(to: UInt8.self)
let len = outStream.write(writeBuffer, maxLength: offset-total)
@@ -891,8 +916,8 @@ open class WebSocket : NSObject, StreamDelegate {
break
}
}
}
writeQueue.addOperation(operation)
}
/**
@@ -901,6 +926,7 @@ open class WebSocket : NSObject, StreamDelegate {
private func doDisconnect(_ error: NSError?) {
guard !didDisconnect else { return }
didDisconnect = true
isConnecting = false
connected = false
guard canDispatch else {return}
callbackQueue.async { [weak self] in
@@ -919,6 +945,7 @@ open class WebSocket : NSObject, StreamDelegate {
readyToWrite = false
mutex.unlock()
cleanupStream()
writeQueue.cancelAllOperations()
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Starscream"
s.version = "2.0.2"
s.version = "2.0.4"
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'
+1 -1
View File
@@ -22,4 +22,4 @@ EM.run {
ws.send Faker::Hacker.say_something_smart
}
end
}
}