summaryrefslogtreecommitdiffstats
path: root/web/server/h2o/libh2o/lib/websocket.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--web/server/h2o/libh2o/lib/websocket.c238
1 files changed, 238 insertions, 0 deletions
diff --git a/web/server/h2o/libh2o/lib/websocket.c b/web/server/h2o/libh2o/lib/websocket.c
new file mode 100644
index 00000000..2886acda
--- /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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <openssl/sha.h>
+#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);
+}