/lib/WebSocketProtocolV8.php

https://github.com/Erika31/phpdaemon · PHP · 184 lines · 135 code · 27 blank · 22 comment · 14 complexity · 45af923af368a11a625f5cab919f2420 MD5 · raw file

  1. <?php
  2. /**
  3. * Websocket protocol hybi-10
  4. * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
  5. */
  6. class WebSocketProtocolV8 extends WebSocketProtocol
  7. {
  8. // @todo manage only the 4 last bits (opcode), as described in the draft
  9. const STRING = 0x81;
  10. const BINARY = 0x82;
  11. public function __construct($session)
  12. {
  13. parent::__construct($session) ;
  14. $this->description = "Websocket protocol version " . $this->session->server['HTTP_SEC_WEBSOCKET_VERSION'] . " (IETF draft 'hybi-10')" ;
  15. }
  16. public function onHandshake()
  17. {
  18. if (!isset($this->session->server['HTTP_SEC_WEBSOCKET_KEY']) || !isset($this->session->server['HTTP_SEC_WEBSOCKET_VERSION']) || ($this->session->server['HTTP_SEC_WEBSOCKET_VERSION'] != 8))
  19. {
  20. return FALSE ;
  21. }
  22. return TRUE ;
  23. }
  24. /**
  25. * Returns handshaked data for reply
  26. * @param string Received data (no use in this class)
  27. * @return string Handshaked data
  28. */
  29. public function getHandshakeReply($data)
  30. {
  31. if ($this->onHandshake())
  32. {
  33. if (!isset($this->session->server['HTTP_SEC_WEBSOCKET_ORIGIN']))
  34. {
  35. $this->session->server['HTTP_SEC_WEBSOCKET_ORIGIN'] = '' ;
  36. }
  37. $reply = "HTTP/1.1 101 Switching Protocols\r\n"
  38. . "Upgrade: websocket\r\n"
  39. . "Connection: Upgrade\r\n"
  40. . "Sec-WebSocket-Origin: " . $this->session->server['HTTP_SEC_WEBSOCKET_ORIGIN'] . "\r\n"
  41. . "Sec-WebSocket-Location: ws://" . $this->session->server['HTTP_HOST'] . $this->session->server['REQUEST_URI'] . "\r\n"
  42. . "Sec-WebSocket-Accept: " . base64_encode(sha1(trim($this->session->server['HTTP_SEC_WEBSOCKET_KEY']) . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)) . "\r\n" ;
  43. if (isset($this->session->server['HTTP_SEC_WEBSOCKET_PROTOCOL']))
  44. {
  45. $reply .= "Sec-WebSocket-Protocol: " . $this->session->server['HTTP_SEC_WEBSOCKET_PROTOCOL'] . "\r\n" ;
  46. }
  47. $reply .= "\r\n" ;
  48. return $reply ;
  49. }
  50. return FALSE ;
  51. }
  52. /**
  53. * Data encoding, according to related IETF draft
  54. *
  55. * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#page-16
  56. */
  57. protected function _dataEncode($decodedData, $type = NULL)
  58. {
  59. $frames = array() ;
  60. $maskingKeys = chr(rand(0, 255)) . chr(rand(0, 255)) . chr(rand(0, 255)) . chr(rand(0, 255)) ;
  61. $frames[0] = ($type === NULL) ? $this->getFrameType("STRING") : $this->getFrameType($type) ;
  62. $dataLength = strlen($decodedData) ;
  63. if ($dataLength <= 125)
  64. {
  65. $frames[1] = $dataLength + 128 ;
  66. }
  67. elseif ($dataLength <= 65535)
  68. {
  69. $frames[1] = 254 ; // 126 + 128
  70. $frames[2] = $dataLength >> 8 ;
  71. $frames[3] = $dataLength & 0xFF ;
  72. }
  73. else
  74. {
  75. $frames[1] = 255 ; // 127 + 128
  76. $frames[2] = $dataLength >> 56 ;
  77. $frames[3] = $dataLength >> 48 ;
  78. $frames[4] = $dataLength >> 40 ;
  79. $frames[5] = $dataLength >> 32 ;
  80. $frames[6] = $dataLength >> 24 ;
  81. $frames[7] = $dataLength >> 16 ;
  82. $frames[8] = $dataLength >> 8 ;
  83. $frames[9] = $dataLength & 0xFF ;
  84. }
  85. $maskingFunc = function($data, $mask)
  86. {
  87. for ($i = 0, $l = strlen($data); $i < $l; $i++)
  88. {
  89. // Avoid storing a new copy of $data...
  90. $data[$i] = $data[$i] ^ $mask[$i % 4] ;
  91. }
  92. return $data ;
  93. } ;
  94. return implode('', array_map('chr', $frames)) . $maskingKeys . $maskingFunc($decodedData, $maskingKeys) ;
  95. }
  96. /**
  97. * Data decoding, according to related IETF draft
  98. *
  99. * @see http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#page-16
  100. */
  101. protected function _dataDecode($encodedData)
  102. {
  103. $isMasked = (bool) (ord($encodedData[1]) >> 7) ;
  104. $dataLength = ord($encodedData[1]) & 127 ;
  105. if ($isMasked)
  106. {
  107. $unmaskingFunc = function($data, $mask)
  108. {
  109. for ($i = 0, $l = strlen($data); $i < $l; $i++)
  110. {
  111. // Avoid storing a new copy of $data...
  112. $data[$i] = $data[$i] ^ $mask[$i % 4] ;
  113. }
  114. return $data ;
  115. } ;
  116. if ($dataLength === 126)
  117. {
  118. $maskingKey = binarySubstr($encodedData, 4, 4) ;
  119. $extDataLength = hexdec(sprintf('%02x%02x', ord($encodedData[2]), ord($encodedData[3]))) ;
  120. $offsetStart = 8 ;
  121. }
  122. elseif ($dataLength === 127)
  123. {
  124. $maskingKey = binarySubstr($encodedData, 10, 4) ;
  125. $extDataLength = hexdec(sprintf('%02x%02x%02x%02x%02x%02x%02x%02x', ord($encodedData[2]), ord($encodedData[3]), ord($encodedData[4]), ord($encodedData[5]), ord($encodedData[6]), ord($encodedData[7]), ord($encodedData[8]), ord($encodedData[9]))) ;
  126. $offsetStart = 14 ;
  127. }
  128. else
  129. {
  130. $maskingKey = binarySubstr($encodedData, 2, 4) ;
  131. $extDataLength = $dataLength ;
  132. $offsetStart = 6 ;
  133. }
  134. if (strlen($encodedData) < $offsetStart + $extDataLength)
  135. {
  136. Daemon::$process->log(get_class($this) . '::' . __METHOD__ . ' : Incorrect data size in frame decoding for client "' . $this->session->clientAddr . '"') ;
  137. }
  138. return $unmaskingFunc(binarySubstr($encodedData, $offsetStart, $extDataLength), $maskingKey) ;
  139. }
  140. else
  141. {
  142. if ($dataLength === 126)
  143. {
  144. return binarySubstr($encodedData, 4) ;
  145. }
  146. elseif ($dataLength === 127)
  147. {
  148. return binarySubstr($encodedData, 10) ;
  149. }
  150. else
  151. {
  152. return binarySubstr($encodedData, 2) ;
  153. }
  154. }
  155. return NULL ;
  156. }
  157. }