Compare commits

..

1 Commits

Author SHA1 Message Date
Michael Kirk
6bdd097519 specify ruby version for CI
CI is currently failing because the default ruby on travis.org is 2.0,
but a dependency xcpretty, requires the latest activesupport, which
requires ruby >=2.2
2016-06-30 16:45:26 -07:00
55 changed files with 1436 additions and 1724 deletions

6
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "Vendor/xctoolchain"]
path = Vendor/xctoolchain
url = https://github.com/ParsePlatform/xctoolchain.git
[submodule "pages"]
path = pages
url = git://github.com/square/SocketRocket.git

View File

@ -28,10 +28,10 @@ script:
- |
if [ "$TEST_TYPE" = iOS ]; then
set -o pipefail
xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-iOS" -sdk iphonesimulator build test
xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket" -sdk iphonesimulator build test
elif [ "$TEST_TYPE" = OSX ]; then
set -o pipefail
xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-macOS" -sdk macosx build | xcpretty -c
xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-OSX" -sdk macosx build | xcpretty -c
elif [ "$TEST_TYPE" = tvOS ]; then
set -o pipefail
xcodebuild -project SocketRocket.xcodeproj -scheme "SocketRocket-tvOS" -sdk appletvsimulator build | xcpretty -c

View File

@ -1 +0,0 @@
../Vendor/xctoolchain/Configurations/

View File

@ -1,17 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/DynamicFramework.xcconfig"
PRODUCT_NAME = SocketRocket
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.ios
IPHONEOS_DEPLOYMENT_TARGET = 8.0
INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist

View File

@ -1,20 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/StaticFramework.xcconfig"
PRODUCT_NAME = SocketRocket
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.ios
IPHONEOS_DEPLOYMENT_TARGET = 6.0
INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist
OTHER_CFLAGS[sdk=iphoneos9.*] = $(inherited) -fembed-bitcode
OTHER_LDFLAGS = $(inherited) -Licucore

View File

@ -1,17 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/macOS.xcconfig"
#include "Shared/Product/DynamicFramework.xcconfig"
PRODUCT_NAME = SocketRocket
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.macos
MACOSX_DEPLOYMENT_TARGET = 10.8
INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist

View File

@ -1,16 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/tvOS.xcconfig"
#include "Shared/Product/DynamicFramework.xcconfig"
PRODUCT_NAME = SocketRocket
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.tvos
INFOPLIST_FILE = $(SRCROOT)/SocketRocket/Resources/Info.plist

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/LogicTests.xcconfig"
PRODUCT_NAME = SocketRocketTests-iOS
PRODUCT_MODULE_NAME = SocketRocketTests
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.tests.ios
IPHONEOS_DEPLOYMENT_TARGET = 7.0
INFOPLIST_FILE = $(SRCROOT)/Tests/Resources/Info.plist

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#include "Shared/Platform/iOS.xcconfig"
#include "Shared/Product/Application.xcconfig"
PRODUCT_NAME = TestChat
PRODUCT_MODULE_NAME = TestChat
PRODUCT_BUNDLE_IDENTIFIER = com.facebook.socketrocket.testchat
IPHONEOS_DEPLOYMENT_TARGET = 8.0
INFOPLIST_FILE = $(SRCROOT)/TestChat/TestChat-Info.plist

View File

@ -1,5 +1,9 @@
# Contributing to SocketRocket
We want to make contributing to this project as easy and transparent as possible.
We want to make contributing to this project as easy and transparent as
possible.
## Our Development Process
... (in particular how this is synced with internal changes to the project)
## Pull Requests
We actively welcome your pull requests.
@ -12,8 +16,8 @@ We actively welcome your pull requests.
6. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA.
You only need to do this once to work on any of Facebook's open source projects.
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
@ -30,4 +34,5 @@ outlined on that page and do not file a public issue.
* Try to keep lines under 140 characters, if possible.
## License
By contributing to SocketRocket, you agree that your contributions will be licensed under its BSD license.
By contributing to SocketRocket, you agree that your contributions will be licensed
under its BSD license.

View File

@ -7,23 +7,19 @@ all:
clean:
$(MAKE) -C SocketRocket clean
.env:
./TestSupport/setup_env.sh .env
test: .env
test:
mkdir -p pages/results
bash ./TestSupport/run_test_server.sh $(TEST_SCENARIOS) $(TEST_URL) Debug || open pages/results/index.html && false
open pages/results/index.html
test_all: .env
test_all:
mkdir -p pages/results
bash ./TestSupport/run_test_server.sh '*' $(TEST_URL) Debug || open pages/results/index.html && false
open pages/results/index.html
test_perf: .env
test_perf:
mkdir -p pages/results
bash ./TestSupport/run_test_server.sh '9.*' $(TEST_URL) Release || open pages/results/index.html && false

213
README.md
View File

@ -1,213 +0,0 @@
# SocketRocket
![Platforms][platforms-svg]
[![License][license-svg]][license-link]
[![Podspec][podspec-svg]][podspec-link]
[![Carthage Compatible][carthage-svg]](carthage-link)
[![Build Status][build-status-svg]][build-status-link]
A conforming WebSocket ([RFC 6455](https://tools.ietf.org/html/rfc6455>)) client library for iOS, macOS and tvOS.
Test results for SocketRocket [here](http://facebook.github.io/SocketRocket/results/).
You can compare to what modern browsers look like [here](http://autobahn.ws/testsuite/reports/clients/index.html).
SocketRocket currently conforms to all core ~300 of [Autobahn](http://autobahn.ws/testsuite/>)'s fuzzing tests
(aside from two UTF-8 ones where it is merely *non-strict* tests 6.4.2 and 6.4.4).
## Features/Design
- TLS (wss) support, including self-signed certificates.
- Seems to perform quite well.
- Supports HTTP Proxies.
- Supports IPv4/IPv6.
- Supports SSL certificate pinning.
- Sends `ping` and can process `pong` events.
- Asynchronous and non-blocking. Most of the work is done on a background thread.
- Supports iOS, macOS, tvOS.
## Installing
There are a few options. Choose one, or just figure it out:
- **[CocoaPods](https://cocoapods.org)**
Add the following line to your Podfile:
```ruby
pod 'SocketRocket'
```
Run `pod install`, and you are all set.
- **[Carthage](https://github.com/carthage/carthage)**
Add the following line to your Cartfile:
```
github "facebook/SocketRocket"
```
Run `carthage update`, and you should now have the latest version of `SocketRocket` in your `Carthage` folder.
- **Using SocketRocket as a sub-project**
You can also include `SocketRocket` as a subproject inside of your application if you'd prefer, although we do not recommend this, as it will increase your indexing time significantly. To do so, just drag and drop the `SocketRocket.xcodeproj` file into your workspace.
## API
### `SRWebSocket`
The Web Socket.
#### Note:
`SRWebSocket` will retain itself between `-(void)open` and when it closes, errors, or fails.
This is similar to how `NSURLConnection` behaves (unlike `NSURLConnection`, `SRWebSocket` won't retain the delegate).
#### Interface
```objective-c
@interface SRWebSocket : NSObject
// Make it with this
- (instancetype)initWithURLRequest:(NSURLRequest *)request;
// Set this before opening
@property (nonatomic, weak) id <SRWebSocketDelegate> delegate;
// Open with this
- (void)open;
// Close it with this
- (void)close;
// Send a Data
- (void)sendData:(nullable NSData *)data error:(NSError **)error;
// Send a UTF8 String
- (void)sendString:(NSString *)string error:(NSError **)error;
@end
```
### `SRWebSocketDelegate`
You implement this
```objective-c
@protocol SRWebSocketDelegate <NSObject>
@optional
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithString:(NSString *)string;
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessageWithData:(NSData *)data;
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean;
@end
```
## Testing
Included are setup scripts for the python testing environment.
It comes packaged with vitualenv so all the dependencies are installed in userland.
To run the short test from the command line, run:
```bash
make test
```
To run all the tests, run:
```bash
make test_all
```
The short tests don't include the performance tests
(the test harness is actually the bottleneck, not SocketRocket).
The first time this is run, it may take a while to install the dependencies. It will be smooth sailing after that.
You can also run tests inside Xcode, which runs the same thing, but makes it easier to debug.
- Choose the `SocketRocket` target
- Run the test action (`⌘+U`)
### TestChat Demo Application
SocketRocket includes a demo app, TestChat.
It will "chat" with a listening websocket on port 9900.
#### TestChat Server
The sever takes a message and broadcasts it to all other connected clients.
It requires some dependencies though to run.
We also want to reuse the virtualenv we made when we ran the tests.
If you haven't run the tests yet, go into the SocketRocket root directory and type:
```bash
make test
```
This will set up your [virtualenv](https://pypi.python.org/pypi/virtualenv).
Now, in your terminal:
```bash
source .env/bin/activate
pip install git+https://github.com/tornadoweb/tornado.git
```
In the same terminal session, start the chatroom server:
```bash
python TestChatServer/py/chatroom.py
```
There's also a Go implementation (with the latest weekly) where you can:
```bash
cd TestChatServer/go
go run chatroom.go
```
#### Chatting
Now, start TestChat.app (just run the target in the Xcode project).
If you had it started already you can hit the refresh button to reconnect.
It should say "Connected!" on top.
To talk with the app, open up your browser to [http://localhost:9000](http://localhost:9000) and start chatting.
## WebSocket Server Implementation Recommendations
SocketRocket has been used with the following libraries:
- [Tornado](https://github.com/tornadoweb/tornado)
- Go's [WebSocket package](https://godoc.org/golang.org/x/net/websocket) or Gorilla's [version](http://www.gorillatoolkit.org/pkg/websocket).
- [Autobahn](http://autobahn.ws/testsuite/) (using its fuzzing client).
The Tornado one is dirt simple and works like a charm.
([IPython notebook](http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html) uses it too).
It's much easier to configure handlers and routes than in Autobahn/twisted.
## Contributing
Were glad youre interested in SocketRocket, and wed love to see where you take it.
Please read our [contributing guidelines](https://github.com/facebook/SocketRocket/blob/master/CONTRIBUTING.md) prior to submitting a Pull Request.
[build-status-svg]: https://img.shields.io/travis/facebook/SocketRocket/master.svg
[build-status-link]: https://travis-ci.org/facebook/SocketRocket/branches
[license-svg]: https://img.shields.io/badge/license-BSD-lightgrey.svg
[license-link]: https://github.com/facebook/SocketRocket/blob/master/LICENSE
[podspec-svg]: https://img.shields.io/cocoapods/v/SocketRocket.svg
[podspec-link]: https://cocoapods.org/pods/SocketRocket
[carthage-svg]: https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat
[carthage-link]: https://github.com/carthage/carthage
[platforms-svg]: http://img.shields.io/cocoapods/p/SocketRocket.svg?style=flat

246
README.rst Normal file
View File

@ -0,0 +1,246 @@
SocketRocket Objective-C WebSocket Client (beta)
================================================
A conforming WebSocket (`RFC 6455 <https://tools.ietf.org/html/rfc6455>`_)
client library.
`Test results for SocketRocket here <http://facebook.github.io/SocketRocket/results/>`_.
You can compare to what `modern browsers look like here
<http://autobahn.ws/testsuite/reports/clients/index.html>`_.
SocketRocket currently conforms to all ~300 of `Autobahn
<http://autobahn.ws/testsuite/>`_'s fuzzing tests (aside from
two UTF-8 ones where it is merely *non-strict*. tests 6.4.2 and 6.4.4)
Features/Design
---------------
- TLS (wss) support. It uses CFStream so we get this for *free*
- Uses NSStream/CFNetworking. Earlier implementations used ``dispatch_io``,
however, this proved to be make TLS nearly impossible. Also I wanted this to
work in iOS 4.x. (SocketRocket only supports 5.0 and above now)
- Uses ARC. It uses the 4.0 compatible subset (no weak refs).
- Seems to perform quite well
- Parallel architecture. Most of the work is done in background worker queues.
- Delegate-based. Had older versions that could use blocks too, but I felt it
didn't blend well with retain cycles and just objective C in general.
Changes
-------
v0.3.1-beta2 - 2013-01-12
`````````````````````````
- Stability fix for ``closeWithCode:reason:`` (Thanks @michaelpetrov!)
- Actually clean up the NSStreams and remove them from their runloops
- ``_SRRunLoopThread``'s ``main`` wasn't correctly wrapped with
``@autoreleasepool``
v0.3.1-beta1 - 2013-01-12
`````````````````````````
- Cleaned up GCD so OS_OBJECT_USE_OBJC_RETAIN_RELEASE is optional
- Removed deprecated ``dispatch_get_current_queue`` in favor of ``dispatch_queue_set_specific`` and ``dispatch_get_specific``
- Dropping support for iOS 4.0 (it may still work)
Installing (iOS)
----------------
There's a few options. Choose one, or just figure it out
- You can copy all the files in the SocketRocket group into your app.
- Include SocketRocket as a subproject and use libSocketRocket
If you do this, you must add -ObjC to your "other linker flags" option
- For OS X you will have to repackage make a .framework target. I will take
contributions. Message me if you are interested.
Depending on how you configure your project you may need to ``#import`` either
``<SocketRocket/SRWebSocket.h>`` or ``"SRWebSocket.h"``
Framework Dependencies
``````````````````````
Your .app must be linked against the following frameworks/dylibs
- libicucore.dylib
- CFNetwork.framework
- Security.framework
- Foundation.framework
Installing (OS X)
-----------------
SocketRocket now has (64-bit only) OS X support. ``SocketRocket.framework``
inside Xcode project is for OS X only. It should be identical in function aside
from the unicode validation. ICU isn't shipped with OS X which is what the
original implementation used for unicode validation. The workaround is much
more rudimentary and less robust.
1. Add SocketRocket.xcodeproj as either a subproject of your app or in your workspace.
2. Add ``SocketRocket.framework`` to the link libraries
3. If you don't have a "copy files" step for ``Framework``, create one
4. Add ``SocketRocket.framework`` to the "copy files" step.
API
---
The classes
``SRWebSocket``
```````````````
The Web Socket.
.. note:: ``SRWebSocket`` will retain itself between ``-(void)open`` and when it
closes, errors, or fails. This is similar to how ``NSURLConnection`` behaves.
(unlike ``NSURLConnection``, ``SRWebSocket`` won't retain the delegate)
What you need to know
.. code-block:: objective-c
@interface SRWebSocket : NSObject
// Make it with this
- (id)initWithURLRequest:(NSURLRequest *)request;
// Set this before opening
@property (nonatomic, assign) id <SRWebSocketDelegate> delegate;
- (void)open;
// Close it with this
- (void)close;
// Send a UTF8 String or Data
- (void)send:(id)data;
@end
``SRWebSocketDelegate``
```````````````````````
You implement this
.. code-block:: objective-c
@protocol SRWebSocketDelegate <NSObject>
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message;
@optional
- (void)webSocketDidOpen:(SRWebSocket *)webSocket;
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error;
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean;
@end
Known Issues/Server Todo's
--------------------------
- Needs auth delegates (like in NSURLConnection)
- Move the streams off the main runloop (most of the work is backgrounded uses
GCD, but I just haven't gotten around to moving it off the main loop since I
converted it from dispatch_io)
- Re-implement server. I removed an existing implementation as well because it
wasn't being used and I wasn't super happy with the interface. Will revisit
this.
- Separate framer and client logic. This will make it nicer when having a
server.
Testing
-------
Included are setup scripts for the python testing environment. It comes
packaged with vitualenv so all the dependencies are installed in userland.
To run the short test from the command line, run::
make test
To run all the tests, run::
make test_all
The short tests don't include the performance tests. (the test harness is
actually the bottleneck, not SocketRocket).
The first time this is run, it may take a while to install the dependencies. It
will be smooth sailing after that. After the test runs the makefile will open
the results page in your browser. If nothing comes up, you failed. Working on
making this interface a bit nicer.
To run from the app, choose the ``SocketRocket`` target and run the test action
(``cmd+u``). It runs the same thing, but makes it easier to debug. There is
some serious pre/post hooks in the Test action. You can edit it to customize
behavior.
.. note:: Xcode only up to version 4.4 is currently supported for the test
harness
TestChat Demo Application
-------------------------
SocketRocket includes a demo app, TestChat. It will "chat" with a listening
websocket on port 9900.
It's a simple project. Uses storyboard. Storyboard is sweet.
TestChat Server
```````````````
We've included a small server for the chat app. It has a simple function.
It will take a message and broadcast it to all other connected clients.
We have to get some dependencies. We also want to reuse the virtualenv we made
when we ran the tests. If you haven't run the tests yet, go into the
SocketRocket root directory and type::
make test
This will set up your `virtualenv <https://pypi.python.org/pypi/virtualenv>`_.
Now, in your terminal::
source .env/bin/activate
pip install git+https://github.com/tornadoweb/tornado.git
In the same terminal session, start the chatroom server::
python TestChatServer/py/chatroom.py
There's also a Go implementation (with the latest weekly) where you can::
cd TestChatServer/go
go run chatroom.go
Chatting
````````
Now, start TestChat.app (just run the target in the Xcode project). If you had
it started already you can hit the refresh button to reconnect. It should say
"Connected!" on top.
To talk with the app, open up your browser to `http://localhost:9000 <http://localhost:9000>`_ and
start chatting.
WebSocket Server Implementation Recommendations
-----------------------------------------------
SocketRocket has been used with the following libraries:
- `Tornado <https://github.com/tornadoweb/tornado>`_
- Go's `WebSocket package <https://godoc.org/golang.org/x/net/websocket>`_ or Gorilla's `version <http://www.gorillatoolkit.org/pkg/websocket>`_
- `Autobahn <http://autobahn.ws/testsuite/>`_ (using its fuzzing
client)
The Tornado one is dirt simple and works like a charm. (`IPython notebook
<http://ipython.org/ipython-doc/dev/interactive/htmlnotebook.html>`_ uses it
too). It's much easier to configure handlers and routes than in
Autobahn/twisted.
As far as Go's goes, it works in my limited testing. I much prefer go's
concurrency model as well. Try it! You may like it.
It could use some more control over things such as pings, etc., but I
am sure it will come in time.
Autobahn is a great test suite. The Python server code is good, and conforms
well (obviously). However for me, twisted would be a deal-breaker for writing
something new. I find it a bit too complex and heavy for a simple service. If
you are already using twisted though, Autobahn is probably for you.
Contributing
------------
Were glad youre interested in SocketRocket, and wed love to see where you take it. Please read our `contributing guidelines <https://github.com/facebook/SocketRocket/blob/master/Contributing.md>`_ prior to submitting a Pull Request.

View File

@ -1,15 +1,15 @@
Pod::Spec.new do |s|
s.name = 'SocketRocket'
s.name = "SocketRocket"
s.version = '0.5.1'
s.summary = 'A conforming WebSocket (RFC 6455) client library for iOS, macOS and tvOS.'
s.summary = 'A conforming WebSocket (RFC 6455) client library.'
s.homepage = 'https://github.com/facebook/SocketRocket'
s.authors = { 'Nikita Lutsenko' => 'nlutsenko@me.com', 'Dan Federman' => 'federman@squareup.com', 'Mike Lewis' => 'mikelikespie@gmail.com' }
s.authors = 'Square'
s.license = 'BSD'
s.source = { :git => 'https://github.com/facebook/SocketRocket.git', :tag => s.version.to_s }
s.requires_arc = true
s.source_files = 'SocketRocket/**/*.{h,m}'
s.public_header_files = 'SocketRocket/*.h'
s.public_header_files = 'SocketRocket/*.h'
s.ios.deployment_target = '6.0'
s.osx.deployment_target = '10.8'
@ -18,5 +18,6 @@ Pod::Spec.new do |s|
s.ios.frameworks = 'CFNetwork', 'Security'
s.osx.frameworks = 'CoreServices', 'Security'
s.tvos.frameworks = 'CFNetwork', 'Security'
s.libraries = 'icucore'
s.libraries = "icucore"
end

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "F668C87F153E91210044DBAC"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-macOS"
BlueprintName = "SocketRocket-OSX"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -47,7 +47,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "F668C87F153E91210044DBAC"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-macOS"
BlueprintName = "SocketRocket-OSX"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
@ -65,7 +65,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "F668C87F153E91210044DBAC"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-macOS"
BlueprintName = "SocketRocket-OSX"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS-Dynamic"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS-Dynamic"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS-Dynamic"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
version = "1.7">
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
@ -14,26 +14,12 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "81D6475F1D2CA78800690609"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
@ -41,63 +27,8 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid&#10;&#10;if [ -r $PIDFILE ]; then&#10;EXISTING_PID=`cat $PIDFILE`&#10;echo &quot;Killing Dangling Autobahn Server PID:&quot; $EXISTING_PID&#10;kill $EXISTING_PID || true&#10;rm $PIDFILE&#10;fi&#10;&#10;pushd $PROJECT_DIR&#10;&#10;source .env/bin/activate&#10;nohup ./TestSupport/run_test_server.sh &amp;&#10;&#10;echo $! &gt; $PIDFILE&#10;&#10;popd&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid&#10;&#10;if [ -r $PIDFILE ]; then&#10;EXISTING_PID=`cat $PIDFILE`&#10;echo &quot;Killing SR TestServer PID:&quot; $EXISTING_PID&#10;kill $EXISTING_PID&#10;rm $PIDFILE&#10;fi&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "81D6475F1D2CA78800690609"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
@ -114,7 +45,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "81D6475F1D2CA78800690609"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
@ -132,7 +63,7 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "81D6475F1D2CA78800690609"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -34,7 +34,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "2D4227611BB4358C000C1A6C"
BuildableName = "SocketRocket.framework"
BlueprintName = "SocketRocket-iOS-Dynamic"
BlueprintName = "SocketRocket-iOS"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6B2082C1450F597009315AF"
BuildableName = "libSocketRocket.a"
BlueprintName = "SocketRocket"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "NO">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid&#10;&#10;if [ -r $PIDFILE ]; then&#10; EXISTING_PID=`cat $PIDFILE`&#10; echo &quot;Killing Dangling Autobahn Server PID:&quot; $EXISTING_PID&#10; kill $EXISTING_PID || true&#10; rm $PIDFILE&#10;fi&#10;&#10;pushd $PROJECT_DIR&#10;&#10;source .env/bin/activate&#10;nohup ./TestSupport/run_test_server.sh &amp;&#10;&#10;echo $! &gt; $PIDFILE&#10;&#10;popd&#10;"
shellToInvoke = "/bin/bash">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<PostActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "PIDFILE=$TMPDIR/sr_test_server.pid&#10;&#10;if [ -r $PIDFILE ]; then&#10; EXISTING_PID=`cat $PIDFILE`&#10; echo &quot;Killing SR TestServer PID:&quot; $EXISTING_PID&#10; kill $EXISTING_PID&#10; rm $PIDFILE&#10;fi&#10;"
shellToInvoke = "/bin/bash">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PostActions>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6B2082C1450F597009315AF"
BuildableName = "libSocketRocket.a"
BlueprintName = "SocketRocket"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "SR_TEST_URL"
value = "ws://localhost:9001"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6B2082C1450F597009315AF"
BuildableName = "libSocketRocket.a"
BlueprintName = "SocketRocket"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "OBJC_PRINT_EXCEPTIONS"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocGuardEdges"
value = ""
isEnabled = "YES">
</AdditionalOption>
<AdditionalOption
key = "MallocScribble"
value = ""
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@ -15,8 +15,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -33,8 +33,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</TestableReference>
@ -56,8 +56,8 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "F6BDA801145900D200FE3253"
BuildableName = "SocketRocketTests-iOS.xctest"
BlueprintName = "SocketRocketTests-iOS"
BuildableName = "SRWebSocketTests.xctest"
BlueprintName = "SRWebSocketTests"
ReferencedContainer = "container:SocketRocket.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0730"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -9,12 +9,10 @@
#import <Foundation/Foundation.h>
#import <SocketRocket/SRWebSocket.h>
#import "SRWebSocket.h"
NS_ASSUME_NONNULL_BEGIN
#if OBJC_BOOL_IS_BOOL
struct SRDelegateAvailableMethods {
BOOL didReceiveMessage : 1;
BOOL didReceiveMessageWithString : 1;
@ -22,27 +20,9 @@ struct SRDelegateAvailableMethods {
BOOL didOpen : 1;
BOOL didFailWithError : 1;
BOOL didCloseWithCode : 1;
BOOL didReceivePing : 1;
BOOL didReceivePong : 1;
BOOL shouldConvertTextFrameToString : 1;
};
#else
struct SRDelegateAvailableMethods {
BOOL didReceiveMessage;
BOOL didReceiveMessageWithString;
BOOL didReceiveMessageWithData;
BOOL didOpen;
BOOL didFailWithError;
BOOL didCloseWithCode;
BOOL didReceivePing;
BOOL didReceivePong;
BOOL shouldConvertTextFrameToString;
};
#endif
typedef struct SRDelegateAvailableMethods SRDelegateAvailableMethods;
typedef void(^SRDelegateBlock)(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods);

View File

@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, strong, readonly) dispatch_queue_t accessQueue;
@property (atomic, assign, readwrite) SRDelegateAvailableMethods availableDelegateMethods;
@property (atomic, readwrite) SRDelegateAvailableMethods availableDelegateMethods;
@end
@ -56,7 +56,6 @@ NS_ASSUME_NONNULL_BEGIN
.didOpen = [delegate respondsToSelector:@selector(webSocketDidOpen:)],
.didFailWithError = [delegate respondsToSelector:@selector(webSocket:didFailWithError:)],
.didCloseWithCode = [delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)],
.didReceivePing = [delegate respondsToSelector:@selector(webSocket:didReceivePingWithData:)],
.didReceivePong = [delegate respondsToSelector:@selector(webSocket:didReceivePong:)],
.shouldConvertTextFrameToString = [delegate respondsToSelector:@selector(webSocketShouldConvertTextFrameToString:)]
};

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <SocketRocket/NSRunLoop+SRWebSocket.h>
// Empty function that force links the object file for the category.
extern void import_NSRunLoop_SRWebSocket(void);

View File

@ -1,13 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <SocketRocket/NSURLRequest+SRWebSocket.h>
// Empty function that force links the object file for the category.
extern void import_NSURLRequest_SRWebSocket(void);

View File

@ -8,12 +8,10 @@
//
#import "SRProxyConnect.h"
#import "NSRunLoop+SRWebSocket.h"
#import "SRConstants.h"
#import "SRError.h"
#import "SRLog.h"
#import "NSRunLoop+SRWebSocket.h"
#import "SRURLUtilities.h"
#import "SRLog.h"
@interface SRProxyConnect() <NSStreamDelegate>
@ -166,14 +164,14 @@
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationURL]) {
NSURL *pacURL = settings[(NSString *)kCFProxyAutoConfigurationURLKey];
if (pacURL) {
[self _fetchPAC:pacURL withProxySettings:proxySettings];
[self _fetchPAC:pacURL];
return;
}
}
if ([proxyType isEqualToString:(__bridge NSString *)kCFProxyTypeAutoConfigurationJavaScript]) {
NSString *script = settings[(__bridge NSString *)kCFProxyAutoConfigurationJavaScriptKey];
if (script) {
[self _runPACScript:script withProxySettings:proxySettings];
[self _runPACScript:script];
return;
}
}
@ -188,9 +186,8 @@
[proxyType isEqualToString:(NSString *)kCFProxyTypeHTTPS]) {
_httpProxyHost = settings[(NSString *)kCFProxyHostNameKey];
NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
if (portValue) {
if (portValue)
_httpProxyPort = [portValue intValue];
}
}
if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
_socksProxyHost = settings[(NSString *)kCFProxyHostNameKey];
@ -209,7 +206,7 @@
}
}
- (void)_fetchPAC:(NSURL *)PACurl withProxySettings:(NSDictionary *)proxySettings
- (void)_fetchPAC:(NSURL *)PACurl
{
SRDebugLog(@"SRWebSocket fetchPAC:%@", PACurl);
@ -222,7 +219,7 @@
if (error) {
[self _openConnection];
} else {
[self _runPACScript:script withProxySettings:proxySettings];
[self _runPACScript:script];
}
return;
}
@ -234,21 +231,24 @@
[self _openConnection];
return;
}
__weak typeof(self) wself = self;
__weak typeof(self) weakSelf = self;
NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
__strong typeof(wself) sself = wself;
if (!error) {
NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[sself _runPACScript:script withProxySettings:proxySettings];
} else {
[sself _openConnection];
}
}] resume];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSString* script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
[weakSelf _runPACScript:script];
} else {
[weakSelf _openConnection];
}
}];
[task resume];
}
- (void)_runPACScript:(NSString *)script withProxySettings:(NSDictionary *)proxySettings
- (void)_runPACScript:(NSString *)script
{
if (!script) {
[self _openConnection];
@ -259,7 +259,8 @@
// Work around <rdar://problem/5530166>. This dummy call to
// CFNetworkCopyProxiesForURL initialise some state within CFNetwork
// that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)proxySettings));
NSDictionary *empty = nil;
CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)empty));
// Obtain the list of proxies by running the autoconfiguration script
CFErrorRef err = NULL;
@ -359,8 +360,7 @@
[self _processInputStream];
}
} break;
case NSStreamEventHasSpaceAvailable:
case NSStreamEventNone:
default:
SRDebugLog(@"(default) %@", aStream);
break;
}
@ -382,12 +382,14 @@
[self _writeData:message];
}
static size_t const SRProxyConnectBufferMaxSize = 4096;
///handles the incoming bytes and sending them to the proper processing method
- (void)_processInputStream
{
NSMutableData *buf = [NSMutableData dataWithCapacity:SRDefaultBufferSize()];
NSMutableData *buf = [NSMutableData dataWithCapacity:SRProxyConnectBufferMaxSize];
uint8_t *buffer = buf.mutableBytes;
NSInteger length = [_inputStream read:buffer maxLength:SRDefaultBufferSize()];
NSInteger length = [_inputStream read:buffer maxLength:SRProxyConnectBufferMaxSize];
if (length <= 0) {
return;
@ -406,17 +408,13 @@
- (void)_dequeueInput
{
while (_inputQueue.count > 0) {
NSData *data = _inputQueue.firstObject;
NSData * data = _inputQueue[0];
[self _proxyProcessHTTPResponseWithData:data];
[_inputQueue removeObjectAtIndex:0];
// No need to process any data further, we got the full header data.
if ([self _proxyProcessHTTPResponseWithData:data]) {
break;
}
}
}
//handle checking the proxy connection status
- (BOOL)_proxyProcessHTTPResponseWithData:(NSData *)data
- (void)_proxyProcessHTTPResponseWithData:(NSData *)data
{
if (_receivedHTTPHeaders == NULL) {
_receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
@ -426,10 +424,7 @@
if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
[self _proxyHTTPHeadersDidFinish];
return YES;
}
return NO;
}
- (void)_proxyHTTPHeadersDidFinish
@ -453,14 +448,13 @@ static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0;
- (void)_writeData:(NSData *)data
{
const uint8_t * bytes = data.bytes;
__block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up
__block NSInteger timeout = SRProxyConnectWriteTimeout * 1000000; // wait timeout before giving up
__weak typeof(self) wself = self;
dispatch_async(_writeQueue, ^{
__strong typeof(wself) sself = self;
if (!sself) {
if (!wself) {
return;
}
NSOutputStream *outStream = sself.outputStream;
NSOutputStream *outStream = wself.outputStream;
if (!outStream) {
return;
}
@ -469,13 +463,12 @@ static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0;
timeout -= 100;
if (timeout < 0) {
NSError *error = SRHTTPErrorWithCodeDescription(408, 2132, @"Proxy timeout");
[sself _failWithError:error];
[self _failWithError:error];
} else if (outStream.streamError != nil) {
[sself _failWithError:outStream.streamError];
[self _failWithError:outStream.streamError];
}
}
[outStream write:bytes maxLength:data.length];
});
}
@end

View File

@ -12,15 +12,21 @@
#import "SRRunLoopThread.h"
@interface SRRunLoopThread ()
{
dispatch_group_t _waitGroup;
}
@property (nonatomic, strong, readwrite) NSRunLoop *runLoop;
@end
@implementation SRRunLoopThread
@implementation SRRunLoopThread {
dispatch_group_t _waitGroup;
}
- (void)dealloc
{
#if !OS_OBJECT_USE_OBJC_RETAIN_RELEASE
dispatch_release(_waitGroup);
#endif
}
+ (instancetype)sharedThread
{

View File

@ -1,26 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSInteger, SROpCode)
{
SROpCodeTextFrame = 0x1,
SROpCodeBinaryFrame = 0x2,
// 3-7 reserved.
SROpCodeConnectionClose = 0x8,
SROpCodePing = 0x9,
SROpCodePong = 0xA,
// B-F reserved.
};
/**
Default buffer size that is used for reading/writing to streams.
*/
extern size_t SRDefaultBufferSize(void);

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "SRConstants.h"
size_t SRDefaultBufferSize(void) {
static size_t size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size = getpagesize();
});
return size;
}

View File

@ -1,27 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
#import <SocketRocket/SRSecurityPolicy.h>
NS_ASSUME_NONNULL_BEGIN
/**
* NOTE: While publicly, SocketRocket does not support configuring the security policy with pinned certificates,
* it is still possible to manually construct a security policy of this class. If you do this, note that you may
* be open to MitM attacks, and we will not support any issues you may have. Dive at your own risk.
*/
@interface SRPinningSecurityPolicy : SRSecurityPolicy
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,73 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "SRPinningSecurityPolicy.h"
#import <Foundation/Foundation.h>
#import "SRLog.h"
NS_ASSUME_NONNULL_BEGIN
@interface SRPinningSecurityPolicy ()
@property (nonatomic, copy, readonly) NSArray *pinnedCertificates;
@end
@implementation SRPinningSecurityPolicy
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
// Do not validate certificate chain since we're pinning to specific certificates.
self = [super initWithCertificateChainValidationEnabled:NO];
#pragma clang diagnostic pop
if (!self) { return self; }
if (pinnedCertificates.count == 0) {
@throw [NSException exceptionWithName:@"Creating security policy failed."
reason:@"Must specify at least one certificate when creating a pinning policy."
userInfo:nil];
}
_pinnedCertificates = [pinnedCertificates copy];
return self;
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count);
NSUInteger requiredCertCount = self.pinnedCertificates.count;
NSUInteger validatedCertCount = 0;
CFIndex serverCertCount = SecTrustGetCertificateCount(serverTrust);
for (CFIndex i = 0; i < serverCertCount; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i);
NSData *data = CFBridgingRelease(SecCertificateCopyData(cert));
for (id ref in self.pinnedCertificates) {
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
// TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time.
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
if ([trustedCertData isEqualToData:data]) {
validatedCertCount++;
break;
}
}
}
return (requiredCertCount == validatedCertCount);
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,74 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface SRSecurityOptions: NSObject
@property (nonatomic, strong, readonly) NSURLRequest *request;
/**
Returns `YES` if request uses SSL, otherwise - `NO`.
*/
@property (nonatomic, assign, readonly) BOOL requestRequiresSSL;
/**
Optional array of `SecCertificateRef` SSL certificates to use for validation.
*/
@property (nullable, nonatomic, strong, readonly) NSArray *pinnedCertificates;
/**
Set to `NO` to disable SSL certificate chain validation.
This option is not taken into account when using pinned certificates.
Default: YES.
*/
@property (nonatomic, assign, readonly) BOOL validatesCertificateChain;
/**
Initializes an instance of a controller into it with a given request and returns it.
@param request Request to initialize with.
*/
- (instancetype)initWithRequest:(NSURLRequest *)request
pinnedCertificates:(nullable NSArray *)pinnedCertificates
chainValidationEnabled:(BOOL)chainValidationEnabled NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
///--------------------------------------
#pragma mark - Streams
///--------------------------------------
/**
Updates all the security options for the current configuration.
@param stream Stream to update the options in.
*/
- (void)updateSecurityOptionsInStream:(NSStream *)stream;
///--------------------------------------
#pragma mark - Pinned Certificates
///--------------------------------------
/**
Validates whether a given security trust contains pinned certificates.
If no certificates are pinned - returns `YES`.
@param trust Security trust to validate.
@return `YES` if certificates where found, otherwise - `NO`.
*/
- (BOOL)securityTrustContainsPinnedCertificates:(SecTrustRef)trust;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,88 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "SRSecurityOptions.h"
#import "SRURLUtilities.h"
NS_ASSUME_NONNULL_BEGIN
@implementation SRSecurityOptions
///--------------------------------------
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithRequest:(NSURLRequest *)request
pinnedCertificates:(nullable NSArray *)pinnedCertificates
chainValidationEnabled:(BOOL)chainValidationEnabled
{
self = [super init];
if (!self) return self;
_request = request;
_requestRequiresSSL = SRURLRequiresSSL(request.URL);
_pinnedCertificates = pinnedCertificates;
_validatesCertificateChain = chainValidationEnabled;
return self;
}
///--------------------------------------
#pragma mark - Stream
///---------------------------------------
- (void)updateSecurityOptionsInStream:(NSStream *)stream
{
// SSL not required, skip everything
if (!self.requestRequiresSSL) {
return;
}
// Enable highest level of security (`.LevelNegotiatedSSL`) for the stream.
[stream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey];
// If we are not using pinned certs and if chain validation is enabled - enable it on a stream.
BOOL chainValidationEnabled = (_pinnedCertificates.count == 0 && self.validatesCertificateChain);
NSDictionary<NSString *, id> *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(chainValidationEnabled) };
[stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
}
///--------------------------------------
#pragma mark - Pinned Certificates
///--------------------------------------
- (BOOL)securityTrustContainsPinnedCertificates:(SecTrustRef)trust
{
NSUInteger requiredCertCount = self.pinnedCertificates.count;
if (requiredCertCount == 0) {
return YES;
}
NSUInteger validatedCertCount = 0;
CFIndex serverCertCount = SecTrustGetCertificateCount(trust);
for (CFIndex i = 0; i < serverCertCount; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
NSData *data = CFBridgingRelease(SecCertificateCopyData(cert));
for (id ref in self.pinnedCertificates) {
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
// TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time.
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
if ([trustedCertData isEqualToData:data]) {
validatedCertCount++;
break;
}
}
}
return (requiredCertCount == validatedCertCount);
}
@end
NS_ASSUME_NONNULL_END

View File

@ -8,7 +8,6 @@
//
#import "SRError.h"
#import "SRWebSocket.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -42,14 +42,12 @@ CFHTTPMessageRef SRHTTPConnectMessageCreate(NSURLRequest *request,
}
// Apply cookies if any have been provided
if (cookies) {
NSDictionary<NSString *, NSString *> *messageCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[messageCookies enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
if (key.length && obj.length) {
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}
}];
}
NSDictionary<NSString *, NSString *> *messageCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:cookies];
[messageCookies enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
if (key.length && obj.length) {
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
}
}];
// set header for http basic auth
NSString *basicAuthorizationString = SRBasicAuthorizationHeaderFromURL(url);

View File

@ -29,7 +29,6 @@ SRMutex SRMutexInitRecursive(void)
void SRMutexDestroy(SRMutex mutex)
{
pthread_mutex_destroy(mutex);
free(mutex);
}
__attribute__((no_thread_safety_analysis))

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
/**
Unmask bytes using XOR via SIMD.
@param bytes The bytes to unmask.
@param length The number of bytes to unmask.
@param maskKey The mask to XOR with MUST be of length sizeof(uint32_t).
*/
void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey);

View File

@ -1,73 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "SRSIMDHelpers.h"
typedef uint8_t uint8x32_t __attribute__((vector_size(32)));
static void SRMaskBytesManual(uint8_t *bytes, size_t length, uint8_t *maskKey) {
for (size_t i = 0; i < length; i++) {
bytes[i] = bytes[i] ^ maskKey[i % sizeof(uint32_t)];
}
}
/**
Right-shift the elements of a vector, circularly.
@param vector The vector to circular shift.
@param by The number of elements to shift by.
@return A shifted vector.
*/
static uint8x32_t SRShiftVector(uint8x32_t vector, size_t by) {
uint8x32_t vectorCopy = vector;
by = by % _Alignof(uint8x32_t);
uint8_t *vectorPointer = (uint8_t *)&vector;
uint8_t *vectorCopyPointer = (uint8_t *)&vectorCopy;
memmove(vectorPointer + by, vectorPointer, sizeof(vector) - by);
memcpy(vectorPointer, vectorCopyPointer + (sizeof(vector) - by), by);
return vector;
}
void SRMaskBytesSIMD(uint8_t *bytes, size_t length, uint8_t *maskKey) {
size_t alignmentBytes = _Alignof(uint8x32_t) - ((uintptr_t)bytes % _Alignof(uint8x32_t));
if (alignmentBytes == _Alignof(uint8x32_t)) {
alignmentBytes = 0;
}
// If the number of bytes that can be processed after aligning is
// less than the number of bytes we can put into a vector,
// then there's no work to do with SIMD, just call the manual version.
if (alignmentBytes > length || (length - alignmentBytes) < sizeof(uint8x32_t)) {
SRMaskBytesManual(bytes, length, maskKey);
return;
}
size_t vectorLength = (length - alignmentBytes) / sizeof(uint8x32_t);
size_t manualStartOffset = alignmentBytes + (vectorLength * sizeof(uint8x32_t));
size_t manualLength = length - manualStartOffset;
uint8x32_t *vector = (uint8x32_t *)(bytes + alignmentBytes);
uint8x32_t maskVector = { };
memset_pattern4(&maskVector, maskKey, sizeof(uint8x32_t));
maskVector = SRShiftVector(maskVector, alignmentBytes);
SRMaskBytesManual(bytes, alignmentBytes, maskKey);
for (size_t vectorIndex = 0; vectorIndex < vectorLength; vectorIndex++) {
vector[vectorIndex] = vector[vectorIndex] ^ maskVector;
}
// Use the shifted mask for the final manual part.
SRMaskBytesManual(bytes + manualStartOffset, manualLength, (uint8_t *) &maskVector);
}

View File

@ -20,7 +20,4 @@ extern BOOL SRURLRequiresSSL(NSURL *url);
// Extracts `user` and `password` from url (if available) into `Basic base64(user:password)`.
extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url);
// Returns a valid value for `NSStreamNetworkServiceType` or `nil`.
extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request);
NS_ASSUME_NONNULL_END

View File

@ -9,8 +9,6 @@
#import "SRURLUtilities.h"
#import "SRHash.h"
NS_ASSUME_NONNULL_BEGIN
NSString *SRURLOrigin(NSURL *url)
@ -44,34 +42,16 @@ extern BOOL SRURLRequiresSSL(NSURL *url)
extern NSString *_Nullable SRBasicAuthorizationHeaderFromURL(NSURL *url)
{
NSData *data = [[NSString stringWithFormat:@"%@:%@", url.user, url.password] dataUsingEncoding:NSUTF8StringEncoding];
return [NSString stringWithFormat:@"Basic %@", SRBase64EncodedStringFromData(data)];
}
extern NSString *_Nullable SRStreamNetworkServiceTypeFromURLRequest(NSURLRequest *request)
{
NSString *networkServiceType = nil;
switch (request.networkServiceType) {
case NSURLNetworkServiceTypeDefault:
break;
case NSURLNetworkServiceTypeVoIP:
networkServiceType = NSStreamNetworkServiceTypeVoIP;
break;
case NSURLNetworkServiceTypeVideo:
networkServiceType = NSStreamNetworkServiceTypeVideo;
break;
case NSURLNetworkServiceTypeBackground:
networkServiceType = NSStreamNetworkServiceTypeBackground;
break;
case NSURLNetworkServiceTypeVoice:
networkServiceType = NSStreamNetworkServiceTypeVoice;
break;
#if (__MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 || __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000 || __TV_OS_VERSION_MAX_ALLOWED >= 100000 || __WATCH_OS_VERSION_MAX_ALLOWED >= 30000)
case NSURLNetworkServiceTypeCallSignaling:
networkServiceType = NSStreamNetworkServiceTypeCallSignaling;
break;
#endif
NSString *userAndPasswordBase64Encoded;
if ([data respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
userAndPasswordBase64Encoded = [data base64EncodedStringWithOptions:0];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
userAndPasswordBase64Encoded = [data base64Encoding];
#pragma clang diagnostic pop
}
return networkServiceType;
return [NSString stringWithFormat:@"Basic %@", userAndPasswordBase64Encoded];
}
NS_ASSUME_NONNULL_END

20
SocketRocket/Makefile Normal file
View File

@ -0,0 +1,20 @@
BINS := SocketRocket.framework libSocketRocket.a
all: $(BINS)
HEADERS := SRWebSocket.h
SRCS := SRWebSocket.m
OBJS := $(SRCS:%.m=%.o)
CFLAGS += -fobjc-arc
libSocketRocket.a: $(OBJS)
$(AR) -rc $(AFLAGS) $@ $^
SocketRocket.framework: libSocketRocket.a
mkdir -p $@/Headers
cp -f $(HEADERS) $@/Headers
cp $^ $@/SocketRocket
clean:
rm -r $(OBJS) $(BINS)

View File

@ -10,13 +10,9 @@
//
#import "NSRunLoop+SRWebSocket.h"
#import "NSRunLoop+SRWebSocketPrivate.h"
#import "SRRunLoopThread.h"
// Required for object file to always be linked.
void import_NSRunLoop_SRWebSocket() { }
@implementation NSRunLoop (SRWebSocket)
+ (NSRunLoop *)SR_networkRunLoop

View File

@ -18,9 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
/**
An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation.
*/
@property (nullable, nonatomic, copy, readonly) NSArray *SR_SSLPinnedCertificates
DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, "
"and leads to security issues. Please use a proper, trust chain validated certificate.");
@property (nullable, nonatomic, strong, readonly) NSArray *SR_SSLPinnedCertificates;
@end
@ -29,9 +27,7 @@ NS_ASSUME_NONNULL_BEGIN
/**
An array of pinned `SecCertificateRef` SSL certificates that `SRWebSocket` will use for validation.
*/
@property (nullable, nonatomic, copy) NSArray *SR_SSLPinnedCertificates
DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, "
"and leads to security issues. Please use a proper, trust chain validated certificate.");
@property (nullable, nonatomic, strong) NSArray *SR_SSLPinnedCertificates;
@end

View File

@ -10,31 +10,28 @@
//
#import "NSURLRequest+SRWebSocket.h"
#import "NSURLRequest+SRWebSocketPrivate.h"
// Required for object file to always be linked.
void import_NSURLRequest_SRWebSocket() { }
NS_ASSUME_NONNULL_BEGIN
static NSString *const SRSSLPinnnedCertificatesKey = @"SocketRocket_SSLPinnedCertificates";
@implementation NSURLRequest (SRWebSocket)
- (nullable NSArray *)SR_SSLPinnedCertificates
{
return nil;
return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self];
}
@end
@implementation NSMutableURLRequest (SRWebSocket)
- (nullable NSArray *)SR_SSLPinnedCertificates
{
return [NSURLProtocol propertyForKey:@"SR_SSLPinnedCertificates" inRequest:self];
}
- (void)setSR_SSLPinnedCertificates:(nullable NSArray *)SR_SSLPinnedCertificates
{
[NSException raise:NSInvalidArgumentException
format:@"Using pinned certificates is neither secure nor supported in SocketRocket, "
"and leads to security issues. Please use a proper, trust chain validated certificate."];
[NSURLProtocol setProperty:SR_SSLPinnedCertificates forKey:@"SR_SSLPinnedCertificates" inRequest:self];
}
@end

View File

@ -1,72 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
#import <Security/Security.h>
NS_ASSUME_NONNULL_BEGIN
@interface SRSecurityPolicy : NSObject
/**
A default `SRSecurityPolicy` implementation specifies socket security and
validates the certificate chain.
Use a subclass of `SRSecurityPolicy` for more fine grained customization.
*/
+ (instancetype)defaultPolicy;
/**
Specifies socket security and provider certificate pinning, disregarding certificate
chain validation.
@param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation.
*/
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
DEPRECATED_MSG_ATTRIBUTE("Using pinned certificates is neither secure nor supported in SocketRocket, "
"and leads to security issues. Please use a proper, trust chain validated certificate.");
/**
Specifies socket security and optional certificate chain validation.
@param enabled Whether or not to validate the SSL certificate chain. If you
are considering using this method because your certificate was not issued by a
recognized certificate authority, consider using `pinningPolicyWithCertificates` instead.
*/
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. "
"Please use a proper Certificate Authority to issue your TLS certificates.")
NS_DESIGNATED_INITIALIZER;
/**
Updates all the security options for input and output streams, for example you
can set your socket security level here.
@param stream Stream to update the options in.
*/
- (void)updateSecurityOptionsInStream:(NSStream *)stream;
/**
Whether or not the specified server trust should be accepted, based on the security policy.
This method should be used when responding to an authentication challenge from
a server. In the default implemenation, no further validation is done here, but
you're free to override it in a subclass. See `SRPinningSecurityPolicy.h` for
an example.
@param serverTrust The X.509 certificate trust of the server.
@param domain The domain of serverTrust.
@return Whether or not to trust the server.
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;
@end
NS_ASSUME_NONNULL_END

View File

@ -1,75 +0,0 @@
//
// Copyright (c) 2016-present, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "SRSecurityPolicy.h"
#import "SRPinningSecurityPolicy.h"
NS_ASSUME_NONNULL_BEGIN
@interface SRSecurityPolicy ()
@property (nonatomic, assign, readonly) BOOL certificateChainValidationEnabled;
@end
@implementation SRSecurityPolicy
+ (instancetype)defaultPolicy
{
return [self new];
}
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
{
[NSException raise:NSInvalidArgumentException
format:@"Using pinned certificates is neither secure nor supported in SocketRocket, "
"and leads to security issues. Please use a proper, trust chain validated certificate."];
return nil;
}
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
{
self = [super init];
if (!self) { return self; }
_certificateChainValidationEnabled = enabled;
return self;
}
- (instancetype)init
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
return [self initWithCertificateChainValidationEnabled:YES];
#pragma clang diagnostic pop
}
- (void)updateSecurityOptionsInStream:(NSStream *)stream
{
// Enforce TLS 1.2
[stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
// Validate certificate chain for this stream if enabled.
NSDictionary<NSString *, id> *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(self.certificateChainValidationEnabled) };
[stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
{
// No further evaluation happens in the default policy.
return YES;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -21,7 +21,7 @@ typedef NS_ENUM(NSInteger, SRReadyState) {
};
typedef NS_ENUM(NSInteger, SRStatusCode) {
// 0-999: Reserved and not used.
// 0999: Reserved and not used.
SRStatusCodeNormal = 1000,
SRStatusCodeGoingAway = 1001,
SRStatusCodeProtocolError = 1002,
@ -38,14 +38,13 @@ typedef NS_ENUM(NSInteger, SRStatusCode) {
SRStatusCodeTryAgainLater = 1013,
// 1014: Reserved for future use by the WebSocket standard.
SRStatusCodeTLSHandshake = 1015,
// 1016-1999: Reserved for future use by the WebSocket standard.
// 2000-2999: Reserved for use by WebSocket extensions.
// 3000-3999: Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve.
// 4000-4999: Available for use by applications.
// 10161999: Reserved for future use by the WebSocket standard.
// 20002999: Reserved for use by WebSocket extensions.
// 30003999: Available for use by libraries and frameworks. May not be used by applications. Available for registration at the IANA via first-come, first-serve.
// 40004999: Available for use by applications.
};
@class SRWebSocket;
@class SRSecurityPolicy;
/**
Error domain used for errors reported by SRWebSocket.
@ -133,14 +132,6 @@ extern NSString *const SRHTTPResponseErrorKey;
*/
- (instancetype)initWithURLRequest:(NSURLRequest *)request;
/**
Initializes a web socket with a given `NSURLRequest`, specifying a transport security policy (e.g. SSL configuration).
@param request Request to initialize with.
@param securityPolicy Policy object describing transport security behavior.
*/
- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy;
/**
Initializes a web socket with a given `NSURLRequest` and list of sub-protocols.
@ -157,17 +148,7 @@ extern NSString *const SRHTTPResponseErrorKey;
@param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`.
*/
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. "
"Please use a proper Certificate Authority to issue your TLS certificates.");
/**
Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed.
@param request Request to initialize with.
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
@param securityPolicy Policy object describing transport security behavior.
*/
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy NS_DESIGNATED_INITIALIZER;
NS_DESIGNATED_INITIALIZER;
/**
Initializes a web socket with a given `NSURL`.
@ -184,14 +165,6 @@ extern NSString *const SRHTTPResponseErrorKey;
*/
- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)protocols;
/**
Initializes a web socket with a given `NSURL`, specifying a transport security policy (e.g. SSL configuration).
@param url URL to initialize with.
@param securityPolicy Policy object describing transport security behavior.
*/
- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy;
/**
Initializes a web socket with a given `NSURL`, list of sub-protocols and whether untrusted SSL certificates are allowed.
@ -199,9 +172,7 @@ extern NSString *const SRHTTPResponseErrorKey;
@param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`.
@param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`.
*/
- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
DEPRECATED_MSG_ATTRIBUTE("Disabling certificate chain validation is unsafe. "
"Please use a proper Certificate Authority to issue your TLS certificates.");
- (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates;
/**
Unavailable initializer. Please use any other initializer.
@ -386,19 +357,11 @@ extern NSString *const SRHTTPResponseErrorKey;
*/
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(nullable NSString *)reason wasClean:(BOOL)wasClean;
/**
Called on receive of a ping message from the server.
@param webSocket An instance of `SRWebSocket` that received a ping frame.
@param data Payload that was received or `nil` if there was no payload.
*/
- (void)webSocket:(SRWebSocket *)webSocket didReceivePingWithData:(nullable NSData *)data;
/**
Called when a pong data was received in response to ping.
@param webSocket An instance of `SRWebSocket` that received a pong frame.
@param pongData Payload that was received or `nil` if there was no payload.
@param webSocket An instance of `SRWebSocket` that received a pong frame.
@param pongPayload Payload that was received or `nil` if there was no payload.
*/
- (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(nullable NSData *)pongData;

View File

@ -19,6 +19,12 @@
#import <unicode/utf8.h>
#endif
#if TARGET_OS_IPHONE
#import <Endian.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import <libkern/OSAtomic.h>
#import "SRDelegateController.h"
@ -30,26 +36,38 @@
#import "NSURLRequest+SRWebSocket.h"
#import "NSRunLoop+SRWebSocket.h"
#import "SRProxyConnect.h"
#import "SRSecurityPolicy.h"
#import "SRSecurityOptions.h"
#import "SRHTTPConnectMessage.h"
#import "SRRandom.h"
#import "SRLog.h"
#import "SRMutex.h"
#import "SRSIMDHelpers.h"
#import "NSURLRequest+SRWebSocketPrivate.h"
#import "NSRunLoop+SRWebSocketPrivate.h"
#import "SRConstants.h"
#if !__has_feature(objc_arc)
#error SocketRocket must be compiled with ARC enabled
#endif
__attribute__((used)) static void importCategories()
{
import_NSURLRequest_SRWebSocket();
import_NSRunLoop_SRWebSocket();
/**
Default buffer size that is used for reading/writing to streams.
*/
static size_t SRDefaultBufferSize(void) {
static size_t size;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size = getpagesize();
});
return size;
}
typedef enum {
SROpCodeTextFrame = 0x1,
SROpCodeBinaryFrame = 0x2,
// 3-7 reserved.
SROpCodeConnectionClose = 0x8,
SROpCodePing = 0x9,
SROpCodePong = 0xA,
// B-F reserved.
} SROpCode;
typedef struct {
BOOL fin;
// BOOL rsv1;
@ -109,8 +127,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
NSString *_secKey;
SRSecurityPolicy *_securityPolicy;
BOOL _requestRequiresSSL;
SRSecurityOptions *_securityOptions;
BOOL _streamSecurityValidated;
uint8_t _currentReadMaskKey[4];
@ -146,7 +163,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
#pragma mark - Init
///--------------------------------------
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
{
self = [super init];
if (!self) return self;
@ -154,9 +171,13 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
assert(request.URL);
_url = request.URL;
_urlRequest = request;
_allowsUntrustedSSLCertificates = allowsUntrustedSSLCertificates;
_requestedProtocols = [protocols copy];
_securityPolicy = securityPolicy;
_requestRequiresSSL = SRURLRequiresSSL(_url);
_securityOptions = [[SRSecurityOptions alloc] initWithRequest:request
pinnedCertificates:request.SR_SSLPinnedCertificates
chainValidationEnabled:allowsUntrustedSSLCertificates];
_readyState = SR_CONNECTING;
@ -183,34 +204,9 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
return self;
}
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
{
SRSecurityPolicy *securityPolicy;
BOOL certificateChainValidationEnabled = !allowsUntrustedSSLCertificates;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
securityPolicy = [[SRSecurityPolicy alloc] initWithCertificateChainValidationEnabled:certificateChainValidationEnabled];
#pragma clang diagnostic pop
return [self initWithURLRequest:request protocols:protocols securityPolicy:securityPolicy];
}
- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy
{
return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy];
}
- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:NO];
#pragma clang diagnostic pop
}
- (instancetype)initWithURLRequest:(NSURLRequest *)request
@ -225,18 +221,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols;
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
return [self initWithURL:url protocols:protocols allowsUntrustedSSLCertificates:NO];
#pragma clang diagnostic pop
}
- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy];
}
- (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
@ -317,9 +302,10 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
_selfRetain = self;
if (_urlRequest.timeoutInterval > 0) {
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_urlRequest.timeoutInterval * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^{
if (_urlRequest.timeoutInterval > 0)
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
if (self.readyState == SR_CONNECTING) {
NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server.");
[self _failWithError:error];
@ -337,6 +323,8 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
- (void)_connectionDoneWithError:(NSError *)error readStream:(NSInputStream *)readStream writeStream:(NSOutputStream *)writeStream
{
_proxyConnect = nil; // Job's done! This is not longer required.
if (error != nil) {
[self _failWithError:error];
} else {
@ -353,17 +341,12 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
// If we don't require SSL validation - consider that we connected.
// Otherwise `didConnect` is called when SSL validation finishes.
if (!_requestRequiresSSL) {
if (!_securityOptions.requestRequiresSSL) {
dispatch_async(_workQueue, ^{
[self didConnect];
});
}
}
// Schedule to run on a work queue, to make sure we don't run this inline and deallocate `self` inside `SRProxyConnect`.
// TODO: (nlutsenko) Find a better structure for this, maybe Bolts Tasks?
dispatch_async(_workQueue, ^{
_proxyConnect = nil;
});
}
- (BOOL)_checkHandshake:(CFHTTPMessageRef)httpMessage;
@ -430,7 +413,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
_receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
}
[self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) {
[self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *self, NSData *data) {
CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
@ -465,13 +448,48 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
- (void)_updateSecureStreamOptions
{
if (_requestRequiresSSL) {
SRDebugLog(@"Setting up security for streams.");
[_securityPolicy updateSecurityOptionsInStream:_inputStream];
[_securityPolicy updateSecurityOptionsInStream:_outputStream];
SRDebugLog(@"Setting up security for streams.");
[_securityOptions updateSecurityOptionsInStream:_inputStream];
[_securityOptions updateSecurityOptionsInStream:_outputStream];
SRDebugLog(@"Allows connection any root cert: %d", _securityOptions.validatesCertificateChain);
SRDebugLog(@"Pinned cert count: %d", _securityOptions.pinnedCertificates.count);
_inputStream.delegate = self;
_outputStream.delegate = self;
[self setupNetworkServiceType:_urlRequest.networkServiceType];
}
- (void)setupNetworkServiceType:(NSURLRequestNetworkServiceType)requestNetworkServiceType
{
NSString *networkServiceType;
switch (requestNetworkServiceType) {
case NSURLNetworkServiceTypeDefault:
break;
case NSURLNetworkServiceTypeVoIP: {
networkServiceType = NSStreamNetworkServiceTypeVoIP;
#if TARGET_OS_IPHONE && __IPHONE_9_0
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_8_3) {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
NSLog(@"SocketRocket: %@ - this service type is deprecated in favor of using PushKit for VoIP control", networkServiceType);
});
}
#endif
break;
}
case NSURLNetworkServiceTypeVideo:
networkServiceType = NSStreamNetworkServiceTypeVideo;
break;
case NSURLNetworkServiceTypeBackground:
networkServiceType = NSStreamNetworkServiceTypeBackground;
break;
case NSURLNetworkServiceTypeVoice:
networkServiceType = NSStreamNetworkServiceTypeVoice;
break;
}
NSString *networkServiceType = SRStreamNetworkServiceTypeFromURLRequest(_urlRequest);
if (networkServiceType != nil) {
[_inputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType];
[_outputStream setProperty:networkServiceType forKey:NSStreamNetworkServiceType];
@ -522,7 +540,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
NSMutableData *mutablePayload = [[NSMutableData alloc] initWithLength:sizeof(uint16_t) + maxMsgSize];
NSData *payload = mutablePayload;
((uint16_t *)mutablePayload.mutableBytes)[0] = CFSwapInt16BigToHost((uint16_t)code);
((uint16_t *)mutablePayload.mutableBytes)[0] = EndianU16_BtoN(code);
if (reason) {
NSRange remainingRange = {0};
@ -669,15 +687,12 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
return YES;
}
- (void)_handlePingWithData:(nullable NSData *)data
- (void)handlePing:(NSData *)pingData;
{
// Need to pingpong this off _callbackQueue first to make sure messages happen in order
[self.delegateController performDelegateBlock:^(id<SRWebSocketDelegate> _Nullable delegate, SRDelegateAvailableMethods availableMethods) {
if (availableMethods.didReceivePing) {
[delegate webSocket:self didReceivePingWithData:data];
}
[self.delegateController performDelegateQueueBlock:^{
dispatch_async(_workQueue, ^{
[self _sendFrameWithOpcode:SROpCodePong data:data];
[self _sendFrameWithOpcode:SROpCodePong data:pingData];
});
}];
}
@ -740,7 +755,7 @@ static inline BOOL closeCodeIsValid(int closeCode) {
return;
} else if (dataSize >= 2) {
[data getBytes:&closeCode length:sizeof(closeCode)];
_closeCode = CFSwapInt16BigToHost(closeCode);
_closeCode = EndianU16_BtoN(closeCode);
if (!closeCodeIsValid(_closeCode)) {
[self _closeWithProtocolError:[NSString stringWithFormat:@"Cannot have close code of %d", _closeCode]];
return;
@ -774,21 +789,21 @@ static inline BOOL closeCodeIsValid(int closeCode) {
[self _pumpWriting];
}
- (void)_handleFrameWithData:(NSData *)frameData opCode:(SROpCode)opcode
- (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;
{
//frameData will be copied before passing to handlers
//otherwise there can be misbehaviours when value at the pointer is changed
frameData = [frameData copy];
// Check that the current data is valid UTF8
BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose);
if (isControlFrame) {
//frameData will be copied before passing to handlers
//otherwise there can be misbehaviours when value at the pointer is changed
frameData = [frameData copy];
if (!isControlFrame) {
[self _readFrameNew];
} else {
dispatch_async(_workQueue, ^{
[self _readFrameContinue];
});
} else {
[self _readFrameNew];
}
switch (opcode) {
@ -838,7 +853,7 @@ static inline BOOL closeCodeIsValid(int closeCode) {
[self handleCloseWithData:frameData];
break;
case SROpCodePing:
[self _handlePingWithData:frameData];
[self handlePing:frameData];
break;
case SROpCodePong:
[self handlePong:frameData];
@ -889,16 +904,17 @@ static inline BOOL closeCodeIsValid(int closeCode) {
}
} else {
assert(frame_header.payload_length <= SIZE_T_MAX);
[self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *sself, NSData *newData) {
[self _addConsumerWithDataLength:(size_t)frame_header.payload_length callback:^(SRWebSocket *self, NSData *newData) {
if (isControlFrame) {
[sself _handleFrameWithData:newData opCode:frame_header.opcode];
[self _handleFrameWithData:newData opCode:frame_header.opcode];
} else {
if (frame_header.fin) {
[sself _handleFrameWithData:sself->_currentFrameData opCode:frame_header.opcode];
[self _handleFrameWithData:self->_currentFrameData opCode:frame_header.opcode];
} else {
// TODO add assert that opcode is not a control;
[sself _readFrameContinue];
[self _readFrameContinue];
}
}
} readToCurrentFrame:!isControlFrame unmaskBytes:frame_header.masked];
}
@ -937,14 +953,14 @@ static const uint8_t SRPayloadLenMask = 0x7F;
{
assert((_currentFrameCount == 0 && _currentFrameOpcode == 0) || (_currentFrameCount > 0 && _currentFrameOpcode > 0));
[self _addConsumerWithDataLength:2 callback:^(SRWebSocket *sself, NSData *data) {
[self _addConsumerWithDataLength:2 callback:^(SRWebSocket *self, NSData *data) {
__block frame_header header = {0};
const uint8_t *headerBuffer = data.bytes;
assert(data.length >= 2);
if (headerBuffer[0] & SRRsvMask) {
[sself _closeWithProtocolError:@"Server used RSV bits"];
[self _closeWithProtocolError:@"Server used RSV bits"];
return;
}
@ -952,17 +968,17 @@ static const uint8_t SRPayloadLenMask = 0x7F;
BOOL isControlFrame = (receivedOpcode == SROpCodePing || receivedOpcode == SROpCodePong || receivedOpcode == SROpCodeConnectionClose);
if (!isControlFrame && receivedOpcode != 0 && sself->_currentFrameCount > 0) {
[sself _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"];
if (!isControlFrame && receivedOpcode != 0 && self->_currentFrameCount > 0) {
[self _closeWithProtocolError:@"all data frames after the initial data frame must have opcode 0"];
return;
}
if (receivedOpcode == 0 && sself->_currentFrameCount == 0) {
[sself _closeWithProtocolError:@"cannot continue a message"];
if (receivedOpcode == 0 && self->_currentFrameCount == 0) {
[self _closeWithProtocolError:@"cannot continue a message"];
return;
}
header.opcode = receivedOpcode == 0 ? sself->_currentFrameOpcode : receivedOpcode;
header.opcode = receivedOpcode == 0 ? self->_currentFrameOpcode : receivedOpcode;
header.fin = !!(SRFinMask & headerBuffer[0]);
@ -973,7 +989,7 @@ static const uint8_t SRPayloadLenMask = 0x7F;
headerBuffer = NULL;
if (header.masked) {
[sself _closeWithProtocolError:@"Client must receive unmasked data"];
[self _closeWithProtocolError:@"Client must receive unmasked data"];
return;
}
@ -986,29 +1002,22 @@ static const uint8_t SRPayloadLenMask = 0x7F;
}
if (extra_bytes_needed == 0) {
[sself _handleFrameHeader:header curData:sself->_currentFrameData];
[self _handleFrameHeader:header curData:self->_currentFrameData];
} else {
[sself _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *eself, NSData *edata) {
size_t mapped_size = edata.length;
[self _addConsumerWithDataLength:extra_bytes_needed callback:^(SRWebSocket *self, NSData *data) {
size_t mapped_size = data.length;
#pragma unused (mapped_size)
const void *mapped_buffer = edata.bytes;
const void *mapped_buffer = data.bytes;
size_t offset = 0;
if (header.payload_length == 126) {
assert(mapped_size >= sizeof(uint16_t));
uint16_t payloadLength = 0;
memcpy(&payloadLength, mapped_buffer, sizeof(uint16_t));
payloadLength = CFSwapInt16BigToHost(payloadLength);
header.payload_length = payloadLength;
uint16_t newLen = EndianU16_BtoN(*(uint16_t *)(mapped_buffer));
header.payload_length = newLen;
offset += sizeof(uint16_t);
} else if (header.payload_length == 127) {
assert(mapped_size >= sizeof(uint64_t));
uint64_t payloadLength = 0;
memcpy(&payloadLength, mapped_buffer, sizeof(uint64_t));
payloadLength = CFSwapInt64BigToHost(payloadLength);
header.payload_length = payloadLength;
header.payload_length = EndianU64_BtoN(*(uint64_t *)(mapped_buffer));
offset += sizeof(uint64_t);
} else {
assert(header.payload_length < 126 && header.payload_length >= 0);
@ -1016,10 +1025,10 @@ static const uint8_t SRPayloadLenMask = 0x7F;
if (header.masked) {
assert(mapped_size >= sizeof(_currentReadMaskOffset) + offset);
memcpy(eself->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(eself->_currentReadMaskKey));
memcpy(self->_currentReadMaskKey, ((uint8_t *)mapped_buffer) + offset, sizeof(self->_currentReadMaskKey));
}
[eself _handleFrameHeader:header curData:eself->_currentFrameData];
[self _handleFrameHeader:header curData:self->_currentFrameData];
} readToCurrentFrame:NO unmaskBytes:NO];
}
} readToCurrentFrame:NO unmaskBytes:NO];
@ -1331,85 +1340,97 @@ static const char CRLFCRLFBytes[] = {'\r', '\n', '\r', '\n'};
static const size_t SRFrameHeaderOverhead = 32;
- (void)_sendFrameWithOpcode:(SROpCode)opCode data:(NSData *)data
- (void)_sendFrameWithOpcode:(SROpCode)opcode data:(id)data;
{
[self assertOnWorkQueue];
if (!data) {
if (nil == data) {
return;
}
size_t payloadLength = data.length;
NSAssert([data isKindOfClass:[NSData class]] || [data isKindOfClass:[NSString class]], @"NSString or NSData");
NSMutableData *frameData = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead];
if (!frameData) {
size_t payloadLength = [data isKindOfClass:[NSString class]] ? [(NSString *)data lengthOfBytesUsingEncoding:NSUTF8StringEncoding] : [data length];
NSMutableData *frame = [[NSMutableData alloc] initWithLength:payloadLength + SRFrameHeaderOverhead];
if (!frame) {
[self closeWithCode:SRStatusCodeMessageTooBig reason:@"Message too big"];
return;
}
uint8_t *frameBuffer = (uint8_t *)frameData.mutableBytes;
uint8_t *frame_buffer = (uint8_t *)[frame mutableBytes];
// set fin
frameBuffer[0] = SRFinMask | opCode;
frame_buffer[0] = SRFinMask | opcode;
// set the mask and header
frameBuffer[1] |= SRMaskMask;
BOOL useMask = YES;
#ifdef NOMASK
useMask = NO;
#endif
size_t frameBufferSize = 2;
if (useMask) {
// set the mask and header
frame_buffer[1] |= SRMaskMask;
}
size_t frame_buffer_size = 2;
const uint8_t *unmasked_payload = NULL;
if ([data isKindOfClass:[NSData class]]) {
unmasked_payload = (uint8_t *)[data bytes];
} else if ([data isKindOfClass:[NSString class]]) {
unmasked_payload = (const uint8_t *)[data UTF8String];
} else {
return;
}
if (payloadLength < 126) {
frameBuffer[1] |= payloadLength;
frame_buffer[1] |= payloadLength;
} else if (payloadLength <= UINT16_MAX) {
frame_buffer[1] |= 126;
*((uint16_t *)(frame_buffer + frame_buffer_size)) = EndianU16_BtoN((uint16_t)payloadLength);
frame_buffer_size += sizeof(uint16_t);
} else {
uint64_t declaredPayloadLength = 0;
size_t declaredPayloadLengthSize = 0;
frame_buffer[1] |= 127;
*((uint64_t *)(frame_buffer + frame_buffer_size)) = EndianU64_BtoN((uint64_t)payloadLength);
frame_buffer_size += sizeof(uint64_t);
}
if (payloadLength <= UINT16_MAX) {
frameBuffer[1] |= 126;
declaredPayloadLength = CFSwapInt16BigToHost((uint16_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint16_t);
} else {
frameBuffer[1] |= 127;
declaredPayloadLength = CFSwapInt64BigToHost((uint64_t)payloadLength);
declaredPayloadLengthSize = sizeof(uint64_t);
if (!useMask) {
for (size_t i = 0; i < payloadLength; i++) {
frame_buffer[frame_buffer_size] = unmasked_payload[i];
frame_buffer_size += 1;
}
} else {
uint8_t *mask_key = frame_buffer + frame_buffer_size;
int result = SecRandomCopyBytes(kSecRandomDefault, sizeof(uint32_t), (uint8_t *)mask_key);
if (result != 0) {
//TODO: (nlutsenko) Check if there was an error.
}
memcpy((frameBuffer + frameBufferSize), &declaredPayloadLength, declaredPayloadLengthSize);
frameBufferSize += declaredPayloadLengthSize;
frame_buffer_size += sizeof(uint32_t);
// TODO: could probably optimize this with SIMD
for (size_t i = 0; i < payloadLength; i++) {
frame_buffer[frame_buffer_size] = unmasked_payload[i] ^ mask_key[i % sizeof(uint32_t)];
frame_buffer_size += 1;
}
}
const uint8_t *unmaskedPayloadBuffer = (uint8_t *)data.bytes;
uint8_t *maskKey = frameBuffer + frameBufferSize;
assert(frame_buffer_size <= [frame length]);
frame.length = frame_buffer_size;
size_t randomBytesSize = sizeof(uint32_t);
int result = SecRandomCopyBytes(kSecRandomDefault, randomBytesSize, maskKey);
if (result != 0) {
//TODO: (nlutsenko) Check if there was an error.
}
frameBufferSize += randomBytesSize;
// Copy and unmask the buffer
uint8_t *frameBufferPayloadPointer = frameBuffer + frameBufferSize;
memcpy(frameBufferPayloadPointer, unmaskedPayloadBuffer, payloadLength);
SRMaskBytesSIMD(frameBufferPayloadPointer, payloadLength, maskKey);
frameBufferSize += payloadLength;
assert(frameBufferSize <= frameData.length);
frameData.length = frameBufferSize;
[self _writeData:frameData];
[self _writeData:frame];
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
__weak typeof(self) wself = self;
if (_requestRequiresSSL && !_streamSecurityValidated &&
if (_securityOptions.requestRequiresSSL && !_streamSecurityValidated &&
(eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) {
SecTrustRef trust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust];
if (trust) {
_streamSecurityValidated = [_securityPolicy evaluateServerTrust:trust forDomain:_urlRequest.URL.host];
_streamSecurityValidated = [_securityOptions securityTrustContainsPinnedCertificates:trust];
}
if (!_streamSecurityValidated) {
dispatch_async(_workQueue, ^{
@ -1439,7 +1460,7 @@ static const size_t SRFrameHeaderOverhead = 32;
}
assert(_readBuffer);
if (!_requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) {
if (!_securityOptions.requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) {
[self didConnect];
}
@ -1518,7 +1539,7 @@ static const size_t SRFrameHeaderOverhead = 32;
break;
}
case NSStreamEventNone:
default:
SRDebugLog(@"(default) %@", aStream);
break;
}
@ -1584,28 +1605,28 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
if (codepoint == -1) {
// Check to see if the last byte is valid or whether it was just continuing
if (!U8_IS_LEAD(str[lastOffset]) || U8_COUNT_TRAIL_BYTES(str[lastOffset]) + lastOffset < (int32_t)size) {
size = -1;
} else {
uint8_t leadByte = str[lastOffset];
U8_MASK_LEAD_BYTE(leadByte, U8_COUNT_TRAIL_BYTES(leadByte));
for (int i = lastOffset + 1; i < offset; i++) {
if (U8_IS_SINGLE(str[i]) || U8_IS_LEAD(str[i]) || !U8_IS_TRAIL(str[i])) {
size = -1;
}
}
if (size != -1) {
size = lastOffset;
}
}
}
if (size != -1 && ![[NSString alloc] initWithBytesNoCopy:(char *)[data bytes] length:size encoding:NSUTF8StringEncoding freeWhenDone:NO]) {
size = -1;
}
return size;
}
@ -1614,14 +1635,14 @@ static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
// This is a hack, and probably not optimal
static inline int32_t validate_dispatch_data_partial_string(NSData *data) {
static const int maxCodepointSize = 3;
for (int i = 0; i < maxCodepointSize; i++) {
NSString *str = [[NSString alloc] initWithBytesNoCopy:(char *)data.bytes length:data.length - i encoding:NSUTF8StringEncoding freeWhenDone:NO];
if (str) {
return (int32_t)data.length - i;
}
}
return -1;
}

View File

@ -1,15 +1,14 @@
//
// Copyright 2012 Square Inc.
// Portions Copyright (c) 2016-present, Facebook, Inc.
//
//
// All rights reserved.
//
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <SocketRocket/SRWebSocket.h>
#import <SocketRocket/NSRunLoop+SRWebSocket.h>
#import <SocketRocket/NSURLRequest+SRWebSocket.h>
#import <SocketRocket/SRSecurityPolicy.h>
#import <SocketRocket/SRWebSocket.h>

View File

@ -99,7 +99,7 @@
- (void)sendPing:(id)sender;
{
[_webSocket sendPing:nil error:NULL];
[_webSocket sendPing:nil];
}
///--------------------------------------
@ -182,7 +182,7 @@
NSString *message = [textView.text stringByReplacingCharactersInRange:range withString:text];
message = [message stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
[_webSocket sendString:message error:NULL];
[_webSocket sendString:message];
[self _addMessage:[[TCMessage alloc] initWithMessage:message incoming:NO]];

View File

@ -85,8 +85,8 @@
NSString *selectorName = [NSString stringWithFormat:@"Case #%@", identifier];
SEL selector = NSSelectorFromString(selectorName);
IMP implementation = imp_implementationWithBlock(^(SRAutobahnTests *sself) {
[sself performTestWithCaseNumber:caseNumber identifier:identifier];
IMP implementation = imp_implementationWithBlock(^(SRAutobahnTests *self) {
[self performTestWithCaseNumber:caseNumber identifier:identifier];
});
NSString *typeString = [NSString stringWithFormat:@"%s%s%s", @encode(id), @encode(id), @encode(SEL)];
class_addMethod(self, selector, implementation, typeString.UTF8String);

1
Vendor/xctoolchain vendored

@ -1 +0,0 @@
Subproject commit a66c63fe948cf8b4d77a51b69f35459fe0c68973

1
pages Submodule

@ -0,0 +1 @@
Subproject commit f103cccb1d138c74a02404dfefcb875b540286a1