Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions http/WebSocketParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ static int on_frame_header(websocket_parser* parser) {
WebSocketParser* wp = (WebSocketParser*)parser->data;
int opcode = parser->flags & WS_OP_MASK;
// printf("on_frame_header opcode=%d\n", opcode);
switch (opcode) {
case WS_OP_CONTINUE:
case WS_OP_TEXT:
case WS_OP_BINARY:
case WS_OP_CLOSE:
case WS_OP_PING:
case WS_OP_PONG: break;
default: return -1;
}
if (wp->is_server && !(parser->flags & WS_HAS_MASK)) {
return -1;
}
if (is_control_frame(parser)) {
// RFC 6455 5.5: control frames MUST NOT be fragmented and MUST have
// a payload length of 125 bytes or less.
Expand Down Expand Up @@ -88,6 +100,7 @@ WebSocketParser::WebSocketParser() {
parser->data = this;
state = WS_FRAME_BEGIN;
opcode = WS_OP_CLOSE;
is_server = false;
}

WebSocketParser::~WebSocketParser() {
Expand Down
1 change: 1 addition & 0 deletions http/WebSocketParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class HV_EXPORT WebSocketParser {
websocket_parser* parser;
websocket_parser_state state;
int opcode;
bool is_server; // Require masked incoming frames; false by default.
std::string message;
std::string control_message;
std::function<void(int opcode, const std::string& msg)> onMessage;
Expand Down
1 change: 1 addition & 0 deletions http/server/HttpHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ bool HttpHandler::SwitchWebSocket() {

protocol = WEBSOCKET;
ws_parser = std::make_shared<WebSocketParser>();
ws_parser->is_server = true;
ws_channel = std::make_shared<WebSocketChannel>(io, WS_SERVER);
ws_parser->onMessage = [this](int opcode, const std::string& msg){
ws_channel->opcode = (enum ws_opcode)opcode;
Expand Down
4 changes: 4 additions & 0 deletions http/websocket_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ size_t websocket_parser_execute(websocket_parser *parser, const websocket_parser
for(p = data; p != end; p++) {
switch(parser->state) {
case s_start:
// No extensions are negotiated, so all RSV bits must be zero.
if(CC & 0x70) {
return (size_t)(p - data);
}
parser->offset = 0;
parser->length = 0;
parser->mask_offset = 0;
Expand Down
96 changes: 94 additions & 2 deletions unittest/websocket_parser_test.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
// Keep assertions active in Release builds: the checks also feed the parser.
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#include <stdio.h>

#include <string>
#include <utility>
Expand All @@ -17,8 +22,10 @@ static std::string frame(enum ws_opcode opcode, const std::string& payload, bool
return buf;
}

static size_t feed(const std::string& data, Messages* messages, bool byte_by_byte = false) {
static size_t feed(const std::string& data, Messages* messages, bool byte_by_byte = false, bool is_server = false) {
WebSocketParser parser;
assert(!parser.is_server);
parser.is_server = is_server;
parser.onMessage = [messages](int opcode, const std::string& msg) {
messages->push_back(Message(opcode, msg));
};
Expand Down Expand Up @@ -95,12 +102,97 @@ static void test_reject_oversized_control_frame() {
assert(messages.empty());
}

int main() {
static void test_reject_reserved_bits() {
for (int rsv = 0x10; rsv <= 0x70; rsv += 0x10) {
for (int server = 0; server < 2; ++server) {
for (int split = 0; split < 2; ++split) {
std::string data = frame(WS_OPCODE_TEXT, "hi");
data[0] |= (char)rsv;
Messages messages;
assert(feed(data, &messages, split != 0, server != 0) < data.size());
assert(messages.empty());
}
}
}
}

static void test_reject_reserved_opcodes() {
for (int opcode = 0; opcode <= 0xF; ++opcode) {
if (opcode <= 0x2 || (opcode >= 0x8 && opcode <= 0xA)) continue;
for (int server = 0; server < 2; ++server) {
for (int split = 0; split < 2; ++split) {
std::string data = frame(WS_OPCODE_TEXT, "zz");
data[0] = (char)(0x80 | opcode);
Messages messages;
assert(feed(data, &messages, split != 0, server != 0) < data.size());
assert(messages.empty());
}
}
}
}

static void test_server_mask_direction() {
const enum ws_opcode opcodes[] = {WS_OPCODE_TEXT, WS_OPCODE_BINARY, WS_OPCODE_CLOSE, WS_OPCODE_PING, WS_OPCODE_PONG};
for (size_t i = 0; i < sizeof(opcodes) / sizeof(opcodes[0]); ++i) {
for (int empty = 0; empty < 2; ++empty) {
for (int split = 0; split < 2; ++split) {
std::string data = frame(opcodes[i], empty ? "" : "hi", true, false);
Messages messages;
assert(feed(data, &messages, split != 0, true) < data.size());
assert(messages.empty());
}
}
}
}

static void test_valid_mask_direction() {
for (int server = 0; server < 2; ++server) {
for (int masked = 0; masked < 2; ++masked) {
if (server && !masked) continue;
for (int split = 0; split < 2; ++split) {
std::string data = frame(WS_OPCODE_TEXT, "Hel", false, masked != 0) + frame(WS_OPCODE_PING, "", true, masked != 0) +
frame(WS_OPCODE_CONTINUE, "lo", true, masked != 0);
Messages messages;
assert(feed(data, &messages, split != 0, server != 0) == data.size());
assert(messages.size() == 2);
assert(messages[0] == Message(WS_OPCODE_PING, ""));
assert(messages[1] == Message(WS_OPCODE_TEXT, "Hello"));
}
}
}
}

int main(int argc, char** argv) {
if (argc == 2) {
std::string name = argv[1];
if (name == "rsv") {
test_reject_reserved_bits();
}
else if (name == "opcode") {
test_reject_reserved_opcodes();
}
else if (name == "mask") {
test_server_mask_direction();
}
else if (name == "valid") {
test_valid_mask_direction();
}
else {
return 1;
}
printf("PASS: %s\n", argv[1]);
return 0;
}
test_ping_between_fragments();
test_control_frames_between_fragments_byte_by_byte();
test_control_frame_between_messages();
test_max_control_payload();
test_reject_fragmented_control_frame();
test_reject_oversized_control_frame();
test_reject_reserved_bits();
test_reject_reserved_opcodes();
test_server_mask_direction();
test_valid_mask_direction();
printf("PASS: 10 WebSocket parser tests\n");
return 0;
}