Implementing handshake for hybi-17 - php

I'm trying to develop the handshake for websocket hybi-17 protocol (https://datatracker.ietf.org/doc/html/draft-ietf-hybi-thewebsocketprotocol-17).
According to that draft, I made the following code for the client (user-agent):
var host = 'ws://localhost/server.php';
if ('MozWebSocket' in window) ws = new MozWebSocket (host);
else ws = new WebSocket (host);
and this code for the server (I skipped the socket initialization/management part):
$key = $value = null;
preg_match ("#Sec-WebSocket-Key: (.*?)\r\n#", $buffer, $match) && $key = $match[1];
$key .= "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
$key = sha1 ($key);
$key = pack ('H*', $key);
$key = base64_encode ($key);
$value =
"HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: {$key}";
socket_write ($socket, $value, strlen ($value));
Now, following an example, starting with the client request (simply done with 'new MozWebSocket (host)' call):
GET /server.php HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-it,it;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 8
Sec-WebSocket-Origin: http://localhost
Sec-WebSocket-Extensions: deflate-stream
Sec-WebSocket-Key: oqFCBULD7k+BM41Bc3VEeA==
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
The server response (echoed in the local shell, as a debug line):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: TlKc0Ck7WpqsLhMm/QXABMQWARk=
I followed what specified in the IETF hybi-17 draft but the client request is still pending and there's no real connection between client and server.
What's wrong?
What I have to do more?
Thanks in advance.

A HTTP response is defined as:
Response = Status-Line ; Section 6.1
*(( general-header ; Section 4.5
| response-header ; Section 6.2
| entity-header ) CRLF) ; Section 7.1
CRLF
[ message-body ] ; Section 7.2
The message body is empty, but there should still be two CRLFs after all headers (one CRLF after each header and one final extra one).
So your code should look like:
$value =
"HTTP/1.1 101 Switching Protocols\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: {$key}\r\n\r\n";

Related

Is it possible to get ALL response headers from from Apache using PHP?

I get the request headers from the browser like this.
<?php
$DataFromBrowser='';
$DataToBrowser='';
$headers = getallheaders();
foreach($headers as $key=>$val){
$DataFromBrowser = $DataFromBrowser . $key . ': ' . $val . "\n";
}
//echo get_raw_http_request();
echo "Data from browser";
echo '<textarea rows="12" style="width:100%;">' . $DataFromBrowser . '</textarea>';
?>
And the output will look like this.
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,/;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Cookie: foo=bar
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Now I want to do exactly the same but for the response headers from the server. I'm aware I can do the following but it will create another connection I need the response headers of the current served page.
<?php
$URL = 'https://www.google.com';
$headers = get_headers($URL);
foreach($headers as $value) {
echo $value;
echo "<br>";
}
?>
If I change the above to http://127.0.0.1 It get's it self in a deadlock and will loop until it bombs out.
The response headers should look like this.
HTTP/1.1 200 OK
Date: Wed, 08 Jun 2022 16:53:45 GMT
Server: Apache/2.4.46 (Win64) OpenSSL/1.1.1j PHP/8.0.3
X-Powered-By: PHP/8.0.3
Content-Length: 589
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

Possible encoding problem using PHP-7 Sockets as WebSocket Server

When trying to use PHP7.3 as SocketServer for WebSockets I encounter a problem where a message send from Firefox come out the socket all messed up. And always differently.
However, messages send from PHP to Firefox are fine.
For example:
socket.onopen = function(e)
{
e.target.send(JSON.stringify({"sessionid" : "5e8a2f30a164e", "sockid" : "5e8a5c8cd99e6"}));
}
one time becomes
)'JR9ZT#GN.Fx+/9JL#
hL+J)/
Z
next time it's
ZXC!+&)1,4<a`m&bj%i9rl=av+,91'xzv?9v9;'c=ux
Clientrequest:
GET / HTTP/1.1
Host: 192.168.5.54:8089
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0
Accept: */*
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://192.168.5.54
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: uhfFVS5mhYptk6FF8jl73g==
Connection: keep-alive, Upgrade
Cookie: XDEBUG_SESSION=XDEBUG_ECLIPSE
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Servercode[shortend]:
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)
$ret = socket_bind($this->socket, $this->host, $this->port);
$ret = socket_listen( $this->socket, 0 );
socket_set_nonblock($this->socket);
while ($this->isServer)
{
$connection = #socket_accept($this->socket);
[... forking for new connection cont in child]
}
Child:
socket_close($this->socket);
[...]
$request = socket_read($connection, 5000); //creates above client request
preg_match('#Sec-WebSocket-Key: (.*)\r\n#', $request, $matches);
$key = base64_encode(pack('H*', sha1($matches[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$headers = "HTTP/1.1 101 Switching Protocols\r\n";
$headers .= "Upgrade: websocket\r\n";
$headers .= "Connection: Upgrade\r\n";
$headers .= "Sec-WebSocket-Version: 13\r\n";
//$headers .= "Sec-WebSocket-Protocol: json\r\n";
$headers .= "Sec-WebSocket-Accept: $key\r\n\r\n";
socket_write($connection, $headers, strlen($headers));
require_once("objs/User.php");
$user = new User();
// Send messages into WebSocket in a loop.
socket_set_nonblock($connection);
while (true)
{
if(($msg = socket_read($connection, 5000)) === "")
{
die;
}
if(!empty($msg))
{
echo $msg."\n";
}
I'm at a loss here and would appreciate your help!
It turns out data send from client->server is always "masked" using WebSockets.
The function unmask($payload) from this post helped me solve the problem.

Problem with WSS handshake (PHP WebSocket Server)

I developed a WebSocket server using PHP and it worked fine with ws://, but in production environment it uses https://, then I must use wss://.
Should I use certificate to start the socket or something like that? I can't parse the headers to complete the handshake.
How can I perform handshake behind a https server?
This is a AWS EC2 machine with Amazon Certificate.
I have tried import .pem file to socket initialization, run ws:// behind my https:// environment, and nothing worked :(
Socket initialization:
$socket = stream_socket_server(
"tcp://0.0.0.0:" . env("APP_WSS_PORTA"),
$errno,
$errstr
);
I have tried also:
use Aws\Acm\AcmClient;
$cert = (new AcmClient(include config_path('aws.php')))->GetCertificate([
"CertificateArn" => "arn:aws:acm:sa-east-1:EDITED_TO_STACKOVERFLOW"
])["CertificateChain"];
$cert_path = "cert.pem";
file_put_contents(base_path($cert_path), $cert);
$context = stream_context_create(
["ssl" => ["local_cert"=> $cert_path]]
);
$socket = stream_socket_server(
"tcp://0.0.0.0:" . env("APP_WSS_PORTA"),
$errno,
$errstr,
STREAM_SERVER_BIND|STREAM_SERVER_LISTEN,
$context
);
My handshake function:
function wsHandshake($data)
{
echo "> Handshake " . remoteIp() . PHP_EOL;
$lines = preg_split("/\r\n/", $data);
$headers = array();
foreach ($lines as $line) {
$line = chop($line);
if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
$headers[$matches[1]] = $matches[2];
}
}
var_dump($data); // to debug it :)
if (!isset($headers['Sec-WebSocket-Version']) || $headers['Sec-WebSocket-Version'] < 6) {
echo '> Versao do WebSocket nao suportada' . PHP_EOL;
return false;
}
$sec_accept = base64_encode(pack('H*', sha1($headers['Sec-WebSocket-Key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$response = "HTTP/1.1 101 Switching Protocols\r\n";
$response .= "Upgrade: websocket\r\n";
$response .= "Connection: Upgrade\r\n";
$response .= "Sec-WebSocket-Accept: " . $sec_accept . "\r\n";
$response .= "\r\n";
return $response;
}
var_dump with ws://
string(448) "GET / HTTP/1.1
Host: 127.0.0.1:3131
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: http://localhost
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: PuIYHJZ4x8IyXajFf4WAsw==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
"
var_dump with wss://
string(517) "\000\000��}hh�հ�h����`�ݘ����O��GQ�E� S�8�#��,��=��c���C8�ǯ�G!6{<\000$�+�/̨̩�,�0�
� ��\0003\0009\000/\0005\000
\000�\000\000\000�\000\000\000
\000\000
\000\000\000\000\000\000
\000\000\000#\000\000\000\000\000
hhttp/1.1\000\000\000\000\000\000\0003\000k\000i\000\000 ��"�c��GLGX�Ƶ��:�"ŵ�)բ
E��)\000\000Al�d��#Q{��t��q>��eb���u�+�d��M�!2�-��tI����z�y�\ĉ�\000\\000-\000\000\000#\000\0"...

Parse socket data on a PHP socket_listener

I was able to open a PHP TCP listener socket, but I don't know how to parse the buffer. The client that connects to my socket send a text/html with an additional boundary data with a xml file and an image file.
How can I parse the response to get the XML file on one side and the Image on the other side?
server = socket_create_listen(8086);
socket_getsockname($server, $addr, $port);
if (!$server) {
$message = 'Start TCP socket: Ko. Could not create socket.';
$this->logger->info($message);
die($message);
} else {
$message = 'Start TCP socket: Ok. TCP socket opened on: ' . $addr . ':' . $port . '.';
$this->logger->info($message);
while ($c = socket_accept($server)) {
socket_getpeername($c, $raddr, $rport);
$this->logger->info("Received Connection from $raddr:$rport\n");
$data = '';
while ($bytes = socket_recv($c, $r_data, 1024, MSG_WAITALL)) {
$data .= $r_data;
}
//Edited: Explode with double line and got data
$parsedData = explode("\r\n\r\n", $data);
$xml = new \SimpleXMLElement($parsedData[2]);
print_r($xml);
else {
echo "socket_recv() failed; reason: " . socket_strerror(socket_last_error($c)) . "\n";
}
socket_close($c);
}
}
fclose($server);
This is the output I received:
Received Connection from :59048
Read 3096 bytes from socket_recv(). Closing socket...POST /test HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: zh-CN
Content-Type: multipart/form-data;boundary=-------------------------7e13971310878
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
Accept-Encoding: gzip, deflate
Host: 0.0.0.0:7200
Content-Length: 516032
Connection: Keep-Alive
Cache-Control: no-cache
---------------------------7e13971310878
Content-Disposition: form-data; name="file.xml";filename="file.xml";
Content-Type: text/xml
Content-Length: 2273
<EventNotificationAlert version="2.0" xmlns="http://www.isapi.org/ver20/XMLSchema">
<ipAddress></ipAddress>
<ipv6Address></ipv6Address>
<portNo></portNo>
<!--REST OF XML DATA-->
</EventNotificationAlert>
---------------------------7e13971310878
Content-Disposition: form-data;name="image.jpg";filename="image.jpg";
Content-Type: image/pjpeg
Content-Length: 7164
����JFIF���
!"$"^C
EDITED: I was able to got the XML data by using "explode" function, but I don't know how to get the 2 images as image files. Any suggestion?
Any help would be really appreciated!
Thank you!
I write my final solution here:
1 - Explode the data in a string to an array(using \r\n\r\n):
$parsedData = explode("\r\n\r\n", $data);
2 - XML is actually on position 2 (position 0 and 1 contain POST HTTP Header and boundary start section):
$xml = new \SimpleXMLElement($parsedData[2]);
Image is on position 4 (array position 3 contains headers such as Content-Disposition that must be skipped):
The code to save the data as an image is:
//Generate GD image from raw string.
if (!$source = #imagecreatefromstring($parsedData[4])){
$message = 'Save Image: Ko. Img path: "' . $imgFullPath . '". Error details: ' . error_get_last()['message'];
$this->logger->error($message);
}else {
//Save GD image on disk
imagejpeg($source, $imgFullPath);
//Clean resources.
imagedestroy($source);
$message = 'Save Image: Ok. Image saved successfully on path: "' . $imgFullPath . '"...';
$this->logger->info($message);
$this->io->writeln($message);

ipinfo.io via PHP's file_get_contents() and cURL fail

I try to get a GeoLocation data from http://ipinfo.io,
Here is my way :
$resp = file_get_contents('http://ipinfo.io/json');
$data = json_decode($resp);
It return an error :
Warning: file_get_contents(http://ipinfo.io/json): failed to open stream: Permission denied in ....
But then I access the link (http://ipinfo.io/json) manually in the URL box of my browser, it shows a correct json.
I also try it with cURL :
$curlSession = curl_init();
curl_setopt($curlSession, CURLOPT_URL, "ipinfo.io/json");
curl_setopt($curlSession, CURLOPT_BINARYTRANSFER, true);
curl_setopt($curlSession, CURLOPT_RETURNTRANSFER, true);
$resp = curl_exec($curlSession);
if (FALSE === $resp) {
echo curl_errno($curlSession);
}
curl_close($curlSession);
It echo a number of 7, and i look up in the internet, error 7 means Couldn't connect to the server.
Any idea why ?
Thank you
I run http://ipinfo.io, and we don't block access to any IPs (we do rate limit requests from IPs, but that'd result in a HTTP status code, not a blocked connection). This sounds like a config issue with your server to me. Some hosts lock down file_get_contents so it can't open URLs, or might have blocked http://ipinfo.io. Are few ways to track this down:
1) Can you open another URL with file_get_contents? Eg. what happens when you file_get_contents('http://google.com'). If you get a permission denied error there then you should speak to your hosting provider
2) Does command line curl work for ipinfo.io? The -i -v flags should give you more information about what's going on here. Here's what a successful request looks like:
$ curl -iv ipinfo.io
* Rebuilt URL to: ipinfo.io/
* Trying 54.68.119.255...
* Connected to ipinfo.io (54.68.119.255) port 80 (#0)
> GET / HTTP/1.1
> Host: ipinfo.io
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
Access-Control-Allow-Origin: *
< Content-Type: application/json; charset=utf-8
Content-Type: application/json; charset=utf-8
< Date: Sun, 15 Jan 2017 18:38:44 GMT
Date: Sun, 15 Jan 2017 18:38:44 GMT
< Server: nginx/1.8.1
Server: nginx/1.8.1
< Set-Cookie: first_referrer=; Path=/
Set-Cookie: first_referrer=; Path=/
< X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff
< Content-Length: 252
Content-Length: 252
< Connection: keep-alive
Connection: keep-alive
<
{
"ip": "24.6.61.239",
"hostname": "c-24-6-61-239.hsd1.ca.comcast.net",
"city": "Mountain View",
"region": "California",
"country": "US",
"loc": "37.3845,-122.0881",
"org": "AS7922 Comcast Cable Communications, LLC",
"postal": "94040"
* Connection #0 to host ipinfo.io left intact
}
Quite often a server will be configured to prevent requests where there is no User-Agent string present in the request headers so you can add a context argument to file_get_contents that supplies a User-Agent and any other headers you need.
$args=array(
'http'=>array(
'method' => "GET",
'header' => implode( "\n", array(
'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:44.0) Gecko/20100101 Firefox/44.0',
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Host: ipinfo.io'
)
)
)
);
/* create the context */
$context=stream_context_create( $args );
$resp = file_get_contents( 'http://ipinfo.io/json', FILE_TEXT, $context );
$data = json_decode( $resp );
echo '<pre>',print_r( $data,true ),'</pre>';
i see 2 plausible explanations here.
1: you use a shitty DNS server. try GoogleDNS (8.8.8.8) instead.
curl_setopt($curlSession,CURLOPT_DNS_LOCAL_IP4,'8.8.8.8');
if that fixes it, contact your DNS provider and sort it out with them
2: you're IP banned. try to just create a TCP socket to their ip, see if you can do that.
<?php
$sock=socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
var_dump($sock,socket_connect($sock,gethostbyname('ipinfo.io'),80),socket_close($sock));
if you can't do that, you're probably IP banned

Categories