Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df8d82047f | |||
| 85c8412646 | |||
| 4820831260 | |||
| 024a27711e | |||
| 5987db1605 | |||
| fe5769f2ae | |||
| 263b9419bb | |||
| 5fbeab4c12 | |||
| 07bccd9c16 | |||
| 51b6c789bd | |||
| 0c7e7abfd7 | |||
| d2f22783b6 | |||
| e6236acc00 | |||
| d003b62b54 | |||
| 70d9e6ec5c | |||
| c0e7d40284 | |||
| 06945ada8b | |||
| 7e5a96341c | |||
| ce4fcdc967 | |||
| da1d9c69a7 | |||
| b6630118fe | |||
| cfc7b7b8dc | |||
| 311b6dd9c7 | |||
| 339ca39461 | |||
| 4e8973e3fc | |||
| 281a49edd8 |
@@ -2,6 +2,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
`Starscream` adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
### [4.0.4](https://github.com/daltoniam/Starscream/tree/4.0.4)
|
||||
|
||||
Bug fixes for 4.0.3.
|
||||
|
||||
[#808](https://github.com/daltoniam/Starscream/pull/808)
|
||||
[#807](https://github.com/daltoniam/Starscream/pull/807)
|
||||
[#799](https://github.com/daltoniam/Starscream/pull/799)
|
||||
[#797](https://github.com/daltoniam/Starscream/pull/797)
|
||||
[#790](https://github.com/daltoniam/Starscream/pull/790)
|
||||
[#788](https://github.com/daltoniam/Starscream/pull/788)
|
||||
[#777](https://github.com/daltoniam/Starscream/pull/777)
|
||||
[#768](https://github.com/daltoniam/Starscream/pull/768)
|
||||
[#766](https://github.com/daltoniam/Starscream/pull/766)
|
||||
[#764](https://github.com/daltoniam/Starscream/pull/764)
|
||||
|
||||
### [4.0.3](https://github.com/daltoniam/Starscream/tree/4.0.3)
|
||||
|
||||
Bug fixes for 4.0.2.
|
||||
|
||||
[#760](https://github.com/daltoniam/Starscream/issues/760)
|
||||
|
||||
### [4.0.2](https://github.com/daltoniam/Starscream/tree/4.0.2)
|
||||
|
||||
Bug fixes for 4.0.1. Fixed native engine is connected/disconnected. Native engine isn't the default since the API lacks features.
|
||||
|
||||
[#697](https://github.com/daltoniam/Starscream/pull/697)
|
||||
|
||||
### [4.0.1](https://github.com/daltoniam/Starscream/tree/4.0.1)
|
||||
|
||||
Bug fixes for 4.0.0. Enabled Native engine now that the API is out of beta and works properly.
|
||||
|
||||
+6
-4
@@ -1,4 +1,4 @@
|
||||
// swift-tools-version:4.2
|
||||
// swift-tools-version:5.2
|
||||
|
||||
//
|
||||
// Package.Swift
|
||||
@@ -27,11 +27,13 @@ let package = Package(
|
||||
products: [
|
||||
.library(name: "Starscream", targets: ["Starscream"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/apple/swift-nio-zlib-support.git", from: "1.0.0")
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(name: "Starscream",
|
||||
path: "Sources")
|
||||
]
|
||||
)
|
||||
|
||||
#if os(Linux)
|
||||
package.dependencies.append(.package(url: "https://github.com/apple/swift-nio-zlib-support.git", from: "1.0.0"))
|
||||
#endif
|
||||
|
||||
@@ -4,7 +4,7 @@ Starscream is a conforming WebSocket ([RFC 6455](http://tools.ietf.org/html/rfc6
|
||||
|
||||
## Features
|
||||
|
||||
- Conforms to all of the base [Autobahn test suite](http://autobahn.ws/testsuite/).
|
||||
- Conforms to all of the base [Autobahn test suite](https://crossbar.io/autobahn/).
|
||||
- Nonblocking. Everything happens in the background, thanks to GCD.
|
||||
- TLS/WSS support.
|
||||
- Compression Extensions support ([RFC 7692](https://tools.ietf.org/html/rfc7692))
|
||||
|
||||
@@ -147,10 +147,12 @@ class Decompressor {
|
||||
strm.avail_in = CUnsignedInt(count)
|
||||
|
||||
repeat {
|
||||
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
|
||||
strm.avail_out = CUnsignedInt(buffer.count)
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = inflate(&strm, 0)
|
||||
res = inflate(&strm, 0)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
out.append(buffer, count: byteCount)
|
||||
@@ -209,10 +211,12 @@ class Compressor {
|
||||
strm.avail_in = CUnsignedInt(data.count)
|
||||
|
||||
repeat {
|
||||
strm.next_out = UnsafeMutablePointer<UInt8>(&buffer)
|
||||
strm.avail_out = CUnsignedInt(buffer.count)
|
||||
buffer.withUnsafeMutableBytes { (bufferPtr) in
|
||||
strm.next_out = bufferPtr.bindMemory(to: UInt8.self).baseAddress
|
||||
strm.avail_out = CUnsignedInt(bufferPtr.count)
|
||||
|
||||
res = deflate(&strm, Z_SYNC_FLUSH)
|
||||
res = deflate(&strm, Z_SYNC_FLUSH)
|
||||
}
|
||||
|
||||
let byteCount = buffer.count - Int(strm.avail_out)
|
||||
compressed.append(buffer, count: byteCount)
|
||||
|
||||
@@ -27,22 +27,20 @@ import Foundation
|
||||
internal extension Data {
|
||||
struct ByteError: Swift.Error {}
|
||||
|
||||
#if swift(>=5.0)
|
||||
func withUnsafeBytes<ResultType, ContentType>(_ completion: (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
#if swift(>=5.0)
|
||||
return try withUnsafeBytes {
|
||||
return try withUnsafeBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
} else {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
#else
|
||||
return try withUnsafeBytes(completion)
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if swift(>=5.0)
|
||||
mutating func withUnsafeMutableBytes<ResultType, ContentType>(_ completion: (UnsafeMutablePointer<ContentType>) throws -> ResultType) rethrows -> ResultType {
|
||||
#if swift(>=5.0)
|
||||
return try withUnsafeMutableBytes {
|
||||
if let baseAddress = $0.baseAddress, $0.count > 0 {
|
||||
return try completion(baseAddress.assumingMemoryBound(to: ContentType.self))
|
||||
@@ -50,8 +48,6 @@ internal extension Data {
|
||||
throw ByteError()
|
||||
}
|
||||
}
|
||||
#else
|
||||
return try withUnsafeMutableBytes(completion)
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -9,20 +9,17 @@
|
||||
import Foundation
|
||||
|
||||
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
|
||||
public class NativeEngine: Engine {
|
||||
public class NativeEngine: NSObject, Engine, URLSessionDataDelegate, URLSessionWebSocketDelegate {
|
||||
private var task: URLSessionWebSocketTask?
|
||||
weak var delegate: EngineDelegate?
|
||||
|
||||
public init() {
|
||||
|
||||
}
|
||||
|
||||
public func register(delegate: EngineDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
public func start(request: URLRequest) {
|
||||
task = URLSession.shared.webSocketTask(with: request)
|
||||
let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
|
||||
task = session.webSocketTask(with: request)
|
||||
doRead()
|
||||
task?.resume()
|
||||
}
|
||||
@@ -83,4 +80,17 @@ public class NativeEngine: Engine {
|
||||
private func broadcast(event: WebSocketEvent) {
|
||||
delegate?.didReceive(event: event)
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
|
||||
let p = `protocol` ?? ""
|
||||
broadcast(event: .connected([HTTPWSHeader.protocolName: p]))
|
||||
}
|
||||
|
||||
public func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
|
||||
var r = ""
|
||||
if let d = reason {
|
||||
r = String(data: d, encoding: .utf8) ?? ""
|
||||
}
|
||||
broadcast(event: .disconnected(r, UInt16(closeCode.rawValue)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,12 @@ FrameCollectorDelegate, HTTPHandlerDelegate {
|
||||
canSend = true
|
||||
mutex.signal()
|
||||
compressionHandler?.load(headers: headers)
|
||||
if let url = request.url {
|
||||
HTTPCookie.cookies(withResponseHeaderFields: headers, for: url).forEach {
|
||||
HTTPCookieStorage.shared.setCookie($0)
|
||||
}
|
||||
}
|
||||
|
||||
broadcast(event: .connected(headers))
|
||||
case .failure(let error):
|
||||
handleError(error)
|
||||
|
||||
@@ -74,7 +74,7 @@ public class FoundationHTTPServerHandler: HTTPServerHandler {
|
||||
return false //not enough data, wait for more
|
||||
}
|
||||
if let method = CFHTTPMessageCopyRequestMethod(response)?.takeRetainedValue() {
|
||||
if method != getVerb {
|
||||
if (method as NSString) != getVerb {
|
||||
delegate?.didReceive(event: .failure(HTTPUpgradeError.invalidData))
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -84,19 +84,15 @@ public class FrameCollector {
|
||||
}
|
||||
buffer.append(payload)
|
||||
frameCount += 1
|
||||
if isText {
|
||||
if String(data: buffer, encoding: .utf8) == nil {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "not valid UTF-8 data", code: errCode)))
|
||||
reset()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if frame.isFin {
|
||||
if isText {
|
||||
let string = String(data: buffer, encoding: .utf8) ?? ""
|
||||
delegate?.didForm(event: .text(string))
|
||||
if let string = String(data: buffer, encoding: .utf8) {
|
||||
delegate?.didForm(event: .text(string))
|
||||
} else {
|
||||
let errCode = CloseCode.protocolError.rawValue
|
||||
delegate?.didForm(event: .error(WSError(type: .protocolError, message: "not valid UTF-8 data", code: errCode)))
|
||||
}
|
||||
} else {
|
||||
delegate?.didForm(event: .binary(buffer))
|
||||
}
|
||||
|
||||
@@ -183,6 +183,9 @@ public class WSFramer: Framer {
|
||||
closeCode = CloseCode.protocolError.rawValue
|
||||
dataLength = 0
|
||||
} else if payloadLen > 1 {
|
||||
if pointer.count < 4 {
|
||||
return .needsMoreData
|
||||
}
|
||||
let size = MemoryLayout<UInt16>.size
|
||||
closeCode = pointer.readUint16(offset: offset)
|
||||
offset += size
|
||||
|
||||
@@ -67,6 +67,13 @@ public struct HTTPWSHeader {
|
||||
req.setValue(HTTPWSHeader.versionValue, forHTTPHeaderField: HTTPWSHeader.versionName)
|
||||
req.setValue(secKeyValue, forHTTPHeaderField: HTTPWSHeader.keyName)
|
||||
|
||||
if let cookies = HTTPCookieStorage.shared.cookies(for: url), !cookies.isEmpty {
|
||||
let headers = HTTPCookie.requestHeaderFields(with: cookies)
|
||||
for (key, val) in headers {
|
||||
req.setValue(val, forHTTPHeaderField: key)
|
||||
}
|
||||
}
|
||||
|
||||
if supportsCompression {
|
||||
let val = "permessage-deflate; client_max_window_bits; server_max_window_bits=15"
|
||||
req.setValue(val, forHTTPHeaderField: HTTPWSHeader.extensionName)
|
||||
|
||||
@@ -101,10 +101,9 @@ public class StringHTTPHandler: HTTPHandler {
|
||||
code = c
|
||||
}
|
||||
} else {
|
||||
let responseSplit = str.components(separatedBy: ":")
|
||||
guard responseSplit.count > 1 else { break }
|
||||
let key = responseSplit[0].trimmingCharacters(in: .whitespaces)
|
||||
let val = responseSplit[1].trimmingCharacters(in: .whitespaces)
|
||||
guard let separatorIndex = str.firstIndex(of: ":") else { break }
|
||||
let key = str.prefix(upTo: separatorIndex).trimmingCharacters(in: .whitespaces)
|
||||
let val = str.suffix(from: str.index(after: separatorIndex)).trimmingCharacters(in: .whitespaces)
|
||||
headers[key.lowercased()] = val
|
||||
}
|
||||
i += 1
|
||||
|
||||
@@ -120,7 +120,7 @@ open class WebSocket: WebSocketClient, EngineDelegate {
|
||||
self.engine = engine
|
||||
}
|
||||
|
||||
public convenience init(request: URLRequest, certPinner: CertificatePinning? = FoundationSecurity(), compressionHandler: CompressionHandler? = nil, useCustomEngine: Bool = false) {
|
||||
public convenience init(request: URLRequest, certPinner: CertificatePinning? = FoundationSecurity(), compressionHandler: CompressionHandler? = nil, useCustomEngine: Bool = true) {
|
||||
if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *), !useCustomEngine {
|
||||
self.init(request: request, engine: NativeEngine())
|
||||
} else if #available(macOS 10.14, iOS 12.0, watchOS 5.0, tvOS 12.0, *) {
|
||||
|
||||
@@ -47,7 +47,12 @@ public class FoundationTransport: NSObject, Transport, StreamDelegate {
|
||||
onConnect = streamConfiguration
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
deinit {
|
||||
inputStream?.delegate = nil
|
||||
outputStream?.delegate = nil
|
||||
}
|
||||
|
||||
public func connect(url: URL, timeout: Double = 10, certificatePinning: CertificatePinning? = nil) {
|
||||
guard let parts = url.getParts() else {
|
||||
delegate?.connectionChanged(state: .failed(FoundationTransportError.invalidRequest))
|
||||
return
|
||||
|
||||
@@ -141,7 +141,16 @@ public class TCPTransport: Transport {
|
||||
if let data = data {
|
||||
s.delegate?.connectionChanged(state: .receive(data))
|
||||
}
|
||||
s.readLoop()
|
||||
|
||||
// Refer to https://developer.apple.com/documentation/network/implementing_netcat_with_network_framework
|
||||
if let context = context, context.isFinal, isComplete {
|
||||
return
|
||||
}
|
||||
|
||||
if error == nil {
|
||||
s.readLoop()
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "Starscream"
|
||||
s.version = "4.0.1"
|
||||
s.version = "4.0.4"
|
||||
s.summary = "A conforming WebSocket RFC 6455 client library in Swift."
|
||||
s.homepage = "https://github.com/daltoniam/Starscream"
|
||||
s.license = 'Apache License, Version 2.0'
|
||||
|
||||
@@ -38,6 +38,16 @@
|
||||
BBB5ABE8215E2217005B48B6 /* WebSocket.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBB5ABE4215E2217005B48B6 /* WebSocket.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
6B0BE7AA24A157BB0051F7A7 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 6B3E79DD19D48B7F006071F7 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 33CCF0841F5DDC030099B092;
|
||||
remoteInfo = Starscream;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
335FA2021F5DF71D00F6D2EC /* Starscream Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Starscream Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
33CCF0921F5DDC030099B092 /* Starscream.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Starscream.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -250,6 +260,7 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
6B0BE7AB24A157BB0051F7A7 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Starscream Tests";
|
||||
productName = StarscreamTests;
|
||||
@@ -298,8 +309,8 @@
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
English,
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 6B3E79DC19D48B7F006071F7;
|
||||
productRefGroup = 6B3E79E719D48B7F006071F7 /* Products */;
|
||||
@@ -372,6 +383,14 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
6B0BE7AB24A157BB0051F7A7 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 33CCF0841F5DDC030099B092 /* Starscream */;
|
||||
targetProxy = 6B0BE7AA24A157BB0051F7A7 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
335FA2001F5DF71D00F6D2EC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
@@ -428,10 +447,10 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 4.0.1;
|
||||
MARKETING_VERSION = 4.0.4;
|
||||
OTHER_LDFLAGS = "-all_load";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -456,10 +475,10 @@
|
||||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = "$(SRCROOT)/Sources/Info.plist";
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MARKETING_VERSION = 4.0.1;
|
||||
MARKETING_VERSION = 4.0.4;
|
||||
OTHER_LDFLAGS = "-all_load";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.vluxe.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -521,7 +540,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
|
||||
@@ -574,7 +593,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos macosx appletvos appletvsimulator watchsimulator watchos";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
BIN
Binary file not shown.
Reference in New Issue
Block a user