Merge branch 'master' of github.com:ratchetphp/Ratchet
This commit is contained in:
		
						commit
						82f2505b13
					
				
							
								
								
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							| @ -1,13 +1,20 @@ | ||||
| language: php | ||||
| 
 | ||||
| php: | ||||
|   - 5.3 | ||||
|   - 5.4 | ||||
|   - 5.5 | ||||
|   - 5.6 | ||||
|   - 7 | ||||
|   - 7.0 | ||||
|   - 7.1 | ||||
|   - hhvm | ||||
| 
 | ||||
| dist: trusty | ||||
| 
 | ||||
| matrix: | ||||
|   allow_failures: | ||||
|     - php: hhvm | ||||
| 
 | ||||
| before_script: | ||||
|   - sh -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo "session.serialize_handler = php" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini; fi;' | ||||
|   - php -m | ||||
|   - composer install --dev --prefer-source | ||||
|  | ||||
							
								
								
									
										169
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @ -1,138 +1,135 @@ | ||||
| CHANGELOG | ||||
| ========= | ||||
| 
 | ||||
| ###Legend | ||||
| ### Legend | ||||
| 
 | ||||
| * "BC": Backwards compatibility break (from public component APIs) | ||||
| * "BF": Bug fix | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| * 0.4.1 (2017-12-11) | ||||
|   * Only enableKeepAlive in App if no WsServer passed allowing user to set their own timeout duration | ||||
|   * Support Symfony 4 | ||||
|   * BF: Plug NOOP controller in connection from router in case of misbehaving client | ||||
|   * BF: Raise error from invalid WAMP payload | ||||
| 
 | ||||
| * 0.4 (2017-09-14) | ||||
|   * BC: $conn->WebSocket->request replaced with $conn->httpRequest which is a PSR-7 object | ||||
|   * Binary messages now supported via Ratchet\WebSocket\MessageComponentInterface | ||||
|   * Added heartbeat support via ping/pong in WsServer | ||||
|   * BC: No longer support old (and insecure) Hixie76 and Hybi protocols | ||||
|   * BC: No longer support disabling UTF-8 checks | ||||
|   * BC: The Session component implements HttpServerInterface instead of WsServerInterface | ||||
|   * BC: PHP 5.3 no longer supported | ||||
|   * BC: Update to newer version of react/socket dependency | ||||
|   * BC: WAMP topics reduced to 0 subscriptions are deleted, new subs to same name will result in new Topic instance | ||||
|   * Significant performance enhancements | ||||
| 
 | ||||
| * 0.3.6 (2017-01-06) | ||||
|  * BF: Keep host and scheme in HTTP request object attatched to connection | ||||
|  * BF: Return correct HTTP response (405) when non-GET request made | ||||
|   * BF: Keep host and scheme in HTTP request object attatched to connection | ||||
|   * BF: Return correct HTTP response (405) when non-GET request made | ||||
| 
 | ||||
| * 0.3.5 (2016-05-25) | ||||
| 
 | ||||
|  * BF: Unmask responding close frame | ||||
|  * Added write handler for PHP session serializer | ||||
|   * BF: Unmask responding close frame | ||||
|   * Added write handler for PHP session serializer | ||||
| 
 | ||||
| * 0.3.4 (2015-12-23) | ||||
| 
 | ||||
|  * BF: Edge case where version check wasn't run on message coalesce | ||||
|  * BF: Session didn't start when using pdo_sqlite | ||||
|  * BF: WAMP currie prefix check when using '#' | ||||
|  * Compatibility with Symfony 3 | ||||
|   * BF: Edge case where version check wasn't run on message coalesce | ||||
|   * BF: Session didn't start when using pdo_sqlite | ||||
|   * BF: WAMP currie prefix check when using '#' | ||||
|   * Compatibility with Symfony 3 | ||||
| 
 | ||||
| * 0.3.3 (2015-05-26) | ||||
| 
 | ||||
|  * BF: Framing bug on large messages upon TCP fragmentation | ||||
|  * BF: Symfony Router query parameter defaults applied to Request | ||||
|  * BF: WAMP CURIE on all URIs | ||||
|  * OriginCheck rules applied to FlashPolicy | ||||
|  * Switched from PSR-0 to PSR-4 | ||||
|   * BF: Framing bug on large messages upon TCP fragmentation | ||||
|   * BF: Symfony Router query parameter defaults applied to Request | ||||
|   * BF: WAMP CURIE on all URIs | ||||
|   * OriginCheck rules applied to FlashPolicy | ||||
|   * Switched from PSR-0 to PSR-4 | ||||
| 
 | ||||
| * 0.3.2 (2014-06-08) | ||||
| 
 | ||||
|  * BF: No messages after closing handshake (fixed rare race condition causing 100% CPU) | ||||
|  * BF: Fixed accidental BC break from v0.3.1 | ||||
|  * Added autoDelete parameter to Topic to destroy when empty of connections | ||||
|  * Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App) | ||||
|  * Normalized Exceptions in WAMP | ||||
|   * BF: No messages after closing handshake (fixed rare race condition causing 100% CPU) | ||||
|   * BF: Fixed accidental BC break from v0.3.1 | ||||
|   * Added autoDelete parameter to Topic to destroy when empty of connections | ||||
|   * Exposed React Socket on IoServer (allowing FlashPolicy shutdown in App) | ||||
|   * Normalized Exceptions in WAMP | ||||
| 
 | ||||
| * 0.3.1 (2014-05-26) | ||||
| 
 | ||||
|  * Added query parameter support to Router, set in HTTP request (ws://server?hello=world) | ||||
|  * HHVM compatibility | ||||
|  * BF: React/0.4 support; CPU starvation bug fixes | ||||
|  * BF: Allow App::route to ignore Host header | ||||
|  * Added expected filters to WAMP Topic broadcast method | ||||
|  * Resource cleanup in WAMP TopicManager | ||||
|   * Added query parameter support to Router, set in HTTP request (ws://server?hello=world) | ||||
|   * HHVM compatibility | ||||
|   * BF: React/0.4 support; CPU starvation bug fixes | ||||
|   * BF: Allow App::route to ignore Host header | ||||
|   * Added expected filters to WAMP Topic broadcast method | ||||
|   * Resource cleanup in WAMP TopicManager | ||||
| 
 | ||||
| * 0.3.0 (2013-10-14) | ||||
| 
 | ||||
|  * Added the `App` class to help making Ratchet so easy to use it's silly | ||||
|  * BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks | ||||
|  * Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router | ||||
|  * BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer | ||||
|  * BF: Single sub-protocol selection to conform with RFC6455 | ||||
|  * BF: Sanity checks on WAMP protocol to prevent errors | ||||
|   * Added the `App` class to help making Ratchet so easy to use it's silly | ||||
|   * BC: Require hostname to do HTTP Host header match and do Origin HTTP header check, verify same name by default, helping prevent CSRF attacks | ||||
|   * Added Symfony/2.2 based HTTP Router component to allowing for a single Ratchet server to handle multiple apps -> Ratchet\Http\Router | ||||
|   * BC: Decoupled HTTP from WebSocket component -> Ratchet\Http\HttpServer | ||||
|   * BF: Single sub-protocol selection to conform with RFC6455 | ||||
|   * BF: Sanity checks on WAMP protocol to prevent errors | ||||
| 
 | ||||
| * 0.2.8 (2013-09-19) | ||||
| 
 | ||||
|  * React 0.3 support | ||||
|   * React 0.3 support | ||||
| 
 | ||||
| * 0.2.7 (2013-06-09) | ||||
| 
 | ||||
|  * BF: Sub-protocol negotation with Guzzle 3.6 | ||||
|   * BF: Sub-protocol negotation with Guzzle 3.6 | ||||
| 
 | ||||
| * 0.2.6 (2013-06-01) | ||||
| 
 | ||||
|  * Guzzle 3.6 support | ||||
|   * Guzzle 3.6 support | ||||
| 
 | ||||
| * 0.2.5 (2013-04-01) | ||||
| 
 | ||||
|  * Fixed Hixie-76 handshake bug | ||||
|   * Fixed Hixie-76 handshake bug | ||||
| 
 | ||||
| * 0.2.4 (2013-03-09) | ||||
| 
 | ||||
|  * Support for Symfony 2.2 and Guzzle 2.3 | ||||
|  * Minor bug fixes when handling errors | ||||
|   * Support for Symfony 2.2 and Guzzle 2.3 | ||||
|   * Minor bug fixes when handling errors | ||||
| 
 | ||||
| * 0.2.3 (2012-11-21) | ||||
| 
 | ||||
|  * Bumped dep: Guzzle to v3, React to v0.2.4 | ||||
|  * More tests | ||||
|   * Bumped dep: Guzzle to v3, React to v0.2.4 | ||||
|   * More tests | ||||
| 
 | ||||
| * 0.2.2 (2012-10-20) | ||||
| 
 | ||||
|  * Bumped deps to use React v0.2 | ||||
|   * Bumped deps to use React v0.2 | ||||
| 
 | ||||
| * 0.2.1 (2012-10-13) | ||||
| 
 | ||||
|  * BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string) | ||||
|  * Documentation corrections | ||||
|  * Using new composer structure | ||||
|   * BF: No more UTF-8 warnings in browsers (no longer sending empty sub-protocol string) | ||||
|   * Documentation corrections | ||||
|   * Using new composer structure | ||||
| 
 | ||||
| * 0.2 (2012-09-07) | ||||
| 
 | ||||
|  * Ratchet passes every non-binary-frame test from the Autobahn Testsuite | ||||
|  * Major performance improvements | ||||
|  * BC: Renamed "WampServer" to "ServerProtocol" | ||||
|  * BC: New "WampServer" component passes Topic container objects of subscribed Connections | ||||
|  * Option to turn off UTF-8 checks in order to increase performance | ||||
|  * Switched dependency guzzle/guzzle to guzzle/http (no API changes) | ||||
|  * mbstring no longer required | ||||
|   * Ratchet passes every non-binary-frame test from the Autobahn Testsuite | ||||
|   * Major performance improvements | ||||
|   * BC: Renamed "WampServer" to "ServerProtocol" | ||||
|   * BC: New "WampServer" component passes Topic container objects of subscribed Connections | ||||
|   * Option to turn off UTF-8 checks in order to increase performance | ||||
|   * Switched dependency guzzle/guzzle to guzzle/http (no API changes) | ||||
|   * mbstring no longer required | ||||
| 
 | ||||
| * 0.1.5 (2012-07-12) | ||||
| 
 | ||||
|  * BF: Error where service wouldn't run on PHP <= 5.3.8 | ||||
|  * Dependency library updates | ||||
|   * BF: Error where service wouldn't run on PHP <= 5.3.8 | ||||
|   * Dependency library updates | ||||
| 
 | ||||
| * 0.1.4 (2012-06-17) | ||||
| 
 | ||||
|  * Fixed dozens of failing AB tests | ||||
|  * BF: Proper socket buffer handling | ||||
|   * Fixed dozens of failing AB tests | ||||
|   * BF: Proper socket buffer handling | ||||
| 
 | ||||
| * 0.1.3 (2012-06-15) | ||||
| 
 | ||||
|  * Major refactor inside WebSocket protocol handling, more loosley coupled | ||||
|  * BF: Proper error handling on failed WebSocket connections | ||||
|  * BF: Handle TCP message concatenation | ||||
|  * Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance | ||||
|  * mb_string now a requirement | ||||
|   * Major refactor inside WebSocket protocol handling, more loosley coupled | ||||
|   * BF: Proper error handling on failed WebSocket connections | ||||
|   * BF: Handle TCP message concatenation | ||||
|   * Inclusion of the AutobahnTestSuite checking WebSocket protocol compliance | ||||
|   * mb_string now a requirement | ||||
| 
 | ||||
| * 0.1.2 (2012-05-19) | ||||
| 
 | ||||
|  * BC/BF: Updated WAMP API to coincide with the official spec | ||||
|  * Tweaks to improve running as a long lived process | ||||
|   * BC/BF: Updated WAMP API to coincide with the official spec | ||||
|   * Tweaks to improve running as a long lived process | ||||
| 
 | ||||
| * 0.1.1 (2012-05-14) | ||||
| 
 | ||||
|  * Separated interfaces allowing WebSockets to support multiple sub protocols | ||||
|  * BF: remoteAddress variable on connections returns proper value | ||||
|   * Separated interfaces allowing WebSockets to support multiple sub protocols | ||||
|   * BF: remoteAddress variable on connections returns proper value | ||||
| 
 | ||||
| * 0.1 (2012-05-11) | ||||
| 
 | ||||
|  * First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList | ||||
|  * I/O now handled by React, making Ratchet fully asynchronous | ||||
|   * First release with components: IoServer, WsServer, SessionProvider, WampServer, FlashPolicy, IpBlackList | ||||
|   * I/O now handled by React, making Ratchet fully asynchronous | ||||
|  | ||||
							
								
								
									
										21
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								Makefile
									
									
									
									
									
								
							| @ -10,26 +10,33 @@ cover: | ||||
| abtests: | ||||
| 	ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8001 LibEvent & | ||||
| 	ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8002 StreamSelect & | ||||
| 	ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver-noutf8.php 8003 StreamSelect & | ||||
| 	ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8004 LibEv & | ||||
| 	wstest -m testeeserver -w ws://localhost:8000 & | ||||
| 	sleep 1 | ||||
| 	wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-all.json | ||||
| 	killall php wstest | ||||
| 
 | ||||
| abtest: | ||||
| 	ulimit -n 2048 && php tests/autobahn/bin/fuzzingserver.php 8000 StreamSelect & | ||||
| 	sleep 1 | ||||
| 	wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-quick.json | ||||
| 	killall php | ||||
| 
 | ||||
| profile: | ||||
| 	php -d 'xdebug.profiler_enable=1' tests/autobahn/bin/fuzzingserver.php 8000 LibEvent & | ||||
| 	sleep 1 | ||||
| 	wstest -m fuzzingclient -s tests/autobahn/fuzzingclient-profile.json | ||||
| 	killall php | ||||
| 
 | ||||
| apidocs: | ||||
| 	apigen --title Ratchet -d reports/api -s src/ \
 | ||||
| 		-s vendor/react \
 | ||||
| 		-s vendor/guzzle \
 | ||||
| 		-s vendor/symfony/http-foundation/Symfony/Component/HttpFoundation/Session \
 | ||||
| 		-s vendor/symfony/routing/Symfony/Component/Routing \
 | ||||
| 		-s vendor/evenement/evenement/src/Evenement | ||||
| 	apigen --title Ratchet -d reports/api \
 | ||||
| 		-s src/ \
 | ||||
| 		-s vendor/ratchet/rfc6455/src \
 | ||||
| 		-s vendor/react/event-loop/src \
 | ||||
| 		-s vendor/react/socket/src \
 | ||||
| 		-s vendor/react/stream/src \
 | ||||
| 		-s vendor/psr/http-message/src \
 | ||||
| 		-s vendor/symfony/http-foundation/Session \
 | ||||
| 		-s vendor/symfony/routing \
 | ||||
| 		-s vendor/evenement/evenement/src/Evenement \
 | ||||
| 		--exclude=vendor/symfony/routing/Tests \
 | ||||
|  | ||||
							
								
								
									
										21
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								README.md
									
									
									
									
									
								
							| @ -1,26 +1,19 @@ | ||||
| #Ratchet | ||||
| # Ratchet | ||||
| 
 | ||||
| [](http://travis-ci.org/ratchetphp/Ratchet) | ||||
| [](http://socketo.me/reports/ab/index.html) | ||||
| [](https://packagist.org/packages/cboden/ratchet) | ||||
| 
 | ||||
| A PHP 5.3 library for asynchronously serving WebSockets. | ||||
| A PHP library for asynchronously serving WebSockets. | ||||
| Build up your application through simple interfaces and re-use your application without changing any of its code just by combining different components. | ||||
| 
 | ||||
| ##WebSocket Compliance | ||||
| 
 | ||||
| * Supports the RFC6455, HyBi-10+, and Hixie76 protocol versions (at the same time) | ||||
| * Tested on Chrome 13+, Firefox 6+, Safari 5+, iOS 4.2+, IE 8+ | ||||
| * Ratchet [passes](http://socketo.me/reports/ab/) the [Autobahn Testsuite](http://autobahn.ws/testsuite) (non-binary messages) | ||||
| 
 | ||||
| ##Requirements | ||||
| ## Requirements | ||||
| 
 | ||||
| Shell access is required and root access is recommended. | ||||
| To avoid proxy/firewall blockage it's recommended WebSockets are requested on port 80 or 443 (SSL), which requires root access. | ||||
| In order to do this, along with your sync web stack, you can either use a reverse proxy or two separate machines. | ||||
| You can find more details in the [server conf docs](http://socketo.me/docs/deploy#serverconfiguration). | ||||
| 
 | ||||
| PHP 5.3.9 (or higher) is required. If you have access, PHP 5.4 (or higher) is *highly* recommended for its performance improvements. | ||||
| 
 | ||||
| ### Documentation | ||||
| 
 | ||||
| User and API documentation is available on Ratchet's website: http://socketo.me | ||||
| @ -31,7 +24,7 @@ Need help?  Have a question?  Want to provide feedback?  Write a message on the | ||||
| 
 | ||||
| --- | ||||
| 
 | ||||
| ###A quick example | ||||
| ### A quick example | ||||
| 
 | ||||
| ```php | ||||
| <?php | ||||
| @ -86,5 +79,5 @@ class MyChat implements MessageComponentInterface { | ||||
|     // Then some JavaScript in the browser: | ||||
|     var conn = new WebSocket('ws://localhost:8080/echo'); | ||||
|     conn.onmessage = function(e) { console.log(e.data); }; | ||||
|     conn.send('Hello Me!'); | ||||
| ``` | ||||
|     conn.onopen = function(e) { conn.send('Hello Me!'); }; | ||||
| ``` | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
|     "name": "cboden/ratchet" | ||||
|   , "type": "library" | ||||
|   , "description": "PHP WebSocket library" | ||||
|   , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets"] | ||||
|   , "keywords": ["WebSockets", "Server", "Ratchet", "Sockets", "WebSocket"] | ||||
|   , "homepage": "http://socketo.me" | ||||
|   , "license": "MIT" | ||||
|   , "authors": [ | ||||
| @ -23,10 +23,14 @@ | ||||
|         } | ||||
|     } | ||||
|   , "require": { | ||||
|         "php": ">=5.3.9" | ||||
|       , "react/socket": "^0.3 || ^0.4" | ||||
|       , "guzzle/http": "^3.6" | ||||
|       , "symfony/http-foundation": "^2.2|^3.0" | ||||
|       , "symfony/routing": "^2.2|^3.0" | ||||
|         "php": ">=5.4.2" | ||||
|       , "ratchet/rfc6455": "^0.2" | ||||
|       , "react/socket": "^1.0 || ^0.8 || ^0.7 || ^0.6 || ^0.5" | ||||
|       , "guzzlehttp/psr7": "^1.0" | ||||
|       , "symfony/http-foundation": "^2.6|^3.0|^4.0" | ||||
|       , "symfony/routing": "^2.6|^3.0|^4.0" | ||||
|     } | ||||
|   , "require-dev": { | ||||
|         "phpunit/phpunit": "~4.8" | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,6 +3,7 @@ namespace Ratchet; | ||||
| use React\EventLoop\LoopInterface; | ||||
| use React\EventLoop\Factory as LoopFactory; | ||||
| use React\Socket\Server as Reactor; | ||||
| use React\Socket\SecureServer as SecureReactor; | ||||
| use Ratchet\Http\HttpServerInterface; | ||||
| use Ratchet\Http\OriginCheck; | ||||
| use Ratchet\Wamp\WampServerInterface; | ||||
| @ -55,20 +56,16 @@ class App { | ||||
|     protected $_routeCounter = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * @param string        $httpHost HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');` | ||||
|      * @param int           $port     Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 | ||||
|      * @param string        $address  IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. | ||||
|      * @param LoopInterface $loop     Specific React\EventLoop to bind the application to. null will create one for you. | ||||
|      * @param string        $httpHost   HTTP hostname clients intend to connect to. MUST match JS `new WebSocket('ws://$httpHost');` | ||||
|      * @param int           $port       Port to listen on. If 80, assuming production, Flash on 843 otherwise expecting Flash to be proxied through 8843 | ||||
|      * @param string        $address    IP address to bind to. Default is localhost/proxy only. '0.0.0.0' for any machine. | ||||
|      * @param LoopInterface $loop       Specific React\EventLoop to bind the application to. null will create one for you. | ||||
|      */ | ||||
|     public function __construct($httpHost = 'localhost', $port = 8080, $address = '127.0.0.1', LoopInterface $loop = null) { | ||||
|         if (extension_loaded('xdebug')) { | ||||
|             trigger_error('XDebug extension detected. Remember to disable this if performance testing or going live!', E_USER_WARNING); | ||||
|         } | ||||
| 
 | ||||
|         if (3 !== strlen('✓')) { | ||||
|             throw new \DomainException('Bad encoding, length of unicode character ✓ should be 3. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); | ||||
|         } | ||||
| 
 | ||||
|         if (null === $loop) { | ||||
|             $loop = LoopFactory::create(); | ||||
|         } | ||||
| @ -76,8 +73,7 @@ class App { | ||||
|         $this->httpHost = $httpHost; | ||||
|         $this->port = $port; | ||||
| 
 | ||||
|         $socket = new Reactor($loop); | ||||
|         $socket->listen($port, $address); | ||||
|         $socket = new Reactor($address . ':' . $port, $loop); | ||||
| 
 | ||||
|         $this->routes  = new RouteCollection; | ||||
|         $this->_server = new IoServer(new HttpServer(new Router(new UrlMatcher($this->routes, new RequestContext))), $socket, $loop); | ||||
| @ -85,13 +81,14 @@ class App { | ||||
|         $policy = new FlashPolicy; | ||||
|         $policy->addAllowedAccess($httpHost, 80); | ||||
|         $policy->addAllowedAccess($httpHost, $port); | ||||
|         $flashSock = new Reactor($loop); | ||||
|         $this->flashServer = new IoServer($policy, $flashSock); | ||||
| 
 | ||||
|         if (80 == $port) { | ||||
|             $flashSock->listen(843, '0.0.0.0'); | ||||
|             $flashUri = '0.0.0.0:843'; | ||||
|         } else { | ||||
|             $flashSock->listen(8843); | ||||
|             $flashUri = 8843; | ||||
|         } | ||||
|         $flashSock = new Reactor($flashUri, $loop); | ||||
|         $this->flashServer = new IoServer($policy, $flashSock); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -107,8 +104,10 @@ class App { | ||||
|             $decorated = $controller; | ||||
|         } elseif ($controller instanceof WampServerInterface) { | ||||
|             $decorated = new WsServer(new WampServer($controller)); | ||||
|             $decorated->enableKeepAlive($this->_server->loop); | ||||
|         } elseif ($controller instanceof MessageComponentInterface) { | ||||
|             $decorated = new WsServer($controller); | ||||
|             $decorated->enableKeepAlive($this->_server->loop); | ||||
|         } else { | ||||
|             $decorated = $controller; | ||||
|         } | ||||
|  | ||||
| @ -5,7 +5,7 @@ namespace Ratchet; | ||||
|  * The version of Ratchet being used | ||||
|  * @var string | ||||
|  */ | ||||
| const VERSION = 'Ratchet/0.3.6'; | ||||
| const VERSION = 'Ratchet/0.4.1'; | ||||
| 
 | ||||
| /** | ||||
|  * A proxy object representing a connection to the application | ||||
|  | ||||
							
								
								
									
										22
									
								
								src/Ratchet/Http/CloseResponseTrait.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/Ratchet/Http/CloseResponseTrait.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use GuzzleHttp\Psr7 as gPsr; | ||||
| use GuzzleHttp\Psr7\Response; | ||||
| 
 | ||||
| trait CloseResponseTrait { | ||||
|     /** | ||||
|      * Close a connection with an HTTP response | ||||
|      * @param \Ratchet\ConnectionInterface $conn | ||||
|      * @param int                          $code HTTP status code | ||||
|      * @return null | ||||
|      */ | ||||
|     private function close(ConnectionInterface $conn, $code = 400, array $additional_headers = []) { | ||||
|         $response = new Response($code, array_merge([ | ||||
|             'X-Powered-By' => \Ratchet\VERSION | ||||
|         ], $additional_headers)); | ||||
| 
 | ||||
|         $conn->send(gPsr\str($response)); | ||||
|         $conn->close(); | ||||
|     } | ||||
| } | ||||
| @ -1,34 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http\Guzzle\Http\Message; | ||||
| use Guzzle\Http\Message\RequestFactory as GuzzleRequestFactory; | ||||
| use Guzzle\Http\EntityBody; | ||||
| 
 | ||||
| class RequestFactory extends GuzzleRequestFactory { | ||||
| 
 | ||||
|     protected static $ratchetInstance; | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public static function getInstance() | ||||
|     { | ||||
|         // @codeCoverageIgnoreStart
 | ||||
|         if (!static::$ratchetInstance) { | ||||
|             static::$ratchetInstance = new static(); | ||||
|         } | ||||
|         // @codeCoverageIgnoreEnd
 | ||||
| 
 | ||||
|         return static::$ratchetInstance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function create($method, $url, $headers = null, $body = '', array $options = array()) { | ||||
|         $c = $this->entityEnclosingRequestClass; | ||||
|         $request = new $c($method, $url, $headers); | ||||
|         $request->setBody(EntityBody::factory($body)); | ||||
| 
 | ||||
|         return $request; | ||||
|     } | ||||
| } | ||||
| @ -2,11 +2,11 @@ | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\MessageInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\Http\Guzzle\Http\Message\RequestFactory; | ||||
| use GuzzleHttp\Psr7 as gPsr; | ||||
| 
 | ||||
| /** | ||||
|  * This class receives streaming data from a client request | ||||
|  * and parses HTTP headers, returning a Guzzle Request object | ||||
|  * and parses HTTP headers, returning a PSR-7 Request object | ||||
|  * once it's been buffered | ||||
|  */ | ||||
| class HttpRequestParser implements MessageInterface { | ||||
| @ -22,7 +22,7 @@ class HttpRequestParser implements MessageInterface { | ||||
|     /** | ||||
|      * @param \Ratchet\ConnectionInterface $context | ||||
|      * @param string                       $data Data stream to buffer | ||||
|      * @return \Guzzle\Http\Message\RequestInterface|null | ||||
|      * @return \Psr\Http\Message\RequestInterface | ||||
|      * @throws \OverflowException If the message buffer has become too large | ||||
|      */ | ||||
|     public function onMessage(ConnectionInterface $context, $data) { | ||||
| @ -37,7 +37,7 @@ class HttpRequestParser implements MessageInterface { | ||||
|         } | ||||
| 
 | ||||
|         if ($this->isEom($context->httpBuffer)) { | ||||
|             $request = RequestFactory::getInstance()->fromMessage($context->httpBuffer); | ||||
|             $request = $this->parse($context->httpBuffer); | ||||
| 
 | ||||
|             unset($context->httpBuffer); | ||||
| 
 | ||||
| @ -53,4 +53,12 @@ class HttpRequestParser implements MessageInterface { | ||||
|     public function isEom($message) { | ||||
|         return (boolean)strpos($message, static::EOM); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string $headers | ||||
|      * @return \Psr\Http\Message\RequestInterface | ||||
|      */ | ||||
|     public function parse($headers) { | ||||
|         return gPsr\parse_request($headers); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,9 +2,10 @@ | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\MessageComponentInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| 
 | ||||
| class HttpServer implements MessageComponentInterface { | ||||
|     use CloseResponseTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * Buffers incoming HTTP requests returning a Guzzle Request when coalesced | ||||
|      * @var HttpRequestParser | ||||
| @ -72,19 +73,4 @@ class HttpServer implements MessageComponentInterface { | ||||
|             $this->close($conn, 500); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close a connection with an HTTP response | ||||
|      * @param \Ratchet\ConnectionInterface $conn | ||||
|      * @param int                          $code HTTP status code | ||||
|      * @return null | ||||
|      */ | ||||
|     protected function close(ConnectionInterface $conn, $code = 400) { | ||||
|         $response = new Response($code, array( | ||||
|             'X-Powered-By' => \Ratchet\VERSION | ||||
|         )); | ||||
| 
 | ||||
|         $conn->send((string)$response); | ||||
|         $conn->close(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,12 +2,12 @@ | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\MessageComponentInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| 
 | ||||
| interface HttpServerInterface extends MessageComponentInterface { | ||||
|     /** | ||||
|      * @param \Ratchet\ConnectionInterface          $conn | ||||
|      * @param \Guzzle\Http\Message\RequestInterface $request null is default because PHP won't let me overload; don't pass null!!! | ||||
|      * @param \Psr\Http\Message\RequestInterface    $request null is default because PHP won't let me overload; don't pass null!!! | ||||
|      * @throws \UnexpectedValueException if a RequestInterface is not passed | ||||
|      */ | ||||
|     public function onOpen(ConnectionInterface $conn, RequestInterface $request = null); | ||||
|  | ||||
							
								
								
									
										18
									
								
								src/Ratchet/Http/NoOpHttpServerController.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Ratchet/Http/NoOpHttpServerController.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,18 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| 
 | ||||
| class NoOpHttpServerController implements HttpServerInterface { | ||||
|     public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { | ||||
|     } | ||||
| 
 | ||||
|     public function onMessage(ConnectionInterface $from, $msg) { | ||||
|     } | ||||
| 
 | ||||
|     public function onClose(ConnectionInterface $conn) { | ||||
|     } | ||||
| 
 | ||||
|     public function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|     } | ||||
| } | ||||
| @ -1,9 +1,8 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\MessageComponentInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| 
 | ||||
| /** | ||||
|  * A middleware to ensure JavaScript clients connecting are from the expected domain. | ||||
| @ -11,18 +10,20 @@ use Guzzle\Http\Message\Response; | ||||
|  * Note: This can be spoofed from non-web browser clients | ||||
|  */ | ||||
| class OriginCheck implements HttpServerInterface { | ||||
|     use CloseResponseTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Ratchet\MessageComponentInterface | ||||
|      */ | ||||
|     protected $_component; | ||||
| 
 | ||||
|     public $allowedOrigins = array(); | ||||
|     public $allowedOrigins = []; | ||||
| 
 | ||||
|     /** | ||||
|      * @param MessageComponentInterface $component Component/Application to decorate | ||||
|      * @param array                     $allowed An array of allowed domains that are allowed to connect from | ||||
|      * @param array                     $allowed   An array of allowed domains that are allowed to connect from | ||||
|      */ | ||||
|     public function __construct(MessageComponentInterface $component, array $allowed = array()) { | ||||
|     public function __construct(MessageComponentInterface $component, array $allowed = []) { | ||||
|         $this->_component = $component; | ||||
|         $this->allowedOrigins += $allowed; | ||||
|     } | ||||
| @ -31,7 +32,7 @@ class OriginCheck implements HttpServerInterface { | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { | ||||
|         $header = (string)$request->getHeader('Origin'); | ||||
|         $header = (string)$request->getHeader('Origin')[0]; | ||||
|         $origin = parse_url($header, PHP_URL_HOST) ?: $header; | ||||
| 
 | ||||
|         if (!in_array($origin, $this->allowedOrigins)) { | ||||
| @ -61,19 +62,4 @@ class OriginCheck implements HttpServerInterface { | ||||
|     function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|         return $this->_component->onError($conn, $e); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close a connection with an HTTP response | ||||
|      * @param \Ratchet\ConnectionInterface $conn | ||||
|      * @param int                          $code HTTP status code | ||||
|      * @return null | ||||
|      */ | ||||
|     protected function close(ConnectionInterface $conn, $code = 400) { | ||||
|         $response = new Response($code, array( | ||||
|             'X-Powered-By' => \Ratchet\VERSION | ||||
|         )); | ||||
| 
 | ||||
|         $conn->send((string)$response); | ||||
|         $conn->close(); | ||||
|     } | ||||
| } | ||||
| } | ||||
| @ -1,21 +1,25 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| use Guzzle\Http\Url; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Symfony\Component\Routing\Matcher\UrlMatcherInterface; | ||||
| use Symfony\Component\Routing\Exception\MethodNotAllowedException; | ||||
| use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||||
| use GuzzleHttp\Psr7 as gPsr; | ||||
| 
 | ||||
| class Router implements HttpServerInterface { | ||||
|     use CloseResponseTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface | ||||
|      */ | ||||
|     protected $_matcher; | ||||
| 
 | ||||
|     private $_noopController; | ||||
| 
 | ||||
|     public function __construct(UrlMatcherInterface $matcher) { | ||||
|         $this->_matcher = $matcher; | ||||
|         $this->_noopController = new NoOpHttpServerController; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -27,12 +31,16 @@ class Router implements HttpServerInterface { | ||||
|             throw new \UnexpectedValueException('$request can not be null'); | ||||
|         } | ||||
| 
 | ||||
|         $conn->controller = $this->_noopController; | ||||
| 
 | ||||
|         $uri = $request->getUri(); | ||||
| 
 | ||||
|         $context = $this->_matcher->getContext(); | ||||
|         $context->setMethod($request->getMethod()); | ||||
|         $context->setHost($request->getHost()); | ||||
|         $context->setHost($uri->getHost()); | ||||
| 
 | ||||
|         try { | ||||
|             $route = $this->_matcher->match($request->getPath()); | ||||
|             $route = $this->_matcher->match($uri->getPath()); | ||||
|         } catch (MethodNotAllowedException $nae) { | ||||
|             return $this->close($conn, 405, array('Allow' => $nae->getAllowedMethods())); | ||||
|         } catch (ResourceNotFoundException $nfe) { | ||||
| @ -47,17 +55,15 @@ class Router implements HttpServerInterface { | ||||
|             throw new \UnexpectedValueException('All routes must implement Ratchet\Http\HttpServerInterface'); | ||||
|         } | ||||
| 
 | ||||
|         $parameters = array(); | ||||
|         $parameters = []; | ||||
|         foreach($route as $key => $value) { | ||||
|             if ((is_string($key)) && ('_' !== substr($key, 0, 1))) { | ||||
|                 $parameters[$key] = $value; | ||||
|             } | ||||
|         } | ||||
|         $parameters = array_merge($parameters, $request->getQuery()->getAll()); | ||||
|         $parameters = array_merge($parameters, gPsr\parse_query($uri->getQuery() ?: '')); | ||||
| 
 | ||||
|         $url = Url::factory($request->getUrl()); | ||||
|         $url->setQuery($parameters); | ||||
|         $request->setUrl($url); | ||||
|         $request = $request->withUri($uri->withQuery(gPsr\build_query($parameters))); | ||||
| 
 | ||||
|         $conn->controller = $route['_controller']; | ||||
|         $conn->controller->onOpen($conn, $request); | ||||
| @ -66,14 +72,14 @@ class Router implements HttpServerInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     function onMessage(ConnectionInterface $from, $msg) { | ||||
|     public function onMessage(ConnectionInterface $from, $msg) { | ||||
|         $from->controller->onMessage($from, $msg); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     function onClose(ConnectionInterface $conn) { | ||||
|     public function onClose(ConnectionInterface $conn) { | ||||
|         if (isset($conn->controller)) { | ||||
|             $conn->controller->onClose($conn); | ||||
|         } | ||||
| @ -82,26 +88,9 @@ class Router implements HttpServerInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|     public function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|         if (isset($conn->controller)) { | ||||
|             $conn->controller->onError($conn, $e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Close a connection with an HTTP response | ||||
|      * @param \Ratchet\ConnectionInterface $conn | ||||
|      * @param int $code HTTP status code | ||||
|      * @param array $additionalHeaders | ||||
|      * @return null | ||||
|      */ | ||||
|     protected function close(ConnectionInterface $conn, $code = 400, array $additionalHeaders = array()) { | ||||
|         $headers = array_merge(array( | ||||
|             'X-Powered-By' => \Ratchet\VERSION | ||||
|         ), $additionalHeaders); | ||||
|         $response = new Response($code, $headers); | ||||
| 
 | ||||
|         $conn->send((string)$response); | ||||
|         $conn->close(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -5,6 +5,7 @@ use React\EventLoop\LoopInterface; | ||||
| use React\Socket\ServerInterface; | ||||
| use React\EventLoop\Factory as LoopFactory; | ||||
| use React\Socket\Server as Reactor; | ||||
| use React\Socket\SecureServer as SecureReactor; | ||||
| 
 | ||||
| /** | ||||
|  * Creates an open-ended socket to listen on a port for incoming connections. | ||||
| @ -21,12 +22,6 @@ class IoServer { | ||||
|      */ | ||||
|     public $app; | ||||
| 
 | ||||
|     /** | ||||
|      * Array of React event handlers | ||||
|      * @var \SplFixedArray | ||||
|      */ | ||||
|     protected $handlers; | ||||
| 
 | ||||
|     /** | ||||
|      * The socket server the Ratchet Application is run off of | ||||
|      * @var \React\Socket\ServerInterface | ||||
| @ -51,23 +46,17 @@ class IoServer { | ||||
|         $this->socket = $socket; | ||||
| 
 | ||||
|         $socket->on('connection', array($this, 'handleConnect')); | ||||
| 
 | ||||
|         $this->handlers = new \SplFixedArray(3); | ||||
|         $this->handlers[0] = array($this, 'handleData'); | ||||
|         $this->handlers[1] = array($this, 'handleEnd'); | ||||
|         $this->handlers[2] = array($this, 'handleError'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received | ||||
|      * @param  int                                $port      The port to server sockets on | ||||
|      * @param  string                             $address   The address to receive sockets on (0.0.0.0 means receive connections from any) | ||||
|      * @param  \Ratchet\MessageComponentInterface $component  The application that I/O will call when events are received | ||||
|      * @param  int                                $port       The port to server sockets on | ||||
|      * @param  string                             $address    The address to receive sockets on (0.0.0.0 means receive connections from any) | ||||
|      * @return IoServer | ||||
|      */ | ||||
|     public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { | ||||
|         $loop   = LoopFactory::create(); | ||||
|         $socket = new Reactor($loop); | ||||
|         $socket->listen($port, $address); | ||||
|         $socket = new Reactor($address . ':' . $port, $loop); | ||||
| 
 | ||||
|         return new static($component, $socket, $loop); | ||||
|     } | ||||
| @ -92,15 +81,25 @@ class IoServer { | ||||
|      */ | ||||
|     public function handleConnect($conn) { | ||||
|         $conn->decor = new IoConnection($conn); | ||||
|         $conn->decor->resourceId = (int)$conn->stream; | ||||
| 
 | ||||
|         $conn->decor->resourceId    = (int)$conn->stream; | ||||
|         $conn->decor->remoteAddress = $conn->getRemoteAddress(); | ||||
|         $uri = $conn->getRemoteAddress(); | ||||
|         $conn->decor->remoteAddress = trim( | ||||
|             parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST), | ||||
|             '[]' | ||||
|         ); | ||||
| 
 | ||||
|         $this->app->onOpen($conn->decor); | ||||
| 
 | ||||
|         $conn->on('data', $this->handlers[0]); | ||||
|         $conn->on('end', $this->handlers[1]); | ||||
|         $conn->on('error', $this->handlers[2]); | ||||
|         $conn->on('data', function ($data) use ($conn) { | ||||
|             $this->handleData($data, $conn); | ||||
|         }); | ||||
|         $conn->on('close', function () use ($conn) { | ||||
|             $this->handleEnd($conn); | ||||
|         }); | ||||
|         $conn->on('error', function (\Exception $e) use ($conn) { | ||||
|             $this->handleError($e, $conn); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| <?php | ||||
| namespace Ratchet\Session; | ||||
| use Ratchet\MessageComponentInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\WebSocket\WsServerInterface; | ||||
| use Ratchet\Http\HttpServerInterface; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Ratchet\Session\Storage\VirtualSessionStorage; | ||||
| use Ratchet\Session\Serialize\HandlerInterface; | ||||
| use Symfony\Component\HttpFoundation\Session\Session; | ||||
| @ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; | ||||
|  * Your website must also use Symfony HttpFoundation Sessions to read your sites session data | ||||
|  * If your are not using at least PHP 5.4 you must include a SessionHandlerInterface stub (is included in Symfony HttpFoundation, loaded w/ composer) | ||||
|  */ | ||||
| class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
| class SessionProvider implements HttpServerInterface { | ||||
|     /** | ||||
|      * @var \Ratchet\MessageComponentInterface | ||||
|      */ | ||||
| @ -38,13 +38,13 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
|     protected $_serializer; | ||||
| 
 | ||||
|     /** | ||||
|      * @param \Ratchet\MessageComponentInterface          $app | ||||
|      * @param \Ratchet\Http\HttpServerInterface           $app | ||||
|      * @param \SessionHandlerInterface                    $handler | ||||
|      * @param array                                       $options | ||||
|      * @param \Ratchet\Session\Serialize\HandlerInterface $serializer | ||||
|      * @throws \RuntimeException | ||||
|      */ | ||||
|     public function __construct(MessageComponentInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { | ||||
|     public function __construct(HttpServerInterface $app, \SessionHandlerInterface $handler, array $options = array(), HandlerInterface $serializer = null) { | ||||
|         $this->_app     = $app; | ||||
|         $this->_handler = $handler; | ||||
|         $this->_null    = new NullSessionHandler; | ||||
| @ -70,8 +70,20 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     function onOpen(ConnectionInterface $conn) { | ||||
|         if (!isset($conn->WebSocket) || null === ($id = $conn->WebSocket->request->getCookie(ini_get('session.name')))) { | ||||
|     public function onOpen(ConnectionInterface $conn, RequestInterface $request = null) { | ||||
|         $sessionName = ini_get('session.name'); | ||||
| 
 | ||||
|         $id = array_reduce($request->getHeader('Cookie'), function($accumulator, $cookie) use ($sessionName) { | ||||
|             if ($accumulator) { | ||||
|                 return $accumulator; | ||||
|             } | ||||
| 
 | ||||
|             $crumbs = $this->parseCookie($cookie); | ||||
| 
 | ||||
|             return isset($crumbs['cookies'][$sessionName]) ? $crumbs['cookies'][$sessionName] : false; | ||||
|         }, false); | ||||
| 
 | ||||
|         if (null === $request || false === $id) { | ||||
|             $saveHandler = $this->_null; | ||||
|             $id = ''; | ||||
|         } else { | ||||
| @ -84,7 +96,7 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
|             $conn->Session->start(); | ||||
|         } | ||||
| 
 | ||||
|         return $this->_app->onOpen($conn); | ||||
|         return $this->_app->onOpen($conn, $request); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -110,17 +122,6 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
|         return $this->_app->onError($conn, $e); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getSubProtocols() { | ||||
|         if ($this->_app instanceof WsServerInterface) { | ||||
|             return $this->_app->getSubProtocols(); | ||||
|         } else { | ||||
|             return array(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set all the php session. ini options | ||||
|      * © Symfony | ||||
| @ -158,4 +159,85 @@ class SessionProvider implements MessageComponentInterface, WsServerInterface { | ||||
|     protected function toClassCase($langDef) { | ||||
|         return str_replace(' ', '', ucwords(str_replace('_', ' ', $langDef))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Taken from Guzzle3 | ||||
|      */ | ||||
|     private static $cookieParts = array( | ||||
|         'domain'      => 'Domain', | ||||
|         'path'        => 'Path', | ||||
|         'max_age'     => 'Max-Age', | ||||
|         'expires'     => 'Expires', | ||||
|         'version'     => 'Version', | ||||
|         'secure'      => 'Secure', | ||||
|         'port'        => 'Port', | ||||
|         'discard'     => 'Discard', | ||||
|         'comment'     => 'Comment', | ||||
|         'comment_url' => 'Comment-Url', | ||||
|         'http_only'   => 'HttpOnly' | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * Taken from Guzzle3 | ||||
|      */ | ||||
|     private function parseCookie($cookie, $host = null, $path = null, $decode = false) { | ||||
|         // Explode the cookie string using a series of semicolons
 | ||||
|         $pieces = array_filter(array_map('trim', explode(';', $cookie))); | ||||
| 
 | ||||
|         // The name of the cookie (first kvp) must include an equal sign.
 | ||||
|         if (empty($pieces) || !strpos($pieces[0], '=')) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         // Create the default return array
 | ||||
|         $data = array_merge(array_fill_keys(array_keys(self::$cookieParts), null), array( | ||||
|             'cookies'   => array(), | ||||
|             'data'      => array(), | ||||
|             'path'      => $path ?: '/', | ||||
|             'http_only' => false, | ||||
|             'discard'   => false, | ||||
|             'domain'    => $host | ||||
|         )); | ||||
|         $foundNonCookies = 0; | ||||
| 
 | ||||
|         // Add the cookie pieces into the parsed data array
 | ||||
|         foreach ($pieces as $part) { | ||||
| 
 | ||||
|             $cookieParts = explode('=', $part, 2); | ||||
|             $key = trim($cookieParts[0]); | ||||
| 
 | ||||
|             if (count($cookieParts) == 1) { | ||||
|                 // Can be a single value (e.g. secure, httpOnly)
 | ||||
|                 $value = true; | ||||
|             } else { | ||||
|                 // Be sure to strip wrapping quotes
 | ||||
|                 $value = trim($cookieParts[1], " \n\r\t\0\x0B\""); | ||||
|                 if ($decode) { | ||||
|                     $value = urldecode($value); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // Only check for non-cookies when cookies have been found
 | ||||
|             if (!empty($data['cookies'])) { | ||||
|                 foreach (self::$cookieParts as $mapValue => $search) { | ||||
|                     if (!strcasecmp($search, $key)) { | ||||
|                         $data[$mapValue] = $mapValue == 'port' ? array_map('trim', explode(',', $value)) : $value; | ||||
|                         $foundNonCookies++; | ||||
|                         continue 2; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // If cookies have not yet been retrieved, or this value was not found in the pieces array, treat it as a
 | ||||
|             // cookie. IF non-cookies have been parsed, then this isn't a cookie, it's cookie data. Cookies then data.
 | ||||
|             $data[$foundNonCookies ? 'data' : 'cookies'][$key] = $value; | ||||
|         } | ||||
| 
 | ||||
|         // Calculate the expires date
 | ||||
|         if (!$data['expires'] && $data['max_age']) { | ||||
|             $data['expires'] = time() + (int) $data['max_age']; | ||||
|         } | ||||
| 
 | ||||
|         return $data; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,7 @@ use Ratchet\ConnectionInterface; | ||||
|  * WebSocket Application Messaging Protocol | ||||
|  * | ||||
|  * @link http://wamp.ws/spec | ||||
|  * @link https://github.com/oberstet/AutobahnJS | ||||
|  * @link https://github.com/oberstet/autobahn-js | ||||
|  * | ||||
|  * +--------------+----+------------------+ | ||||
|  * | Message Type | ID | DIRECTION        | | ||||
| @ -62,9 +62,9 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface { | ||||
|             $subs[] = 'wamp'; | ||||
| 
 | ||||
|             return $subs; | ||||
|         } else { | ||||
|             return array('wamp'); | ||||
|         } | ||||
| 
 | ||||
|         return ['wamp']; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -93,6 +93,10 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface { | ||||
|             throw new Exception("Invalid WAMP message format"); | ||||
|         } | ||||
| 
 | ||||
|         if (isset($json[1]) && !(is_string($json[1]) || is_numeric($json[1]))) { | ||||
|             throw new Exception('Invalid Topic, must be a string'); | ||||
|         } | ||||
| 
 | ||||
|         switch ($json[0]) { | ||||
|             case static::MSG_PREFIX: | ||||
|                 $from->WAMP->prefixes[$json[1]] = $json[2]; | ||||
| @ -122,13 +126,13 @@ class ServerProtocol implements MessageComponentInterface, WsServerInterface { | ||||
|                 $exclude  = (array_key_exists(3, $json) ? $json[3] : null); | ||||
|                 if (!is_array($exclude)) { | ||||
|                     if (true === (boolean)$exclude) { | ||||
|                         $exclude = array($from->WAMP->sessionId); | ||||
|                         $exclude = [$from->WAMP->sessionId]; | ||||
|                     } else { | ||||
|                         $exclude = array(); | ||||
|                         $exclude = []; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 $eligible = (array_key_exists(4, $json) ? $json[4] : array()); | ||||
|                 $eligible = (array_key_exists(4, $json) ? $json[4] : []); | ||||
| 
 | ||||
|                 $this->_decorating->onPublish($from, $from->getUri($json[1]), $json[2], $exclude, $eligible); | ||||
|             break; | ||||
|  | ||||
| @ -6,13 +6,6 @@ use Ratchet\ConnectionInterface; | ||||
|  * A topic/channel containing connections that have subscribed to it | ||||
|  */ | ||||
| class Topic implements \IteratorAggregate, \Countable { | ||||
|     /** | ||||
|      * If true the TopicManager will destroy this object if it's ever empty of connections | ||||
|      * @deprecated in v0.4 | ||||
|      * @type bool | ||||
|      */ | ||||
|     public $autoDelete = false; | ||||
| 
 | ||||
|     private $id; | ||||
| 
 | ||||
|     private $subscribers; | ||||
| @ -38,7 +31,7 @@ class Topic implements \IteratorAggregate, \Countable { | ||||
| 
 | ||||
|     /** | ||||
|      * Send a message to all the connections in this topic | ||||
|      * @param string $msg Payload to publish | ||||
|      * @param string|array $msg Payload to publish | ||||
|      * @param array $exclude A list of session IDs the message should be excluded from (blacklist) | ||||
|      * @param array $eligible A list of session Ids the message should be send to (whitelist) | ||||
|      * @return Topic The same Topic object to chain | ||||
|  | ||||
| @ -118,7 +118,7 @@ class TopicManager implements WsServerInterface, WampServerInterface { | ||||
| 
 | ||||
|         $this->topicLookup[$topic->getId()]->remove($conn); | ||||
| 
 | ||||
|         if ($topic->autoDelete && 0 === $topic->count()) { | ||||
|         if (0 === $topic->count()) { | ||||
|             unset($this->topicLookup[$topic->getId()]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -8,7 +8,7 @@ use Ratchet\ConnectionInterface; | ||||
|  * Enable support for the official WAMP sub-protocol in your application | ||||
|  * WAMP allows for Pub/Sub and RPC | ||||
|  * @link http://wamp.ws The WAMP specification | ||||
|  * @link https://github.com/oberstet/AutobahnJS Souce for client side library | ||||
|  * @link https://github.com/oberstet/autobahn-js Souce for client side library | ||||
|  * @link http://autobahn.s3.amazonaws.com/js/autobahn.min.js Minified client side library | ||||
|  */ | ||||
| class WampServer implements MessageComponentInterface, WsServerInterface { | ||||
|  | ||||
							
								
								
									
										20
									
								
								src/Ratchet/WebSocket/ConnContext.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/Ratchet/WebSocket/ConnContext.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\RFC6455\Messaging\MessageBuffer; | ||||
| 
 | ||||
| class ConnContext { | ||||
|     /** | ||||
|      * @var \Ratchet\WebSocket\WsConnection | ||||
|      */ | ||||
|     public $connection; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Ratchet\RFC6455\Messaging\MessageBuffer; | ||||
|      */ | ||||
|     public $buffer; | ||||
| 
 | ||||
|     public function __construct(WsConnection $conn, MessageBuffer $buffer) { | ||||
|         $this->connection = $conn; | ||||
|         $this->buffer = $buffer; | ||||
|     } | ||||
| } | ||||
| @ -1,31 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Encoding; | ||||
| 
 | ||||
| class ToggleableValidator implements ValidatorInterface { | ||||
|     /** | ||||
|      * Toggle if checkEncoding checks the encoding or not | ||||
|      * @var bool | ||||
|      */ | ||||
|     public $on; | ||||
| 
 | ||||
|     /** | ||||
|      * @var Validator | ||||
|      */ | ||||
|     private $validator; | ||||
| 
 | ||||
|     public function __construct($on = true) { | ||||
|         $this->validator = new Validator; | ||||
|         $this->on        = (boolean)$on; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function checkEncoding($str, $encoding) { | ||||
|         if (!(boolean)$this->on) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return $this->validator->checkEncoding($str, $encoding); | ||||
|     } | ||||
| } | ||||
| @ -1,93 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Encoding; | ||||
| 
 | ||||
| /** | ||||
|  * This class handled encoding validation | ||||
|  */ | ||||
| class Validator { | ||||
|     const UTF8_ACCEPT = 0; | ||||
|     const UTF8_REJECT = 1; | ||||
| 
 | ||||
|     /** | ||||
|      * Incremental UTF-8 validator with constant memory consumption (minimal state). | ||||
|      * | ||||
|      * Implements the algorithm "Flexible and Economical UTF-8 Decoder" by | ||||
|      * Bjoern Hoehrmann (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/). | ||||
|      */ | ||||
|     protected static $dfa = array( | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 00..1f
 | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 20..3f
 | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 40..5f
 | ||||
|         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, # 60..7f
 | ||||
|         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, # 80..9f
 | ||||
|         7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, # a0..bf
 | ||||
|         8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, # c0..df
 | ||||
|         0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, # e0..ef
 | ||||
|         0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, # f0..ff
 | ||||
|         0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, # s0..s0
 | ||||
|         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, # s1..s2
 | ||||
|         1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, # s3..s4
 | ||||
|         1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, # s5..s6
 | ||||
|         1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, # s7..s8
 | ||||
|     ); | ||||
| 
 | ||||
|     /** | ||||
|      * Lookup if mbstring is available | ||||
|      * @var bool | ||||
|      */ | ||||
|      private $hasMbString = false; | ||||
| 
 | ||||
|      /** | ||||
|       * Lookup if iconv is available | ||||
|       * @var bool | ||||
|       */ | ||||
|      private $hasIconv = false; | ||||
| 
 | ||||
|     public function __construct() { | ||||
|         $this->hasMbString = extension_loaded('mbstring'); | ||||
|         $this->hasIconv    = extension_loaded('iconv'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  string $str     The value to check the encoding | ||||
|      * @param  string $against The type of encoding to check against | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function checkEncoding($str, $against) { | ||||
|         if ('UTF-8' == $against) { | ||||
|             return $this->isUtf8($str); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->hasMbString) { | ||||
|             return mb_check_encoding($str, $against); | ||||
|         } elseif ($this->hasIconv) { | ||||
|             return ($str == iconv($against, "{$against}//IGNORE", $str)); | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     protected function isUtf8($str) { | ||||
|         if ($this->hasMbString) { | ||||
|             if (false === mb_check_encoding($str, 'UTF-8')) { | ||||
|                 return false; | ||||
|             } | ||||
|         } elseif ($this->hasIconv) { | ||||
|             if ($str != iconv('UTF-8', 'UTF-8//IGNORE', $str)) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $state = static::UTF8_ACCEPT; | ||||
| 
 | ||||
|         for ($i = 0, $len   = strlen($str); $i < $len; $i++) { | ||||
|             $state = static::$dfa[256 + ($state << 4) + static::$dfa[ord($str[$i])]]; | ||||
| 
 | ||||
|             if (static::UTF8_REJECT === $state) { | ||||
|                 return false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Encoding; | ||||
| 
 | ||||
| interface ValidatorInterface { | ||||
|     /** | ||||
|      * Verify a string matches the encoding type | ||||
|      * @param  string $str      The string to check | ||||
|      * @param  string $encoding The encoding type to check against | ||||
|      * @return bool | ||||
|      */ | ||||
|     function checkEncoding($str, $encoding); | ||||
| } | ||||
							
								
								
									
										8
									
								
								src/Ratchet/WebSocket/MessageCallableInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/Ratchet/WebSocket/MessageCallableInterface.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\RFC6455\Messaging\MessageInterface; | ||||
| 
 | ||||
| interface MessageCallableInterface { | ||||
|     public function onMessage(ConnectionInterface $conn, MessageInterface $msg); | ||||
| } | ||||
							
								
								
									
										6
									
								
								src/Ratchet/WebSocket/MessageComponentInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/Ratchet/WebSocket/MessageComponentInterface.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\ComponentInterface; | ||||
| 
 | ||||
| interface MessageComponentInterface extends ComponentInterface, MessageCallableInterface { | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| 
 | ||||
| interface DataInterface { | ||||
|     /** | ||||
|      * Determine if the message is complete or still fragmented | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isCoalesced(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the number of bytes the payload is set to be | ||||
|      * @return int | ||||
|      */ | ||||
|     function getPayloadLength(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the payload (message) sent from peer | ||||
|      * @return string | ||||
|      */ | ||||
|     function getPayload(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get raw contents of the message | ||||
|      * @return string | ||||
|      */ | ||||
|     function getContents(); | ||||
| } | ||||
| @ -1,38 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| 
 | ||||
| interface FrameInterface extends DataInterface { | ||||
|     /** | ||||
|      * Add incoming data to the frame from peer | ||||
|      * @param string | ||||
|      */ | ||||
|     function addBuffer($buf); | ||||
| 
 | ||||
|     /** | ||||
|      * Is this the final frame in a fragmented message? | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isFinal(); | ||||
| 
 | ||||
|     /** | ||||
|      * Is the payload masked? | ||||
|      * @return bool | ||||
|      */ | ||||
|     function isMasked(); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     function getOpcode(); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     //function getReceivedPayloadLength();
 | ||||
| 
 | ||||
|     /** | ||||
|      * 32-big string | ||||
|      * @return string | ||||
|      */ | ||||
|     function getMaskingKey(); | ||||
| } | ||||
| @ -1,120 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\MessageInterface; | ||||
| use Ratchet\WebSocket\Version\Hixie76\Connection; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| use Ratchet\WebSocket\Version\Hixie76\Frame; | ||||
| 
 | ||||
| /** | ||||
|  * FOR THE LOVE OF BEER, PLEASE PLEASE PLEASE DON'T allow the use of this in your application! | ||||
|  * Hixie76 is bad for 2 (there's more) reasons: | ||||
|  *  1) The handshake is done in HTTP, which includes a key for signing in the body... | ||||
|  *     BUT there is no Length defined in the header (as per HTTP spec) so the TCP buffer can't tell when the message is done! | ||||
|  *  2) By nature it's insecure.  Google did a test study where they were able to do a | ||||
|  *     man-in-the-middle attack on 10%-15% of the people who saw their ad who had a browser (currently only Safari) supporting the Hixie76 protocol. | ||||
|  *     This was exploited by taking advantage of proxy servers in front of the user who ignored some HTTP headers in the handshake | ||||
|  * The Hixie76 is currently implemented by Safari | ||||
|  * @link http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 | ||||
|  */ | ||||
| class Hixie76 implements VersionInterface { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isProtocol(RequestInterface $request) { | ||||
|         return !(null === $request->getHeader('Sec-WebSocket-Key2')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getVersionNumber() { | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Guzzle\Http\Message\RequestInterface $request | ||||
|      * @return \Guzzle\Http\Message\Response | ||||
|      * @throws \UnderflowException If there hasn't been enough data received | ||||
|      */ | ||||
|     public function handshake(RequestInterface $request) { | ||||
|         $body = substr($request->getBody(), 0, 8); | ||||
|         if (8 !== strlen($body)) { | ||||
|             throw new \UnderflowException("Not enough data received to issue challenge response"); | ||||
|         } | ||||
| 
 | ||||
|         $challenge = $this->sign((string)$request->getHeader('Sec-WebSocket-Key1'), (string)$request->getHeader('Sec-WebSocket-Key2'), $body); | ||||
| 
 | ||||
|         $headers = array( | ||||
|             'Upgrade'                => 'WebSocket' | ||||
|           , 'Connection'             => 'Upgrade' | ||||
|           , 'Sec-WebSocket-Origin'   => (string)$request->getHeader('Origin') | ||||
|           , 'Sec-WebSocket-Location' => 'ws://' . (string)$request->getHeader('Host') . $request->getPath() | ||||
|         ); | ||||
| 
 | ||||
|         $response = new Response(101, $headers, $challenge); | ||||
|         $response->setStatus(101, 'WebSocket Protocol Handshake'); | ||||
| 
 | ||||
|         return $response; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { | ||||
|         $upgraded = new Connection($conn); | ||||
| 
 | ||||
|         if (!isset($upgraded->WebSocket)) { | ||||
|             $upgraded->WebSocket = new \StdClass; | ||||
|         } | ||||
| 
 | ||||
|         $upgraded->WebSocket->coalescedCallback = $coalescedCallback; | ||||
| 
 | ||||
|         return $upgraded; | ||||
|     } | ||||
| 
 | ||||
|     public function onMessage(ConnectionInterface $from, $data) { | ||||
|         $overflow = ''; | ||||
| 
 | ||||
|         if (!isset($from->WebSocket->frame)) { | ||||
|             $from->WebSocket->frame = $this->newFrame(); | ||||
|         } | ||||
| 
 | ||||
|         $from->WebSocket->frame->addBuffer($data); | ||||
|         if ($from->WebSocket->frame->isCoalesced()) { | ||||
|             $overflow = $from->WebSocket->frame->extractOverflow(); | ||||
| 
 | ||||
|             $parsed = $from->WebSocket->frame->getPayload(); | ||||
|             unset($from->WebSocket->frame); | ||||
| 
 | ||||
|             $from->WebSocket->coalescedCallback->onMessage($from, $parsed); | ||||
| 
 | ||||
|             unset($from->WebSocket->frame); | ||||
|         } | ||||
| 
 | ||||
|         if (strlen($overflow) > 0) { | ||||
|             $this->onMessage($from, $overflow); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function newFrame() { | ||||
|         return new Frame; | ||||
|     } | ||||
| 
 | ||||
|     public function generateKeyNumber($key) { | ||||
|         if (0 === substr_count($key, ' ')) { | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         return preg_replace('[\D]', '', $key) / substr_count($key, ' '); | ||||
|     } | ||||
| 
 | ||||
|     protected function sign($key1, $key2, $code) { | ||||
|         return md5( | ||||
|             pack('N', $this->generateKeyNumber($key1)) | ||||
|           . pack('N', $this->generateKeyNumber($key2)) | ||||
|           . $code | ||||
|         , true); | ||||
|     } | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\Hixie76; | ||||
| use Ratchet\AbstractConnectionDecorator; | ||||
| 
 | ||||
| /** | ||||
|  * {@inheritdoc} | ||||
|  * @property \StdClass $WebSocket | ||||
|  */ | ||||
| class Connection extends AbstractConnectionDecorator { | ||||
|     public function send($msg) { | ||||
|         if (!$this->WebSocket->closing) { | ||||
|             $this->getConnection()->send(chr(0) . $msg . chr(255)); | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     public function close() { | ||||
|         if (!$this->WebSocket->closing) { | ||||
|             $this->getConnection()->send(chr(255)); | ||||
|             $this->getConnection()->close(); | ||||
| 
 | ||||
|             $this->WebSocket->closing = true; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1,86 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\Hixie76; | ||||
| use Ratchet\WebSocket\Version\FrameInterface; | ||||
| 
 | ||||
| /** | ||||
|  * This does not entirely follow the protocol to spec, but (mostly) works | ||||
|  * Hixie76 probably should not even be supported | ||||
|  */ | ||||
| class Frame implements FrameInterface { | ||||
|     /** | ||||
|      * @type string | ||||
|      */ | ||||
|     protected $_data = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced() { | ||||
|         return (boolean)($this->_data[0] == chr(0) && substr($this->_data, -1) == chr(255)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addBuffer($buf) { | ||||
|         $this->_data .= (string)$buf; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isFinal() { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isMasked() { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode() { | ||||
|         return 1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Not enough of the message has been buffered to determine the length of the payload'); | ||||
|         } | ||||
| 
 | ||||
|         return strlen($this->_data) - 2; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getMaskingKey() { | ||||
|         return ''; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             return new \UnderflowException('Not enough data buffered to read payload'); | ||||
|         } | ||||
| 
 | ||||
|         return substr($this->_data, 1, strlen($this->_data) - 2); | ||||
|     } | ||||
| 
 | ||||
|     public function getContents() { | ||||
|         return $this->_data; | ||||
|     } | ||||
| 
 | ||||
|     public function extractOverflow() { | ||||
|         return ''; | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| 
 | ||||
| class HyBi10 extends RFC6455 { | ||||
|     public function isProtocol(RequestInterface $request) { | ||||
|         $version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); | ||||
| 
 | ||||
|         return ($version >= 6 && $version < 13); | ||||
|     } | ||||
| 
 | ||||
|     public function getVersionNumber() { | ||||
|         return 6; | ||||
|     } | ||||
| } | ||||
| @ -1,15 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| 
 | ||||
| interface MessageInterface extends DataInterface { | ||||
|     /** | ||||
|      * @param FrameInterface $fragment | ||||
|      * @return MessageInterface | ||||
|      */ | ||||
|     function addFrame(FrameInterface $fragment); | ||||
| 
 | ||||
|     /** | ||||
|      * @return int | ||||
|      */ | ||||
|     function getOpcode(); | ||||
| } | ||||
| @ -1,273 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\MessageInterface; | ||||
| use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Message; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Frame; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Connection; | ||||
| use Ratchet\WebSocket\Encoding\ValidatorInterface; | ||||
| use Ratchet\WebSocket\Encoding\Validator; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| 
 | ||||
| /** | ||||
|  * The latest version of the WebSocket protocol | ||||
|  * @link http://tools.ietf.org/html/rfc6455 | ||||
|  * @todo Unicode: return mb_convert_encoding(pack("N",$u), mb_internal_encoding(), 'UCS-4BE'); | ||||
|  */ | ||||
| class RFC6455 implements VersionInterface { | ||||
|     const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; | ||||
| 
 | ||||
|     /** | ||||
|      * @var RFC6455\HandshakeVerifier | ||||
|      */ | ||||
|     protected $_verifier; | ||||
| 
 | ||||
|     /** | ||||
|      * A lookup of the valid close codes that can be sent in a frame | ||||
|      * @var array | ||||
|      */ | ||||
|     private $closeCodes = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * @var \Ratchet\WebSocket\Encoding\ValidatorInterface | ||||
|      */ | ||||
|     protected $validator; | ||||
| 
 | ||||
|     public function __construct(ValidatorInterface $validator = null) { | ||||
|         $this->_verifier = new HandshakeVerifier; | ||||
|         $this->setCloseCodes(); | ||||
| 
 | ||||
|         if (null === $validator) { | ||||
|             $validator = new Validator; | ||||
|         } | ||||
| 
 | ||||
|         $this->validator = $validator; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isProtocol(RequestInterface $request) { | ||||
|         $version = (int)(string)$request->getHeader('Sec-WebSocket-Version'); | ||||
| 
 | ||||
|         return ($this->getVersionNumber() === $version); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getVersionNumber() { | ||||
|         return 13; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function handshake(RequestInterface $request) { | ||||
|         if (true !== $this->_verifier->verifyAll($request)) { | ||||
|             return new Response(400); | ||||
|         } | ||||
| 
 | ||||
|         return new Response(101, array( | ||||
|             'Upgrade'              => 'websocket' | ||||
|           , 'Connection'           => 'Upgrade' | ||||
|           , 'Sec-WebSocket-Accept' => $this->sign((string)$request->getHeader('Sec-WebSocket-Key')) | ||||
|         )); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Ratchet\ConnectionInterface $conn | ||||
|      * @param  \Ratchet\MessageInterface    $coalescedCallback | ||||
|      * @return \Ratchet\WebSocket\Version\RFC6455\Connection | ||||
|      */ | ||||
|     public function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback) { | ||||
|         $upgraded = new Connection($conn); | ||||
| 
 | ||||
|         if (!isset($upgraded->WebSocket)) { | ||||
|             $upgraded->WebSocket = new \StdClass; | ||||
|         } | ||||
| 
 | ||||
|         $upgraded->WebSocket->coalescedCallback = $coalescedCallback; | ||||
| 
 | ||||
|         return $upgraded; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param \Ratchet\WebSocket\Version\RFC6455\Connection $from | ||||
|      * @param string                                        $data | ||||
|      */ | ||||
|     public function onMessage(ConnectionInterface $from, $data) { | ||||
|         $overflow = ''; | ||||
| 
 | ||||
|         if (!isset($from->WebSocket->message)) { | ||||
|             $from->WebSocket->message = $this->newMessage(); | ||||
|         } | ||||
| 
 | ||||
|         // There is a frame fragment attached to the connection, add to it
 | ||||
|         if (!isset($from->WebSocket->frame)) { | ||||
|             $from->WebSocket->frame = $this->newFrame(); | ||||
|         } | ||||
| 
 | ||||
|         $from->WebSocket->frame->addBuffer($data); | ||||
|         if ($from->WebSocket->frame->isCoalesced()) { | ||||
|             $frame = $from->WebSocket->frame; | ||||
| 
 | ||||
|             if (false !== $frame->getRsv1() || | ||||
|                 false !== $frame->getRsv2() || | ||||
|                 false !== $frame->getRsv3() | ||||
|             ) { | ||||
|                 return $from->close($frame::CLOSE_PROTOCOL); | ||||
|             } | ||||
| 
 | ||||
|             if (!$frame->isMasked()) { | ||||
|                 return $from->close($frame::CLOSE_PROTOCOL); | ||||
|             } | ||||
| 
 | ||||
|             $opcode = $frame->getOpcode(); | ||||
| 
 | ||||
|             if ($opcode > 2) { | ||||
|                 if ($frame->getPayloadLength() > 125 || !$frame->isFinal()) { | ||||
|                     return $from->close($frame::CLOSE_PROTOCOL); | ||||
|                 } | ||||
| 
 | ||||
|                 switch ($opcode) { | ||||
|                     case $frame::OP_CLOSE: | ||||
|                         $closeCode = 0; | ||||
| 
 | ||||
|                         $bin = $frame->getPayload(); | ||||
| 
 | ||||
|                         if (empty($bin)) { | ||||
|                             return $from->close(); | ||||
|                         } | ||||
| 
 | ||||
|                         if (strlen($bin) >= 2) { | ||||
|                             list($closeCode) = array_merge(unpack('n*', substr($bin, 0, 2))); | ||||
|                         } | ||||
| 
 | ||||
|                         if (!$this->isValidCloseCode($closeCode)) { | ||||
|                             return $from->close($frame::CLOSE_PROTOCOL); | ||||
|                         } | ||||
| 
 | ||||
|                         if (!$this->validator->checkEncoding(substr($bin, 2), 'UTF-8')) { | ||||
|                             return $from->close($frame::CLOSE_BAD_PAYLOAD); | ||||
|                         } | ||||
| 
 | ||||
|                         $frame->unMaskPayload(); | ||||
| 
 | ||||
|                         return $from->close($frame); | ||||
|                     break; | ||||
|                     case $frame::OP_PING: | ||||
|                         $from->send($this->newFrame($frame->getPayload(), true, $frame::OP_PONG)); | ||||
|                     break; | ||||
|                     case $frame::OP_PONG: | ||||
|                     break; | ||||
|                     default: | ||||
|                         return $from->close($frame::CLOSE_PROTOCOL); | ||||
|                     break; | ||||
|                 } | ||||
| 
 | ||||
|                 $overflow = $from->WebSocket->frame->extractOverflow(); | ||||
| 
 | ||||
|                 unset($from->WebSocket->frame, $frame, $opcode); | ||||
| 
 | ||||
|                 if (strlen($overflow) > 0) { | ||||
|                     $this->onMessage($from, $overflow); | ||||
|                 } | ||||
| 
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             $overflow = $from->WebSocket->frame->extractOverflow(); | ||||
| 
 | ||||
|             if ($frame::OP_CONTINUE == $frame->getOpcode() && 0 == count($from->WebSocket->message)) { | ||||
|                 return $from->close($frame::CLOSE_PROTOCOL); | ||||
|             } | ||||
| 
 | ||||
|             if (count($from->WebSocket->message) > 0 && $frame::OP_CONTINUE != $frame->getOpcode()) { | ||||
|                 return $from->close($frame::CLOSE_PROTOCOL); | ||||
|             } | ||||
| 
 | ||||
|             $from->WebSocket->message->addFrame($from->WebSocket->frame); | ||||
|             unset($from->WebSocket->frame); | ||||
|         } | ||||
| 
 | ||||
|         if ($from->WebSocket->message->isCoalesced()) { | ||||
|             $parsed = $from->WebSocket->message->getPayload(); | ||||
|             unset($from->WebSocket->message); | ||||
| 
 | ||||
|             if (!$this->validator->checkEncoding($parsed, 'UTF-8')) { | ||||
|                 return $from->close(Frame::CLOSE_BAD_PAYLOAD); | ||||
|             } | ||||
| 
 | ||||
|             $from->WebSocket->coalescedCallback->onMessage($from, $parsed); | ||||
|         } | ||||
| 
 | ||||
|         if (strlen($overflow) > 0) { | ||||
|             $this->onMessage($from, $overflow); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return RFC6455\Message | ||||
|      */ | ||||
|     public function newMessage() { | ||||
|         return new Message; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $payload | ||||
|      * @param bool|null   $final | ||||
|      * @param int|null    $opcode | ||||
|      * @return RFC6455\Frame | ||||
|      */ | ||||
|     public function newFrame($payload = null, $final = null, $opcode = null) { | ||||
|         return new Frame($payload, $final, $opcode); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Used when doing the handshake to encode the key, verifying client/server are speaking the same language | ||||
|      * @param  string $key | ||||
|      * @return string | ||||
|      * @internal | ||||
|      */ | ||||
|     public function sign($key) { | ||||
|         return base64_encode(sha1($key . static::GUID, true)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Determine if a close code is valid | ||||
|      * @param int|string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isValidCloseCode($val) { | ||||
|         if (array_key_exists($val, $this->closeCodes)) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         if ($val >= 3000 && $val <= 4999) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates a private lookup of valid, private close codes | ||||
|      */ | ||||
|     protected function setCloseCodes() { | ||||
|         $this->closeCodes[Frame::CLOSE_NORMAL]      = true; | ||||
|         $this->closeCodes[Frame::CLOSE_GOING_AWAY]  = true; | ||||
|         $this->closeCodes[Frame::CLOSE_PROTOCOL]    = true; | ||||
|         $this->closeCodes[Frame::CLOSE_BAD_DATA]    = true; | ||||
|         //$this->closeCodes[Frame::CLOSE_NO_STATUS]   = true;
 | ||||
|         //$this->closeCodes[Frame::CLOSE_ABNORMAL]    = true;
 | ||||
|         $this->closeCodes[Frame::CLOSE_BAD_PAYLOAD] = true; | ||||
|         $this->closeCodes[Frame::CLOSE_POLICY]      = true; | ||||
|         $this->closeCodes[Frame::CLOSE_TOO_BIG]     = true; | ||||
|         $this->closeCodes[Frame::CLOSE_MAND_EXT]    = true; | ||||
|         $this->closeCodes[Frame::CLOSE_SRV_ERR]     = true; | ||||
|         //$this->closeCodes[Frame::CLOSE_TLS]         = true;
 | ||||
|     } | ||||
| } | ||||
| @ -1,451 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\FrameInterface; | ||||
| 
 | ||||
| class Frame implements FrameInterface { | ||||
|     const OP_CONTINUE =  0; | ||||
|     const OP_TEXT     =  1; | ||||
|     const OP_BINARY   =  2; | ||||
|     const OP_CLOSE    =  8; | ||||
|     const OP_PING     =  9; | ||||
|     const OP_PONG     = 10; | ||||
| 
 | ||||
|     const CLOSE_NORMAL      = 1000; | ||||
|     const CLOSE_GOING_AWAY  = 1001; | ||||
|     const CLOSE_PROTOCOL    = 1002; | ||||
|     const CLOSE_BAD_DATA    = 1003; | ||||
|     const CLOSE_NO_STATUS   = 1005; | ||||
|     const CLOSE_ABNORMAL    = 1006; | ||||
|     const CLOSE_BAD_PAYLOAD = 1007; | ||||
|     const CLOSE_POLICY      = 1008; | ||||
|     const CLOSE_TOO_BIG     = 1009; | ||||
|     const CLOSE_MAND_EXT    = 1010; | ||||
|     const CLOSE_SRV_ERR     = 1011; | ||||
|     const CLOSE_TLS         = 1015; | ||||
| 
 | ||||
|     const MASK_LENGTH = 4; | ||||
| 
 | ||||
|     /** | ||||
|      * The contents of the frame | ||||
|      * @var string | ||||
|      */ | ||||
|     protected $data = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * Number of bytes received from the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     public $bytesRecvd = 0; | ||||
| 
 | ||||
|     /** | ||||
|      * Number of bytes in the payload (as per framing protocol) | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $defPayLen = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * If the frame is coalesced this is true | ||||
|      * This is to prevent doing math every time ::isCoalesced is called | ||||
|      * @var boolean | ||||
|      */ | ||||
|     private $isCoalesced = false; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked first byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $firstByte = -1; | ||||
| 
 | ||||
|     /** | ||||
|      * The unpacked second byte of the frame | ||||
|      * @var int | ||||
|      */ | ||||
|     protected $secondByte = -1; | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null $payload | ||||
|      * @param bool        $final | ||||
|      * @param int         $opcode | ||||
|      */ | ||||
|     public function __construct($payload = null, $final = true, $opcode = 1) { | ||||
|         if (null === $payload) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         $this->defPayLen   = strlen($payload); | ||||
|         $this->firstByte   = ($final ? 128 : 0) + $opcode; | ||||
|         $this->secondByte  = $this->defPayLen; | ||||
|         $this->isCoalesced = true; | ||||
| 
 | ||||
|         $ext = ''; | ||||
|         if ($this->defPayLen > 65535) { | ||||
|             $ext = pack('NN', 0, $this->defPayLen); | ||||
|             $this->secondByte = 127; | ||||
|         } elseif ($this->defPayLen > 125) { | ||||
|             $ext = pack('n', $this->defPayLen); | ||||
|             $this->secondByte = 126; | ||||
|         } | ||||
| 
 | ||||
|         $this->data       = chr($this->firstByte) . chr($this->secondByte) . $ext . $payload; | ||||
|         $this->bytesRecvd = 2 + strlen($ext) + $this->defPayLen; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced() { | ||||
|         if (true === $this->isCoalesced) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             $payload_length = $this->getPayloadLength(); | ||||
|             $payload_start  = $this->getPayloadStartingByte(); | ||||
|         } catch (\UnderflowException $e) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $this->isCoalesced = $this->bytesRecvd >= $payload_length + $payload_start; | ||||
| 
 | ||||
|         return $this->isCoalesced; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function addBuffer($buf) { | ||||
|         $len = strlen($buf); | ||||
| 
 | ||||
|         $this->data       .= $buf; | ||||
|         $this->bytesRecvd += $len; | ||||
| 
 | ||||
|         if ($this->firstByte === -1 && $this->bytesRecvd !== 0) { | ||||
|             $this->firstByte = ord($this->data[0]); | ||||
|         } | ||||
| 
 | ||||
|         if ($this->secondByte === -1 && $this->bytesRecvd >= 2) { | ||||
|             $this->secondByte = ord($this->data[1]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isFinal() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received to determine if this is the final frame in message'); | ||||
|         } | ||||
| 
 | ||||
|         return 128 === ($this->firstByte & 128); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv1() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| 
 | ||||
|         return 64 === ($this->firstByte & 64); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv2() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| 
 | ||||
|         return 32 === ($this->firstByte & 32); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return boolean | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     public function getRsv3() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received to determine reserved bit'); | ||||
|         } | ||||
| 
 | ||||
|         return 16 == ($this->firstByte & 16); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isMasked() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw new \UnderflowException("Not enough bytes received ({$this->bytesRecvd}) to determine if mask is set"); | ||||
|         } | ||||
| 
 | ||||
|         return 128 === ($this->secondByte & 128); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getMaskingKey() { | ||||
|         if (!$this->isMasked()) { | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         $start  = 1 + $this->getNumPayloadBytes(); | ||||
| 
 | ||||
|         if ($this->bytesRecvd < $start + static::MASK_LENGTH) { | ||||
|             throw new \UnderflowException('Not enough data buffered to calculate the masking key'); | ||||
|         } | ||||
| 
 | ||||
|         return substr($this->data, $start, static::MASK_LENGTH); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Create a 4 byte masking key | ||||
|      * @return string | ||||
|      */ | ||||
|     public function generateMaskingKey() { | ||||
|         $mask = ''; | ||||
| 
 | ||||
|         for ($i = 1; $i <= static::MASK_LENGTH; $i++) { | ||||
|             $mask .= chr(rand(32, 126)); | ||||
|         } | ||||
| 
 | ||||
|         return $mask; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Apply a mask to the payload | ||||
|      * @param string|null If NULL is passed a masking key will be generated | ||||
|      * @throws \OutOfBoundsException | ||||
|      * @throws \InvalidArgumentException If there is an issue with the given masking key | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function maskPayload($maskingKey = null) { | ||||
|         if (null === $maskingKey) { | ||||
|             $maskingKey = $this->generateMaskingKey(); | ||||
|         } | ||||
| 
 | ||||
|         if (static::MASK_LENGTH !== strlen($maskingKey)) { | ||||
|             throw new \InvalidArgumentException("Masking key must be " . static::MASK_LENGTH ." characters"); | ||||
|         } | ||||
| 
 | ||||
|         if (extension_loaded('mbstring') && true !== mb_check_encoding($maskingKey, 'US-ASCII')) { | ||||
|             throw new \OutOfBoundsException("Masking key MUST be ASCII"); | ||||
|         } | ||||
| 
 | ||||
|         $this->unMaskPayload(); | ||||
| 
 | ||||
|         $this->secondByte = $this->secondByte | 128; | ||||
|         $this->data[1]    = chr($this->secondByte); | ||||
| 
 | ||||
|         $this->data = substr_replace($this->data, $maskingKey, $this->getNumPayloadBytes() + 1, 0); | ||||
| 
 | ||||
|         $this->bytesRecvd += static::MASK_LENGTH; | ||||
|         $this->data        = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Remove a mask from the payload | ||||
|      * @throws \UnderFlowException If the frame is not coalesced | ||||
|      * @return Frame | ||||
|      */ | ||||
|     public function unMaskPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Frame must be coalesced before applying mask'); | ||||
|         } | ||||
| 
 | ||||
|         if (!$this->isMasked()) { | ||||
|             return $this; | ||||
|         } | ||||
| 
 | ||||
|         $maskingKey = $this->getMaskingKey(); | ||||
| 
 | ||||
|         $this->secondByte = $this->secondByte & ~128; | ||||
|         $this->data[1] = chr($this->secondByte); | ||||
| 
 | ||||
|         $this->data = substr_replace($this->data, '', $this->getNumPayloadBytes() + 1, static::MASK_LENGTH); | ||||
| 
 | ||||
|         $this->bytesRecvd -= static::MASK_LENGTH; | ||||
|         $this->data        = substr_replace($this->data, $this->applyMask($maskingKey), $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Apply a mask to a string or the payload of the instance | ||||
|      * @param string $maskingKey   The 4 character masking key to be applied | ||||
|      * @param string|null $payload A string to mask or null to use the payload | ||||
|      * @throws \UnderflowException If using the payload but enough hasn't been buffered | ||||
|      * @return string              The masked string | ||||
|      */ | ||||
|     public function applyMask($maskingKey, $payload = null) { | ||||
|         if (null === $payload) { | ||||
|             if (!$this->isCoalesced()) { | ||||
|                 throw new \UnderflowException('Frame must be coalesced to apply a mask'); | ||||
|             } | ||||
| 
 | ||||
|             $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
|         } | ||||
| 
 | ||||
|         $applied = ''; | ||||
|         for ($i = 0, $len = strlen($payload); $i < $len; $i++) { | ||||
|             $applied .= $payload[$i] ^ $maskingKey[$i % static::MASK_LENGTH]; | ||||
|         } | ||||
| 
 | ||||
|         return $applied; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode() { | ||||
|         if (-1 === $this->firstByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received to determine opcode'); | ||||
|         } | ||||
| 
 | ||||
|         return ($this->firstByte & ~240); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Gets the decimal value of bits 9 (10th) through 15 inclusive | ||||
|      * @return int | ||||
|      * @throws \UnderflowException If the buffer doesn't have enough data to determine this | ||||
|      */ | ||||
|     protected function getFirstPayloadVal() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received'); | ||||
|         } | ||||
| 
 | ||||
|         return $this->secondByte & 127; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @return int (7|23|71) Number of bits defined for the payload length in the fame | ||||
|      * @throws \UnderflowException | ||||
|      */ | ||||
|     protected function getNumPayloadBits() { | ||||
|         if (-1 === $this->secondByte) { | ||||
|             throw new \UnderflowException('Not enough bytes received'); | ||||
|         } | ||||
| 
 | ||||
|         // By default 7 bits are used to describe the payload length
 | ||||
|         // These are bits 9 (10th) through 15 inclusive
 | ||||
|         $bits = 7; | ||||
| 
 | ||||
|         // Get the value of those bits
 | ||||
|         $check = $this->getFirstPayloadVal(); | ||||
| 
 | ||||
|         // If the value is 126 the 7 bits plus the next 16 are used to describe the payload length
 | ||||
|         if ($check >= 126) { | ||||
|             $bits += 16; | ||||
|         } | ||||
| 
 | ||||
|         // If the value of the initial payload length are is 127 an additional 48 bits are used to describe length
 | ||||
|         // Note: The documentation specifies the length is to be 63 bits, but I think that's a typo and is 64 (16+48)
 | ||||
|         if ($check === 127) { | ||||
|             $bits += 48; | ||||
|         } | ||||
| 
 | ||||
|         return $bits; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This just returns the number of bytes used in the frame to describe the payload length (as opposed to # of bits)
 | ||||
|      * @see getNumPayloadBits | ||||
|      */ | ||||
|     protected function getNumPayloadBytes() { | ||||
|         return (1 + $this->getNumPayloadBits()) / 8; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength() { | ||||
|         if ($this->defPayLen !== -1) { | ||||
|             return $this->defPayLen; | ||||
|         } | ||||
| 
 | ||||
|         $this->defPayLen = $this->getFirstPayloadVal(); | ||||
|         if ($this->defPayLen <= 125) { | ||||
|             return $this->getPayloadLength(); | ||||
|         } | ||||
| 
 | ||||
|         $byte_length = $this->getNumPayloadBytes(); | ||||
|         if ($this->bytesRecvd < 1 + $byte_length) { | ||||
|             $this->defPayLen = -1; | ||||
|             throw new \UnderflowException('Not enough data buffered to determine payload length'); | ||||
|         } | ||||
| 
 | ||||
|         $len = 0; | ||||
|         for ($i = 2; $i <= $byte_length; $i++) { | ||||
|             $len <<= 8; | ||||
|             $len  += ord($this->data[$i]); | ||||
|         } | ||||
| 
 | ||||
|         $this->defPayLen = $len; | ||||
| 
 | ||||
|         return $this->getPayloadLength(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadStartingByte() { | ||||
|         return 1 + $this->getNumPayloadBytes() + ($this->isMasked() ? static::MASK_LENGTH : 0); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * @todo Consider not checking mask, always returning the payload, masked or not | ||||
|      */ | ||||
|     public function getPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Can not return partial message'); | ||||
|         } | ||||
| 
 | ||||
|         $payload = substr($this->data, $this->getPayloadStartingByte(), $this->getPayloadLength()); | ||||
| 
 | ||||
|         if ($this->isMasked()) { | ||||
|             $payload = $this->applyMask($this->getMaskingKey(), $payload); | ||||
|         } | ||||
| 
 | ||||
|         return $payload; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the raw contents of the frame | ||||
|      * @todo This is untested, make sure the substr is right - trying to return the frame w/o the overflow | ||||
|      */ | ||||
|     public function getContents() { | ||||
|         return substr($this->data, 0, $this->getPayloadStartingByte() + $this->getPayloadLength()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sometimes clients will concatenate more than one frame over the wire | ||||
|      * This method will take the extra bytes off the end and return them | ||||
|      * @todo Consider returning new Frame | ||||
|      * @return string | ||||
|      */ | ||||
|     public function extractOverflow() { | ||||
|         if ($this->isCoalesced()) { | ||||
|             $endPoint  = $this->getPayloadLength(); | ||||
|             $endPoint += $this->getPayloadStartingByte(); | ||||
| 
 | ||||
|             if ($this->bytesRecvd > $endPoint) { | ||||
|                 $overflow   = substr($this->data, $endPoint); | ||||
|                 $this->data = substr($this->data, 0, $endPoint); | ||||
| 
 | ||||
|                 return $overflow; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return ''; | ||||
|     } | ||||
| } | ||||
| @ -1,137 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| 
 | ||||
| /** | ||||
|  * These are checks to ensure the client requested handshake are valid | ||||
|  * Verification rules come from section 4.2.1 of the RFC6455 document | ||||
|  * @todo Currently just returning invalid - should consider returning appropriate HTTP status code error #s
 | ||||
|  */ | ||||
| class HandshakeVerifier { | ||||
|     /** | ||||
|      * Given an array of the headers this method will run through all verification methods | ||||
|      * @param \Guzzle\Http\Message\RequestInterface $request | ||||
|      * @return bool TRUE if all headers are valid, FALSE if 1 or more were invalid | ||||
|      */ | ||||
|     public function verifyAll(RequestInterface $request) { | ||||
|         $passes = 0; | ||||
| 
 | ||||
|         $passes += (int)$this->verifyMethod($request->getMethod()); | ||||
|         $passes += (int)$this->verifyHTTPVersion($request->getProtocolVersion()); | ||||
|         $passes += (int)$this->verifyRequestURI($request->getPath()); | ||||
|         $passes += (int)$this->verifyHost((string)$request->getHeader('Host')); | ||||
|         $passes += (int)$this->verifyUpgradeRequest((string)$request->getHeader('Upgrade')); | ||||
|         $passes += (int)$this->verifyConnection((string)$request->getHeader('Connection')); | ||||
|         $passes += (int)$this->verifyKey((string)$request->getHeader('Sec-WebSocket-Key')); | ||||
|         //$passes += (int)$this->verifyVersion($headers['Sec-WebSocket-Version']); // Temporarily breaking functionality
 | ||||
| 
 | ||||
|         return (7 === $passes); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Test the HTTP method.  MUST be "GET" | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyMethod($val) { | ||||
|         return ('get' === strtolower($val)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Test the HTTP version passed.  MUST be 1.1 or greater | ||||
|      * @param string|int | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyHTTPVersion($val) { | ||||
|         return (1.1 <= (double)$val); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyRequestURI($val) { | ||||
|         if ($val[0] != '/') { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (false !== strstr($val, '#')) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (!extension_loaded('mbstring')) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         return mb_check_encoding($val, 'US-ASCII'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string|null | ||||
|      * @return bool | ||||
|      * @todo Find out if I can find the master socket, ensure the port is attached to header if not 80 or 443 - not sure if this is possible, as I tried to hide it | ||||
|      * @todo Once I fix HTTP::getHeaders just verify this isn't NULL or empty...or maybe need to verify it's a valid domain??? Or should it equal $_SERVER['HOST'] ? | ||||
|      */ | ||||
|     public function verifyHost($val) { | ||||
|         return (null !== $val); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Verify the Upgrade request to WebSockets. | ||||
|      * @param  string $val MUST equal "websocket" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyUpgradeRequest($val) { | ||||
|         return ('websocket' === strtolower($val)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Verify the Connection header | ||||
|      * @param  string $val MUST equal "Upgrade" | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function verifyConnection($val) { | ||||
|         $val = strtolower($val); | ||||
| 
 | ||||
|         if ('upgrade' === $val) { | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         $vals = explode(',', str_replace(', ', ',', $val)); | ||||
| 
 | ||||
|         return (false !== array_search('upgrade', $vals)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This function verifies the nonce is valid (64 big encoded, 16 bytes random string) | ||||
|      * @param string|null | ||||
|      * @return bool | ||||
|      * @todo The spec says we don't need to base64_decode - can I just check if the length is 24 and not decode? | ||||
|      * @todo Check the spec to see what the encoding of the key could be | ||||
|      */ | ||||
|     public function verifyKey($val) { | ||||
|         return (16 === strlen(base64_decode((string)$val))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Verify the version passed matches this RFC | ||||
|      * @param string|int MUST equal 13|"13" | ||||
|      * @return bool | ||||
|      * @todo Ran in to a problem here...I'm having HyBi use the RFC files, this breaks it!  oops | ||||
|      */ | ||||
|     public function verifyVersion($val) { | ||||
|         return (13 === (int)$val); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @todo Write logic for this method.  See section 4.2.1.8 | ||||
|      */ | ||||
|     public function verifyProtocol($val) { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @todo Write logic for this method.  See section 4.2.1.9 | ||||
|      */ | ||||
|     public function verifyExtensions($val) { | ||||
|     } | ||||
| } | ||||
| @ -1,107 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\MessageInterface; | ||||
| use Ratchet\WebSocket\Version\FrameInterface; | ||||
| 
 | ||||
| class Message implements MessageInterface, \Countable { | ||||
|     /** | ||||
|      * @var \SplDoublyLinkedList | ||||
|      */ | ||||
|     protected $_frames; | ||||
| 
 | ||||
|     public function __construct() { | ||||
|         $this->_frames = new \SplDoublyLinkedList; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function count() { | ||||
|         return count($this->_frames); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function isCoalesced() { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $last = $this->_frames->top(); | ||||
| 
 | ||||
|         return ($last->isCoalesced() && $last->isFinal()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * @todo Also, I should perhaps check the type...control frames (ping/pong/close) are not to be considered part of a message | ||||
|      */ | ||||
|     public function addFrame(FrameInterface $fragment) { | ||||
|         $this->_frames->push($fragment); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getOpcode() { | ||||
|         if (count($this->_frames) == 0) { | ||||
|             throw new \UnderflowException('No frames have been added to this message'); | ||||
|         } | ||||
| 
 | ||||
|         return $this->_frames->bottom()->getOpcode(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayloadLength() { | ||||
|         $len = 0; | ||||
| 
 | ||||
|         foreach ($this->_frames as $frame) { | ||||
|             try { | ||||
|                 $len += $frame->getPayloadLength(); | ||||
|             } catch (\UnderflowException $e) { | ||||
|                 // Not an error, want the current amount buffered
 | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return $len; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getPayload() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException('Message has not been put back together yet'); | ||||
|         } | ||||
| 
 | ||||
|         $buffer = ''; | ||||
| 
 | ||||
|         foreach ($this->_frames as $frame) { | ||||
|             $buffer .= $frame->getPayload(); | ||||
|         } | ||||
| 
 | ||||
|         return $buffer; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function getContents() { | ||||
|         if (!$this->isCoalesced()) { | ||||
|             throw new \UnderflowException("Message has not been put back together yet"); | ||||
|         } | ||||
| 
 | ||||
|         $buffer = ''; | ||||
| 
 | ||||
|         foreach ($this->_frames as $frame) { | ||||
|             $buffer .= $frame->getContents(); | ||||
|         } | ||||
| 
 | ||||
|         return $buffer; | ||||
|     } | ||||
| } | ||||
| @ -1,57 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\MessageInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| 
 | ||||
| /** | ||||
|  * A standard interface for interacting with the various version of the WebSocket protocol | ||||
|  */ | ||||
| interface VersionInterface extends MessageInterface { | ||||
|     /** | ||||
|      * Given an HTTP header, determine if this version should handle the protocol | ||||
|      * @param \Guzzle\Http\Message\RequestInterface $request | ||||
|      * @return bool | ||||
|      * @throws \UnderflowException If the protocol thinks the headers are still fragmented | ||||
|      */ | ||||
|     function isProtocol(RequestInterface $request); | ||||
| 
 | ||||
|     /** | ||||
|      * Although the version has a name associated with it the integer returned is the proper identification | ||||
|      * @return int | ||||
|      */ | ||||
|     function getVersionNumber(); | ||||
| 
 | ||||
|     /** | ||||
|      * Perform the handshake and return the response headers | ||||
|      * @param \Guzzle\Http\Message\RequestInterface $request | ||||
|      * @return \Guzzle\Http\Message\Response | ||||
|      * @throws \UnderflowException If the message hasn't finished buffering (not yet implemented, theoretically will only happen with Hixie version) | ||||
|      */ | ||||
|     function handshake(RequestInterface $request); | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Ratchet\ConnectionInterface $conn | ||||
|      * @param  \Ratchet\MessageInterface    $coalescedCallback | ||||
|      * @return \Ratchet\ConnectionInterface | ||||
|      */ | ||||
|     function upgradeConnection(ConnectionInterface $conn, MessageInterface $coalescedCallback); | ||||
| 
 | ||||
|     /** | ||||
|      * @return MessageInterface | ||||
|      */ | ||||
|     //function newMessage();
 | ||||
| 
 | ||||
|     /** | ||||
|      * @return FrameInterface | ||||
|      */ | ||||
|     //function newFrame();
 | ||||
| 
 | ||||
|     /** | ||||
|      * @param string | ||||
|      * @param bool | ||||
|      * @return string | ||||
|      * @todo Change to use other classes, this will be removed eventually | ||||
|      */ | ||||
|     //function frame($message, $mask = true);
 | ||||
| } | ||||
| @ -1,90 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\WebSocket\Version\VersionInterface; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| 
 | ||||
| /** | ||||
|  * Manage the various versions of the WebSocket protocol | ||||
|  * This accepts interfaces of versions to enable/disable | ||||
|  */ | ||||
| class VersionManager { | ||||
|     /** | ||||
|      * The header string to let clients know which versions are supported | ||||
|      * @var string | ||||
|      */ | ||||
|     private $versionString = ''; | ||||
| 
 | ||||
|     /** | ||||
|      * Storage of each version enabled | ||||
|      * @var array | ||||
|      */ | ||||
|     protected $versions = array(); | ||||
| 
 | ||||
|     /** | ||||
|      * Get the protocol negotiator for the request, if supported | ||||
|      * @param  \Guzzle\Http\Message\RequestInterface $request | ||||
|      * @throws \InvalidArgumentException | ||||
|      * @return \Ratchet\WebSocket\Version\VersionInterface | ||||
|      */ | ||||
|     public function getVersion(RequestInterface $request) { | ||||
|         foreach ($this->versions as $version) { | ||||
|             if ($version->isProtocol($request)) { | ||||
|                 return $version; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         throw new \InvalidArgumentException("Version not found"); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Guzzle\Http\Message\RequestInterface | ||||
|      * @return bool | ||||
|      */ | ||||
|     public function isVersionEnabled(RequestInterface $request) { | ||||
|         foreach ($this->versions as $version) { | ||||
|             if ($version->isProtocol($request)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Enable support for a specific version of the WebSocket protocol | ||||
|      * @param  \Ratchet\WebSocket\Version\VersionInterface $version | ||||
|      * @return VersionManager | ||||
|      */ | ||||
|     public function enableVersion(VersionInterface $version) { | ||||
|         $this->versions[$version->getVersionNumber()] = $version; | ||||
| 
 | ||||
|         if (empty($this->versionString)) { | ||||
|             $this->versionString = (string)$version->getVersionNumber(); | ||||
|         } else { | ||||
|             $this->versionString .= ", {$version->getVersionNumber()}"; | ||||
|         } | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Disable support for a specific WebSocket protocol version | ||||
|      * @param  int $versionId The version ID to un-support | ||||
|      * @return VersionManager | ||||
|      */ | ||||
|     public function disableVersion($versionId) { | ||||
|         unset($this->versions[$versionId]); | ||||
| 
 | ||||
|         $this->versionString = implode(',', array_keys($this->versions)); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a string of version numbers supported (comma delimited) | ||||
|      * @return string | ||||
|      */ | ||||
|     public function getSupportedVersionString() { | ||||
|         return $this->versionString; | ||||
|     } | ||||
| } | ||||
| @ -1,13 +1,14 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\AbstractConnectionDecorator; | ||||
| use Ratchet\WebSocket\Version\DataInterface; | ||||
| use Ratchet\RFC6455\Messaging\DataInterface; | ||||
| use Ratchet\RFC6455\Messaging\Frame; | ||||
| 
 | ||||
| /** | ||||
|  * {@inheritdoc} | ||||
|  * @property \StdClass $WebSocket | ||||
|  */ | ||||
| class Connection extends AbstractConnectionDecorator { | ||||
| class WsConnection extends AbstractConnectionDecorator { | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
| @ -24,7 +25,7 @@ class Connection extends AbstractConnectionDecorator { | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * {@inheritdoc} | ||||
|      * @param int|\Ratchet\RFC6455\Messaging\DataInterface | ||||
|      */ | ||||
|     public function close($code = 1000) { | ||||
|         if ($this->WebSocket->closing) { | ||||
| @ -1,12 +1,20 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\MessageComponentInterface; | ||||
| use Ratchet\ComponentInterface; | ||||
| use Ratchet\ConnectionInterface; | ||||
| use Ratchet\MessageComponentInterface as DataComponentInterface; | ||||
| use Ratchet\Http\HttpServerInterface; | ||||
| use Guzzle\Http\Message\RequestInterface; | ||||
| use Guzzle\Http\Message\Response; | ||||
| use Ratchet\WebSocket\Version; | ||||
| use Ratchet\WebSocket\Encoding\ToggleableValidator; | ||||
| use Ratchet\Http\CloseResponseTrait; | ||||
| use Psr\Http\Message\RequestInterface; | ||||
| use Ratchet\RFC6455\Messaging\MessageInterface; | ||||
| use Ratchet\RFC6455\Messaging\FrameInterface; | ||||
| use Ratchet\RFC6455\Messaging\Frame; | ||||
| use Ratchet\RFC6455\Messaging\MessageBuffer; | ||||
| use Ratchet\RFC6455\Messaging\CloseFrameChecker; | ||||
| use Ratchet\RFC6455\Handshake\ServerNegotiator; | ||||
| use Ratchet\RFC6455\Handshake\RequestVerifier; | ||||
| use React\EventLoop\LoopInterface; | ||||
| use GuzzleHttp\Psr7 as gPsr; | ||||
| 
 | ||||
| /** | ||||
|  * The adapter to handle WebSocket requests/responses | ||||
| @ -15,18 +23,13 @@ use Ratchet\WebSocket\Encoding\ToggleableValidator; | ||||
|  * @link http://dev.w3.org/html5/websockets/ | ||||
|  */ | ||||
| class WsServer implements HttpServerInterface { | ||||
|     /** | ||||
|      * Manage the various WebSocket versions to support | ||||
|      * @var VersionManager | ||||
|      * @note May not expose this in the future, may do through facade methods | ||||
|      */ | ||||
|     public $versioner; | ||||
|     use CloseResponseTrait; | ||||
| 
 | ||||
|     /** | ||||
|      * Decorated component | ||||
|      * @var \Ratchet\MessageComponentInterface | ||||
|      * @var \Ratchet\ComponentInterface | ||||
|      */ | ||||
|     public $component; | ||||
|     private $delegate; | ||||
| 
 | ||||
|     /** | ||||
|      * @var \SplObjectStorage | ||||
| @ -34,38 +37,68 @@ class WsServer implements HttpServerInterface { | ||||
|     protected $connections; | ||||
| 
 | ||||
|     /** | ||||
|      * Holder of accepted protocols, implement through WampServerInterface | ||||
|      * @var \Ratchet\RFC6455\Messaging\CloseFrameChecker | ||||
|      */ | ||||
|     protected $acceptedSubProtocols = array(); | ||||
|     private $closeFrameChecker; | ||||
| 
 | ||||
|     /** | ||||
|      * UTF-8 validator | ||||
|      * @var \Ratchet\WebSocket\Encoding\ValidatorInterface | ||||
|      * @var \Ratchet\RFC6455\Handshake\ServerNegotiator | ||||
|      */ | ||||
|     protected $validator; | ||||
|     private $handshakeNegotiator; | ||||
| 
 | ||||
|     /** | ||||
|      * Flag if we have checked the decorated component for sub-protocols | ||||
|      * @var boolean | ||||
|      * @var \Closure | ||||
|      */ | ||||
|     private $isSpGenerated = false; | ||||
|     private $ueFlowFactory; | ||||
| 
 | ||||
|     /** | ||||
|      * @param \Ratchet\MessageComponentInterface $component Your application to run with WebSockets | ||||
|      * If you want to enable sub-protocols have your component implement WsServerInterface as well | ||||
|      * @var \Closure | ||||
|      */ | ||||
|     public function __construct(MessageComponentInterface $component) { | ||||
|         $this->versioner = new VersionManager; | ||||
|         $this->validator = new ToggleableValidator; | ||||
|     private $pongReceiver; | ||||
| 
 | ||||
|         $this->versioner | ||||
|             ->enableVersion(new Version\RFC6455($this->validator)) | ||||
|             ->enableVersion(new Version\HyBi10($this->validator)) | ||||
|             ->enableVersion(new Version\Hixie76) | ||||
|         ; | ||||
|     /** | ||||
|      * @var \Closure | ||||
|      */ | ||||
|     private $msgCb; | ||||
| 
 | ||||
|         $this->component   = $component; | ||||
|     /** | ||||
|      * @param \Ratchet\WebSocket\MessageComponentInterface|\Ratchet\MessageComponentInterface $component Your application to run with WebSockets | ||||
|      * @note If you want to enable sub-protocols have your component implement WsServerInterface as well | ||||
|      */ | ||||
|     public function __construct(ComponentInterface $component) { | ||||
|         if ($component instanceof MessageComponentInterface) { | ||||
|             $this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { | ||||
|                 $this->delegate->onMessage($conn, $msg); | ||||
|             }; | ||||
|         } elseif ($component instanceof DataComponentInterface) { | ||||
|             $this->msgCb = function(ConnectionInterface $conn, MessageInterface $msg) { | ||||
|                 $this->delegate->onMessage($conn, $msg->getPayload()); | ||||
|             }; | ||||
|         } else { | ||||
|             throw new \UnexpectedValueException('Expected instance of \Ratchet\WebSocket\MessageComponentInterface or \Ratchet\MessageComponentInterface'); | ||||
|         } | ||||
| 
 | ||||
|         if (bin2hex('✓') !== 'e29c93') { | ||||
|             throw new \DomainException('Bad encoding, unicode character ✓ did not match expected value. Ensure charset UTF-8 and check ini val mbstring.func_autoload'); | ||||
|         } | ||||
| 
 | ||||
|         $this->delegate    = $component; | ||||
|         $this->connections = new \SplObjectStorage; | ||||
| 
 | ||||
|         $this->closeFrameChecker   = new CloseFrameChecker; | ||||
|         $this->handshakeNegotiator = new ServerNegotiator(new RequestVerifier); | ||||
|         $this->handshakeNegotiator->setStrictSubProtocolCheck(true); | ||||
| 
 | ||||
|         if ($component instanceof WsServerInterface) { | ||||
|             $this->handshakeNegotiator->setSupportedSubProtocols($component->getSubProtocols()); | ||||
|         } | ||||
| 
 | ||||
|         $this->pongReceiver = function() {}; | ||||
| 
 | ||||
|         $reusableUnderflowException = new \UnderflowException; | ||||
|         $this->ueFlowFactory = function() use ($reusableUnderflowException) { | ||||
|             return $reusableUnderflowException; | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -76,12 +109,37 @@ class WsServer implements HttpServerInterface { | ||||
|             throw new \UnexpectedValueException('$request can not be null'); | ||||
|         } | ||||
| 
 | ||||
|         $conn->WebSocket              = new \StdClass; | ||||
|         $conn->WebSocket->request     = $request; | ||||
|         $conn->WebSocket->established = false; | ||||
|         $conn->WebSocket->closing     = false; | ||||
|         $conn->httpRequest = $request; | ||||
| 
 | ||||
|         $this->attemptUpgrade($conn); | ||||
|         $conn->WebSocket            = new \StdClass; | ||||
|         $conn->WebSocket->closing   = false; | ||||
| 
 | ||||
|         $response = $this->handshakeNegotiator->handshake($request)->withHeader('X-Powered-By', \Ratchet\VERSION); | ||||
| 
 | ||||
|         $conn->send(gPsr\str($response)); | ||||
| 
 | ||||
|         if (101 !== $response->getStatusCode()) { | ||||
|             return $conn->close(); | ||||
|         } | ||||
| 
 | ||||
|         $wsConn = new WsConnection($conn); | ||||
| 
 | ||||
|         $streamer = new MessageBuffer( | ||||
|             $this->closeFrameChecker, | ||||
|             function(MessageInterface $msg) use ($wsConn) { | ||||
|                 $cb = $this->msgCb; | ||||
|                 $cb($wsConn, $msg); | ||||
|             }, | ||||
|             function(FrameInterface $frame) use ($wsConn) { | ||||
|                 $this->onControlFrame($frame, $wsConn); | ||||
|             }, | ||||
|             true, | ||||
|             $this->ueFlowFactory | ||||
|         ); | ||||
| 
 | ||||
|         $this->connections->attach($conn, new ConnContext($wsConn, $streamer)); | ||||
| 
 | ||||
|         return $this->delegate->onOpen($wsConn); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -92,50 +150,7 @@ class WsServer implements HttpServerInterface { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (true === $from->WebSocket->established) { | ||||
|             return $from->WebSocket->version->onMessage($this->connections[$from], $msg); | ||||
|         } | ||||
| 
 | ||||
|         $this->attemptUpgrade($from, $msg); | ||||
|     } | ||||
| 
 | ||||
|     protected function attemptUpgrade(ConnectionInterface $conn, $data = '') { | ||||
|         if ('' !== $data) { | ||||
|             $conn->WebSocket->request->getBody()->write($data); | ||||
|         } | ||||
| 
 | ||||
|         if (!$this->versioner->isVersionEnabled($conn->WebSocket->request)) { | ||||
|             return $this->close($conn); | ||||
|         } | ||||
| 
 | ||||
|         $conn->WebSocket->version = $this->versioner->getVersion($conn->WebSocket->request); | ||||
| 
 | ||||
|         try { | ||||
|             $response = $conn->WebSocket->version->handshake($conn->WebSocket->request); | ||||
|         } catch (\UnderflowException $e) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (null !== ($subHeader = $conn->WebSocket->request->getHeader('Sec-WebSocket-Protocol'))) { | ||||
|             if ('' !== ($agreedSubProtocols = $this->getSubProtocolString($subHeader->normalize()))) { | ||||
|                 $response->setHeader('Sec-WebSocket-Protocol', $agreedSubProtocols); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         $response->setHeader('X-Powered-By', \Ratchet\VERSION); | ||||
|         $conn->send((string)$response); | ||||
| 
 | ||||
|         if (101 != $response->getStatusCode()) { | ||||
|             return $conn->close(); | ||||
|         } | ||||
| 
 | ||||
|         $upgraded = $conn->WebSocket->version->upgradeConnection($conn, $this->component); | ||||
| 
 | ||||
|         $this->connections->attach($conn, $upgraded); | ||||
| 
 | ||||
|         $upgraded->WebSocket->established = true; | ||||
| 
 | ||||
|         return $this->component->onOpen($upgraded); | ||||
|         $this->connections[$from]->buffer->onData($msg); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -143,10 +158,10 @@ class WsServer implements HttpServerInterface { | ||||
|      */ | ||||
|     public function onClose(ConnectionInterface $conn) { | ||||
|         if ($this->connections->contains($conn)) { | ||||
|             $decor = $this->connections[$conn]; | ||||
|             $context = $this->connections[$conn]; | ||||
|             $this->connections->detach($conn); | ||||
| 
 | ||||
|             $this->component->onClose($decor); | ||||
|             $this->delegate->onClose($context->connection); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| @ -154,79 +169,57 @@ class WsServer implements HttpServerInterface { | ||||
|      * {@inheritdoc} | ||||
|      */ | ||||
|     public function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|         if ($conn->WebSocket->established && $this->connections->contains($conn)) { | ||||
|             $this->component->onError($this->connections[$conn], $e); | ||||
|         if ($this->connections->contains($conn)) { | ||||
|             $this->delegate->onError($this->connections[$conn]->connection, $e); | ||||
|         } else { | ||||
|             $conn->close(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Disable a specific version of the WebSocket protocol | ||||
|      * @param int $versionId Version ID to disable | ||||
|      * @return WsServer | ||||
|      */ | ||||
|     public function disableVersion($versionId) { | ||||
|         $this->versioner->disableVersion($versionId); | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Toggle weather to check encoding of incoming messages | ||||
|      * @param bool | ||||
|      * @return WsServer | ||||
|      */ | ||||
|     public function setEncodingChecks($opt) { | ||||
|         $this->validator->on = (boolean)$opt; | ||||
| 
 | ||||
|         return $this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param string | ||||
|      * @return boolean | ||||
|      */ | ||||
|     public function isSubProtocolSupported($name) { | ||||
|         if (!$this->isSpGenerated) { | ||||
|             if ($this->component instanceof WsServerInterface) { | ||||
|                 $this->acceptedSubProtocols = array_flip($this->component->getSubProtocols()); | ||||
|             } | ||||
| 
 | ||||
|             $this->isSpGenerated = true; | ||||
|     public function onControlFrame(FrameInterface $frame, WsConnection $conn) { | ||||
|         switch ($frame->getOpCode()) { | ||||
|             case Frame::OP_CLOSE: | ||||
|                 $conn->close($frame); | ||||
|                 break; | ||||
|             case Frame::OP_PING: | ||||
|                 $conn->send(new Frame($frame->getPayload(), true, Frame::OP_PONG)); | ||||
|                 break; | ||||
|             case Frame::OP_PONG: | ||||
|                 $pongReceiver = $this->pongReceiver; | ||||
|                 $pongReceiver($frame, $conn); | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         return array_key_exists($name, $this->acceptedSubProtocols); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @param  \Traversable|null $requested | ||||
|      * @return string | ||||
|      */ | ||||
|     protected function getSubProtocolString(\Traversable $requested = null) { | ||||
|         if (null !== $requested) { | ||||
|             foreach ($requested as $sub) { | ||||
|                 if ($this->isSubProtocolSupported($sub)) { | ||||
|                     return $sub; | ||||
|                 } | ||||
|     public function setStrictSubProtocolCheck($enable) { | ||||
|         $this->handshakeNegotiator->setStrictSubProtocolCheck($enable); | ||||
|     } | ||||
| 
 | ||||
|     public function enableKeepAlive(LoopInterface $loop, $interval = 30) { | ||||
|         $lastPing = new Frame(uniqid(), true, Frame::OP_PING); | ||||
|         $pingedConnections = new \SplObjectStorage; | ||||
|         $splClearer = new \SplObjectStorage; | ||||
| 
 | ||||
|         $this->pongReceiver = function(FrameInterface $frame, $wsConn) use ($pingedConnections, &$lastPing) { | ||||
|             if ($frame->getPayload() === $lastPing->getPayload()) { | ||||
|                 $pingedConnections->detach($wsConn); | ||||
|             } | ||||
|         } | ||||
|         }; | ||||
| 
 | ||||
|         return ''; | ||||
|     } | ||||
|         $loop->addPeriodicTimer((int)$interval, function() use ($pingedConnections, &$lastPing, $splClearer) { | ||||
|             foreach ($pingedConnections as $wsConn) { | ||||
|                 $wsConn->close(); | ||||
|             } | ||||
|             $pingedConnections->removeAllExcept($splClearer); | ||||
| 
 | ||||
|     /** | ||||
|      * Close a connection with an HTTP response | ||||
|      * @param \Ratchet\ConnectionInterface $conn | ||||
|      * @param int                          $code HTTP status code | ||||
|      */ | ||||
|     protected function close(ConnectionInterface $conn, $code = 400) { | ||||
|         $response = new Response($code, array( | ||||
|             'Sec-WebSocket-Version' => $this->versioner->getSupportedVersionString() | ||||
|           , 'X-Powered-By'          => \Ratchet\VERSION | ||||
|         )); | ||||
|             $lastPing = new Frame(uniqid(), true, Frame::OP_PING); | ||||
| 
 | ||||
|         $conn->send((string)$response); | ||||
|         $conn->close(); | ||||
|     } | ||||
|             foreach ($this->connections as $key => $conn) { | ||||
|                 $wsConn  = $this->connections[$conn]->connection; | ||||
| 
 | ||||
|                 $wsConn->send($lastPing); | ||||
|                 $pingedConnections->attach($wsConn); | ||||
|             } | ||||
|         }); | ||||
|    } | ||||
| } | ||||
|  | ||||
| @ -1,17 +0,0 @@ | ||||
| <?php | ||||
| 
 | ||||
|     require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; | ||||
| 
 | ||||
|     $port = $argc > 1 ? $argv[1] : 8000; | ||||
|     $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); | ||||
| 
 | ||||
|     $loop = new $impl; | ||||
|     $sock = new React\Socket\Server($loop); | ||||
|     $web  = new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer); | ||||
|     $app  = new Ratchet\Http\HttpServer($web); | ||||
|     $web->setEncodingChecks(false); | ||||
| 
 | ||||
|     $sock->listen($port, '0.0.0.0'); | ||||
| 
 | ||||
|     $server = new Ratchet\Server\IoServer($app, $sock, $loop); | ||||
|     $server->run(); | ||||
| @ -1,15 +1,36 @@ | ||||
| <?php | ||||
| use Ratchet\ConnectionInterface; | ||||
| 
 | ||||
|     require dirname(dirname(dirname(__DIR__))) . '/vendor/autoload.php'; | ||||
| 
 | ||||
| class BinaryEcho implements \Ratchet\WebSocket\MessageComponentInterface { | ||||
|     public function onMessage(ConnectionInterface $from, \Ratchet\RFC6455\Messaging\MessageInterface $msg) { | ||||
|         $from->send($msg); | ||||
|     } | ||||
| 
 | ||||
|     public function onOpen(ConnectionInterface $conn) { | ||||
|     } | ||||
| 
 | ||||
|     public function onClose(ConnectionInterface $conn) { | ||||
|     } | ||||
| 
 | ||||
|     public function onError(ConnectionInterface $conn, \Exception $e) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     $port = $argc > 1 ? $argv[1] : 8000; | ||||
|     $impl = sprintf('React\EventLoop\%sLoop', $argc > 2 ? $argv[2] : 'StreamSelect'); | ||||
| 
 | ||||
|     $loop = new $impl; | ||||
|     $sock = new React\Socket\Server($loop); | ||||
|     $app  = new Ratchet\Http\HttpServer(new Ratchet\WebSocket\WsServer(new Ratchet\Server\EchoServer)); | ||||
|     $sock = new React\Socket\Server('0.0.0.0:' . $port, $loop); | ||||
| 
 | ||||
|     $sock->listen($port, '0.0.0.0'); | ||||
|     $wsServer = new Ratchet\WebSocket\WsServer(new BinaryEcho); | ||||
|     // This is enabled to test https://github.com/ratchetphp/Ratchet/issues/430
 | ||||
|     // The time is left at 10 minutes so that it will not try to every ping anything
 | ||||
|     // This causes the Ratchet server to crash on test 2.7
 | ||||
|     $wsServer->enableKeepAlive($loop, 600); | ||||
| 
 | ||||
|     $app = new Ratchet\Http\HttpServer($wsServer); | ||||
| 
 | ||||
|     $server = new Ratchet\Server\IoServer($app, $sock, $loop); | ||||
|     $server->run(); | ||||
|  | ||||
| @ -3,14 +3,13 @@ | ||||
|   , "outdir": "reports/ab" | ||||
| 
 | ||||
|   , "servers": [ | ||||
|         {"agent": "Ratchet/0.3 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} | ||||
|       , {"agent": "Ratchet/0.3 libev", "url": "ws://localhost:8004", "options": {"version": 18}} | ||||
|       , {"agent": "Ratchet/0.3 streams", "url": "ws://localhost:8002", "options": {"version": 18}} | ||||
|       , {"agent": "Ratchet/0.3 -utf8", "url": "ws://localhost:8003", "options": {"version": 18}} | ||||
|         {"agent": "Ratchet/0.4 libevent", "url": "ws://localhost:8001", "options": {"version": 18}} | ||||
|       , {"agent": "Ratchet/0.4 libev", "url": "ws://localhost:8004", "options": {"version": 18}} | ||||
|       , {"agent": "Ratchet/0.4 streams", "url": "ws://localhost:8002", "options": {"version": 18}} | ||||
|       , {"agent": "AutobahnTestSuite/0.5.9", "url": "ws://localhost:8000", "options": {"version": 18}} | ||||
|     ] | ||||
| 
 | ||||
|   , "cases": ["*"] | ||||
|   , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] | ||||
|   , "exclude-cases": [] | ||||
|   , "exclude-agent-cases": {} | ||||
| } | ||||
|  | ||||
| @ -7,6 +7,6 @@ | ||||
|     ] | ||||
| 
 | ||||
|   , "cases": ["*"] | ||||
|   , "exclude-cases": ["1.2.*", "2.3", "2.4", "2.6", "9.2.*", "9.4.*", "9.6.*", "9.8.*"] | ||||
|   , "exclude-cases": [] | ||||
|   , "exclude-agent-cases": {} | ||||
| } | ||||
|  | ||||
| @ -1,53 +0,0 @@ | ||||
| <?php | ||||
| use Guzzle\Http\Message\Request; | ||||
| 
 | ||||
| class GuzzleTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_request; | ||||
| 
 | ||||
|     protected $_headers = array( | ||||
|         'Upgrade' => 'websocket' | ||||
|       , 'Connection' => 'Upgrade' | ||||
|       , 'Host' => 'localhost:8080' | ||||
|       , 'Origin' => 'chrome://newtab' | ||||
|       , 'Sec-WebSocket-Protocol' => 'one, two, three' | ||||
|       , 'Sec-WebSocket-Key' => '9bnXNp3ae6FbFFRtPdiPXA==' | ||||
|       , 'Sec-WebSocket-Version' => '13' | ||||
|     ); | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_request = new Request('GET', 'http://localhost', $this->_headers); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetHeaderString() { | ||||
|         $this->assertEquals('Upgrade', (string)$this->_request->getHeader('connection')); | ||||
|         $this->assertEquals('9bnXNp3ae6FbFFRtPdiPXA==', (string)$this->_request->getHeader('Sec-Websocket-Key')); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetHeaderInteger() { | ||||
|         $this->assertSame('13', (string)$this->_request->getHeader('Sec-Websocket-Version')); | ||||
|         $this->assertSame(13, (int)(string)$this->_request->getHeader('Sec-WebSocket-Version')); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetHeaderObject() { | ||||
|         $this->assertInstanceOf('Guzzle\Http\Message\Header', $this->_request->getHeader('Origin')); | ||||
|         $this->assertNull($this->_request->getHeader('Non-existant-header')); | ||||
|     } | ||||
| 
 | ||||
|     public function testHeaderObjectNormalizeValues() { | ||||
|         $expected  = 1 + substr_count($this->_headers['Sec-WebSocket-Protocol'], ','); | ||||
|         $protocols = $this->_request->getHeader('Sec-WebSocket-Protocol')->normalize(); | ||||
|         $count     = 0; | ||||
| 
 | ||||
|         foreach ($protocols as $protocol) { | ||||
|             $count++; | ||||
|         } | ||||
| 
 | ||||
|         $this->assertEquals($expected, $count); | ||||
|         $this->assertEquals($expected, count($protocols)); | ||||
|     } | ||||
| 
 | ||||
|     public function testRequestFactoryCreateSignature() { | ||||
|         $ref = new \ReflectionMethod('Guzzle\Http\Message\RequestFactory', 'create'); | ||||
|         $this->assertEquals(2, $ref->getNumberOfRequiredParameters()); | ||||
|     } | ||||
| } | ||||
| @ -1,67 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http\Guzzle\Http\Message; | ||||
| use Ratchet\Http\Guzzle\Http\Message\RequestFactory; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\Http\Guzzle\Http\Message\RequestFactory | ||||
|  */ | ||||
| class RequestFactoryTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $factory; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->factory = RequestFactory::getInstance(); | ||||
|     } | ||||
| 
 | ||||
|     public function testMessageProvider() { | ||||
|         return array( | ||||
|             'status' => 'GET / HTTP/1.1' | ||||
|           , 'headers' => array( | ||||
|                 'Upgrade'    => 'WebSocket' | ||||
|               , 'Connection' => 'Upgrade' | ||||
|               , 'Host'       => 'localhost:8000' | ||||
|               , 'Sec-WebSocket-Key1' => '> b3lU Z0 fh f 3+83394 6  (zG4' | ||||
|               , 'Sec-WebSocket-Key2' => ',3Z0X0677 dV-d [159 Z*4' | ||||
|             ) | ||||
|           , 'body' => "123456\r\n\r\n" | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function combineMessage($status, array $headers, $body = '') { | ||||
|         $message = $status . "\r\n"; | ||||
| 
 | ||||
|         foreach ($headers as $key => $val) { | ||||
|             $message .= "{$key}: {$val}\r\n"; | ||||
|         } | ||||
| 
 | ||||
|         $message .= "\r\n{$body}"; | ||||
| 
 | ||||
|         return $message; | ||||
|     } | ||||
| 
 | ||||
|     public function testExpectedDataFromGuzzleHeaders() { | ||||
|         $parts   = $this->testMessageProvider(); | ||||
|         $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); | ||||
|         $object  = $this->factory->fromMessage($message); | ||||
| 
 | ||||
|         foreach ($parts['headers'] as $key => $val) { | ||||
|             $this->assertEquals($val, $object->getHeader($key, true)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testExpectedDataFromNonGuzzleHeaders() { | ||||
|         $parts   = $this->testMessageProvider(); | ||||
|         $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); | ||||
|         $object  = $this->factory->fromMessage($message); | ||||
| 
 | ||||
|         $this->assertNull($object->getHeader('Nope', true)); | ||||
|         $this->assertNull($object->getHeader('Nope')); | ||||
|     } | ||||
| 
 | ||||
|     public function testExpectedDataFromNonGuzzleBody() { | ||||
|         $parts   = $this->testMessageProvider(); | ||||
|         $message = $this->combineMessage($parts['status'], $parts['headers'], $parts['body']); | ||||
|         $object  = $this->factory->fromMessage($message); | ||||
| 
 | ||||
|         $this->assertEquals($parts['body'], (string)$object->getBody()); | ||||
|     } | ||||
| } | ||||
| @ -1,6 +1,5 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\Http\HttpRequestParser; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\Http\HttpRequestParser | ||||
| @ -46,6 +45,6 @@ class HttpRequestParserTest extends \PHPUnit_Framework_TestCase { | ||||
|         $conn = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $return = $this->parser->onMessage($conn, "GET / HTTP/1.1\r\nHost: socketo.me\r\n\r\n"); | ||||
| 
 | ||||
|         $this->assertInstanceOf('\Guzzle\Http\Message\RequestInterface', $return); | ||||
|         $this->assertInstanceOf('\Psr\Http\Message\RequestInterface', $return); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,8 +9,8 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { | ||||
|     protected $_reqStub; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_reqStub = $this->getMock('Guzzle\Http\Message\RequestInterface'); | ||||
|         $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue('localhost')); | ||||
|         $this->_reqStub = $this->getMock('Psr\Http\Message\RequestInterface'); | ||||
|         $this->_reqStub->expects($this->any())->method('getHeader')->will($this->returnValue(['localhost'])); | ||||
| 
 | ||||
|         parent::setUp(); | ||||
| 
 | ||||
| @ -34,7 +34,7 @@ class OriginCheckTest extends AbstractMessageComponentTestCase { | ||||
|     } | ||||
| 
 | ||||
|     public function testCloseOnNonMatchingOrigin() { | ||||
|         $this->_serv->allowedOrigins = array('socketo.me'); | ||||
|         $this->_serv->allowedOrigins = ['socketo.me']; | ||||
|         $this->_conn->expects($this->once())->method('close'); | ||||
| 
 | ||||
|         $this->_serv->onOpen($this->_conn, $this->_reqStub); | ||||
|  | ||||
| @ -1,9 +1,12 @@ | ||||
| <?php | ||||
| namespace Ratchet\Http; | ||||
| use Ratchet\Http\Router; | ||||
| use Ratchet\WebSocket\WsServerInterface; | ||||
| use Symfony\Component\Routing\Exception\ResourceNotFoundException; | ||||
| use Symfony\Component\Routing\Matcher\UrlMatcherInterface; | ||||
| use Symfony\Component\Routing\RequestContext; | ||||
| use Symfony\Component\Routing\RouteCollection; | ||||
| use Symfony\Component\Routing\Matcher\UrlMatcher; | ||||
| 
 | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\Http\Router | ||||
| @ -12,21 +15,17 @@ class RouterTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_router; | ||||
|     protected $_matcher; | ||||
|     protected $_conn; | ||||
|     protected $_uri; | ||||
|     protected $_req; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $queryMock = $this->getMock('Guzzle\Http\QueryString'); | ||||
|         $queryMock | ||||
|             ->expects($this->any()) | ||||
|             ->method('getAll') | ||||
|             ->will($this->returnValue(array())); | ||||
| 
 | ||||
|         $this->_conn    = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $this->_req     = $this->getMock('\Guzzle\Http\Message\RequestInterface'); | ||||
|         $this->_conn = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $this->_uri  = $this->getMock('Psr\Http\Message\UriInterface'); | ||||
|         $this->_req  = $this->getMock('\Psr\Http\Message\RequestInterface'); | ||||
|         $this->_req | ||||
|             ->expects($this->any()) | ||||
|             ->method('getQuery') | ||||
|             ->will($this->returnValue($queryMock)); | ||||
|             ->method('getUri') | ||||
|             ->will($this->returnValue($this->_uri)); | ||||
|         $this->_matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); | ||||
|         $this->_matcher | ||||
|             ->expects($this->any()) | ||||
| @ -34,7 +33,14 @@ class RouterTest extends \PHPUnit_Framework_TestCase { | ||||
|             ->will($this->returnValue($this->getMock('Symfony\Component\Routing\RequestContext'))); | ||||
|         $this->_router  = new Router($this->_matcher); | ||||
| 
 | ||||
|         $this->_req->expects($this->any())->method('getPath')->will($this->returnValue('/whatever')); | ||||
|         $this->_uri->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); | ||||
|         $this->_uri->expects($this->any())->method('withQuery')->with($this->callback(function($val) { | ||||
|             $this->setResult($val); | ||||
| 
 | ||||
|             return true; | ||||
|         }))->will($this->returnSelf()); | ||||
|         $this->_uri->expects($this->any())->method('getQuery')->will($this->returnCallback([$this, 'getResult'])); | ||||
|         $this->_req->expects($this->any())->method('withUri')->will($this->returnSelf()); | ||||
|     } | ||||
| 
 | ||||
|     public function testFourOhFour() { | ||||
| @ -103,41 +109,57 @@ class RouterTest extends \PHPUnit_Framework_TestCase { | ||||
|         $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); | ||||
|         /** @var $matcher UrlMatcherInterface */ | ||||
|         $this->_matcher->expects($this->any())->method('match')->will( | ||||
|             $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) | ||||
|             $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) | ||||
|         ); | ||||
|         $conn = $this->getMock('Ratchet\Mock\Connection'); | ||||
| 
 | ||||
|         $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', 'ws://random.url'), '', false); | ||||
|         $request->expects($this->any())->method('getPath')->will($this->returnValue('ws://doesnt.matter/')); | ||||
| 
 | ||||
|         $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); | ||||
|         $request->setUrl('ws://doesnt.matter/'); | ||||
| 
 | ||||
|         $router = new Router($this->_matcher); | ||||
| 
 | ||||
|         $router->onOpen($conn, $request); | ||||
|         $router->onOpen($conn, $this->_req); | ||||
| 
 | ||||
|         $this->assertEquals(array('foo' => 'bar', 'baz' => 'qux'), $request->getQuery()->getAll()); | ||||
|         $this->assertEquals('foo=bar&baz=qux', $this->_req->getUri()->getQuery()); | ||||
|     } | ||||
| 
 | ||||
|     public function testQueryParams() { | ||||
|         $controller = $this->getMockBuilder('\Ratchet\WebSocket\WsServer')->disableOriginalConstructor()->getMock(); | ||||
|         $this->_matcher->expects($this->any())->method('match')->will( | ||||
|             $this->returnValue(array('_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux')) | ||||
|             $this->returnValue(['_controller' => $controller, 'foo' => 'bar', 'baz' => 'qux']) | ||||
|         ); | ||||
| 
 | ||||
|         $conn    = $this->getMock('Ratchet\Mock\Connection'); | ||||
|         /**@var $request \Guzzle\Http\Message\Request */ | ||||
|         $request = $this->getMock('Guzzle\Http\Message\Request', array('getPath'), array('GET', ''), '', false); | ||||
|         $request = $this->getMock('Psr\Http\Message\RequestInterface'); | ||||
|         $uri = new \GuzzleHttp\Psr7\Uri('ws://doesnt.matter/endpoint?hello=world&foo=nope'); | ||||
| 
 | ||||
|         $request->setHeaderFactory($this->getMock('Guzzle\Http\Message\Header\HeaderFactoryInterface')); | ||||
|         $request->setUrl('ws://doesnt.matter?hello=world&foo=nope'); | ||||
|         $request->expects($this->any())->method('getUri')->will($this->returnCallback(function() use (&$uri) { | ||||
|             return $uri; | ||||
|         })); | ||||
|         $request->expects($this->any())->method('withUri')->with($this->callback(function($url) use (&$uri) { | ||||
|             $uri = $url; | ||||
| 
 | ||||
|             return true; | ||||
|         }))->will($this->returnSelf()); | ||||
| 
 | ||||
|         $router = new Router($this->_matcher); | ||||
|         $router->onOpen($conn, $request); | ||||
| 
 | ||||
|         $this->assertEquals(array('foo' => 'nope', 'baz' => 'qux', 'hello' => 'world'), $request->getQuery()->getAll()); | ||||
|         $this->assertEquals('ws', $request->getScheme()); | ||||
|         $this->assertEquals('doesnt.matter', $request->getHost()); | ||||
|         $this->assertEquals('foo=nope&baz=qux&hello=world', $request->getUri()->getQuery()); | ||||
|         $this->assertEquals('ws', $request->getUri()->getScheme()); | ||||
|         $this->assertEquals('doesnt.matter', $request->getUri()->getHost()); | ||||
|     } | ||||
| 
 | ||||
|     public function testImpatientClientOverflow() { | ||||
|         $this->_conn->expects($this->once())->method('close'); | ||||
| 
 | ||||
|         $header = "GET /nope HTTP/1.1
 | ||||
| Upgrade: websocket                                    | ||||
| Connection: upgrade                                   | ||||
| Host: localhost                                  | ||||
| Origin: http://localhost                         | ||||
| Sec-WebSocket-Version: 13\r\n\r\n";
 | ||||
| 
 | ||||
|         $app = new HttpServer(new Router(new UrlMatcher(new RouteCollection, new RequestContext))); | ||||
|         $app->onOpen($this->_conn); | ||||
|         $app->onMessage($this->_conn, $header); | ||||
|         $app->onMessage($this->_conn, 'Silly body'); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -20,10 +20,10 @@ class IoServerTest extends \PHPUnit_Framework_TestCase { | ||||
|         $this->app = $this->getMock('\\Ratchet\\MessageComponentInterface'); | ||||
| 
 | ||||
|         $loop = new StreamSelectLoop; | ||||
|         $this->reactor = new Server($loop); | ||||
|         $this->reactor->listen(0); | ||||
|         $this->reactor = new Server(0, $loop); | ||||
| 
 | ||||
|         $this->port   = $this->reactor->getPort(); | ||||
|         $uri = $this->reactor->getAddress(); | ||||
|         $this->port   = parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_PORT); | ||||
|         $this->server = new IoServer($this->app, $this->reactor, $loop); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -1,11 +1,8 @@ | ||||
| <?php | ||||
| namespace Ratchet\Session; | ||||
| use Ratchet\AbstractMessageComponentTestCase; | ||||
| use Ratchet\Session\SessionProvider; | ||||
| use Ratchet\Mock\MemorySessionHandler; | ||||
| use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; | ||||
| use Symfony\Component\HttpFoundation\Session\Storage\Handler\NullSessionHandler; | ||||
| use Guzzle\Http\Message\Request; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\Session\SessionProvider | ||||
| @ -35,7 +32,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
|     } | ||||
| 
 | ||||
|     public function getComponentClassString() { | ||||
|         return '\Ratchet\MessageComponentInterface'; | ||||
|         return '\Ratchet\Http\HttpServerInterface'; | ||||
|     } | ||||
| 
 | ||||
|     public function classCaseProvider() { | ||||
| @ -53,7 +50,7 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
|         $method = $ref->getMethod('toClassCase'); | ||||
|         $method->setAccessible(true); | ||||
| 
 | ||||
|         $component = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); | ||||
|         $component = new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); | ||||
|         $this->assertEquals($out, $method->invokeArgs($component, array($in))); | ||||
|     } | ||||
| 
 | ||||
| @ -82,16 +79,13 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
|         $pdoHandler = new PdoSessionHandler($pdo, $dbOptions); | ||||
|         $pdoHandler->write($sessionId, '_sf2_attributes|a:2:{s:5:"hello";s:5:"world";s:4:"last";i:1332872102;}_sf2_flashes|a:0:{}'); | ||||
| 
 | ||||
|         $component  = new SessionProvider($this->getMock('Ratchet\\MessageComponentInterface'), $pdoHandler, array('auto_start' => 1)); | ||||
|         $component  = new SessionProvider($this->getMock($this->getComponentClassString()), $pdoHandler, array('auto_start' => 1)); | ||||
|         $connection = $this->getMock('Ratchet\\ConnectionInterface'); | ||||
| 
 | ||||
|         $headers = $this->getMock('Guzzle\\Http\\Message\\Request', array('getCookie'), array('POST', '/', array())); | ||||
|         $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue($sessionId)); | ||||
|         $headers = $this->getMock('Psr\Http\Message\RequestInterface'); | ||||
|         $headers->expects($this->once())->method('getHeader')->will($this->returnValue([ini_get('session.name') . "={$sessionId};"])); | ||||
| 
 | ||||
|         $connection->WebSocket          = new \StdClass; | ||||
|         $connection->WebSocket->request = $headers; | ||||
| 
 | ||||
|         $component->onOpen($connection); | ||||
|         $component->onOpen($connection, $headers); | ||||
| 
 | ||||
|         $this->assertEquals('world', $connection->Session->get('hello')); | ||||
|     } | ||||
| @ -99,12 +93,9 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
|     protected function newConn() { | ||||
|         $conn = $this->getMock('Ratchet\ConnectionInterface'); | ||||
| 
 | ||||
|         $headers = $this->getMock('Guzzle\Http\Message\Request', array('getCookie'), array('POST', '/', array())); | ||||
|         $headers = $this->getMock('Psr\Http\Message\Request', array('getCookie'), array('POST', '/', array())); | ||||
|         $headers->expects($this->once())->method('getCookie', array(ini_get('session.name')))->will($this->returnValue(null)); | ||||
| 
 | ||||
|         $conn->WebSocket          = new \StdClass; | ||||
|         $conn->WebSocket->request = $headers; | ||||
| 
 | ||||
|         return $conn; | ||||
|     } | ||||
| 
 | ||||
| @ -114,21 +105,6 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
|         $this->_serv->onMessage($this->_conn, $message); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetSubProtocolsReturnsArray() { | ||||
|         $mock = $this->getMock('Ratchet\\MessageComponentInterface'); | ||||
|         $comp = new SessionProvider($mock, new NullSessionHandler); | ||||
| 
 | ||||
|         $this->assertInternalType('array', $comp->getSubProtocols()); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetSubProtocolsGetFromApp() { | ||||
|         $mock = $this->getMock('Ratchet\WebSocket\Stub\WsMessageComponentInterface'); | ||||
|         $mock->expects($this->once())->method('getSubProtocols')->will($this->returnValue(array('hello', 'world'))); | ||||
|         $comp = new SessionProvider($mock, new NullSessionHandler); | ||||
| 
 | ||||
|         $this->assertGreaterThanOrEqual(2, count($comp->getSubProtocols())); | ||||
|     } | ||||
| 
 | ||||
|     public function testRejectInvalidSeralizers() { | ||||
|         if (!function_exists('wddx_serialize_value')) { | ||||
|             $this->markTestSkipped(); | ||||
| @ -136,6 +112,13 @@ class SessionProviderTest extends AbstractMessageComponentTestCase { | ||||
| 
 | ||||
|         ini_set('session.serialize_handler', 'wddx'); | ||||
|         $this->setExpectedException('\RuntimeException'); | ||||
|         new SessionProvider($this->getMock('\Ratchet\MessageComponentInterface'), $this->getMock('\SessionHandlerInterface')); | ||||
|         new SessionProvider($this->getMock($this->getComponentClassString()), $this->getMock('\SessionHandlerInterface')); | ||||
|     } | ||||
| 
 | ||||
|     protected function doOpen($conn) { | ||||
|         $request = $this->getMock('Psr\Http\Message\RequestInterface'); | ||||
|         $request->expects($this->any())->method('getHeader')->will($this->returnValue([])); | ||||
| 
 | ||||
|         $this->_serv->onOpen($conn, $request); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,15 +1,11 @@ | ||||
| <?php | ||||
| 
 | ||||
| namespace Ratchet\Session\Storage; | ||||
| 
 | ||||
| 
 | ||||
| use Ratchet\Session\Serialize\PhpHandler; | ||||
| use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag; | ||||
| use Symfony\Component\HttpFoundation\Session\Flash\FlashBag; | ||||
| use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; | ||||
| 
 | ||||
| class VirtualSessionStoragePDOTest extends \PHPUnit_Framework_TestCase | ||||
| { | ||||
| class VirtualSessionStoragePDOTest extends \PHPUnit_Framework_TestCase { | ||||
|     /** | ||||
|      * @var VirtualSessionStorage | ||||
|      */ | ||||
| @ -17,8 +13,11 @@ class VirtualSessionStoragePDOTest extends \PHPUnit_Framework_TestCase | ||||
| 
 | ||||
|     protected $_pathToDB; | ||||
| 
 | ||||
|     public function setUp() | ||||
|     { | ||||
|     public function setUp() { | ||||
|         if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) { | ||||
|             return $this->markTestSkipped('Session test requires PDO and pdo_sqlite'); | ||||
|         } | ||||
| 
 | ||||
|         $schema = <<<SQL | ||||
| CREATE TABLE `sessions` ( | ||||
|     `sess_id` VARBINARY(128) NOT NULL PRIMARY KEY, | ||||
| @ -42,17 +41,13 @@ SQL; | ||||
|         $this->_virtualSessionStorage->registerBag(new AttributeBag()); | ||||
|     } | ||||
| 
 | ||||
|     public function tearDown() | ||||
|     { | ||||
|     public function tearDown() { | ||||
|         unlink($this->_pathToDB); | ||||
|     } | ||||
| 
 | ||||
|     public function testStartWithDSN() | ||||
|     { | ||||
|     public function testStartWithDSN() { | ||||
|         $this->_virtualSessionStorage->start(); | ||||
| 
 | ||||
|         $this->assertTrue($this->_virtualSessionStorage->isStarted()); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -4,9 +4,9 @@ use Ratchet\Mock\Connection; | ||||
| use Ratchet\Mock\WampComponent as TestComponent; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\Wamp\ServerProtocol | ||||
|  * @covers Ratchet\Wamp\WampServerInterface | ||||
|  * @covers Ratchet\Wamp\WampConnection | ||||
|  * @covers \Ratchet\Wamp\ServerProtocol | ||||
|  * @covers \Ratchet\Wamp\WampServerInterface | ||||
|  * @covers \Ratchet\Wamp\WampConnection | ||||
|  */ | ||||
| class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_comp; | ||||
| @ -23,13 +23,13 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|     } | ||||
| 
 | ||||
|     public function invalidMessageProvider() { | ||||
|         return array( | ||||
|             array(0) | ||||
|           , array(3) | ||||
|           , array(4) | ||||
|           , array(8) | ||||
|           , array(9) | ||||
|         ); | ||||
|         return [ | ||||
|             [0] | ||||
|           , [3] | ||||
|           , [4] | ||||
|           , [8] | ||||
|           , [9] | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -40,7 +40,7 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
| 
 | ||||
|         $conn = $this->newConn(); | ||||
|         $this->_comp->onOpen($conn); | ||||
|         $this->_comp->onMessage($conn, json_encode(array($type))); | ||||
|         $this->_comp->onMessage($conn, json_encode([$type])); | ||||
|     } | ||||
| 
 | ||||
|     public function testWelcomeMessage() { | ||||
| @ -82,16 +82,16 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|     } | ||||
| 
 | ||||
|     public function callProvider() { | ||||
|         return array( | ||||
|             array(2, 'a', 'b') | ||||
|           , array(2, array('a', 'b')) | ||||
|           , array(1, 'one') | ||||
|           , array(3, 'one', 'two', 'three') | ||||
|           , array(3, array('un', 'deux', 'trois')) | ||||
|           , array(2, 'hi', array('hello', 'world')) | ||||
|           , array(2, array('hello', 'world'), 'hi') | ||||
|           , array(2, array('hello' => 'world', 'herp' => 'derp')) | ||||
|         ); | ||||
|         return [ | ||||
|             [2, 'a', 'b'] | ||||
|           , [2, ['a', 'b']] | ||||
|           , [1, 'one'] | ||||
|           , [3, 'one', 'two', 'three'] | ||||
|           , [3, ['un', 'deux', 'trois']] | ||||
|           , [2, 'hi', ['hello', 'world']] | ||||
|           , [2, ['hello', 'world'], 'hi'] | ||||
|           , [2, ['hello' => 'world', 'herp' => 'derp']] | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -102,7 +102,7 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|         $paramNum = array_shift($args); | ||||
| 
 | ||||
|         $uri = 'http://example.com/endpoint/' . rand(1, 100); | ||||
|         $id  = uniqid(); | ||||
|         $id  = uniqid('', false); | ||||
|         $clientMessage = array_merge(array(2, $id, $uri), $args); | ||||
| 
 | ||||
|         $conn = $this->newConn(); | ||||
| @ -145,8 +145,8 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|     public function testPublishAndEligible() { | ||||
|         $conn = $this->newConn(); | ||||
| 
 | ||||
|         $buddy  = uniqid(); | ||||
|         $friend = uniqid(); | ||||
|         $buddy  = uniqid('', false); | ||||
|         $friend = uniqid('', false); | ||||
| 
 | ||||
|         $this->_comp->onOpen($conn); | ||||
|         $this->_comp->onMessage($conn, json_encode(array(7, 'topic', 'event', false, array($buddy, $friend)))); | ||||
| @ -265,4 +265,31 @@ class ServerProtocolTest extends \PHPUnit_Framework_TestCase { | ||||
|         $this->_comp->onOpen($conn); | ||||
|         $this->_comp->onMessage($conn, $message); | ||||
|     } | ||||
| 
 | ||||
|     public function testBadClientInputFromNonStringTopic() { | ||||
|         $this->setExpectedException('\Ratchet\Wamp\Exception'); | ||||
| 
 | ||||
|         $conn = new WampConnection($this->newConn()); | ||||
|         $this->_comp->onOpen($conn); | ||||
| 
 | ||||
|         $this->_comp->onMessage($conn, json_encode([5, ['hells', 'nope']])); | ||||
|     } | ||||
| 
 | ||||
|     public function testBadPrefixWithNonStringTopic() { | ||||
|         $this->setExpectedException('\Ratchet\Wamp\Exception'); | ||||
| 
 | ||||
|         $conn = new WampConnection($this->newConn()); | ||||
|         $this->_comp->onOpen($conn); | ||||
| 
 | ||||
|         $this->_comp->onMessage($conn, json_encode([1, ['hells', 'nope'], ['bad', 'input']])); | ||||
|     } | ||||
| 
 | ||||
|     public function testBadPublishWithNonStringTopic() { | ||||
|         $this->setExpectedException('\Ratchet\Wamp\Exception'); | ||||
| 
 | ||||
|         $conn = new WampConnection($this->newConn()); | ||||
|         $this->_comp->onOpen($conn); | ||||
| 
 | ||||
|         $this->_comp->onMessage($conn, json_encode([7, ['bad', 'input'], 'Hider'])); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -185,21 +185,18 @@ class TopicManagerTest extends \PHPUnit_Framework_TestCase { | ||||
|     } | ||||
| 
 | ||||
|     public static function topicConnExpectationProvider() { | ||||
|         return array( | ||||
|             array(true, 'onClose', 0) | ||||
|           , array(true, 'onUnsubscribe', 0) | ||||
|           , array(false, 'onClose', 1) | ||||
|           , array(false, 'onUnsubscribe', 1) | ||||
|         ); | ||||
|         return [ | ||||
|             [ 'onClose', 0] | ||||
|           , ['onUnsubscribe', 0] | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider topicConnExpectationProvider | ||||
|      */ | ||||
|     public function testTopicRetentionFromLeavingConnections($autoDelete, $methodCall, $expectation) { | ||||
|     public function testTopicRetentionFromLeavingConnections($methodCall, $expectation) { | ||||
|         $topicName = 'checkTopic'; | ||||
|         list($topic, $attribute) = $this->topicProvider($topicName); | ||||
|         $topic->autoDelete = $autoDelete; | ||||
| 
 | ||||
|         $this->mngr->onSubscribe($this->conn, $topicName); | ||||
|         call_user_func_array(array($this->mngr, $methodCall), array($this->conn, $topicName)); | ||||
|  | ||||
| @ -1,103 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\WebSocket\Version\Hixie76; | ||||
| use Ratchet\Http\HttpServer; | ||||
| use Ratchet\WebSocket\WsServer; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\Version\Hixie76 | ||||
|  */ | ||||
| class Hixie76Test extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_crlf = "\r\n"; | ||||
|     protected $_body = '6dW+XgKfWV0='; | ||||
| 
 | ||||
|     protected $_version; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_version = new Hixie76; | ||||
|     } | ||||
| 
 | ||||
|     public function testClassImplementsVersionInterface() { | ||||
|         $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); | ||||
|         $this->assertThat($this->_version, $constraint); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider keyProvider | ||||
|      */ | ||||
|     public function testKeySigningForHandshake($accept, $key) { | ||||
|         $this->assertEquals($accept, $this->_version->generateKeyNumber($key)); | ||||
|     } | ||||
| 
 | ||||
|     public static function keyProvider() { | ||||
|         return array( | ||||
|             array(179922739, '17  9 G`ZD9   2 2b 7X 3 /r90') | ||||
|           , array(906585445, '3e6b263  4 17 80') | ||||
|           , array(0, '3e6b26341780') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function headerProvider() { | ||||
|         $key1 = base64_decode('QTN+ICszNiA2IDJvICBWOG4gNyAgc08yODhZ'); | ||||
|         $key2 = base64_decode('TzEyICAgeVsgIFFSNDUgM1IgLiAyOFggNC00dn4z'); | ||||
| 
 | ||||
|         $headers  = "GET / HTTP/1.1"; | ||||
|         $headers .= "Upgrade: WebSocket{$this->_crlf}"; | ||||
|         $headers .= "Connection: Upgrade{$this->_crlf}"; | ||||
|         $headers .= "Host: socketo.me{$this->_crlf}"; | ||||
|         $headers .= "Origin: http://fiddle.jshell.net{$this->_crlf}"; | ||||
|         $headers .= "Sec-WebSocket-Key1:17 Z4< F94 N3  7P41  7{$this->_crlf}"; | ||||
|         $headers .= "Sec-WebSocket-Key2:1 23C3:,2% 1-29  4 f0{$this->_crlf}"; | ||||
|         $headers .= "(Key3):70:00:EE:6E:33:20:90:69{$this->_crlf}"; | ||||
|         $headers .= $this->_crlf; | ||||
| 
 | ||||
|         return $headers; | ||||
|     } | ||||
| 
 | ||||
|     public function testNoUpgradeBeforeBody() { | ||||
|         $headers = $this->headerProvider(); | ||||
| 
 | ||||
|         $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $mockApp  = $this->getMock('\Ratchet\MessageComponentInterface'); | ||||
| 
 | ||||
|         $server = new HttpServer(new WsServer($mockApp)); | ||||
|         $server->onOpen($mockConn); | ||||
|         $mockApp->expects($this->exactly(0))->method('onOpen'); | ||||
|         $server->onMessage($mockConn, $headers); | ||||
|     } | ||||
| 
 | ||||
|     public function testTcpFragmentedUpgrade() { | ||||
|         $headers = $this->headerProvider(); | ||||
|         $body    = base64_decode($this->_body); | ||||
| 
 | ||||
|         $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $mockApp  = $this->getMock('\Ratchet\MessageComponentInterface'); | ||||
| 
 | ||||
|         $server = new HttpServer(new WsServer($mockApp)); | ||||
|         $server->onOpen($mockConn); | ||||
|         $server->onMessage($mockConn, $headers); | ||||
| 
 | ||||
|         $mockApp->expects($this->once())->method('onOpen'); | ||||
|         $server->onMessage($mockConn, $body . $this->_crlf . $this->_crlf); | ||||
|     } | ||||
| 
 | ||||
|     public function testTcpFragmentedBodyUpgrade() { | ||||
|         $headers = $this->headerProvider(); | ||||
|         $body    = base64_decode($this->_body); | ||||
|         $body1   = substr($body, 0, 4); | ||||
|         $body2   = substr($body, 4); | ||||
| 
 | ||||
|         $mockConn = $this->getMock('\Ratchet\ConnectionInterface'); | ||||
|         $mockApp  = $this->getMock('\Ratchet\MessageComponentInterface'); | ||||
| 
 | ||||
|         $server = new HttpServer(new WsServer($mockApp)); | ||||
|         $server->onOpen($mockConn); | ||||
|         $server->onMessage($mockConn, $headers); | ||||
| 
 | ||||
|         $mockApp->expects($this->once())->method('onOpen'); | ||||
| 
 | ||||
|         $server->onMessage($mockConn, $body1); | ||||
|         $server->onMessage($mockConn, $body2); | ||||
|         $server->onMessage($mockConn, $this->_crlf . $this->_crlf); | ||||
|     } | ||||
| } | ||||
| @ -1,67 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\WebSocket\Version\HyBi10; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Frame; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\Version\Hybi10 | ||||
|  */ | ||||
| class HyBi10Test extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_version; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_version = new HyBi10(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Is this useful? | ||||
|      */ | ||||
|     public function testClassImplementsVersionInterface() { | ||||
|         $constraint = $this->isInstanceOf('\\Ratchet\\WebSocket\\Version\\VersionInterface'); | ||||
|         $this->assertThat($this->_version, $constraint); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider HandshakeProvider | ||||
|      */ | ||||
|     public function testKeySigningForHandshake($key, $accept) { | ||||
|         $this->assertEquals($accept, $this->_version->sign($key)); | ||||
|     } | ||||
| 
 | ||||
|     public static function HandshakeProvider() { | ||||
|         return array( | ||||
|             array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') | ||||
|           , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      */ | ||||
|     public function testUnframeMessage($message, $framed) { | ||||
| //        $decoded = $this->_version->unframe(base64_decode($framed));
 | ||||
|         $frame = new Frame; | ||||
|         $frame->addBuffer(base64_decode($framed)); | ||||
| 
 | ||||
|         $this->assertEquals($message, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     public static function UnframeMessageProvider() { | ||||
|         return array( | ||||
|             array('Hello World!',                'gYydAIfa1WXrtvIg0LXvbOP7') | ||||
|           , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') | ||||
|           , array('ಠ_ಠ',                         'gYfnSpu5B/g75gf4Ow==') | ||||
|           , array("The quick brown fox jumps over the lazy dog.  All work and no play makes Chris a dull boy.  I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testUnframeMatchesPreFraming() { | ||||
|         $string   = 'Hello World!'; | ||||
|         $framed   = $this->_version->newFrame($string)->getContents(); | ||||
| 
 | ||||
|         $frame = new Frame; | ||||
|         $frame->addBuffer($framed); | ||||
| 
 | ||||
|         $this->assertEquals($string, $frame->getPayload()); | ||||
|     } | ||||
| } | ||||
| @ -1,543 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Frame; | ||||
| 
 | ||||
| /** | ||||
|  * @todo getMaskingKey, getPayloadStartingByte don't have tests yet | ||||
|  * @todo Could use some clean up in general, I had to rush to fix a bug for a deadline, sorry. | ||||
|  */ | ||||
| class FrameTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $_firstByteFinText    = '10000001'; | ||||
|     protected $_secondByteMaskedSPL = '11111101'; | ||||
| 
 | ||||
|     protected $_frame; | ||||
| 
 | ||||
|     protected $_packer; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_frame = new Frame; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Encode the fake binary string to send over the wire | ||||
|      * @param string of 1's and 0's | ||||
|      * @return string | ||||
|      */ | ||||
|     public static function encode($in) { | ||||
|         if (strlen($in) > 8) { | ||||
|             $out = ''; | ||||
| 
 | ||||
|             while (strlen($in) >= 8) { | ||||
|                 $out .= static::encode(substr($in, 0, 8)); | ||||
|                 $in   = substr($in, 8); | ||||
|             } | ||||
| 
 | ||||
|             return $out; | ||||
|         } | ||||
| 
 | ||||
|         return chr(bindec($in)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * This is a data provider | ||||
|      * @param string The UTF8 message | ||||
|      * @param string The WebSocket framed message, then base64_encoded | ||||
|      */ | ||||
|     public static function UnframeMessageProvider() { | ||||
|         return array( | ||||
|             array('Hello World!',                'gYydAIfa1WXrtvIg0LXvbOP7') | ||||
|           , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') | ||||
|           , array('ಠ_ಠ',                         'gYfnSpu5B/g75gf4Ow==') | ||||
|           , array("The quick brown fox jumps over the lazy dog.  All work and no play makes Chris a dull boy.  I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public static function underflowProvider() { | ||||
|         return array( | ||||
|             array('isFinal', '') | ||||
|           , array('getRsv1', '') | ||||
|           , array('getRsv2', '') | ||||
|           , array('getRsv3', '') | ||||
|           , array('getOpcode', '') | ||||
|           , array('isMasked', '10000001') | ||||
|           , array('getPayloadLength', '10000001') | ||||
|           , array('getPayloadLength', '1000000111111110') | ||||
|           , array('getMaskingKey', '1000000110000111') | ||||
|           , array('getPayload', '100000011000000100011100101010101001100111110100') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider underflowProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload | ||||
|      */ | ||||
|     public function testUnderflowExceptionFromAllTheMethodsMimickingBuffering($method, $bin) { | ||||
|         $this->setExpectedException('\UnderflowException'); | ||||
| 
 | ||||
|         if (!empty($bin)) { | ||||
|             $this->_frame->addBuffer(static::encode($bin)); | ||||
|         } | ||||
| 
 | ||||
|         call_user_func(array($this->_frame, $method)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A data provider for testing the first byte of a WebSocket frame | ||||
|      * @param bool Given, is the byte indicate this is the final frame | ||||
|      * @param int Given, what is the expected opcode | ||||
|      * @param string of 0|1 Each character represents a bit in the byte | ||||
|      */ | ||||
|     public static function firstByteProvider() { | ||||
|         return array( | ||||
|             array(false, false, false, true,   8, '00011000') | ||||
|           , array(true,  false, true,  false, 10, '10101010') | ||||
|           , array(false, false, false, false, 15, '00001111') | ||||
|           , array(true,  false, false, false,  1, '10000001') | ||||
|           , array(true,  true,  true,  true,  15, '11111111') | ||||
|           , array(true,  true,  false, false,  7, '11000111') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider firstByteProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal | ||||
|      */ | ||||
|     public function testFinCodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
|         $this->assertEquals($fin, $this->_frame->isFinal()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider firstByteProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv1 | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv2 | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getRsv3 | ||||
|      */ | ||||
|     public function testGetRsvFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
| 
 | ||||
|         $this->assertEquals($rsv1, $this->_frame->getRsv1()); | ||||
|         $this->assertEquals($rsv2, $this->_frame->getRsv2()); | ||||
|         $this->assertEquals($rsv3, $this->_frame->getRsv3()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider firstByteProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode | ||||
|      */ | ||||
|     public function testOpcodeFromBits($fin, $rsv1, $rsv2, $rsv3, $opcode, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
|         $this->assertEquals($opcode, $this->_frame->getOpcode()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isFinal | ||||
|      */ | ||||
|     public function testFinCodeFromFullMessage($msg, $encoded) { | ||||
|         $this->_frame->addBuffer(base64_decode($encoded)); | ||||
|         $this->assertTrue($this->_frame->isFinal()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getOpcode | ||||
|      */ | ||||
|     public function testOpcodeFromFullMessage($msg, $encoded) { | ||||
|         $this->_frame->addBuffer(base64_decode($encoded)); | ||||
|         $this->assertEquals(1, $this->_frame->getOpcode()); | ||||
|     } | ||||
| 
 | ||||
|     public static function payloadLengthDescriptionProvider() { | ||||
|         return array( | ||||
|             array(7,  '01110101') | ||||
|           , array(7,  '01111101') | ||||
|           , array(23, '01111110') | ||||
|           , array(71, '01111111') | ||||
|           , array(7,  '00000000') // Should this throw an exception?  Can a payload be empty?
 | ||||
|           , array(7,  '00000001') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider payloadLengthDescriptionProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::addBuffer | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal | ||||
|      */ | ||||
|     public function testFirstPayloadDesignationValue($bits, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
| 
 | ||||
|         $ref = new \ReflectionClass($this->_frame); | ||||
|         $cb  = $ref->getMethod('getFirstPayloadVal'); | ||||
|         $cb->setAccessible(true); | ||||
| 
 | ||||
|         $this->assertEquals(bindec($bin), $cb->invoke($this->_frame)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getFirstPayloadVal | ||||
|      */ | ||||
|     public function testFirstPayloadValUnderflow() { | ||||
|         $ref = new \ReflectionClass($this->_frame); | ||||
|         $cb  = $ref->getMethod('getFirstPayloadVal'); | ||||
|         $cb->setAccessible(true); | ||||
| 
 | ||||
|         $this->setExpectedException('UnderflowException'); | ||||
|         $cb->invoke($this->_frame); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider payloadLengthDescriptionProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits | ||||
|      */ | ||||
|     public function testDetermineHowManyBitsAreUsedToDescribePayload($expected_bits, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
| 
 | ||||
|         $ref = new \ReflectionClass($this->_frame); | ||||
|         $cb  = $ref->getMethod('getNumPayloadBits'); | ||||
|         $cb->setAccessible(true); | ||||
| 
 | ||||
|         $this->assertEquals($expected_bits, $cb->invoke($this->_frame)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getNumPayloadBits | ||||
|      */ | ||||
|     public function testgetNumPayloadBitsUnderflow() { | ||||
|         $ref = new \ReflectionClass($this->_frame); | ||||
|         $cb  = $ref->getMethod('getNumPayloadBits'); | ||||
|         $cb->setAccessible(true); | ||||
| 
 | ||||
|         $this->setExpectedException('UnderflowException'); | ||||
|         $cb->invoke($this->_frame); | ||||
|     } | ||||
| 
 | ||||
|     public function secondByteProvider() { | ||||
|         return array( | ||||
|             array(true,   1, '10000001') | ||||
|           , array(false,  1, '00000001') | ||||
|           , array(true, 125, $this->_secondByteMaskedSPL) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider secondByteProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked | ||||
|      */ | ||||
|     public function testIsMaskedReturnsExpectedValue($masked, $payload_length, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
| 
 | ||||
|         $this->assertEquals($masked, $this->_frame->isMasked()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::isMasked | ||||
|      */ | ||||
|     public function testIsMaskedFromFullMessage($msg, $encoded) { | ||||
|         $this->_frame->addBuffer(base64_decode($encoded)); | ||||
|         $this->assertTrue($this->_frame->isMasked()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider secondByteProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      */ | ||||
|     public function testGetPayloadLengthWhenOnlyFirstFrameIsUsed($masked, $payload_length, $bin) { | ||||
|         $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); | ||||
|         $this->_frame->addBuffer(static::encode($bin)); | ||||
| 
 | ||||
|         $this->assertEquals($payload_length, $this->_frame->getPayloadLength()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      * @todo Not yet testing when second additional payload length descriptor | ||||
|      */ | ||||
|     public function testGetPayloadLengthFromFullMessage($msg, $encoded) { | ||||
|         $this->_frame->addBuffer(base64_decode($encoded)); | ||||
|         $this->assertEquals(strlen($msg), $this->_frame->getPayloadLength()); | ||||
|     } | ||||
| 
 | ||||
|     public function maskingKeyProvider() { | ||||
|         $frame = new Frame; | ||||
| 
 | ||||
|         return array( | ||||
|             array($frame->generateMaskingKey()) | ||||
|           , array($frame->generateMaskingKey()) | ||||
|           , array($frame->generateMaskingKey()) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider maskingKeyProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey | ||||
|      * @todo I I wrote the dataProvider incorrectly, skipping for now | ||||
|      */ | ||||
|     public function testGetMaskingKey($mask) { | ||||
|         $this->_frame->addBuffer(static::encode($this->_firstByteFinText)); | ||||
|         $this->_frame->addBuffer(static::encode($this->_secondByteMaskedSPL)); | ||||
|         $this->_frame->addBuffer($mask); | ||||
| 
 | ||||
|         $this->assertEquals($mask, $this->_frame->getMaskingKey()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getMaskingKey | ||||
|      */ | ||||
|     public function testGetMaskingKeyOnUnmaskedPayload() { | ||||
|         $frame = new Frame('Hello World!'); | ||||
| 
 | ||||
|         $this->assertEquals('', $frame->getMaskingKey()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload | ||||
|      * @todo Move this test to bottom as it requires all methods of the class | ||||
|      */ | ||||
|     public function testUnframeFullMessage($unframed, $base_framed) { | ||||
|         $this->_frame->addBuffer(base64_decode($base_framed)); | ||||
|         $this->assertEquals($unframed, $this->_frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     public static function messageFragmentProvider() { | ||||
|         return array( | ||||
|             array(false, '', '', '', '', '') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload | ||||
|      */ | ||||
|     public function testCheckPiecingTogetherMessage($msg, $encoded) { | ||||
|         $framed = base64_decode($encoded); | ||||
|         for ($i = 0, $len = strlen($framed);$i < $len; $i++) { | ||||
|             $this->_frame->addBuffer(substr($framed, $i, 1)); | ||||
|         } | ||||
| 
 | ||||
|         $this->assertEquals($msg, $this->_frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayload | ||||
|      */ | ||||
|     public function testLongCreate() { | ||||
|         $len = 65525; | ||||
|         $pl  = $this->generateRandomString($len); | ||||
| 
 | ||||
|         $frame = new Frame($pl, true, Frame::OP_PING); | ||||
| 
 | ||||
|         $this->assertTrue($frame->isFinal()); | ||||
|         $this->assertEquals(Frame::OP_PING, $frame->getOpcode()); | ||||
|         $this->assertFalse($frame->isMasked()); | ||||
|         $this->assertEquals($len, $frame->getPayloadLength()); | ||||
|         $this->assertEquals($pl, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      */ | ||||
|     public function testReallyLongCreate() { | ||||
|         $len = 65575; | ||||
| 
 | ||||
|         $frame = new Frame($this->generateRandomString($len)); | ||||
| 
 | ||||
|         $this->assertEquals($len, $frame->getPayloadLength()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::__construct | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow | ||||
|      */ | ||||
|     public function testExtractOverflow() { | ||||
|         $string1 = $this->generateRandomString(); | ||||
|         $frame1  = new Frame($string1); | ||||
| 
 | ||||
|         $string2 = $this->generateRandomString(); | ||||
|         $frame2  = new Frame($string2); | ||||
| 
 | ||||
|         $cat = new Frame; | ||||
|         $cat->addBuffer($frame1->getContents() . $frame2->getContents()); | ||||
| 
 | ||||
|         $this->assertEquals($frame1->getContents(), $cat->getContents()); | ||||
|         $this->assertEquals($string1, $cat->getPayload()); | ||||
| 
 | ||||
|         $uncat = new Frame; | ||||
|         $uncat->addBuffer($cat->extractOverflow()); | ||||
| 
 | ||||
|         $this->assertEquals($string1, $cat->getPayload()); | ||||
|         $this->assertEquals($string2, $uncat->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow | ||||
|      */ | ||||
|     public function testEmptyExtractOverflow() { | ||||
|         $string = $this->generateRandomString(); | ||||
|         $frame  = new Frame($string); | ||||
| 
 | ||||
|         $this->assertEquals($string, $frame->getPayload()); | ||||
|         $this->assertEquals('', $frame->extractOverflow()); | ||||
|         $this->assertEquals($string, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getContents | ||||
|      */ | ||||
|     public function testGetContents() { | ||||
|         $msg = 'The quick brown fox jumps over the lazy dog.'; | ||||
| 
 | ||||
|         $frame1 = new Frame($msg); | ||||
|         $frame2 = new Frame($msg); | ||||
|         $frame2->maskPayload(); | ||||
| 
 | ||||
|         $this->assertNotEquals($frame1->getContents(), $frame2->getContents()); | ||||
|         $this->assertEquals(strlen($frame1->getContents()) + 4, strlen($frame2->getContents())); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload | ||||
|      */ | ||||
|     public function testMasking() { | ||||
|         $msg   = 'The quick brown fox jumps over the lazy dog.'; | ||||
|         $frame = new Frame($msg); | ||||
|         $frame->maskPayload(); | ||||
| 
 | ||||
|         $this->assertTrue($frame->isMasked()); | ||||
|         $this->assertEquals($msg, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::unMaskPayload | ||||
|      */ | ||||
|     public function testUnMaskPayload() { | ||||
|         $string = $this->generateRandomString(); | ||||
|         $frame  = new Frame($string); | ||||
|         $frame->maskPayload()->unMaskPayload(); | ||||
| 
 | ||||
|         $this->assertFalse($frame->isMasked()); | ||||
|         $this->assertEquals($string, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::generateMaskingKey | ||||
|      */ | ||||
|     public function testGenerateMaskingKey() { | ||||
|         $dupe = false; | ||||
|         $done = array(); | ||||
| 
 | ||||
|         for ($i = 0; $i < 10; $i++) { | ||||
|             $new = $this->_frame->generateMaskingKey(); | ||||
| 
 | ||||
|             if (in_array($new, $done)) { | ||||
|                 $dupe = true; | ||||
|             } | ||||
| 
 | ||||
|             $done[] = $new; | ||||
|         } | ||||
| 
 | ||||
|         $this->assertEquals(4, strlen($new)); | ||||
|         $this->assertFalse($dupe); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload | ||||
|      */ | ||||
|     public function testGivenMaskIsValid() { | ||||
|         $this->setExpectedException('InvalidArgumentException'); | ||||
|         $this->_frame->maskPayload('hello world'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::maskPayload | ||||
|      */ | ||||
|     public function testGivenMaskIsValidAscii() { | ||||
|         if (!extension_loaded('mbstring')) { | ||||
|             return $this->markTestSkipped("mbstring required for this test"); | ||||
|         } | ||||
| 
 | ||||
|         $this->setExpectedException('OutOfBoundsException'); | ||||
|         $this->_frame->maskPayload('x✖'); | ||||
|     } | ||||
| 
 | ||||
|     protected function generateRandomString($length = 10, $addSpaces = true, $addNumbers = true) { | ||||
|         $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&/()=[]{}'; // ยง
 | ||||
| 
 | ||||
|         $useChars = array(); | ||||
|         for($i = 0; $i < $length; $i++) { | ||||
|             $useChars[] = $characters[mt_rand(0, strlen($characters) - 1)]; | ||||
|         } | ||||
| 
 | ||||
|         if($addSpaces === true) { | ||||
|             array_push($useChars, ' ', ' ', ' ', ' ', ' ', ' '); | ||||
|         } | ||||
| 
 | ||||
|         if($addNumbers === true) { | ||||
|             array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9)); | ||||
|         } | ||||
| 
 | ||||
|         shuffle($useChars); | ||||
| 
 | ||||
|         $randomString = trim(implode('', $useChars)); | ||||
|         $randomString = substr($randomString, 0, $length); | ||||
| 
 | ||||
|         return $randomString; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * There was a frame boundary issue when the first 3 bytes of a frame with a payload greater than | ||||
|      * 126 was added to the frame buffer and then Frame::getPayloadLength was called. It would cause the frame | ||||
|      * to set the payload length to 126 and then not recalculate it once the full length information was available. | ||||
|      * | ||||
|      * This is fixed by setting the defPayLen back to -1 before the underflow exception is thrown. | ||||
|      * | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::getPayloadLength | ||||
|      * @covers Ratchet\WebSocket\Version\RFC6455\Frame::extractOverflow | ||||
|      */ | ||||
|     public function testFrameDeliveredOneByteAtATime() { | ||||
|         $startHeader = "\x01\x7e\x01\x00"; // header for a text frame of 256 - non-final
 | ||||
|         $framePayload = str_repeat("*", 256); | ||||
|         $rawOverflow = "xyz"; | ||||
|         $rawFrame = $startHeader . $framePayload . $rawOverflow; | ||||
| 
 | ||||
|         $frame = new Frame(); | ||||
|         $payloadLen = 256; | ||||
| 
 | ||||
|         for ($i = 0; $i < strlen($rawFrame); $i++) { | ||||
|             $frame->addBuffer($rawFrame[$i]); | ||||
| 
 | ||||
|             try { | ||||
|                 // payloadLen will
 | ||||
|                 $payloadLen = $frame->getPayloadLength(); | ||||
|             } catch (\UnderflowException $e) { | ||||
|                 if ($i > 2) { // we should get an underflow on 0,1,2
 | ||||
|                     $this->fail("Underflow exception when the frame length should be available"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if ($payloadLen !== 256) { | ||||
|                 $this->fail("Payload length of " . $payloadLen . " should have been 256."); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // make sure the overflow is good
 | ||||
|         $this->assertEquals($rawOverflow, $frame->extractOverflow()); | ||||
|     } | ||||
| } | ||||
| @ -1,170 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier | ||||
|  */ | ||||
| class HandshakeVerifierTest extends \PHPUnit_Framework_TestCase { | ||||
|     /** | ||||
|      * @var Ratchet\WebSocket\Version\RFC6455\HandshakeVerifier | ||||
|      */ | ||||
|     protected $_v; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->_v = new HandshakeVerifier; | ||||
|     } | ||||
| 
 | ||||
|     public static function methodProvider() { | ||||
|         return array( | ||||
|             array(true,  'GET') | ||||
|           , array(true,  'get') | ||||
|           , array(true,  'Get') | ||||
|           , array(false, 'POST') | ||||
|           , array(false, 'DELETE') | ||||
|           , array(false, 'PUT') | ||||
|           , array(false, 'PATCH') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider methodProvider | ||||
|      */ | ||||
|     public function testMethodMustBeGet($result, $in) { | ||||
|         $this->assertEquals($result, $this->_v->verifyMethod($in)); | ||||
|     } | ||||
| 
 | ||||
|     public static function httpVersionProvider() { | ||||
|         return array( | ||||
|             array(true,  1.1) | ||||
|           , array(true,  '1.1') | ||||
|           , array(true,  1.2) | ||||
|           , array(true,  '1.2') | ||||
|           , array(true,  2) | ||||
|           , array(true,  '2') | ||||
|           , array(true,  '2.0') | ||||
|           , array(false, '1.0') | ||||
|           , array(false, 1) | ||||
|           , array(false, '0.9') | ||||
|           , array(false, '') | ||||
|           , array(false, 'hello') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider httpVersionProvider | ||||
|      */ | ||||
|     public function testHttpVersionIsAtLeast1Point1($expected, $in) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyHTTPVersion($in)); | ||||
|     } | ||||
| 
 | ||||
|     public static function uRIProvider() { | ||||
|         return array( | ||||
|             array(true, '/chat') | ||||
|           , array(true, '/hello/world?key=val') | ||||
|           , array(false, '/chat#bad') | ||||
|           , array(false, 'nope') | ||||
|           , array(false, '/ ಠ_ಠ ') | ||||
|           , array(false, '/✖') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider URIProvider | ||||
|      */ | ||||
|     public function testRequestUri($expected, $in) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyRequestURI($in)); | ||||
|     } | ||||
| 
 | ||||
|     public static function hostProvider() { | ||||
|         return array( | ||||
|             array(true, 'server.example.com') | ||||
|           , array(false, null) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider HostProvider | ||||
|      */ | ||||
|     public function testVerifyHostIsSet($expected, $in) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyHost($in)); | ||||
|     } | ||||
| 
 | ||||
|     public static function upgradeProvider() { | ||||
|         return array( | ||||
|             array(true,  'websocket') | ||||
|           , array(true,  'Websocket') | ||||
|           , array(true,  'webSocket') | ||||
|           , array(false, null) | ||||
|           , array(false, '') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider upgradeProvider | ||||
|      */ | ||||
|     public function testVerifyUpgradeIsWebSocket($expected, $val) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyUpgradeRequest($val)); | ||||
|     } | ||||
| 
 | ||||
|     public static function connectionProvider() { | ||||
|         return array( | ||||
|             array(true,  'Upgrade') | ||||
|           , array(true,  'upgrade') | ||||
|           , array(true,  'keep-alive, Upgrade') | ||||
|           , array(true,  'Upgrade, keep-alive') | ||||
|           , array(true,  'keep-alive, Upgrade, something') | ||||
|           , array(false, '') | ||||
|           , array(false, null) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider connectionProvider | ||||
|      */ | ||||
|     public function testConnectionHeaderVerification($expected, $val) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyConnection($val)); | ||||
|     } | ||||
| 
 | ||||
|     public static function keyProvider() { | ||||
|         return array( | ||||
|             array(true,  'hkfa1L7uwN6DCo4IS3iWAw==') | ||||
|           , array(true,  '765vVoQpKSGJwPzJIMM2GA==') | ||||
|           , array(true,  'AQIDBAUGBwgJCgsMDQ4PEC==') | ||||
|           , array(true,  'axa2B/Yz2CdpfQAY2Q5P7w==') | ||||
|           , array(false, 0) | ||||
|           , array(false, 'Hello World') | ||||
|           , array(false, '1234567890123456') | ||||
|           , array(false, '123456789012345678901234') | ||||
|           , array(true,  base64_encode('UTF8allthngs+✓')) | ||||
|           , array(true,  'dGhlIHNhbXBsZSBub25jZQ==') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider keyProvider | ||||
|      */ | ||||
|     public function testKeyIsBase64Encoded16BitNonce($expected, $val) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyKey($val)); | ||||
|     } | ||||
| 
 | ||||
|     public static function versionProvider() { | ||||
|         return array( | ||||
|             array(true,  13) | ||||
|           , array(true,  '13') | ||||
|           , array(false, 12) | ||||
|           , array(false, 14) | ||||
|           , array(false, '14') | ||||
|           , array(false, 'hi') | ||||
|           , array(false, '') | ||||
|           , array(false, null) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider versionProvider | ||||
|      */ | ||||
|     public function testVersionEquals13($expected, $in) { | ||||
|         $this->assertEquals($expected, $this->_v->verifyVersion($in)); | ||||
|     } | ||||
| } | ||||
| @ -1,63 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version\RFC6455\Message; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Message; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Frame; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\Version\RFC6455\Message | ||||
|  */ | ||||
| class MessageTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $message; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->message = new Message; | ||||
|     } | ||||
| 
 | ||||
|     public function testNoFrames() { | ||||
|         $this->assertFalse($this->message->isCoalesced()); | ||||
|     } | ||||
| 
 | ||||
|     public function testNoFramesOpCode() { | ||||
|         $this->setExpectedException('UnderflowException'); | ||||
|         $this->message->getOpCode(); | ||||
|     } | ||||
| 
 | ||||
|     public function testFragmentationPayload() { | ||||
|         $a = 'Hello '; | ||||
|         $b = 'World!'; | ||||
| 
 | ||||
|         $f1 = new Frame($a, false); | ||||
|         $f2 = new Frame($b, true, Frame::OP_CONTINUE); | ||||
| 
 | ||||
|         $this->message->addFrame($f1)->addFrame($f2); | ||||
| 
 | ||||
|         $this->assertEquals(strlen($a . $b), $this->message->getPayloadLength()); | ||||
|         $this->assertEquals($a . $b, $this->message->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     public function testUnbufferedFragment() { | ||||
|         $this->message->addFrame(new Frame('The quick brow', false)); | ||||
| 
 | ||||
|         $this->setExpectedException('UnderflowException'); | ||||
|         $this->message->getPayload(); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetOpCode() { | ||||
|         $this->message | ||||
|             ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) | ||||
|             ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) | ||||
|             ->addFrame(new Frame('er the lazy dog', true, Frame::OP_CONTINUE)) | ||||
|         ; | ||||
| 
 | ||||
|         $this->assertEquals(Frame::OP_TEXT, $this->message->getOpCode()); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetUnBufferedPayloadLength() { | ||||
|         $this->message | ||||
|             ->addFrame(new Frame('The quick brow', false, Frame::OP_TEXT)) | ||||
|             ->addFrame(new Frame('n fox jumps ov', false, Frame::OP_CONTINUE)) | ||||
|         ; | ||||
| 
 | ||||
|         $this->assertEquals(28, $this->message->getPayloadLength()); | ||||
|     } | ||||
| } | ||||
| @ -1,151 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket\Version; | ||||
| use Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\RFC6455\Frame; | ||||
| use Guzzle\Http\Message\RequestFactory; | ||||
| use Guzzle\Http\Message\EntityEnclosingRequest; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\Version\RFC6455 | ||||
|  */ | ||||
| class RFC6455Test extends \PHPUnit_Framework_TestCase { | ||||
|     protected $version; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->version = new RFC6455; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider handshakeProvider | ||||
|      */ | ||||
|     public function testKeySigningForHandshake($key, $accept) { | ||||
|         $this->assertEquals($accept, $this->version->sign($key)); | ||||
|     } | ||||
| 
 | ||||
|     public static function handshakeProvider() { | ||||
|         return array( | ||||
|             array('x3JJHMbDL1EzLkh9GBhXDw==', 'HSmrc0sMlYUkAGmm5OPpG2HaGWk=') | ||||
|           , array('dGhlIHNhbXBsZSBub25jZQ==', 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider UnframeMessageProvider | ||||
|      */ | ||||
|     public function testUnframeMessage($message, $framed) { | ||||
|         $frame = new Frame; | ||||
|         $frame->addBuffer(base64_decode($framed)); | ||||
| 
 | ||||
|         $this->assertEquals($message, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     public static function UnframeMessageProvider() { | ||||
|         return array( | ||||
|             array('Hello World!',                'gYydAIfa1WXrtvIg0LXvbOP7') | ||||
|           , array('!@#$%^&*()-=_+[]{}\|/.,<>`~', 'gZv+h96r38f9j9vZ+IHWrvOWoayF9oX6gtfRqfKXwOeg') | ||||
|           , array('ಠ_ಠ',                         'gYfnSpu5B/g75gf4Ow==') | ||||
|           , array("The quick brown fox jumps over the lazy dog.  All work and no play makes Chris a dull boy.  I'm trying to get past 128 characters for a unit test here...", 'gf4Amahb14P8M7Kj2S6+4MN7tfHHLLmjzjSvo8IuuvPbe7j1zSn398A+9+/JIa6jzDSwrYh7lu/Ee6Ds2jD34sY/9+3He6fvySL37skwsvCIGL/xwSj34og/ou/Ee7Xs0XX3o+F8uqPcKa7qxjz398d7sObce6fi2y/3sppj9+DAOqXiyy+y8dt7sezae7aj3TW+94gvsvDce7/m2j75rYY=') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function testUnframeMatchesPreFraming() { | ||||
|         $string = 'Hello World!'; | ||||
|         $framed = $this->version->newFrame($string)->getContents(); | ||||
| 
 | ||||
|         $frame = new Frame; | ||||
|         $frame->addBuffer($framed); | ||||
| 
 | ||||
|         $this->assertEquals($string, $frame->getPayload()); | ||||
|     } | ||||
| 
 | ||||
|     public static $good_rest = 'GET /chat HTTP/1.1'; | ||||
| 
 | ||||
|     public static $good_header = array( | ||||
|         'Host'                   => 'server.example.com' | ||||
|       , 'Upgrade'                => 'websocket' | ||||
|       , 'Connection'             => 'Upgrade' | ||||
|       , 'Sec-WebSocket-Key'      => 'dGhlIHNhbXBsZSBub25jZQ==' | ||||
|       , 'Origin'                 => 'http://example.com' | ||||
|       , 'Sec-WebSocket-Protocol' => 'chat, superchat' | ||||
|       , 'Sec-WebSocket-Version'  => 13 | ||||
|     ); | ||||
| 
 | ||||
|     public function caseVariantProvider() { | ||||
|         return array( | ||||
|             array('Sec-Websocket-Version') | ||||
|           , array('sec-websocket-version') | ||||
|           , array('SEC-WEBSOCKET-VERSION') | ||||
|           , array('sEC-wEBsOCKET-vERSION') | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider caseVariantProvider | ||||
|      */ | ||||
|     public function testIsProtocolWithCaseInsensitivity($headerName) { | ||||
|         $header = static::$good_header; | ||||
|         unset($header['Sec-WebSocket-Version']); | ||||
|         $header[$headerName] = 13; | ||||
| 
 | ||||
|         $this->assertTrue($this->version->isProtocol(new EntityEnclosingRequest('get', '/', $header))); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A helper function to try and quickly put together a valid WebSocket HTTP handshake | ||||
|      * but optionally replace a piece to an invalid value for failure testing | ||||
|      */ | ||||
|     public static function getAndSpliceHeader($key = null, $val = null) { | ||||
|         $headers = static::$good_header; | ||||
| 
 | ||||
|         if (null !== $key && null !== $val) { | ||||
|             $headers[$key] = $val; | ||||
|         } | ||||
| 
 | ||||
|         $header = ''; | ||||
|         foreach ($headers as $key => $val) { | ||||
|             if (!empty($key)) { | ||||
|                 $header .= "{$key}: "; | ||||
|             } | ||||
| 
 | ||||
|             $header .= "{$val}\r\n"; | ||||
|         } | ||||
|         $header .= "\r\n"; | ||||
| 
 | ||||
|         return $header; | ||||
|     } | ||||
| 
 | ||||
|     public static function headerHandshakeProvider() { | ||||
|         return array( | ||||
|             array(false, "GET /test HTTP/1.0\r\n" . static::getAndSpliceHeader()) | ||||
|           , array(true,  static::$good_rest . "\r\n" . static::getAndSpliceHeader()) | ||||
|           , array(false, "POST / HTTP:/1.1\r\n" . static::getAndSpliceHeader()) | ||||
|           , array(false, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Upgrade', 'useless')) | ||||
|           , array(false, "GET /ಠ_ಠ HTTP/1.1\r\n" . static::getAndSpliceHeader()) | ||||
|           , array(true, static::$good_rest . "\r\n" . static::getAndSpliceHeader('Connection', 'Herp, Upgrade, Derp')) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider headerHandshakeProvider | ||||
|      */ | ||||
|     public function testVariousHeadersToCheckHandshakeTolerance($pass, $header) { | ||||
|         $request  = RequestFactory::getInstance()->fromMessage($header); | ||||
|         $response = $this->version->handshake($request); | ||||
| 
 | ||||
|         $this->assertInstanceOf('\\Guzzle\\Http\\Message\\Response', $response); | ||||
| 
 | ||||
|         if ($pass) { | ||||
|             $this->assertEquals(101, $response->getStatusCode()); | ||||
|         } else { | ||||
|             $this->assertGreaterThanOrEqual(400, $response->getStatusCode()); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function testNewMessage() { | ||||
|         $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Message', $this->version->newMessage()); | ||||
|     } | ||||
| 
 | ||||
|     public function testNewFrame() { | ||||
|         $this->assertInstanceOf('\\Ratchet\\WebSocket\\Version\\RFC6455\\Frame', $this->version->newFrame()); | ||||
|     } | ||||
| } | ||||
| @ -1,91 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\WebSocket\VersionManager; | ||||
| use Ratchet\WebSocket\Version\RFC6455; | ||||
| use Ratchet\WebSocket\Version\HyBi10; | ||||
| use Ratchet\WebSocket\Version\Hixie76; | ||||
| use Guzzle\Http\Message\EntityEnclosingRequest; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\VersionManager | ||||
|  */ | ||||
| class VersionManagerTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $vm; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->vm = new VersionManager; | ||||
|     } | ||||
| 
 | ||||
|     public function testFluentInterface() { | ||||
|         $rfc = new RFC6455; | ||||
| 
 | ||||
|         $this->assertSame($this->vm, $this->vm->enableVersion($rfc)); | ||||
|         $this->assertSame($this->vm, $this->vm->disableVersion(13)); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetVersion() { | ||||
|         $rfc = new RFC6455; | ||||
|         $this->vm->enableVersion($rfc); | ||||
| 
 | ||||
|         $req = new EntityEnclosingRequest('get', '/', array( | ||||
|             'Host' => 'socketo.me' | ||||
|           , 'Sec-WebSocket-Version' => 13 | ||||
|         )); | ||||
| 
 | ||||
|         $this->assertSame($rfc, $this->vm->getVersion($req)); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetNopeVersionAndDisable() { | ||||
|         $req = new EntityEnclosingRequest('get', '/', array( | ||||
|             'Host' => 'socketo.me' | ||||
|           , 'Sec-WebSocket-Version' => 13 | ||||
|         )); | ||||
| 
 | ||||
|         $this->setExpectedException('InvalidArgumentException'); | ||||
| 
 | ||||
|         $this->vm->getVersion($req); | ||||
|     } | ||||
| 
 | ||||
|     public function testYesIsVersionEnabled() { | ||||
|         $this->vm->enableVersion(new RFC6455); | ||||
| 
 | ||||
|         $this->assertTrue($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( | ||||
|             'Host' => 'socketo.me' | ||||
|           , 'Sec-WebSocket-Version' => 13 | ||||
|         )))); | ||||
|     } | ||||
| 
 | ||||
|     public function testNoIsVersionEnabled() { | ||||
|         $this->assertFalse($this->vm->isVersionEnabled(new EntityEnclosingRequest('get', '/', array( | ||||
|             'Host' => 'socketo.me' | ||||
|           , 'Sec-WebSocket-Version' => 9000 | ||||
|         )))); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetSupportedVersionString() { | ||||
|         $v1 = new RFC6455; | ||||
|         $v2 = new HyBi10; | ||||
| 
 | ||||
|         $this->vm->enableVersion($v1); | ||||
|         $this->vm->enableVersion($v2); | ||||
| 
 | ||||
|         $string = $this->vm->getSupportedVersionString(); | ||||
|         $values = explode(',', $string); | ||||
| 
 | ||||
|         $this->assertContains($v1->getVersionNumber(), $values); | ||||
|         $this->assertContains($v2->getVersionNumber(), $values); | ||||
|     } | ||||
| 
 | ||||
|     public function testGetSupportedVersionAfterRemoval() { | ||||
|         $this->vm->enableVersion(new RFC6455); | ||||
|         $this->vm->enableVersion(new HyBi10); | ||||
|         $this->vm->enableVersion(new Hixie76); | ||||
| 
 | ||||
|         $this->vm->disableVersion(0); | ||||
| 
 | ||||
|         $values = explode(',', $this->vm->getSupportedVersionString()); | ||||
| 
 | ||||
|         $this->assertEquals(2, count($values)); | ||||
|         $this->assertFalse(array_search(0, $values)); | ||||
|     } | ||||
| } | ||||
| @ -1,51 +0,0 @@ | ||||
| <?php | ||||
| namespace Ratchet\WebSocket; | ||||
| use Ratchet\WebSocket\WsServer; | ||||
| use Ratchet\Mock\Component as MockComponent; | ||||
| 
 | ||||
| /** | ||||
|  * @covers Ratchet\WebSocket\WsServer | ||||
|  * @covers Ratchet\ComponentInterface | ||||
|  * @covers Ratchet\MessageComponentInterface | ||||
|  */ | ||||
| class WsServerTest extends \PHPUnit_Framework_TestCase { | ||||
|     protected $comp; | ||||
| 
 | ||||
|     protected $serv; | ||||
| 
 | ||||
|     public function setUp() { | ||||
|         $this->comp = new MockComponent; | ||||
|         $this->serv = new WsServer($this->comp); | ||||
|     } | ||||
| 
 | ||||
|     public function testIsSubProtocolSupported() { | ||||
|         $this->comp->protocols = array('hello', 'world'); | ||||
| 
 | ||||
|         $this->assertTrue($this->serv->isSubProtocolSupported('hello')); | ||||
|         $this->assertFalse($this->serv->isSubProtocolSupported('nope')); | ||||
|     } | ||||
| 
 | ||||
|     public function protocolProvider() { | ||||
|         return array( | ||||
|             array('hello', array('hello', 'world'), array('hello', 'world')) | ||||
|           , array('', array('hello', 'world'), array('wamp')) | ||||
|           , array('', array(), null) | ||||
|           , array('wamp', array('hello', 'wamp', 'world'), array('herp', 'derp', 'wamp')) | ||||
|           , array('wamp', array('wamp'), array('wamp')) | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @dataProvider protocolProvider | ||||
|      */ | ||||
|     public function testGetSubProtocolString($expected, $supported, $requested) { | ||||
|         $this->comp->protocols = $supported; | ||||
|         $req = (null === $requested ? $requested : new \ArrayIterator($requested)); | ||||
| 
 | ||||
|         $class  = new \ReflectionClass('Ratchet\\WebSocket\\WsServer'); | ||||
|         $method = $class->getMethod('getSubProtocolString'); | ||||
|         $method->setAccessible(true); | ||||
| 
 | ||||
|         $this->assertSame($expected, $method->invokeArgs($this->serv, array($req))); | ||||
|     } | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 samizdam
						samizdam