poco/Net/src/WebSocket.cpp
Aleksandar Fabijanic d75e68c027 new trunk (base for 1.5)
windows build only
2012-04-23 01:14:34 +00:00

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