4 * This script takes a WebSocket connection from a web client, creates a
5 * new session and simply proxies the data between the 2 connections. No
6 * data processing is done here at all.
14 ######################################################
15 Lol, Kiwi websocket support is starting.
17 Support: support@kiwiirc.com
18 ######################################################
26 // Stores the sockets in pairs, websocket=>ircsocket
33 $handshakes = array();
37 require(dirname(__FILE__
).'/config.php');
38 require(dirname(__FILE__
).'/common.php');
39 require(dirname(__FILE__
).'/class_session.php');
42 error_reporting(E_ALL
);
46 $server = WebSocket($config['websocket']['bind_addr'], $config['websocket']['bind_port']);
47 $soks[(int)$server] = $server;
50 echo time()." - $last_dump (".(time() - $last_dump).")\n";
51 if(time() - $last_dump >= 3){
61 stream_select($changed, $write=NULL, $except=NULL, NULL);
63 foreach($changed as $socket){
65 if($socket == $server){
66 $client = stream_socket_accept($server);
68 console("socket_accept() failed"); continue;
74 $buffer = fread($socket, 2048);
75 if($buffer === false ||
$buffer == ''){
79 //$buffer = substr($buffer, 0, strlen($buffer));
80 //console("INCOMING:\n".$buffer."########\n");
81 if(isset($handshakes[(int)$socket])){
83 dohandshake($socket, $buffer);
86 transfer($socket, $buffer);
97 function WebSocket($address,$port){
98 //$master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");
99 $master = stream_socket_server("tcp://$address:$port") or die("socket_create() failed");
100 //socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");
101 //socket_bind($master, $address, $port) or die("socket_bind() failed");
102 //socket_listen($master,20) or die("socket_listen() failed");
103 echo "Server Started : ".date('Y-m-d H:i:s')."\n";
104 echo "Master socket : ".$master."\n";
105 echo "Listening on : ".$address." port ".$port."\n\n";
113 function connect($socket){
114 global $soks, $pairs, $handshakes;
116 //$session_id = SESSIONS::create();
117 //if(!$session_id) return false;
119 //$pairs[$socket]= SESSION::open($session_id);
121 $soks[(int)$socket] = $socket;
122 $handshakes[(int)$socket] = false;
123 //array_push($soks, $pairs[$socket]);
125 console($socket." connection..");
128 function disconnect($sok){
129 global $soks, $pairs;
130 console("disconnected?\n");
132 $pair = findPair($sok);
133 if($pair === false) return false;
134 foreach($pair as $websocket => $local_con){
135 @fclose
($soks[$websocket]);
136 unset($soks[$websocket]);
138 @fclose
($soks[$local_con]);
139 unset($soks[$local_con]);
141 unset($pairs[$websocket]);
144 console($sok." DISCONNECTED!");
147 function transfer($sok, $buffer){
148 global $soks, $pairs;
149 console("Transfering data?\n");
151 $pair = findPair($sok);
152 if($pair === false) return false;
154 console("Transfering ".strlen($buffer)." bytes.. '".$buffer."'");
155 //$buffer = wrap($buffer);
156 foreach($pair as $websocket => $local_con){
157 if($sok == $soks[$websocket]){
159 fwrite($soks[$local_con], unwrap($buffer));
161 } elseif($sok == $soks[$local_con]){
163 fwrite($soks[$websocket], chr(0).$buffer.chr(255));
169 function findPair($socket){
170 global $soks, $pairs;
171 console("Finding pair: ".(int)$socket."\n");
173 // If it's a websocket, then this will find it..
174 if(isset($pairs[(int)$socket]))
175 return array((int)$socket=>$pairs[(int)$socket]);
177 // If it's an irc client socket, then we will find it when flipped..
178 $flipped = array_flip($pairs);
179 if(isset($flipped[(int)$socket]))
180 return array($flipped[(int)$socket] => (int)$socket);
185 function dohandshake($sok, $buffer){
186 global $handshakes, $soks, $pairs;
187 console("\nRequesting handshake...");
189 console("Handshaking...");
191 list($resource, $host, $origin) = getheaders($buffer);
192 $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
193 "Upgrade: WebSocket\r\n" .
194 "Connection: Upgrade\r\n" .
195 "WebSocket-Origin: " . $origin . "\r\n" .
196 "WebSocket-Location: ws://" . $host . $resource . "\r\n" .
199 if(!strpos($buffer, 'WebSocket-Key1:')){
200 $upgrade = (string)new WebSocket75($buffer);
202 $upgrade = (string)new WebSocket76($buffer);
205 fwrite($sok, $upgrade.chr(0));
207 // Done the handshake so remove it from the handshaking array
208 unset($handshakes[(int)$sok]);
210 console("Done handshaking...");
212 //socket_getsockname($sok, $sok_name);
213 $sok_name = stream_socket_get_name($sok, true);
214 $session_id = SESSIONS
::create($sok_name);
215 if(!$session_id) return false;
216 $irc_client_sok = SESSIONS
::open($session_id);
218 $soks[(int)$irc_client_sok] = $irc_client_sok;
219 $pairs[(int)$sok] = (int)$irc_client_sok;
221 fwrite($irc_client_sok, json_encode(array('method'=>'read')));
223 console($sok." CONNECTED!");
234 public function __toString() {
235 return $this->__value__
;
238 public function __construct($buffer){
239 list($resource, $host, $origin) = $this->getheaders($buffer);
240 $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
241 "Upgrade: WebSocket\r\n" .
242 "Connection: Upgrade\r\n" .
243 "WebSocket-Origin: " . $origin . "\r\n" .
244 "WebSocket-Location: ws://" . $host . $resource . "\r\n" .
247 $this->__value__
= $upgrade;
250 private function getheaders($req){
252 if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; }
253 if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; }
254 if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; }
255 return array($r,$h,$o);
262 /*! Easy way to handshake a WebSocket via draft-ietf-hybi-thewebsocketprotocol-00
263 * @link http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt
264 * @author Andrea Giammarchi
265 * @blog webreflection.blogspot.com
266 * @date 4th June 2010
268 * // via function call ...
269 * $handshake = WebSocketHandshake($buffer);
270 * // ... or via class
271 * $handshake = (string)new WebSocketHandshake($buffer);
273 * socket_write($socket, $handshake, strlen($handshake));
278 public function __construct($buffer) {
279 $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null;
280 preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];
281 preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];
282 preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1];
283 preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1];
284 preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1];
285 preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];
286 preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1];
288 "HTTP/1.1 101 WebSocket Protocol Handshake\r\n".
289 "Upgrade: WebSocket\r\n".
290 "Connection: Upgrade\r\n".
291 "Sec-WebSocket-Origin: {$origin}\r\n".
292 "Sec-WebSocket-Location: ws://{$host}{$resource}\r\n".
293 ($protocol ?
"Sec-WebSocket-Protocol: {$protocol}\r\n" : "").
295 $this->_createHandshakeThingy($key1, $key2, $code)
299 public function __toString() {
300 return $this->__value__
;
303 private function _doStuffToObtainAnInt32($key) {
304 return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ?
305 implode('', $number[0]) / count($space[0]) :
310 private function _createHandshakeThingy($key1, $key2, $code) {
312 pack('N', $this->_doStuffToObtainAnInt32($key1)).
313 pack('N', $this->_doStuffToObtainAnInt32($key2)).
321 function say($msg=""){ echo $msg."\n"; }
322 function wrap($msg=""){ return chr(0).$msg.chr(255); }
323 function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); }
324 function console($msg=""){ global $debug; if($debug){ echo time().' '.trim($msg)."\n"; } }