Skip to content
Merged
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: 11 additions & 2 deletions include/aws/http/private/websocket_decoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum aws_websocket_decoder_state {
AWS_WEBSOCKET_DECODER_STATE_MASKING_KEY,
AWS_WEBSOCKET_DECODER_STATE_PAYLOAD_CHECK,
AWS_WEBSOCKET_DECODER_STATE_PAYLOAD,
AWS_WEBSOCKET_DECODER_STATE_FRAME_END,
AWS_WEBSOCKET_DECODER_STATE_DONE,
};

Expand All @@ -38,6 +39,11 @@ struct aws_websocket_decoder {

bool expecting_continuation_data_frame; /* True when the next data frame must be CONTINUATION frame */

/* True while processing a TEXT "message" (from the start of a TEXT frame,
* until the end of the TEXT or CONTINUATION frame with the FIN bit set). */
bool processing_text_message;
struct aws_utf8_validator *text_message_validator;

void *user_data;
aws_websocket_decoder_frame_fn *on_frame;
aws_websocket_decoder_payload_fn *on_payload;
Expand All @@ -48,19 +54,22 @@ AWS_EXTERN_C_BEGIN
AWS_HTTP_API
void aws_websocket_decoder_init(
struct aws_websocket_decoder *decoder,
struct aws_allocator *alloc,
aws_websocket_decoder_frame_fn *on_frame,
aws_websocket_decoder_payload_fn *on_payload,
void *user_data);

AWS_HTTP_API
void aws_websocket_decoder_clean_up(struct aws_websocket_decoder *decoder);

/**
* Returns when all data is processed, or a frame and its payload have completed.
* `data` will be advanced to reflect the amount of data processed by this call.
* `frame_complete` will be set true if this call returned due to completion of a frame.
* The `on_frame` and `on_payload` callbacks may each be invoked once as a result of this call.
* If an error occurs, the decoder is invalid forevermore.
*/
AWS_HTTP_API
int aws_websocket_decoder_process(
AWS_HTTP_API int aws_websocket_decoder_process(
struct aws_websocket_decoder *decoder,
struct aws_byte_cursor *data,
bool *frame_complete);
Expand Down
4 changes: 3 additions & 1 deletion source/websocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ struct aws_websocket *aws_websocket_handler_new(const struct aws_websocket_handl

aws_websocket_encoder_init(&websocket->thread_data.encoder, s_encoder_stream_outgoing_payload, websocket);

aws_websocket_decoder_init(&websocket->thread_data.decoder, s_decoder_on_frame, s_decoder_on_payload, websocket);
aws_websocket_decoder_init(
&websocket->thread_data.decoder, options->allocator, s_decoder_on_frame, s_decoder_on_payload, websocket);

aws_linked_list_init(&websocket->synced_data.outgoing_frame_list);

Expand Down Expand Up @@ -346,6 +347,7 @@ static void s_handler_destroy(struct aws_channel_handler *handler) {

AWS_LOGF_TRACE(AWS_LS_HTTP_WEBSOCKET, "id=%p: Destroying websocket.", (void *)websocket);

aws_websocket_decoder_clean_up(&websocket->thread_data.decoder);
aws_byte_buf_clean_up(&websocket->thread_data.incoming_ping_payload);
aws_mutex_clean_up(&websocket->synced_data.lock);
aws_mem_release(websocket->alloc, websocket);
Expand Down
52 changes: 49 additions & 3 deletions source/websocket_decoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <aws/http/private/websocket_decoder.h>

#include <aws/common/encoding.h>

#include <inttypes.h>

typedef int(state_fn)(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data);
Expand Down Expand Up @@ -86,6 +88,10 @@ static int s_state_opcode_byte(struct aws_websocket_decoder *decoder, struct aws
}
}

if (decoder->current_frame.opcode == AWS_WEBSOCKET_OPCODE_TEXT) {
decoder->processing_text_message = true;
}

decoder->state = AWS_WEBSOCKET_DECODER_STATE_LENGTH_BYTE;
return AWS_OP_SUCCESS;
}
Expand Down Expand Up @@ -234,7 +240,7 @@ static int s_state_payload_check(struct aws_websocket_decoder *decoder, struct a
decoder->state_bytes_processed = 0;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_PAYLOAD;
} else {
decoder->state = AWS_WEBSOCKET_DECODER_STATE_DONE;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_FRAME_END;
}

return AWS_OP_SUCCESS;
Expand Down Expand Up @@ -266,9 +272,16 @@ static int s_state_payload(struct aws_websocket_decoder *decoder, struct aws_byt
}
}

/* TODO: validate utf-8 */
/* TODO: validate payload of CLOSE frame */

/* Validate the UTF-8 for TEXT messages (a TEXT frame and any subsequent CONTINUATION frames) */
if (decoder->processing_text_message && aws_websocket_is_data_frame(decoder->current_frame.opcode)) {
if (aws_utf8_validator_update(decoder->text_message_validator, payload)) {
AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET, "id=%p: Received invalid UTF-8", (void *)decoder->user_data);
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
}
}

/* Invoke on_payload() callback to inform user of payload data */
int err = decoder->on_payload(payload, decoder->user_data);
if (err) {
Expand All @@ -280,9 +293,34 @@ static int s_state_payload(struct aws_websocket_decoder *decoder, struct aws_byt

/* If all data consumed, proceed to next state. */
if (decoder->state_bytes_processed == decoder->current_frame.payload_length) {
decoder->state++;
decoder->state = AWS_WEBSOCKET_DECODER_STATE_FRAME_END;
}

return AWS_OP_SUCCESS;
}

/* FRAME_END: Perform checks once we reach the end of the frame. */
static int s_state_frame_end(struct aws_websocket_decoder *decoder, struct aws_byte_cursor *data) {
(void)data;

/* If we're done processing a text message (a TEXT frame and any subsequent CONTINUATION frames),
* complete the UTF-8 validation */
if (decoder->processing_text_message && aws_websocket_is_data_frame(decoder->current_frame.opcode) &&
decoder->current_frame.fin) {

if (aws_utf8_validator_finalize(decoder->text_message_validator)) {
AWS_LOGF_ERROR(
AWS_LS_HTTP_WEBSOCKET,
"id=%p: Received invalid UTF-8 (incomplete encoding)",
(void *)decoder->user_data);
return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_PROTOCOL_ERROR);
}

decoder->processing_text_message = false;
}

/* Done! */
decoder->state = AWS_WEBSOCKET_DECODER_STATE_DONE;
return AWS_OP_SUCCESS;
}

Expand All @@ -295,6 +333,7 @@ static state_fn *s_state_functions[AWS_WEBSOCKET_DECODER_STATE_DONE] = {
s_state_masking_key,
s_state_payload_check,
s_state_payload,
s_state_frame_end,
};

int aws_websocket_decoder_process(
Expand Down Expand Up @@ -330,6 +369,7 @@ int aws_websocket_decoder_process(

void aws_websocket_decoder_init(
struct aws_websocket_decoder *decoder,
struct aws_allocator *alloc,
aws_websocket_decoder_frame_fn *on_frame,
aws_websocket_decoder_payload_fn *on_payload,
void *user_data) {
Expand All @@ -338,4 +378,10 @@ void aws_websocket_decoder_init(
decoder->user_data = user_data;
decoder->on_frame = on_frame;
decoder->on_payload = on_payload;
decoder->text_message_validator = aws_utf8_validator_new(alloc);
}

void aws_websocket_decoder_clean_up(struct aws_websocket_decoder *decoder) {
aws_utf8_validator_destroy(decoder->text_message_validator);
AWS_ZERO_STRUCT(*decoder);
}
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ add_test_case(websocket_decoder_fail_on_unknown_opcode)
add_test_case(websocket_decoder_fragmented_message)
add_test_case(websocket_decoder_fail_on_bad_fragmentation)
add_test_case(websocket_decoder_control_frame_cannot_be_fragmented)
add_test_case(websocket_decoder_utf8_text)
add_test_case(websocket_decoder_fail_on_bad_utf8_text)
add_test_case(websocket_decoder_fragmented_utf8_text)
add_test_case(websocket_decoder_fail_on_fragmented_bad_utf8_text)
add_test_case(websocket_decoder_on_frame_callback_can_fail_decoder)
add_test_case(websocket_decoder_on_payload_callback_can_fail_decoder)
add_test_case(websocket_encoder_sanity_check)
Expand Down
Loading