Skip to content

Commit e87ec70

Browse files
authored
Merge pull request #282 from CrowCpp/ws_fix
Opt-in websocket protocol enforcement
2 parents aa084c4 + f8fabe3 commit e87ec70

File tree

5 files changed

+56
-14
lines changed

5 files changed

+56
-14
lines changed

docs/guides/websockets.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ To create a websocket in Crow, you need a websocket route.<br>
44
A websocket route differs from a normal route quite a bit. While it uses the same `CROW_ROUTE(app, "/url")` macro, that's about where the similarities end.<br>
55
A websocket route follows the macro with `.websocket()` which is then followed by a series of methods (with handlers inside) for each event. These are (sorted by order of execution):
66

7+
!!! Warning
8+
9+
By default, Crow allows Clients to send unmasked websocket messages, which is useful for debugging but goes against the protocol specification. Production Crow applications should enforce the protocol by adding `#!cpp #define CROW_ENFORCE_WS_SPEC` to their source code.
10+
11+
712
- `#!cpp onaccept([&](const crow::request&){handler code goes here})` (This handler has to return bool)
813
- `#!cpp onopen([&](crow::websocket::connection& conn){handler code goes here})`
914
- `#!cpp onmessage([&](crow::websocket::connection& conn, const std::string message, bool is_binary){handler code goes here})`

examples/example.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ int main()
104104
});
105105

106106
// Same as above, but using crow::status
107-
CROW_ROUTE(app,"/hello/<int>")
107+
CROW_ROUTE(app,"/hello_status/<int>")
108108
([](int count){
109109
if (count > 100)
110110
return crow::response(crow::status::BAD_REQUEST);

include/crow/settings.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
/* #ifdef - enables ssl */
1212
//#define CROW_ENABLE_SSL
1313

14+
/* #ifdef - enforces section 5.2 and 6.1 of RFC6455 (only accepting masked messages from clients) */
15+
//#define CROW_ENFORCE_WS_SPEC
16+
1417
/* #define - specifies log level */
1518
/*
1619
Debug = 0

include/crow/websocket.h

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ namespace crow
2121
///A base class for websocket connection.
2222
struct connection
2323
{
24-
virtual void send_binary(const std::string& msg) = 0;
25-
virtual void send_text(const std::string& msg) = 0;
26-
virtual void send_ping(const std::string& msg) = 0;
27-
virtual void send_pong(const std::string& msg) = 0;
24+
virtual void send_binary(const std::string& msg) = 0;
25+
virtual void send_text(const std::string& msg) = 0;
26+
virtual void send_ping(const std::string& msg) = 0;
27+
virtual void send_pong(const std::string& msg) = 0;
2828
virtual void close(const std::string& msg = "quit") = 0;
29-
virtual std::string get_remote_ip() = 0;
29+
virtual std::string get_remote_ip() = 0;
3030
virtual ~connection(){}
3131

3232
void userdata(void* u) { userdata_ = u; }
@@ -36,6 +36,9 @@ namespace crow
3636
void* userdata_;
3737
};
3838

39+
// Modified version of the illustration in RFC6455 Section-5.2
40+
//
41+
//
3942
// 0 1 2 3 -byte
4043
// 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 -bit
4144
// +-+-+-+-+-------+-+-------------+-------------------------------+
@@ -54,14 +57,14 @@ namespace crow
5457
// + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
5558
// | Payload Data continued ... |
5659
// +---------------------------------------------------------------+
60+
//
5761

5862
/// A websocket connection.
5963
template <typename Adaptor>
6064
class Connection : public connection
6165
{
6266
public:
6367
/// Constructor for a connection.
64-
6568
///
6669
/// Requires a request with an "Upgrade: websocket" header.<br>
6770
/// Automatically handles the handshake.
@@ -116,7 +119,6 @@ namespace crow
116119
}
117120

118121
/// Send a "Ping" message.
119-
120122
///
121123
/// Usually invoked to check if the other point is still online.
122124
void send_ping(const std::string& msg) override
@@ -130,7 +132,6 @@ namespace crow
130132
}
131133

132134
/// Send a "Pong" message.
133-
134135
///
135136
/// Usually automatically invoked as a response to a "Ping" message.
136137
void send_pong(const std::string& msg) override
@@ -166,7 +167,6 @@ namespace crow
166167
}
167168

168169
/// Send a close signal.
169-
170170
///
171171
/// Sets a flag to destroy the object once the message is sent.
172172
void close(const std::string& msg) override
@@ -218,7 +218,6 @@ namespace crow
218218
}
219219

220220
/// Send the HTTP upgrade response.
221-
222221
///
223222
/// Finishes the handshake process, then starts reading messages from the socket.
224223
void start(std::string&& hello)
@@ -239,7 +238,6 @@ namespace crow
239238
}
240239

241240
/// Read a websocket message.
242-
243241
///
244242
/// Involves:<br>
245243
/// Handling headers (opcodes, size).<br>
@@ -276,6 +274,18 @@ namespace crow
276274
{
277275
if ((mini_header_ & 0x80) == 0x80)
278276
has_mask_ = true;
277+
else //if the websocket specification is enforced and the message isn't masked, terminate the connection
278+
{
279+
#ifndef CROW_ENFORCE_WS_SPEC
280+
has_mask_ = false;
281+
#else
282+
close_connection_ = true;
283+
adaptor_.close();
284+
if (error_handler_)
285+
error_handler_(*this);
286+
check_destroy();
287+
#endif
288+
}
279289

280290
if ((mini_header_ & 0x7f) == 127)
281291
{
@@ -461,7 +471,6 @@ namespace crow
461471
}
462472

463473
/// Process the payload fragment.
464-
465474
///
466475
/// Unmasks the fragment, checks the opcode, merges fragments into 1 message body, and calls the appropriate handler.
467476
void handle_fragment()
@@ -547,7 +556,6 @@ namespace crow
547556
}
548557

549558
/// Send the buffers' data through the socket.
550-
551559
///
552560
/// Also destroyes the object if the Close flag is set.
553561
void do_write()

tests/unittest.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1909,6 +1909,32 @@ TEST_CASE("websocket")
19091909
std::string checkstring4(std::string(buf).substr(0, 16));
19101910
CHECK(checkstring4 == "\x82\x0EHello back bin");
19111911
}
1912+
//----------16bit Length Text----------
1913+
{
1914+
std::fill_n (buf, 2048, 0);
1915+
char b16_text_message[2+2+5+1]("\x81\x7E"
1916+
"\x00\x05"
1917+
"Hello");
1918+
1919+
c.send(asio::buffer(b16_text_message, 9));
1920+
c.receive(asio::buffer(buf, 2048));
1921+
std::this_thread::sleep_for(std::chrono::milliseconds(5));
1922+
std::string checkstring(std::string(buf).substr(0, 12));
1923+
CHECK(checkstring == "\x81\x0AHello back");
1924+
}
1925+
//----------64bit Length Text----------
1926+
{
1927+
std::fill_n (buf, 2048, 0);
1928+
char b64text_message[2+8+5+1]("\x81\x7F"
1929+
"\x00\x00\x00\x00\x00\x00\x00\x05"
1930+
"Hello");
1931+
1932+
c.send(asio::buffer(b64text_message, 15));
1933+
c.receive(asio::buffer(buf, 2048));
1934+
std::this_thread::sleep_for(std::chrono::milliseconds(5));
1935+
std::string checkstring(std::string(buf).substr(0, 12));
1936+
CHECK(checkstring == "\x81\x0AHello back");
1937+
}
19121938
//----------Close----------
19131939
{
19141940
std::fill_n (buf, 2048, 0);

0 commit comments

Comments
 (0)