mirror of
https://github.com/pocoproject/poco.git
synced 2025-01-08 02:39:22 +01:00
d75e68c027
windows build only
245 lines
8.0 KiB
C++
245 lines
8.0 KiB
C++
//
|
|
// WebSocket.cpp
|
|
//
|
|
// $Id: //poco/1.4/Net/src/WebSocket.cpp#4 $
|
|
//
|
|
// Library: Net
|
|
// Package: WebSocket
|
|
// Module: WebSocket
|
|
//
|
|
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
|
|
// and Contributors.
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person or organization
|
|
// obtaining a copy of the software and accompanying documentation covered by
|
|
// this license (the "Software") to use, reproduce, display, distribute,
|
|
// execute, and transmit the Software, and to prepare derivative works of the
|
|
// Software, and to permit third-parties to whom the Software is furnished to
|
|
// do so, all subject to the following:
|
|
//
|
|
// The copyright notices in the Software and this entire statement, including
|
|
// the above license grant, this restriction and the following disclaimer,
|
|
// must be included in all copies of the Software, in whole or in part, and
|
|
// all derivative works of the Software, unless such copies or derivative
|
|
// works are solely in the form of machine-executable object code generated by
|
|
// a source language processor.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
|
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
|
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
|
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
// DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
|
|
#include "Poco/Net/WebSocket.h"
|
|
#include "Poco/Net/WebSocketImpl.h"
|
|
#include "Poco/Net/HTTPServerRequestImpl.h"
|
|
#include "Poco/Net/HTTPServerResponse.h"
|
|
#include "Poco/Net/HTTPClientSession.h"
|
|
#include "Poco/Net/NetException.h"
|
|
#include "Poco/Buffer.h"
|
|
#include "Poco/MemoryStream.h"
|
|
#include "Poco/NullStream.h"
|
|
#include "Poco/BinaryWriter.h"
|
|
#include "Poco/SHA1Engine.h"
|
|
#include "Poco/Base64Encoder.h"
|
|
#include "Poco/String.h"
|
|
#include "Poco/Random.h"
|
|
#include "Poco/StreamCopier.h"
|
|
#include <sstream>
|
|
|
|
|
|
namespace Poco {
|
|
namespace Net {
|
|
|
|
|
|
const std::string WebSocket::WEBSOCKET_GUID("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
|
const std::string WebSocket::WEBSOCKET_VERSION("13");
|
|
HTTPCredentials WebSocket::_defaultCreds;
|
|
|
|
|
|
WebSocket::WebSocket(HTTPServerRequest& request, HTTPServerResponse& response):
|
|
StreamSocket(accept(request, response))
|
|
{
|
|
}
|
|
|
|
|
|
WebSocket::WebSocket(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response):
|
|
StreamSocket(connect(cs, request, response, _defaultCreds))
|
|
{
|
|
}
|
|
|
|
|
|
WebSocket::WebSocket(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response, HTTPCredentials& credentials):
|
|
StreamSocket(connect(cs, request, response, credentials))
|
|
{
|
|
}
|
|
|
|
|
|
WebSocket::~WebSocket()
|
|
{
|
|
}
|
|
|
|
|
|
WebSocket& WebSocket::operator = (const Socket& socket)
|
|
{
|
|
if (dynamic_cast<WebSocketImpl*>(socket.impl()))
|
|
Socket::operator = (socket);
|
|
else
|
|
throw InvalidArgumentException("Cannot assign incompatible socket");
|
|
return *this;
|
|
}
|
|
|
|
|
|
void WebSocket::shutdown()
|
|
{
|
|
shutdown(WS_NORMAL_CLOSE);
|
|
}
|
|
|
|
|
|
void WebSocket::shutdown(Poco::UInt16 statusCode, const std::string& statusMessage)
|
|
{
|
|
Poco::Buffer<char> buffer(statusMessage.size() + 2);
|
|
Poco::MemoryOutputStream ostr(buffer.begin(), buffer.size());
|
|
Poco::BinaryWriter writer(ostr, Poco::BinaryWriter::NETWORK_BYTE_ORDER);
|
|
writer << statusCode;
|
|
writer.writeRaw(statusMessage);
|
|
sendFrame(buffer.begin(), static_cast<int>(ostr.charsWritten()), FRAME_FLAG_FIN | FRAME_OP_CLOSE);
|
|
}
|
|
|
|
|
|
int WebSocket::sendFrame(const void* buffer, int length, int flags)
|
|
{
|
|
return static_cast<WebSocketImpl*>(impl())->sendBytes(buffer, length, flags);
|
|
}
|
|
|
|
|
|
int WebSocket::receiveFrame(void* buffer, int length, int& flags)
|
|
{
|
|
int n = static_cast<WebSocketImpl*>(impl())->receiveBytes(buffer, length, 0);
|
|
flags = static_cast<WebSocketImpl*>(impl())->frameFlags();
|
|
return n;
|
|
}
|
|
|
|
|
|
WebSocket::Mode WebSocket::mode() const
|
|
{
|
|
return static_cast<WebSocketImpl*>(impl())->mustMaskPayload() ? WS_CLIENT : WS_SERVER;
|
|
}
|
|
|
|
|
|
WebSocketImpl* WebSocket::accept(HTTPServerRequest& request, HTTPServerResponse& response)
|
|
{
|
|
if (icompare(request.get("Connection", ""), "Upgrade") == 0 &&
|
|
icompare(request.get("Upgrade", ""), "websocket") == 0)
|
|
{
|
|
std::string version = request.get("Sec-WebSocket-Version", "");
|
|
if (version.empty()) throw WebSocketException("Missing Sec-WebSocket-Version in handshake request", WS_ERR_HANDSHAKE_NO_VERSION);
|
|
if (version != WEBSOCKET_VERSION) throw WebSocketException("Unsupported WebSocket version requested", version, WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION);
|
|
std::string key = request.get("Sec-WebSocket-Key", "");
|
|
Poco::trimInPlace(key);
|
|
if (key.empty()) throw WebSocketException("Missing Sec-WebSocket-Key in handshake request", WS_ERR_HANDSHAKE_NO_KEY);
|
|
|
|
response.setStatusAndReason(HTTPResponse::HTTP_SWITCHING_PROTOCOLS);
|
|
response.set("Upgrade", "websocket");
|
|
response.set("Connection", "Upgrade");
|
|
response.set("Sec-WebSocket-Accept", computeAccept(key));
|
|
response.setContentLength(0);
|
|
response.send().flush();
|
|
return new WebSocketImpl(static_cast<StreamSocketImpl*>(static_cast<HTTPServerRequestImpl&>(request).detachSocket().impl()), false);
|
|
}
|
|
else throw WebSocketException("No WebSocket handshake", WS_ERR_NO_HANDSHAKE);
|
|
}
|
|
|
|
|
|
WebSocketImpl* WebSocket::connect(HTTPClientSession& cs, HTTPRequest& request, HTTPResponse& response, HTTPCredentials& credentials)
|
|
{
|
|
if (!cs.getProxyHost().empty() && !cs.secure())
|
|
{
|
|
cs.proxyTunnel();
|
|
}
|
|
std::string key = createKey();
|
|
request.set("Connection", "Upgrade");
|
|
request.set("Upgrade", "websocket");
|
|
request.set("Sec-WebSocket-Version", WEBSOCKET_VERSION);
|
|
request.set("Sec-WebSocket-Key", key);
|
|
request.setChunkedTransferEncoding(false);
|
|
cs.setKeepAlive(true);
|
|
cs.sendRequest(request);
|
|
std::istream& istr = cs.receiveResponse(response);
|
|
if (response.getStatus() == HTTPResponse::HTTP_SWITCHING_PROTOCOLS)
|
|
{
|
|
return completeHandshake(cs, response, key);
|
|
}
|
|
else if (response.getStatus() == HTTPResponse::HTTP_UNAUTHORIZED)
|
|
{
|
|
Poco::NullOutputStream null;
|
|
Poco::StreamCopier::copyStream(istr, null);
|
|
credentials.authenticate(request, response);
|
|
if (!cs.getProxyHost().empty() && !cs.secure())
|
|
{
|
|
cs.reset();
|
|
cs.proxyTunnel();
|
|
}
|
|
cs.sendRequest(request);
|
|
cs.receiveResponse(response);
|
|
if (response.getStatus() == HTTPResponse::HTTP_SWITCHING_PROTOCOLS)
|
|
{
|
|
return completeHandshake(cs, response, key);
|
|
}
|
|
else if (response.getStatus() == HTTPResponse::HTTP_UNAUTHORIZED)
|
|
{
|
|
throw WebSocketException("Not authorized", WS_ERR_UNAUTHORIZED);
|
|
}
|
|
}
|
|
throw WebSocketException("Cannot upgrade to WebSocket connection", response.getReason(), WS_ERR_NO_HANDSHAKE);
|
|
}
|
|
|
|
|
|
WebSocketImpl* WebSocket::completeHandshake(HTTPClientSession& cs, HTTPResponse& response, const std::string& key)
|
|
{
|
|
std::string connection = response.get("Connection", "");
|
|
if (Poco::icompare(connection, "Upgrade") != 0)
|
|
throw WebSocketException("No Connection: Upgrade header in handshake response", WS_ERR_NO_HANDSHAKE);
|
|
std::string upgrade = response.get("Upgrade", "");
|
|
if (Poco::icompare(upgrade, "websocket") != 0)
|
|
throw WebSocketException("No Upgrade: websocket header in handshake response", WS_ERR_NO_HANDSHAKE);
|
|
std::string accept = response.get("Sec-WebSocket-Accept", "");
|
|
if (accept != computeAccept(key))
|
|
throw WebSocketException("Invalid or missing Sec-WebSocket-Accept header in handshake response", WS_ERR_NO_HANDSHAKE);
|
|
return new WebSocketImpl(static_cast<StreamSocketImpl*>(cs.socket().impl()), true);
|
|
}
|
|
|
|
|
|
std::string WebSocket::createKey()
|
|
{
|
|
Poco::Random rnd;
|
|
std::ostringstream ostr;
|
|
Poco::Base64Encoder base64(ostr);
|
|
Poco::BinaryWriter writer(base64);
|
|
writer << rnd.next() << rnd.next() << rnd.next() << rnd.next();
|
|
base64.close();
|
|
return ostr.str();
|
|
}
|
|
|
|
|
|
std::string WebSocket::computeAccept(const std::string& key)
|
|
{
|
|
std::string accept(key);
|
|
accept += WEBSOCKET_GUID;
|
|
Poco::SHA1Engine sha1;
|
|
sha1.update(accept);
|
|
Poco::DigestEngine::Digest d = sha1.digest();
|
|
std::ostringstream ostr;
|
|
Poco::Base64Encoder base64(ostr);
|
|
base64.write(reinterpret_cast<const char*>(&d[0]), d.size());
|
|
base64.close();
|
|
return ostr.str();
|
|
}
|
|
|
|
|
|
} } // namespace Poco::Net
|