|  | #ifndef THIRD_PARTY_GBMCWEB_HTTP_WEBSOCKET_H_ | 
|  | #define THIRD_PARTY_GBMCWEB_HTTP_WEBSOCKET_H_ | 
|  |  | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <functional> | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <string_view> | 
|  | #include <utility> | 
|  |  | 
|  | #include "boost/asio/buffer.hpp"  // NOLINT | 
|  | #include "boost/beast/core/multi_buffer.hpp"  // NOLINT | 
|  | #include "boost/beast/websocket.hpp"  // NOLINT | 
|  | #include "http_request.hpp" | 
|  | #include "logging.hpp" | 
|  | #include "utility.hpp" | 
|  | #include "sessions.hpp" | 
|  |  | 
|  | #ifdef BMCWEB_ENABLE_SSL | 
|  | #include "boost/beast/websocket/ssl.hpp"  // NOLINT | 
|  | #endif | 
|  |  | 
|  | namespace crow { | 
|  | namespace websocket { | 
|  |  | 
|  | enum class MessageType : std::uint8_t { | 
|  | Binary, | 
|  | Text, | 
|  | }; | 
|  |  | 
|  | struct Connection : std::enable_shared_from_this<Connection> { | 
|  | public: | 
|  | explicit Connection(const crow::Request& reqIn) : req(reqIn.req) {} | 
|  |  | 
|  | Connection(const Connection&) = delete; | 
|  | Connection(Connection&&) = delete; | 
|  | Connection& operator=(const Connection&) = delete; | 
|  | Connection& operator=(const Connection&&) = delete; | 
|  |  | 
|  | virtual void sendBinary(std::string_view msg) = 0; | 
|  | virtual void sendBinary(std::string&& msg) = 0; | 
|  | virtual void sendEx(MessageType type, std::string_view msg, | 
|  | std::function<void()>&& onDone) = 0; | 
|  | virtual void sendText(std::string_view msg) = 0; | 
|  | virtual void sendText(std::string&& msg) = 0; | 
|  | virtual void close(std::string_view msg) = 0; | 
|  | virtual void deferRead() = 0; | 
|  | virtual void resumeRead() = 0; | 
|  | virtual boost::asio::io_context& getIoContext() = 0; | 
|  | virtual ~Connection() = default; | 
|  |  | 
|  | boost::beast::http::request<boost::beast::http::string_body> req; | 
|  | }; | 
|  |  | 
|  | template <typename Adaptor> | 
|  | class ConnectionImpl : public Connection { | 
|  | public: | 
|  | ConnectionImpl( | 
|  | const crow::Request& reqIn, Adaptor adaptorIn, | 
|  | std::function<void(Connection&)> openHandlerIn, | 
|  | std::function<void(Connection&, const std::string&, bool)> | 
|  | messageHandlerIn, | 
|  | std::function<void(crow::websocket::Connection&, std::string_view, | 
|  | crow::websocket::MessageType type, | 
|  | std::function<void()>&& whenComplete)> | 
|  | messageExHandlerIn, | 
|  | std::function<void(Connection&, const std::string&)> closeHandlerIn, | 
|  | std::function<void(Connection&)> errorHandlerIn) | 
|  | : Connection(reqIn), | 
|  | ws(std::move(adaptorIn)), | 
|  | inBuffer(inString, 131088), | 
|  | openHandler(std::move(openHandlerIn)), | 
|  | messageHandler(std::move(messageHandlerIn)), | 
|  | messageExHandler(std::move(messageExHandlerIn)), | 
|  | closeHandler(std::move(closeHandlerIn)), | 
|  | errorHandler(std::move(errorHandlerIn)), | 
|  | session(reqIn.session) { | 
|  | /* Turn on the timeouts on websocket stream to server role */ | 
|  | ws.set_option(boost::beast::websocket::stream_base::timeout::suggested( | 
|  | boost::beast::role_type::server)); | 
|  | BMCWEB_LOG_DEBUG << "Creating new connection " << this; | 
|  | } | 
|  |  | 
|  | boost::asio::io_context& getIoContext() override { | 
|  | return static_cast<boost::asio::io_context&>(ws.get_executor().context()); | 
|  | } | 
|  |  | 
|  | void start() { | 
|  | BMCWEB_LOG_DEBUG << "starting connection " << this; | 
|  |  | 
|  | using bf = boost::beast::http::field; | 
|  |  | 
|  | std::string_view protocol = req[bf::sec_websocket_protocol]; | 
|  |  | 
|  | ws.set_option(boost::beast::websocket::stream_base::decorator( | 
|  | [session{session}, protocol{std::string(protocol)}]( | 
|  | boost::beast::websocket::response_type& m) { | 
|  | #ifndef BMCWEB_INSECURE_DISABLE_CSRF_PREVENTION | 
|  | if (session != nullptr) { | 
|  | // use protocol for csrf checking | 
|  | if (session->cookieAuth && | 
|  | !crow::utility::constantTimeStringCompare(protocol, | 
|  | session->csrfToken)) { | 
|  | BMCWEB_LOG_ERROR << "Websocket CSRF error"; | 
|  | m.result(boost::beast::http::status::unauthorized); | 
|  | return; | 
|  | } | 
|  | } | 
|  | #endif | 
|  | if (!protocol.empty()) { | 
|  | m.insert(bf::sec_websocket_protocol, protocol); | 
|  | } | 
|  |  | 
|  | m.insert(bf::strict_transport_security, | 
|  | "max-age=31536000; " | 
|  | "includeSubdomains; " | 
|  | "preload"); | 
|  | m.insert(bf::pragma, "no-cache"); | 
|  | m.insert(bf::cache_control, "no-Store,no-Cache"); | 
|  | m.insert("Content-Security-Policy", "default-src 'self'"); | 
|  | m.insert("X-XSS-Protection", | 
|  | "1; " | 
|  | "mode=block"); | 
|  | m.insert("X-Content-Type-Options", "nosniff"); | 
|  | })); | 
|  |  | 
|  | // Perform the websocket upgrade | 
|  | ws.async_accept(req, [this, self(shared_from_this())]( | 
|  | const boost::system::error_code& ec) { | 
|  | if (ec) { | 
|  | BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec; | 
|  | return; | 
|  | } | 
|  | acceptDone(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | void sendBinary(std::string_view msg) override { | 
|  | ws.binary(true); | 
|  | outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), | 
|  | boost::asio::buffer(msg))); | 
|  | doWrite(); | 
|  | } | 
|  |  | 
|  | void sendEx(MessageType type, std::string_view msg, | 
|  | std::function<void()>&& onDone) override { | 
|  | if (doingWrite) { | 
|  | BMCWEB_LOG_CRITICAL | 
|  | << "Cannot mix sendEx usage with sendBinary or sendText"; | 
|  | onDone(); | 
|  | return; | 
|  | } | 
|  | ws.binary(type == MessageType::Binary); | 
|  |  | 
|  | ws.async_write(boost::asio::buffer(msg), | 
|  | [weak(weak_from_this()), onDone{std::move(onDone)}]( | 
|  | const boost::beast::error_code& ec, size_t) { | 
|  | std::shared_ptr<Connection> self = weak.lock(); | 
|  |  | 
|  | // Call the done handler regardless of whether we | 
|  | // errored, but before we close things out | 
|  | onDone(); | 
|  |  | 
|  | if (ec) { | 
|  | BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec; | 
|  | self->close("write error"); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void sendBinary(std::string&& msg) override { | 
|  | ws.binary(true); | 
|  | outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), | 
|  | boost::asio::buffer(msg))); | 
|  | doWrite(); | 
|  | } | 
|  |  | 
|  | void sendText(std::string_view msg) override { | 
|  | ws.text(true); | 
|  | outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), | 
|  | boost::asio::buffer(msg))); | 
|  | doWrite(); | 
|  | } | 
|  |  | 
|  | void sendText(std::string&& msg) override { | 
|  | ws.text(true); | 
|  | outBuffer.commit(boost::asio::buffer_copy(outBuffer.prepare(msg.size()), | 
|  | boost::asio::buffer(msg))); | 
|  | doWrite(); | 
|  | } | 
|  |  | 
|  | void close(std::string_view msg) override { | 
|  | ws.async_close( | 
|  | {boost::beast::websocket::close_code::normal, msg}, | 
|  | [self(shared_from_this())](const boost::system::error_code& ec) { | 
|  | if (ec == boost::asio::error::operation_aborted) { | 
|  | return; | 
|  | } | 
|  | if (ec) { | 
|  | BMCWEB_LOG_ERROR << "Error closing websocket " << ec; | 
|  | return; | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | void acceptDone() { | 
|  | BMCWEB_LOG_DEBUG << "Websocket accepted connection"; | 
|  |  | 
|  | if (openHandler) { | 
|  | openHandler(*this); | 
|  | } | 
|  | doRead(); | 
|  | } | 
|  |  | 
|  | void deferRead() override { | 
|  | readingDefered = true; | 
|  |  | 
|  | // If we're not actively reading, we need to take ownership of | 
|  | // ourselves for a small portion of time, do that, and clear when we | 
|  | // resume. | 
|  | selfOwned = shared_from_this(); | 
|  | } | 
|  |  | 
|  | void resumeRead() override { | 
|  | readingDefered = false; | 
|  | doRead(); | 
|  |  | 
|  | // No longer need to keep ourselves alive now that read is active. | 
|  | selfOwned.reset(); | 
|  | } | 
|  |  | 
|  | void doRead() { | 
|  | if (readingDefered) { | 
|  | return; | 
|  | } | 
|  | ws.async_read(inBuffer, | 
|  | [this, self(shared_from_this())]( | 
|  | const boost::beast::error_code& ec, size_t bytesRead) { | 
|  | if (ec) { | 
|  | if (ec != boost::beast::websocket::error::closed) { | 
|  | BMCWEB_LOG_ERROR << "doRead error " << ec; | 
|  | } | 
|  | if (closeHandler) { | 
|  | std::string reason{ws.reason().reason.c_str()}; | 
|  | closeHandler(*this, reason); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | handleMessage(bytesRead); | 
|  | }); | 
|  | } | 
|  | void doWrite() { | 
|  | // If we're already doing a write, ignore the request, it will be picked | 
|  | // up when the current write is complete | 
|  | if (doingWrite) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (outBuffer.size() == 0) { | 
|  | // Done for now | 
|  | return; | 
|  | } | 
|  | doingWrite = true; | 
|  | ws.async_write(outBuffer.data(), | 
|  | [this, self(shared_from_this())]( | 
|  | const boost::beast::error_code& ec, size_t bytesSent) { | 
|  | doingWrite = false; | 
|  | outBuffer.consume(bytesSent); | 
|  | if (ec == boost::beast::websocket::error::closed) { | 
|  | // Do nothing here.  doRead handler will call the | 
|  | // closeHandler. | 
|  | close("Write error"); | 
|  | return; | 
|  | } | 
|  | if (ec) { | 
|  | BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec; | 
|  | return; | 
|  | } | 
|  | doWrite(); | 
|  | }); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void handleMessage(size_t bytesRead) { | 
|  | if (messageExHandler) { | 
|  | // Note, because of the interactions with the read buffers, | 
|  | // this message handler overrides the normal message handler | 
|  | messageExHandler(*this, inString, MessageType::Binary, | 
|  | [this, self(shared_from_this()), bytesRead]() { | 
|  | if (self == nullptr) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | inBuffer.consume(bytesRead); | 
|  | inString.clear(); | 
|  |  | 
|  | doRead(); | 
|  | }); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (messageHandler) { | 
|  | messageHandler(*this, inString, ws.got_text()); | 
|  | } | 
|  | inBuffer.consume(bytesRead); | 
|  | inString.clear(); | 
|  | doRead(); | 
|  | } | 
|  | // NOLINTBEGIN | 
|  | boost::beast::websocket::stream<Adaptor, false> ws; | 
|  |  | 
|  | bool readingDefered = false; | 
|  | std::string inString; | 
|  | boost::asio::dynamic_string_buffer<std::string::value_type, | 
|  | std::string::traits_type, | 
|  | std::string::allocator_type> | 
|  | inBuffer; | 
|  |  | 
|  | boost::beast::multi_buffer outBuffer; | 
|  | bool doingWrite = false; | 
|  |  | 
|  | std::function<void(Connection&)> openHandler; | 
|  | std::function<void(Connection&, const std::string&, bool)> messageHandler; | 
|  | std::function<void(crow::websocket::Connection&, std::string_view, | 
|  | crow::websocket::MessageType type, | 
|  | std::function<void()>&& whenComplete)> | 
|  | messageExHandler; | 
|  | std::function<void(Connection&, const std::string&)> closeHandler; | 
|  | std::function<void(Connection&)> errorHandler; | 
|  | std::shared_ptr<persistent_data::UserSession> session; | 
|  |  | 
|  | std::shared_ptr<Connection> selfOwned; | 
|  | // NOLINTEND | 
|  | }; | 
|  | }  // namespace websocket | 
|  | }  // namespace crow | 
|  |  | 
|  | #endif  // THIRD_PARTY_GBMCWEB_HTTP_WEBSOCKET_H_ |