2019-01-03 05:07:54 +01:00
|
|
|
/*
|
|
|
|
* IXWebSocketHandshake.h
|
|
|
|
* Author: Benjamin Sergeant
|
|
|
|
* Copyright (c) 2019 Machine Zone, Inc. All rights reserved.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "IXWebSocketHandshake.h"
|
2019-09-23 19:25:23 +02:00
|
|
|
|
|
|
|
#include "IXHttp.h"
|
2019-01-03 05:07:54 +01:00
|
|
|
#include "IXSocketConnect.h"
|
2020-11-16 17:41:08 +01:00
|
|
|
#include "IXStrCaseCompare.h"
|
2019-03-01 06:54:03 +01:00
|
|
|
#include "IXUrlParser.h"
|
2019-08-30 21:48:18 +02:00
|
|
|
#include "IXUserAgent.h"
|
2020-04-24 21:47:47 +02:00
|
|
|
#include "IXWebSocketHandshakeKeyGen.h"
|
2019-01-03 05:07:54 +01:00
|
|
|
#include <algorithm>
|
2020-02-19 06:38:28 +01:00
|
|
|
#include <iostream>
|
2019-09-23 19:25:23 +02:00
|
|
|
#include <random>
|
|
|
|
#include <sstream>
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
namespace ix
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
WebSocketHandshake::WebSocketHandshake(
|
|
|
|
std::atomic<bool>& requestInitCancellation,
|
2020-03-24 20:40:58 +01:00
|
|
|
std::unique_ptr<Socket>& socket,
|
2020-03-24 02:46:30 +01:00
|
|
|
WebSocketPerMessageDeflatePtr& perMessageDeflate,
|
2019-09-23 19:25:23 +02:00
|
|
|
WebSocketPerMessageDeflateOptions& perMessageDeflateOptions,
|
|
|
|
std::atomic<bool>& enablePerMessageDeflate)
|
|
|
|
: _requestInitCancellation(requestInitCancellation)
|
|
|
|
, _socket(socket)
|
|
|
|
, _perMessageDeflate(perMessageDeflate)
|
|
|
|
, _perMessageDeflateOptions(perMessageDeflateOptions)
|
|
|
|
, _enablePerMessageDeflate(enablePerMessageDeflate)
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:17:47 +01:00
|
|
|
bool WebSocketHandshake::insensitiveStringCompare(const std::string& a, const std::string& b)
|
|
|
|
{
|
2020-11-16 17:41:08 +01:00
|
|
|
return CaseInsensitiveLess::cmp(a, b) == 0;
|
2019-01-26 01:17:47 +01:00
|
|
|
}
|
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
std::string WebSocketHandshake::genRandomString(const int len)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
std::string alphanum = "0123456789"
|
|
|
|
"ABCDEFGH"
|
|
|
|
"abcdefgh";
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
std::random_device r;
|
|
|
|
std::default_random_engine e1(r());
|
|
|
|
std::uniform_int_distribution<int> dist(0, (int) alphanum.size() - 1);
|
|
|
|
|
|
|
|
std::string s;
|
|
|
|
s.resize(len);
|
|
|
|
|
|
|
|
for (int i = 0; i < len; ++i)
|
|
|
|
{
|
|
|
|
int x = dist(e1);
|
|
|
|
s[i] = alphanum[x];
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
WebSocketInitResult WebSocketHandshake::sendErrorResponse(int code, const std::string& reason)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "HTTP/1.1 ";
|
|
|
|
ss << code;
|
2019-03-22 17:52:19 +01:00
|
|
|
ss << " ";
|
2019-01-03 05:07:54 +01:00
|
|
|
ss << reason;
|
|
|
|
ss << "\r\n";
|
2019-09-26 18:45:59 +02:00
|
|
|
ss << "Server: " << userAgent() << "\r\n";
|
2019-01-03 05:07:54 +01:00
|
|
|
|
2019-01-03 21:53:44 +01:00
|
|
|
// Socket write can only be cancelled through a timeout here, not manually.
|
|
|
|
static std::atomic<bool> requestInitCancellation(false);
|
|
|
|
auto isCancellationRequested =
|
|
|
|
makeCancellationRequestWithTimeout(1, requestInitCancellation);
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
|
|
{
|
|
|
|
return WebSocketInitResult(false, 500, "Timed out while sending error response");
|
|
|
|
}
|
|
|
|
|
|
|
|
return WebSocketInitResult(false, code, reason);
|
|
|
|
}
|
|
|
|
|
2019-09-23 19:25:23 +02:00
|
|
|
WebSocketInitResult WebSocketHandshake::clientHandshake(
|
|
|
|
const std::string& url,
|
|
|
|
const WebSocketHttpHeaders& extraHeaders,
|
|
|
|
const std::string& host,
|
|
|
|
const std::string& path,
|
|
|
|
int port,
|
|
|
|
int timeoutSecs)
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
_requestInitCancellation = false;
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
auto isCancellationRequested =
|
2019-01-04 03:33:08 +01:00
|
|
|
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
std::string errMsg;
|
|
|
|
bool success = _socket->connect(host, port, errMsg, isCancellationRequested);
|
|
|
|
if (!success)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "Unable to connect to " << host << " on port " << port << ", error: " << errMsg;
|
2019-01-03 05:07:54 +01:00
|
|
|
return WebSocketInitResult(false, 0, ss.str());
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// Generate a random 24 bytes string which looks like it is base64 encoded
|
|
|
|
// y3JJHMbDL1EzLkh9GBhXDw==
|
|
|
|
// 0cb3Vd9HkbpVVumoS3Noka==
|
|
|
|
//
|
|
|
|
// See https://stackoverflow.com/questions/18265128/what-is-sec-websocket-key-for
|
|
|
|
//
|
|
|
|
std::string secWebSocketKey = genRandomString(22);
|
|
|
|
secWebSocketKey += "==";
|
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "GET " << path << " HTTP/1.1\r\n";
|
2019-09-23 19:25:23 +02:00
|
|
|
ss << "Host: " << host << ":" << port << "\r\n";
|
2019-01-03 05:07:54 +01:00
|
|
|
ss << "Upgrade: websocket\r\n";
|
|
|
|
ss << "Connection: Upgrade\r\n";
|
|
|
|
ss << "Sec-WebSocket-Version: 13\r\n";
|
|
|
|
ss << "Sec-WebSocket-Key: " << secWebSocketKey << "\r\n";
|
|
|
|
|
2019-08-30 21:48:18 +02:00
|
|
|
// User-Agent can be customized by users
|
|
|
|
if (extraHeaders.find("User-Agent") == extraHeaders.end())
|
|
|
|
{
|
|
|
|
ss << "User-Agent: " << userAgent() << "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (auto& it : extraHeaders)
|
|
|
|
{
|
2020-04-25 20:39:37 +02:00
|
|
|
ss << it.first << ": " << it.second << "\r\n";
|
2019-08-26 18:37:40 +02:00
|
|
|
}
|
2019-08-30 21:48:18 +02:00
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
if (_enablePerMessageDeflate)
|
|
|
|
{
|
|
|
|
ss << _perMessageDeflateOptions.generateHeader();
|
|
|
|
}
|
|
|
|
|
|
|
|
ss << "\r\n";
|
|
|
|
|
|
|
|
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return WebSocketInitResult(
|
|
|
|
false, 0, std::string("Failed sending GET request to ") + url);
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read HTTP status line
|
|
|
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
|
|
auto lineValid = lineResult.first;
|
|
|
|
auto line = lineResult.second;
|
|
|
|
|
|
|
|
if (!lineValid)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return WebSocketInitResult(
|
|
|
|
false, 0, std::string("Failed reading HTTP status line from ") + url);
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate status
|
2019-09-10 06:23:57 +02:00
|
|
|
auto statusLine = Http::parseStatusLine(line);
|
|
|
|
std::string httpVersion = statusLine.first;
|
|
|
|
int status = statusLine.second;
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
// HTTP/1.0 is too old.
|
2019-09-10 06:23:57 +02:00
|
|
|
if (httpVersion != "HTTP/1.1")
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
std::stringstream ss;
|
2019-09-10 06:23:57 +02:00
|
|
|
ss << "Expecting HTTP/1.1, got " << httpVersion << ". "
|
2020-08-15 03:13:34 +02:00
|
|
|
<< "Rejecting connection to " << url << ", status: " << status
|
2019-01-03 05:07:54 +01:00
|
|
|
<< ", HTTP Status line: " << line;
|
|
|
|
return WebSocketInitResult(false, status, ss.str());
|
|
|
|
}
|
|
|
|
|
2019-03-01 06:54:03 +01:00
|
|
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
2019-01-03 05:07:54 +01:00
|
|
|
auto headersValid = result.first;
|
|
|
|
auto headers = result.second;
|
|
|
|
|
|
|
|
if (!headersValid)
|
|
|
|
{
|
|
|
|
return WebSocketInitResult(false, status, "Error parsing HTTP headers");
|
|
|
|
}
|
|
|
|
|
2020-08-15 03:13:34 +02:00
|
|
|
// We want an 101 HTTP status for websocket, otherwise it could be
|
|
|
|
// a redirection (like 301)
|
|
|
|
if (status != 101)
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Expecting status 101 (Switching Protocol), got " << status
|
|
|
|
<< " status connecting to " << url << ", HTTP Status line: " << line;
|
|
|
|
|
|
|
|
return WebSocketInitResult(false, status, ss.str(), headers, path);
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:17:47 +01:00
|
|
|
// Check the presence of the connection field
|
|
|
|
if (headers.find("connection") == headers.end())
|
2019-01-15 18:31:37 +01:00
|
|
|
{
|
2019-01-26 01:17:47 +01:00
|
|
|
std::string errorMsg("Missing connection value");
|
2019-01-15 18:31:37 +01:00
|
|
|
return WebSocketInitResult(false, status, errorMsg);
|
|
|
|
}
|
|
|
|
|
2019-01-26 01:17:47 +01:00
|
|
|
// Check the value of the connection field
|
2019-02-21 03:59:07 +01:00
|
|
|
// Some websocket servers (Go/Gorilla?) send lowercase values for the
|
2019-01-26 01:17:47 +01:00
|
|
|
// connection header, so do a case insensitive comparison
|
|
|
|
if (!insensitiveStringCompare(headers["connection"], "Upgrade"))
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "Invalid connection value: " << headers["connection"];
|
|
|
|
return WebSocketInitResult(false, status, ss.str());
|
|
|
|
}
|
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
char output[29] = {};
|
2019-05-17 06:58:04 +02:00
|
|
|
WebSocketHandshakeKeyGen::generate(secWebSocketKey, output);
|
2019-01-03 05:07:54 +01:00
|
|
|
if (std::string(output) != headers["sec-websocket-accept"])
|
|
|
|
{
|
|
|
|
std::string errorMsg("Invalid Sec-WebSocket-Accept value");
|
|
|
|
return WebSocketInitResult(false, status, errorMsg);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_enablePerMessageDeflate)
|
|
|
|
{
|
|
|
|
// Parse the server response. Does it support deflate ?
|
|
|
|
std::string header = headers["sec-websocket-extensions"];
|
|
|
|
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
|
|
|
|
|
|
|
// If the server does not support that extension, disable it.
|
|
|
|
if (!webSocketPerMessageDeflateOptions.enabled())
|
|
|
|
{
|
|
|
|
_enablePerMessageDeflate = false;
|
|
|
|
}
|
|
|
|
// Otherwise try to initialize the deflate engine (zlib)
|
2020-03-24 02:46:30 +01:00
|
|
|
else if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
return WebSocketInitResult(
|
2019-09-02 01:23:00 +02:00
|
|
|
false, 0, "Failed to initialize per message deflate engine");
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-04 02:44:10 +01:00
|
|
|
return WebSocketInitResult(true, status, "", headers, path);
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
|
2019-10-01 06:48:55 +02:00
|
|
|
WebSocketInitResult WebSocketHandshake::serverHandshake(int timeoutSecs)
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
_requestInitCancellation = false;
|
|
|
|
|
2019-02-21 03:59:07 +01:00
|
|
|
auto isCancellationRequested =
|
2019-01-04 03:33:08 +01:00
|
|
|
makeCancellationRequestWithTimeout(timeoutSecs, _requestInitCancellation);
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
// Read first line
|
|
|
|
auto lineResult = _socket->readLine(isCancellationRequested);
|
|
|
|
auto lineValid = lineResult.first;
|
|
|
|
auto line = lineResult.second;
|
|
|
|
|
|
|
|
if (!lineValid)
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Error reading HTTP request line");
|
|
|
|
}
|
2019-02-21 03:59:07 +01:00
|
|
|
|
2019-01-03 22:41:06 +01:00
|
|
|
// Validate request line (GET /foo HTTP/1.1\r\n)
|
2019-06-23 23:54:21 +02:00
|
|
|
auto requestLine = Http::parseRequestLine(line);
|
2019-09-23 19:25:23 +02:00
|
|
|
auto method = std::get<0>(requestLine);
|
|
|
|
auto uri = std::get<1>(requestLine);
|
2019-01-03 22:41:06 +01:00
|
|
|
auto httpVersion = std::get<2>(requestLine);
|
2019-01-03 05:07:54 +01:00
|
|
|
|
2019-01-03 22:41:06 +01:00
|
|
|
if (method != "GET")
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Invalid HTTP method, need GET, got " + method);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (httpVersion != "HTTP/1.1")
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return sendErrorResponse(400,
|
|
|
|
"Invalid HTTP version, need HTTP/1.1, got: " + httpVersion);
|
2019-01-03 22:41:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve and validate HTTP headers
|
2019-03-01 06:54:03 +01:00
|
|
|
auto result = parseHttpHeaders(_socket, isCancellationRequested);
|
2019-01-03 05:07:54 +01:00
|
|
|
auto headersValid = result.first;
|
|
|
|
auto headers = result.second;
|
|
|
|
|
|
|
|
if (!headersValid)
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Error parsing HTTP headers");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (headers.find("sec-websocket-key") == headers.end())
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Missing Sec-WebSocket-Key value");
|
|
|
|
}
|
|
|
|
|
2019-09-10 06:23:57 +02:00
|
|
|
if (headers.find("upgrade") == headers.end())
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Missing Upgrade header");
|
|
|
|
}
|
|
|
|
|
2019-09-05 03:23:56 +02:00
|
|
|
if (!insensitiveStringCompare(headers["upgrade"], "WebSocket"))
|
2019-01-03 22:41:06 +01:00
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return sendErrorResponse(400,
|
|
|
|
"Invalid Upgrade header, "
|
|
|
|
"need WebSocket, got " +
|
|
|
|
headers["upgrade"]);
|
2019-01-03 22:41:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (headers.find("sec-websocket-version") == headers.end())
|
|
|
|
{
|
|
|
|
return sendErrorResponse(400, "Missing Sec-WebSocket-Version value");
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << headers["sec-websocket-version"];
|
|
|
|
int version;
|
|
|
|
ss >> version;
|
|
|
|
|
|
|
|
if (version != 13)
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return sendErrorResponse(400,
|
|
|
|
"Invalid Sec-WebSocket-Version, "
|
|
|
|
"need 13, got " +
|
|
|
|
ss.str());
|
2019-01-03 22:41:06 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-03 05:07:54 +01:00
|
|
|
char output[29] = {};
|
2019-05-17 06:58:04 +02:00
|
|
|
WebSocketHandshakeKeyGen::generate(headers["sec-websocket-key"], output);
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
std::stringstream ss;
|
2019-03-21 21:43:47 +01:00
|
|
|
ss << "HTTP/1.1 101 Switching Protocols\r\n";
|
2019-01-03 05:07:54 +01:00
|
|
|
ss << "Sec-WebSocket-Accept: " << std::string(output) << "\r\n";
|
2019-01-15 08:35:37 +01:00
|
|
|
ss << "Upgrade: websocket\r\n";
|
2019-01-15 18:31:37 +01:00
|
|
|
ss << "Connection: Upgrade\r\n";
|
2019-09-05 03:23:56 +02:00
|
|
|
ss << "Server: " << userAgent() << "\r\n";
|
2019-01-03 05:07:54 +01:00
|
|
|
|
|
|
|
// Parse the client headers. Does it support deflate ?
|
|
|
|
std::string header = headers["sec-websocket-extensions"];
|
|
|
|
WebSocketPerMessageDeflateOptions webSocketPerMessageDeflateOptions(header);
|
|
|
|
|
2020-02-19 06:38:28 +01:00
|
|
|
// If the client has requested that extension,
|
2020-03-03 01:53:08 +01:00
|
|
|
if (webSocketPerMessageDeflateOptions.enabled())
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
_enablePerMessageDeflate = true;
|
|
|
|
|
2020-03-24 02:46:30 +01:00
|
|
|
if (!_perMessageDeflate->init(webSocketPerMessageDeflateOptions))
|
2019-01-03 05:07:54 +01:00
|
|
|
{
|
|
|
|
return WebSocketInitResult(
|
2019-09-23 19:25:23 +02:00
|
|
|
false, 0, "Failed to initialize per message deflate engine");
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
ss << webSocketPerMessageDeflateOptions.generateHeader();
|
|
|
|
}
|
|
|
|
|
|
|
|
ss << "\r\n";
|
|
|
|
|
|
|
|
if (!_socket->writeBytes(ss.str(), isCancellationRequested))
|
|
|
|
{
|
2019-09-23 19:25:23 +02:00
|
|
|
return WebSocketInitResult(
|
2019-10-01 06:48:55 +02:00
|
|
|
false, 0, std::string("Failed sending response to remote end"));
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
|
|
|
|
2019-01-04 02:44:10 +01:00
|
|
|
return WebSocketInitResult(true, 200, "", headers, uri);
|
2019-01-03 05:07:54 +01:00
|
|
|
}
|
2019-09-23 19:25:23 +02:00
|
|
|
} // namespace ix
|