Compare commits
1 Commits
master
...
specify-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bdd097519 |
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1 +0,0 @@
|
||||
../Vendor/xctoolchain/Configurations/
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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.
|
||||
10
Makefile
10
Makefile
@ -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
213
README.md
@ -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
|
||||
|
||||
We’re glad you’re interested in SocketRocket, and we’d 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
246
README.rst
Normal 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
|
||||
------------
|
||||
We’re glad you’re interested in SocketRocket, and we’d 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.
|
||||
@ -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
@ -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>
|
||||
@ -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>
|
||||
@ -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 if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing Dangling Autobahn Server PID:" $EXISTING_PID kill $EXISTING_PID || true rm $PIDFILE fi pushd $PROJECT_DIR source .env/bin/activate nohup ./TestSupport/run_test_server.sh & echo $! > $PIDFILE popd ">
|
||||
<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 if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing SR TestServer PID:" $EXISTING_PID kill $EXISTING_PID rm $PIDFILE fi ">
|
||||
<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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing Dangling Autobahn Server PID:" $EXISTING_PID kill $EXISTING_PID || true rm $PIDFILE fi pushd $PROJECT_DIR source .env/bin/activate nohup ./TestSupport/run_test_server.sh & echo $! > $PIDFILE popd "
|
||||
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 if [ -r $PIDFILE ]; then EXISTING_PID=`cat $PIDFILE` echo "Killing SR TestServer PID:" $EXISTING_PID kill $EXISTING_PID rm $PIDFILE fi "
|
||||
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>
|
||||
@ -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>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0730"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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:)]
|
||||
};
|
||||
|
||||
@ -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);
|
||||
@ -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);
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
@ -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
|
||||
@ -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
|
||||
74
SocketRocket/Internal/Security/SRSecurityOptions.h
Normal file
74
SocketRocket/Internal/Security/SRSecurityOptions.h
Normal 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
|
||||
88
SocketRocket/Internal/Security/SRSecurityOptions.m
Normal file
88
SocketRocket/Internal/Security/SRSecurityOptions.m
Normal 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
|
||||
@ -8,7 +8,6 @@
|
||||
//
|
||||
|
||||
#import "SRError.h"
|
||||
|
||||
#import "SRWebSocket.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -29,7 +29,6 @@ SRMutex SRMutexInitRecursive(void)
|
||||
void SRMutexDestroy(SRMutex mutex)
|
||||
{
|
||||
pthread_mutex_destroy(mutex);
|
||||
free(mutex);
|
||||
}
|
||||
|
||||
__attribute__((no_thread_safety_analysis))
|
||||
|
||||
@ -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);
|
||||
@ -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);
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
20
SocketRocket/Makefile
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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
|
||||
@ -21,7 +21,7 @@ typedef NS_ENUM(NSInteger, SRReadyState) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SRStatusCode) {
|
||||
// 0-999: Reserved and not used.
|
||||
// 0–999: 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.
|
||||
// 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.
|
||||
};
|
||||
|
||||
@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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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]];
|
||||
|
||||
|
||||
@ -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
Vendor/xctoolchain
vendored
@ -1 +0,0 @@
|
||||
Subproject commit a66c63fe948cf8b4d77a51b69f35459fe0c68973
|
||||
1
pages
Submodule
1
pages
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f103cccb1d138c74a02404dfefcb875b540286a1
|
||||
Loading…
Reference in New Issue
Block a user