From 58daab21cd043e1dc37024a7f99b396788372918 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 9 Mar 2024 14:19:48 +0100 Subject: Merging upstream version 1.44.3. Signed-off-by: Daniel Baumann --- web/server/h2o/libh2o/lib/websocket.c | 238 ++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 web/server/h2o/libh2o/lib/websocket.c (limited to 'web/server/h2o/libh2o/lib/websocket.c') diff --git a/web/server/h2o/libh2o/lib/websocket.c b/web/server/h2o/libh2o/lib/websocket.c new file mode 100644 index 000000000..2886acda6 --- /dev/null +++ b/web/server/h2o/libh2o/lib/websocket.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2014 DeNA Co., Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#include +#include "h2o/websocket.h" + +#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +static void create_accept_key(char *dst, const char *client_key) +{ + uint8_t sha1buf[20], key_src[60]; + + memcpy(key_src, client_key, 24); + memcpy(key_src + 24, WS_GUID, 36); + SHA1(key_src, sizeof(key_src), sha1buf); + h2o_base64_encode(dst, sha1buf, sizeof(sha1buf), 0); + dst[28] = '\0'; +} + +static void on_close(h2o_websocket_conn_t *conn) +{ + (*conn->cb)(conn, NULL); +} + +static void on_recv(h2o_socket_t *sock, const char *err) +{ + h2o_websocket_conn_t *conn = sock->data; + + if (err != NULL) { + on_close(conn); + return; + } + h2o_websocket_proceed(conn); +} + +static void on_write_complete(h2o_socket_t *sock, const char *err) +{ + h2o_websocket_conn_t *conn = sock->data; + + if (err != NULL) { + on_close(conn); + return; + } + h2o_websocket_proceed(conn); +} + +static ssize_t recv_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, int flags, void *_conn) +{ + h2o_websocket_conn_t *conn = _conn; + + /* return WOULDBLOCK if no data */ + if (conn->sock->input->size == 0) { + wslay_event_set_error(conn->ws_ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + + if (conn->sock->input->size < len) + len = conn->sock->input->size; + memcpy(buf, conn->sock->input->bytes, len); + h2o_buffer_consume(&conn->sock->input, len); + return len; +} + +static ssize_t send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *_conn) +{ + h2o_websocket_conn_t *conn = _conn; + h2o_iovec_t buf; + + /* return WOULDBLOCK if pending (TODO: queue fixed number of chunks, instead of only one) */ + if (h2o_socket_is_writing(conn->sock)) { + wslay_event_set_error(conn->ws_ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + + /* copy data */ + conn->_write_buf = h2o_mem_realloc(conn->_write_buf, len); + memcpy(conn->_write_buf, data, len); + + /* write */ + buf.base = conn->_write_buf; + buf.len = len; + h2o_socket_write(conn->sock, &buf, 1, on_write_complete); + + return len; +} + +static void on_msg_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *_conn) +{ + h2o_websocket_conn_t *conn = _conn; + (*conn->cb)(conn, arg); +} + +static void on_complete(void *user_data, h2o_socket_t *sock, size_t reqsize) +{ + h2o_websocket_conn_t *conn = user_data; + + /* close the connection on error */ + if (sock == NULL) { + (*conn->cb)(conn, NULL); + return; + } + + conn->sock = sock; + sock->data = conn; + h2o_buffer_consume(&sock->input, reqsize); + h2o_websocket_proceed(conn); +} + +h2o_websocket_conn_t *h2o_upgrade_to_websocket(h2o_req_t *req, const char *client_key, void *data, h2o_websocket_msg_callback cb) +{ + h2o_websocket_conn_t *conn = h2o_mem_alloc(sizeof(*conn)); + char accept_key[29]; + + /* only for http1 connection */ + assert(req->version < 0x200); + + /* setup the context */ + memset(conn, 0, sizeof(*conn)); + // conn->sock = sock; set by on_complete + conn->ws_callbacks.recv_callback = recv_callback; + conn->ws_callbacks.send_callback = send_callback; + conn->ws_callbacks.on_msg_recv_callback = on_msg_callback; + conn->data = data; + conn->cb = cb; + + wslay_event_context_server_init(&conn->ws_ctx, &conn->ws_callbacks, conn); + + /* build response */ + create_accept_key(accept_key, client_key); + req->res.status = 101; + req->res.reason = "Switching Protocols"; + h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_UPGRADE, NULL, H2O_STRLIT("websocket")); + h2o_add_header_by_str(&req->pool, &req->res.headers, H2O_STRLIT("sec-websocket-accept"), 0, NULL, accept_key, + strlen(accept_key)); + + /* send */ + h2o_http1_upgrade(req, NULL, 0, on_complete, conn); + + return conn; +} + +int h2o_is_websocket_handshake(h2o_req_t *req, const char **ws_client_key) +{ + ssize_t key_header_index; + + *ws_client_key = NULL; + + /* method */ + if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("GET"))) { + /* ok */ + } else { + return 0; + } + + /* upgrade header */ + if (req->upgrade.base != NULL && h2o_lcstris(req->upgrade.base, req->upgrade.len, H2O_STRLIT("websocket"))) { + /* ok */ + } else { + return 0; + } + /* sec-websocket-key header */ + if ((key_header_index = h2o_find_header_by_str(&req->headers, H2O_STRLIT("sec-websocket-key"), -1)) != -1) { + if (req->headers.entries[key_header_index].value.len != 24) { + return -1; + } + } else { + return 0; + } + + *ws_client_key = req->headers.entries[key_header_index].value.base; + return 0; +} + +void h2o_websocket_close(h2o_websocket_conn_t *conn) +{ + if (conn->sock != NULL) + h2o_socket_close(conn->sock); + free(conn->_write_buf); + wslay_event_context_free(conn->ws_ctx); + free(conn); +} + +void h2o_websocket_proceed(h2o_websocket_conn_t *conn) +{ + int handled; + + /* run the loop until getting to a point where no more progress can be achieved */ + do { + handled = 0; + if (!h2o_socket_is_writing(conn->sock) && wslay_event_want_write(conn->ws_ctx)) { + if (wslay_event_send(conn->ws_ctx) != 0) { + goto Close; + } + handled = 1; + } + if (conn->sock->input->size != 0 && wslay_event_want_read(conn->ws_ctx)) { + if (wslay_event_recv(conn->ws_ctx) != 0) { + goto Close; + } + handled = 1; + } + } while (handled); + + if (wslay_event_want_read(conn->ws_ctx)) { + h2o_socket_read_start(conn->sock, on_recv); + } else if (h2o_socket_is_writing(conn->sock) || wslay_event_want_write(conn->ws_ctx)) { + h2o_socket_read_stop(conn->sock); + } else { + /* nothing is going on... close the socket */ + goto Close; + } + + return; + +Close: + on_close(conn); +} -- cgit v1.2.3