mirror of
https://github.com/faye/websocket-driver-ruby.git
synced 2025-11-01 13:59:38 +00:00
332 lines
9.8 KiB
C
332 lines
9.8 KiB
C
#define _GNU_SOURCE
|
|
|
|
#include "parser.h"
|
|
|
|
struct wsd_Parser {
|
|
int require_masking;
|
|
|
|
wsd_StreamReader *reader;
|
|
wsd_Extensions *extensions;
|
|
wsd_Observer *observer;
|
|
|
|
int stage;
|
|
wsd_Frame *frame;
|
|
wsd_Message *message;
|
|
|
|
int error_code;
|
|
char *error_reason;
|
|
};
|
|
|
|
wsd_Parser *wsd_Parser_create(wsd_Extensions *extensions, wsd_Observer *observer, int require_masking)
|
|
{
|
|
wsd_Parser *parser = calloc(1, sizeof(wsd_Parser));
|
|
if (parser == NULL) return NULL;
|
|
|
|
parser->reader = wsd_StreamReader_create();
|
|
if (parser->reader == NULL) {
|
|
free(parser);
|
|
return NULL;
|
|
}
|
|
|
|
parser->require_masking = require_masking;
|
|
parser->extensions = extensions;
|
|
parser->observer = observer;
|
|
|
|
parser->stage = 1;
|
|
parser->frame = NULL;
|
|
parser->message = NULL;
|
|
|
|
parser->error_code = 0;
|
|
parser->error_reason = NULL;
|
|
|
|
return parser;
|
|
}
|
|
|
|
void wsd_Parser_destroy(wsd_Parser *parser)
|
|
{
|
|
if (parser == NULL) return;
|
|
|
|
WSD_CLEAR_POINTER(wsd_StreamReader_destroy, parser->reader);
|
|
WSD_CLEAR_POINTER(wsd_Frame_destroy, parser->frame);
|
|
WSD_CLEAR_POINTER(wsd_Message_destroy, parser->message);
|
|
WSD_CLEAR_POINTER(wsd_Extensions_destroy, parser->extensions);
|
|
WSD_CLEAR_POINTER(wsd_Observer_destroy, parser->observer);
|
|
WSD_CLEAR_POINTER(free, parser->error_reason);
|
|
|
|
free(parser);
|
|
}
|
|
|
|
int wsd_Parser_parse(wsd_Parser *parser, size_t length, uint8_t *data)
|
|
{
|
|
wsd_Chunk *chunk = NULL;
|
|
size_t n = 0;
|
|
size_t readlen = 0;
|
|
|
|
chunk = wsd_Chunk_create(length, data);
|
|
if (chunk == NULL) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to allocate chunk");
|
|
return parser->error_code;
|
|
}
|
|
|
|
if (!wsd_StreamReader_push(parser->reader, chunk)) {
|
|
wsd_Chunk_destroy(chunk);
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to push chunk[%zu] to read buffer", length);
|
|
return parser->error_code;
|
|
}
|
|
|
|
chunk = wsd_Chunk_alloc(8);
|
|
if (chunk == NULL) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to allocate memory for frame header");
|
|
return parser->error_code;
|
|
}
|
|
|
|
while (readlen == n) {
|
|
switch (parser->stage) {
|
|
case 1:
|
|
n = 2;
|
|
readlen = wsd_StreamReader_read(parser->reader, n, chunk);
|
|
if (readlen == n) wsd_Parser_parse_head(parser, chunk);
|
|
break;
|
|
case 2:
|
|
n = parser->frame->length_bytes;
|
|
readlen = wsd_StreamReader_read(parser->reader, n, chunk);
|
|
if (readlen == n) wsd_Parser_parse_extended_length(parser, chunk);
|
|
break;
|
|
case 3:
|
|
n = 4;
|
|
readlen = wsd_StreamReader_read(parser->reader, n, parser->frame->masking_key);
|
|
if (readlen == n) parser->stage = 4;
|
|
break;
|
|
case 4:
|
|
n = parser->frame->length;
|
|
readlen = wsd_Parser_parse_payload(parser);
|
|
if (readlen == n) wsd_Parser_emit_frame(parser);
|
|
break;
|
|
default:
|
|
n = 1;
|
|
readlen = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
wsd_Chunk_destroy(chunk);
|
|
return parser->error_code;
|
|
}
|
|
|
|
void wsd_Parser_parse_head(wsd_Parser *parser, wsd_Chunk *chunk)
|
|
{
|
|
wsd_Frame *frame = wsd_Frame_create();
|
|
if (frame == NULL) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to allocate frame");
|
|
return;
|
|
}
|
|
|
|
uint8_t b1 = wsd_Chunk_get(chunk, 0);
|
|
uint8_t b2 = wsd_Chunk_get(chunk, 1);
|
|
|
|
frame->final = (b1 & WSD_FIN) == WSD_FIN;
|
|
frame->rsv1 = (b1 & WSD_RSV1) == WSD_RSV1;
|
|
frame->rsv2 = (b1 & WSD_RSV2) == WSD_RSV2;
|
|
frame->rsv3 = (b1 & WSD_RSV3) == WSD_RSV3;
|
|
frame->opcode = (b1 & WSD_OPCODE);
|
|
frame->masked = (b2 & WSD_MASK) == WSD_MASK;
|
|
frame->length = (b2 & WSD_LENGTH);
|
|
|
|
parser->frame = frame;
|
|
|
|
if (!wsd_Extensions_valid_frame_rsv(parser->extensions, frame)) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR,
|
|
"One or more reserved bits are on: reserved1 = %d, reserved2 = %d, reserved3 = %d",
|
|
frame->rsv1, frame->rsv2, frame->rsv3);
|
|
return;
|
|
}
|
|
|
|
if (!wsd_Parser_valid_opcode(frame->opcode)) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR, "Unrecognized frame opcode: %d", frame->opcode);
|
|
return;
|
|
}
|
|
|
|
if (wsd_Parser_control_opcode(frame->opcode) && !frame->final) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR, "Received fragmented control frame: opcode = %d", frame->opcode);
|
|
return;
|
|
}
|
|
|
|
if (parser->message == NULL && frame->opcode == WSD_OPCODE_CONTINUTATION) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR, "Received unexpected continuation frame");
|
|
return;
|
|
}
|
|
|
|
if (parser->message != NULL && wsd_Parser_opening_opcode(frame->opcode)) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR, "Received new data frame but previous continuous frame is unfinished");
|
|
return;
|
|
}
|
|
|
|
if (parser->require_masking && !frame->masked) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNACCEPTABLE, "Received unmasked frame but masking is required");
|
|
return;
|
|
}
|
|
|
|
if (frame->length <= 125) {
|
|
if (!wsd_Parser_check_frame_length(parser)) return;
|
|
parser->stage = frame->masked ? 3 : 4;
|
|
} else {
|
|
parser->stage = 2;
|
|
frame->length_bytes = (frame->length == 126) ? 2 : 8;
|
|
}
|
|
}
|
|
|
|
int wsd_Parser_valid_opcode(int opcode)
|
|
{
|
|
return wsd_Parser_control_opcode(opcode) ||
|
|
wsd_Parser_message_opcode(opcode);
|
|
}
|
|
|
|
int wsd_Parser_control_opcode(int opcode)
|
|
{
|
|
return opcode == WSD_OPCODE_CLOSE ||
|
|
opcode == WSD_OPCODE_PING ||
|
|
opcode == WSD_OPCODE_PONG;
|
|
}
|
|
|
|
int wsd_Parser_message_opcode(int opcode)
|
|
{
|
|
return wsd_Parser_opening_opcode(opcode) ||
|
|
opcode == WSD_OPCODE_CONTINUTATION;
|
|
}
|
|
|
|
int wsd_Parser_opening_opcode(int opcode)
|
|
{
|
|
return opcode == WSD_OPCODE_TEXT ||
|
|
opcode == WSD_OPCODE_BINARY;
|
|
}
|
|
|
|
void wsd_Parser_parse_extended_length(wsd_Parser *parser, wsd_Chunk *chunk)
|
|
{
|
|
wsd_Frame *frame = parser->frame;
|
|
|
|
if (frame->length == 126) {
|
|
frame->length = wsd_Chunk_read_uint16(chunk, 0);
|
|
} else if (frame->length == 127) {
|
|
frame->length = wsd_Chunk_read_uint64(chunk, 0);
|
|
}
|
|
|
|
if (wsd_Parser_control_opcode(frame->opcode) && frame->length > 125) {
|
|
WSD_PARSER_ERROR(parser, WSD_PROTOCOL_ERROR, "Received control frame having too long payload: %" PRIu64, frame->length);
|
|
return;
|
|
}
|
|
|
|
if (!wsd_Parser_check_frame_length(parser)) return;
|
|
|
|
parser->stage = frame->masked ? 3 : 4;
|
|
}
|
|
|
|
int wsd_Parser_check_frame_length(wsd_Parser *parser)
|
|
{
|
|
if (wsd_Message_would_overflow(parser->message, parser->frame)) {
|
|
WSD_PARSER_ERROR(parser, WSD_TOO_LARGE, "WebSocket frame length too large");
|
|
return 0;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
size_t wsd_Parser_parse_payload(wsd_Parser *parser)
|
|
{
|
|
wsd_StreamReader *reader = parser->reader;
|
|
wsd_Frame *frame = parser->frame;
|
|
size_t n = (size_t)frame->length;
|
|
|
|
if (!wsd_StreamReader_has_capacity(reader, n)) return 0;
|
|
|
|
frame->payload = wsd_Chunk_alloc(n);
|
|
if (frame->payload == NULL) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to allocate frame payload[%zu]", n);
|
|
return 0;
|
|
}
|
|
|
|
n = wsd_StreamReader_read(reader, n, frame->payload);
|
|
wsd_Frame_mask(frame);
|
|
|
|
return n;
|
|
}
|
|
|
|
void wsd_Parser_emit_frame(wsd_Parser *parser)
|
|
{
|
|
wsd_Frame *frame = parser->frame;
|
|
|
|
int code = 0;
|
|
wsd_Chunk *reason = NULL;
|
|
|
|
parser->stage = 1;
|
|
|
|
switch (frame->opcode) {
|
|
case WSD_OPCODE_CONTINUTATION:
|
|
if (!wsd_Message_push_frame(parser->message, frame)) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to add frame to message");
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case WSD_OPCODE_TEXT:
|
|
case WSD_OPCODE_BINARY:
|
|
parser->message = wsd_Message_create(frame);
|
|
if (parser->message == NULL) {
|
|
WSD_PARSER_ERROR(parser, WSD_UNEXPECTED_CONDITION, "Failed to allocate message");
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case WSD_OPCODE_CLOSE:
|
|
if (frame->length == 0) {
|
|
code = WSD_DEFAULT_ERROR_CODE;
|
|
reason = NULL;
|
|
} else if (frame->length >= 2) {
|
|
code = wsd_Chunk_read_uint16(frame->payload, 0);
|
|
reason = wsd_Chunk_slice(frame->payload, 2, 0);
|
|
}
|
|
|
|
if (!wsd_Parser_valid_close_code(code)) {
|
|
code = WSD_PROTOCOL_ERROR;
|
|
}
|
|
wsd_Observer_on_close(parser->observer, code, reason);
|
|
wsd_Chunk_destroy(reason);
|
|
break;
|
|
|
|
case WSD_OPCODE_PING:
|
|
wsd_Observer_on_ping(parser->observer, frame->payload);
|
|
break;
|
|
|
|
case WSD_OPCODE_PONG:
|
|
wsd_Observer_on_pong(parser->observer, frame->payload);
|
|
break;
|
|
}
|
|
|
|
if (frame->opcode <= WSD_OPCODE_BINARY) {
|
|
parser->frame = NULL;
|
|
if (frame->final) wsd_Parser_emit_message(parser);
|
|
} else {
|
|
WSD_CLEAR_POINTER(wsd_Frame_destroy, parser->frame);
|
|
}
|
|
}
|
|
|
|
int wsd_Parser_valid_close_code(int code)
|
|
{
|
|
return code == WSD_NORMAL_CLOSURE ||
|
|
code == WSD_GOING_AWAY ||
|
|
code == WSD_PROTOCOL_ERROR ||
|
|
code == WSD_UNACCEPTABLE ||
|
|
code == WSD_ENCODING_ERROR ||
|
|
code == WSD_POLICY_VIOLATION ||
|
|
code == WSD_TOO_LARGE ||
|
|
code == WSD_EXTENSION_ERROR ||
|
|
code == WSD_UNEXPECTED_CONDITION ||
|
|
(code >= WSD_MIN_RESERVED_ERROR && code <= WSD_MAX_RESERVED_ERROR);
|
|
}
|
|
|
|
void wsd_Parser_emit_message(wsd_Parser *parser)
|
|
{
|
|
wsd_Observer_on_message(parser->observer, parser->message);
|
|
|
|
WSD_CLEAR_POINTER(wsd_Message_destroy, parser->message);
|
|
}
|