Passes jslint with regexp: true, confusion: true, undef: false, node: true, sloppy...
[KiwiIRC.git] / ws.php
1 <?php
2
3 /*
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.
7 *
8 */
9
10
11 $msg = <<<MSG
12
13
14 ######################################################
15 Lol, Kiwi websocket support is starting.
16
17 Support: support@kiwiirc.com
18 ######################################################
19
20 MSG;
21 echo $msg;
22
23
24 $debug = true;
25
26 // Stores the sockets in pairs, websocket=>ircsocket
27 $pairs = array();
28
29 // Stores all sockets
30 $soks = array();
31
32 // Handshaken sockets
33 $handshakes = array();
34
35
36
37 require(dirname(__FILE__).'/config.php');
38 require(dirname(__FILE__).'/common.php');
39 require(dirname(__FILE__).'/class_session.php');
40
41
42 error_reporting(E_ALL);
43 set_time_limit(0);
44 ob_implicit_flush();
45
46 $server = WebSocket($config['websocket']['bind_addr'], $config['websocket']['bind_port']);
47 $soks[(int)$server] = $server;
48 $last_dump = 0;
49 while(1){
50 echo time()." - $last_dump (".(time() - $last_dump).")\n";
51 if(time() - $last_dump >= 3){
52 $last_dump = time();
53 echo "\nPairs: ";
54 var_dump($pairs);
55
56 echo "\nSoks: ";
57 var_dump($soks);
58 }
59
60 $changed = $soks;
61 stream_select($changed, $write=NULL, $except=NULL, NULL);
62
63 foreach($changed as $socket){
64 // New connection?
65 if($socket == $server){
66 $client = stream_socket_accept($server);
67 if($client<0){
68 console("socket_accept() failed"); continue;
69 } else {
70 connect($client);
71 }
72 } else {
73
74 $buffer = fread($socket, 2048);
75 if($buffer === false || $buffer == ''){
76 // Disconnected
77 disconnect($socket);
78 } else {
79 //$buffer = substr($buffer, 0, strlen($buffer));
80 //console("INCOMING:\n".$buffer."########\n");
81 if(isset($handshakes[(int)$socket])){
82 // websocket upgrade
83 dohandshake($socket, $buffer);
84 } else {
85 // Data transfering..
86 transfer($socket, $buffer);
87 }
88 }
89
90 }
91 }
92 }
93
94
95
96
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";
106 return $master;
107 }
108
109
110
111
112
113 function connect($socket){
114 global $soks, $pairs, $handshakes;
115
116 //$session_id = SESSIONS::create();
117 //if(!$session_id) return false;
118
119 //$pairs[$socket]= SESSION::open($session_id);
120
121 $soks[(int)$socket] = $socket;
122 $handshakes[(int)$socket] = false;
123 //array_push($soks, $pairs[$socket]);
124
125 console($socket." connection..");
126 }
127
128 function disconnect($sok){
129 global $soks, $pairs;
130 console("disconnected?\n");
131
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]);
137
138 @fclose($soks[$local_con]);
139 unset($soks[$local_con]);
140
141 unset($pairs[$websocket]);
142 }
143
144 console($sok." DISCONNECTED!");
145 }
146
147 function transfer($sok, $buffer){
148 global $soks, $pairs;
149 console("Transfering data?\n");
150
151 $pair = findPair($sok);
152 if($pair === false) return false;
153
154 console("Transfering ".strlen($buffer)." bytes.. '".$buffer."'");
155 //$buffer = wrap($buffer);
156 foreach($pair as $websocket => $local_con){
157 if($sok == $soks[$websocket]){
158 // From websocket..
159 fwrite($soks[$local_con], unwrap($buffer));
160 break;
161 } elseif($sok == $soks[$local_con]){
162 // From irc client
163 fwrite($soks[$websocket], chr(0).$buffer.chr(255));
164 break;
165 }
166 }
167 }
168
169 function findPair($socket){
170 global $soks, $pairs;
171 console("Finding pair: ".(int)$socket."\n");
172
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]);
176
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);
181
182 return false;
183 }
184
185 function dohandshake($sok, $buffer){
186 global $handshakes, $soks, $pairs;
187 console("\nRequesting handshake...");
188
189 console("Handshaking...");
190 /*
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" .
197 "\r\n";
198 */
199 if(!strpos($buffer, 'WebSocket-Key1:')){
200 $upgrade = (string)new WebSocket75($buffer);
201 } else {
202 $upgrade = (string)new WebSocket76($buffer);
203 }
204
205 fwrite($sok, $upgrade.chr(0));
206
207 // Done the handshake so remove it from the handshaking array
208 unset($handshakes[(int)$sok]);
209
210 console("Done handshaking...");
211
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);
217
218 $soks[(int)$irc_client_sok] = $irc_client_sok;
219 $pairs[(int)$sok] = (int)$irc_client_sok;
220
221 fwrite($irc_client_sok, json_encode(array('method'=>'read')));
222
223 console($sok." CONNECTED!");
224 return true;
225 }
226
227
228
229
230
231 class WebSocket75 {
232 private $__value__;
233
234 public function __toString() {
235 return $this->__value__;
236 }
237
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" .
245 "\r\n";
246
247 $this->__value__ = $upgrade;
248 }
249
250 private function getheaders($req){
251 $r=$h=$o=null;
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);
256 }
257 }
258
259
260 class WebSocket76 {
261
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
267 * @example
268 * // via function call ...
269 * $handshake = WebSocketHandshake($buffer);
270 * // ... or via class
271 * $handshake = (string)new WebSocketHandshake($buffer);
272 *
273 * socket_write($socket, $handshake, strlen($handshake));
274 */
275
276 private $__value__;
277
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];
287 $this->__value__ =
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" : "").
294 "\r\n".
295 $this->_createHandshakeThingy($key1, $key2, $code)
296 ;
297 }
298
299 public function __toString() {
300 return $this->__value__;
301 }
302
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]) :
306 ''
307 ;
308 }
309
310 private function _createHandshakeThingy($key1, $key2, $code) {
311 return md5(
312 pack('N', $this->_doStuffToObtainAnInt32($key1)).
313 pack('N', $this->_doStuffToObtainAnInt32($key2)).
314 $code,
315 true
316 );
317 }
318 }
319
320
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"; } }