diff options
Diffstat (limited to 'thirdparty/lws/client/client-parser.c')
-rw-r--r-- | thirdparty/lws/client/client-parser.c | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/thirdparty/lws/client/client-parser.c b/thirdparty/lws/client/client-parser.c new file mode 100644 index 0000000000..0e42dac362 --- /dev/null +++ b/thirdparty/lws/client/client-parser.c @@ -0,0 +1,598 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * parsers.c: lws_rx_sm() needs to be roughly kept in + * sync with changes here, esp related to ext draining + */ + +int lws_client_rx_sm(struct lws *wsi, unsigned char c) +{ + int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; + int handled, n, m, rx_draining_ext = 0; + unsigned short close_code; + struct lws_tokens eff_buf; + unsigned char *pp; + + if (wsi->u.ws.rx_draining_ext) { + assert(!c); + eff_buf.token = NULL; + eff_buf.token_len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); + rx_draining_ext = 1; + lwsl_debug("%s: doing draining flow\n", __func__); + + goto drain_extension; + } + + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + /* control frames (PING) may interrupt checkable sequences */ + wsi->u.ws.defeat_check_utf8 = 0; + + switch (wsi->ietf_spec_revision) { + case 13: + wsi->u.ws.opcode = c & 0xf; + /* revisit if an extension wants them... */ + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.continuation_possible = 1; + wsi->u.ws.check_utf8 = lws_check_opt( + wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8); + wsi->u.ws.utf8 = 0; + break; + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.continuation_possible = 1; + break; + case LWSWSOPC_CONTINUATION: + if (!wsi->u.ws.continuation_possible) { + lwsl_info("disordered continuation\n"); + return -1; + } + break; + case LWSWSOPC_CLOSE: + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.utf8 = 0; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 0xb: + case 0xc: + case 0xd: + case 0xe: + case 0xf: + lwsl_info("illegal opcode\n"); + return -1; + default: + wsi->u.ws.defeat_check_utf8 = 1; + break; + } + wsi->u.ws.rsv = (c & 0x70); + /* revisit if an extension wants them... */ + if ( +#ifndef LWS_NO_EXTENSIONS + !wsi->count_act_ext && +#endif + wsi->u.ws.rsv) { + lwsl_info("illegal rsv bits set\n"); + return -1; + } + wsi->u.ws.final = !!((c >> 7) & 1); + lwsl_ext("%s: This RX frame Final %d\n", __func__, + wsi->u.ws.final); + + if (wsi->u.ws.owed_a_fin && + (wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME || + wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) { + lwsl_info("hey you owed us a FIN\n"); + return -1; + } + if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) { + wsi->u.ws.continuation_possible = 0; + wsi->u.ws.owed_a_fin = 0; + } + + if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) { + lwsl_info("control msg can't be fragmented\n"); + return -1; + } + if (!wsi->u.ws.final) + wsi->u.ws.owed_a_fin = 1; + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.frame_is_binary = wsi->u.ws.opcode == + LWSWSOPC_BINARY_FRAME; + break; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + + default: + lwsl_err("unknown spec version %02d\n", + wsi->ietf_spec_revision); + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN: + + wsi->u.ws.this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->u.ws.rx_packet_length = c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (c) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->u.ws.rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->u.ws.rx_packet_length |= c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + lwsl_warn("b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } +#if defined __LP64__ + wsi->u.ws.rx_packet_length = ((size_t)c) << 56; +#else + wsi->u.ws.rx_packet_length = 0; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->u.ws.rx_packet_length |= (size_t)c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_1: + wsi->u.ws.mask[0] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_2: + wsi->u.ws.mask[1] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_3: + wsi->u.ws.mask[2] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_4: + wsi->u.ws.mask[3] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + + assert(wsi->u.ws.rx_ubuf); + + if (wsi->u.ws.rx_draining_ext) + goto drain_extension; + + if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce) + c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3]; + + wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c; + + if (--wsi->u.ws.rx_packet_length == 0) { + /* spill because we have the whole frame */ + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + + /* + * if there's no protocol max frame size given, we are + * supposed to default to context->pt_serv_buf_size + */ + if (!wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) + break; + + if (wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) + break; + + /* spill because we filled our rx buffer */ +spill: + + handled = 0; + + /* + * is this frame a control packet we should take care of at this + * layer? If so service it and hide it from the user callback + */ + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_CLOSE: + pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE]; + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8) && + wsi->u.ws.rx_ubuf_head > 2 && + lws_check_utf8(&wsi->u.ws.utf8, pp + 2, + wsi->u.ws.rx_ubuf_head - 2)) + goto utf8_fail; + + /* is this an acknowledgement of our close? */ + if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + lwsl_parser("seen server's close ack\n"); + return -1; + } + + lwsl_parser("client sees server close len = %d\n", + wsi->u.ws.rx_ubuf_head); + if (wsi->u.ws.rx_ubuf_head >= 2) { + close_code = (pp[0] << 8) | pp[1]; + if (close_code < 1000 || + close_code == 1004 || + close_code == 1005 || + close_code == 1006 || + close_code == 1012 || + close_code == 1013 || + close_code == 1014 || + close_code == 1015 || + (close_code >= 1016 && close_code < 3000) + ) { + pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff; + pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff; + } + } + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, pp, + wsi->u.ws.rx_ubuf_head)) + return -1; + + if (lws_partial_buffered(wsi)) + /* + * if we're in the middle of something, + * we can't do a normal close response and + * have to just close our end. + */ + wsi->socket_is_permanently_unusable = 1; + else + /* + * parrot the close packet payload back + * we do not care about how it went, we are closing + * immediately afterwards + */ + lws_write(wsi, (unsigned char *) + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head, + LWS_WRITE_CLOSE); + wsi->state = LWSS_RETURNED_CLOSE_ALREADY; + /* close the connection */ + return -1; + + case LWSWSOPC_PING: + lwsl_info("received %d byte ping, sending pong\n", + wsi->u.ws.rx_ubuf_head); + + /* he set a close reason on this guy, ignore PING */ + if (wsi->u.ws.close_in_ping_buffer_len) + goto ping_drop; + + if (wsi->u.ws.ping_pending_flag) { + /* + * there is already a pending ping payload + * we should just log and drop + */ + lwsl_parser("DROP PING since one pending\n"); + goto ping_drop; + } + + /* control packets can only be < 128 bytes long */ + if (wsi->u.ws.rx_ubuf_head > 128 - 3) { + lwsl_parser("DROP PING payload too large\n"); + goto ping_drop; + } + + /* stash the pong payload */ + memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; + wsi->u.ws.ping_pending_flag = 1; + + /* get it sent as soon as possible */ + lws_callback_on_writable(wsi); +ping_drop: + wsi->u.ws.rx_ubuf_head = 0; + handled = 1; + break; + + case LWSWSOPC_PONG: + lwsl_info("client receied pong\n"); + lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + if (wsi->pending_timeout == + PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { + lwsl_info("%p: received expected PONG\n", wsi); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + } + + /* issue it */ + callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; + break; + + case LWSWSOPC_CONTINUATION: + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + break; + + default: + + lwsl_parser("Reserved opc 0x%2X\n", wsi->u.ws.opcode); + + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (lws_ext_cb_active(wsi, + LWS_EXT_CB_EXTENDED_PAYLOAD_RX, + &eff_buf, 0) <= 0) { + /* not handled or failed */ + lwsl_ext("Unhandled ext opc 0x%x\n", + wsi->u.ws.opcode); + wsi->u.ws.rx_ubuf_head = 0; + + return 0; + } + handled = 1; + break; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using lws_write + */ + if (handled) + goto already_done; + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) + goto already_done; + +drain_extension: + lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); + lwsl_ext("Ext RX returned %d\n", n); + if (n < 0) { + wsi->socket_is_permanently_unusable = 1; + return -1; + } + + lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len); + + if (rx_draining_ext && !eff_buf.token_len) { + lwsl_debug(" --- ending drain on 0 read result\n"); + goto already_done; + } + + if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) { + if (lws_check_utf8(&wsi->u.ws.utf8, + (unsigned char *)eff_buf.token, + eff_buf.token_len)) + goto utf8_fail; + + /* we are ending partway through utf-8 character? */ + if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final && + wsi->u.ws.utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); +utf8_fail: + lwsl_info("utf8 error\n"); + return -1; + } + } + + if (eff_buf.token_len < 0 && + callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG) + goto already_done; + + if (!eff_buf.token) + goto already_done; + + eff_buf.token[eff_buf.token_len] = '\0'; + + if (!wsi->protocol->callback) + goto already_done; + + if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG) + lwsl_info("Client doing pong callback\n"); + + if (n && eff_buf.token_len) + /* extension had more... main loop will come back + * we want callback to be done with this set, if so, + * because lws_is_final() hides it was final until the + * last chunk + */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state == LWSS_AWAITING_CLOSE_ACK) + goto already_done; + + m = wsi->protocol->callback(wsi, + (enum lws_callback_reasons)callback_action, + wsi->user_space, eff_buf.token, eff_buf.token_len); + + /* if user code wants to close, let caller know */ + if (m) + return 1; + +already_done: + wsi->u.ws.rx_ubuf_head = 0; + break; + default: + lwsl_err("client rx illegal state\n"); + return 1; + } + + return 0; + +illegal_ctl_length: + lwsl_warn("Control frame asking for extended length is illegal\n"); + + /* kill the connection */ + return -1; +} + + |