Possible encoding problem using PHP-7 Sockets as WebSocket Server - php

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.

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

PHP & C++ $_FILES and HttpSendRequestA

Having problems with uploading a file from c++ to a PHP form. Iv seen the posts similar to my issue.
Upload Image File in C++
Uploading file to server using C++ HTTP request
And according to the documentation laid out in each, and in HERE my code by all accounts should be correct.
I also cannot use CURL for my implementation. As much as I would like too.
Iv tried encoding all the data into BASE64 to alleviate any file data problems, but no dice.
Iv double checked my php.ini file to allow uploads. And tested the PHP page with a simple HTML page that allows me to upload a file. All good, the form gives correct results.
Basically the headers I send to the server is:
Host: dzwxgames.com
Connection: close
Content-Length: 418
Origin: http://dzwxgames.com
Content-Type: multipart/form-data; boundary=WrbKitFormBoundaryXtRo8bh7ZpnTrTwd
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36
Pragma: no-cache
--WrbKitFormBoundaryXtRo8bh7ZpnTrTwd
Content-Disposition: form-data; name="file"; filename="02-11-2019 09-44.log"
Content-Type: text/plain;
Content-Transfer-Encoding: BASE64
W0FsdF1bVGFiXVtUYWJdW1RhYl1bQ11bRF1bU3BhY2VdW1VdW1BdW1RhYl1bRW50ZXJdW0xdW1NdW0VudGVyXVtDXVtEXVtTcGFjZV1bLl1bLl1bL11bRW50ZXJdW0xdW1NdW0VudGVyXVtBbHRdW1RhYl1bVGFiXVtUYWJdW1RhYl1bQWx0XVtUYWJdW1RhYl0=
--WrbKitFormBoundaryXtRo8bh7ZpnTrTwd--
Which my PHP page returns:
HTTP/1.1 200 OK
Date: Sun, 03 Nov 2019 09:09:19 GMT
Server: Apache/2.4.29 (Ubuntu)
Content-Length: 35
Connection: close
Content-Type: text/plain; charset=utf-8
NULL
Array
(
)
File does not exist.
The code for the php page is simple:
if (!isset($_FILES['file']['error'])){
var_dump($_FILES['file']);
print_r($_FILES);
throw new RuntimeException('File does not exist.');
}
Finaly my C++ Code is:
bool sendFileToRemoteServer(std::filesystem::path file, const std::string& url,const std::string& iaddr) {
std::string headder = ""; //Headder information
std::string datahead = "";
std::string content = "";
if (!File::fastFileToString(file.string(), content)) {
std::cout << "ERROR_FILE_OPEN" << std::endl;
return false;
}
content = base64_encode(reinterpret_cast<const unsigned char*>(content.c_str()),content.length());
datahead += "--WrbKitFormBoundaryXtRo8bh7ZpnTrTwd\r\n";
datahead += "Content-Disposition: form-data; name=\"file\"; filename=\"" + file.filename().string() +"\"\r\n"; //Content - Disposition : form - data; name = "fileToUpload"; filename = "testMac.cpp"
datahead += "Content-Type: text/plain;\r\n";
datahead += "Content-Transfer-Encoding: BASE64\r\n\n";
datahead += content;
datahead += "\r\n--WrbKitFormBoundaryXtRo8bh7ZpnTrTwd--\r\n";
//datahead = base64_encode(reinterpret_cast<const unsigned char*>(datahead.c_str()), datahead.length());
//Build Headder
headder += "Host: " + iaddr + "\r\n"; //Host: localhost
headder += "Connection: close\r\n"; //Connection : keep - alive
headder += "Content-Length: " + std::to_string(datahead.size()) + "\r\n"; //Content - Length : 800
headder += "Origin: http://dzwxgames.com\r\n";
//headder += "Content-Transfer-Encoding: base64\r\n";
headder += "Content-Type: multipart/form-data; boundary=WrbKitFormBoundaryXtRo8bh7ZpnTrTwd\r\n"; //Content - Type : multipart / form - data; boundary = WrbKitFormBoundaryXtRo8bh7ZpnTrTwd
headder += "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36\r\n"; //User - Agent: 1337
headder += "Pragma: no-cache\r\n\r\n";
std::cout << "SENT HEAD:" << std::endl;
std::cout << headder;
std::cout << datahead;
//Open internet connection
HINTERNET hSession = InternetOpen("WINDOWS", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hSession == NULL){
printLastError(GetLastError());
printf("ERROR_INTERNET_OPEN");
return false;
}
HINTERNET hConnect = InternetConnect(hSession, iaddr.c_str(), INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 1);
if (hConnect == NULL){
printLastError(GetLastError());
printf("ERROR_INTERNET_CONN");
return false;
}
HINTERNET hRequest = HttpOpenRequest(hConnect, (const char*)"POST", _T(url.c_str()), NULL, NULL, NULL, INTERNET_FLAG_RELOAD, 1);
if (hRequest == NULL){
printLastError(GetLastError());
printf("ERROR_INTERNET_REQ");
return false;
}
if (!HttpSendRequestA(hRequest, headder.c_str(), headder.size(), (void*)datahead.c_str(), datahead.size())){
printLastError(GetLastError());
printf("ERROR_INTERNET_SEND");
return false;
}
//Request Done, get responce
DWORD dwSize = 0;
if (!InternetQueryDataAvailable(hRequest, &dwSize, 0, 0)) {
printLastError(GetLastError());
printf("ERROR_InternetQueryDataAvailable");
return false;
}
char* responce = new char[dwSize + 1];
DWORD dwlpsizeread = 0;
if (!InternetReadFile(hRequest, responce, dwSize, &dwlpsizeread)) {
printLastError(GetLastError());
printf("ERROR_InternetReadFile");
return false;
}
printf("\nRESPONCE HEAD : \n");
SampleCodeOne(hRequest);
responce[dwSize] = 0;
std::cout << responce << std::endl;
delete[] responce;
InternetCloseHandle(hSession);
InternetCloseHandle(hConnect);
InternetCloseHandle(hRequest);
return true;
}
What am I missing? I cant seem to find the problem.
After checking with wire-shark, I found a 301 Error header sent back to me, with my force upgrade to HTTPS. After removing the HTTPS for testing, This fixed the error. Now I just have to fix the error code 3 on upload now. EUGH.
For some reason, HttpQueryInfo did not report the error 301. Instead it resent the packet to the new address, but without the data being encrypted, it had no idea what to do with it.
Funny enough, Apache silently discarded the un-encrypted data since it could not be encrypted. Nothing in the logs, no mention. Odd.
Not sure if intended. But ok.

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);

Implementing handshake for hybi-17

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";

Categories