diff options
| author | eeeee <eeeee@qwe123.info> | 2015-02-07 14:15:19 -0800 |
|---|---|---|
| committer | def <dennis@felsin9.de> | 2015-04-19 16:05:13 +0200 |
| commit | 424ce4987ced7deda3e355b7b6373680bf506646 (patch) | |
| tree | 239537b1938ac18c6542a05088bc26c6a2860408 /src/engine/external/libwebsockets/server.c | |
| parent | 9bbe115c522835c34c29196f2595c29c6bf00701 (diff) | |
| download | zcatch-424ce4987ced7deda3e355b7b6373680bf506646.tar.gz zcatch-424ce4987ced7deda3e355b7b6373680bf506646.zip | |
added libwebsockets source code
Diffstat (limited to 'src/engine/external/libwebsockets/server.c')
| -rw-r--r-- | src/engine/external/libwebsockets/server.c | 1162 |
1 files changed, 1162 insertions, 0 deletions
diff --git a/src/engine/external/libwebsockets/server.c b/src/engine/external/libwebsockets/server.c new file mode 100644 index 00000000..ed5c47b2 --- /dev/null +++ b/src/engine/external/libwebsockets/server.c @@ -0,0 +1,1162 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2013 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" + +int lws_context_init_server(struct lws_context_creation_info *info, + struct libwebsocket_context *context) +{ + int n; + int sockfd; + struct sockaddr_in sin; + socklen_t len = sizeof(sin); + int opt = 1; + struct libwebsocket *wsi; +#ifdef LWS_USE_IPV6 + struct sockaddr_in6 serv_addr6; +#endif + struct sockaddr_in serv_addr4; + struct sockaddr *v; + + /* set up our external listening socket we serve on */ + + if (info->port == CONTEXT_PORT_NO_LISTEN) + return 0; + +#ifdef LWS_USE_IPV6 + if (LWS_IPV6_ENABLED(context)) + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd < 0) { + lwsl_err("ERROR opening socket\n"); + return 1; + } + + /* + * allow us to restart even if old sockets in TIME_WAIT + */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (const void *)&opt, sizeof(opt)) < 0) { + compatible_close(sockfd); + return 1; + } + + lws_plat_set_socket_options(context, sockfd); + +#ifdef LWS_USE_IPV6 + if (LWS_IPV6_ENABLED(context)) { + v = (struct sockaddr *)&serv_addr6; + n = sizeof(struct sockaddr_in6); + bzero((char *) &serv_addr6, sizeof(serv_addr6)); + serv_addr6.sin6_addr = in6addr_any; + serv_addr6.sin6_family = AF_INET6; + serv_addr6.sin6_port = htons(info->port); + } else +#endif + { + v = (struct sockaddr *)&serv_addr4; + n = sizeof(serv_addr4); + bzero((char *) &serv_addr4, sizeof(serv_addr4)); + serv_addr4.sin_addr.s_addr = INADDR_ANY; + serv_addr4.sin_family = AF_INET; + + if (info->iface) { + if (interface_to_sa(context, info->iface, + (struct sockaddr_in *)v, n) < 0) { + lwsl_err("Unable to find interface %s\n", + info->iface); + compatible_close(sockfd); + return 1; + } + } + + serv_addr4.sin_port = htons(info->port); + } /* ipv4 */ + + n = bind(sockfd, v, n); + if (n < 0) { + lwsl_err("ERROR on binding to port %d (%d %d)\n", + info->port, n, LWS_ERRNO); + compatible_close(sockfd); + return 1; + } + + if (getsockname(sockfd, (struct sockaddr *)&sin, &len) == -1) + lwsl_warn("getsockname: %s\n", strerror(LWS_ERRNO)); + else + info->port = ntohs(sin.sin_port); + + context->listen_port = info->port; + + wsi = lws_zalloc(sizeof(struct libwebsocket)); + if (wsi == NULL) { + lwsl_err("Out of mem\n"); + compatible_close(sockfd); + return 1; + } + wsi->sock = sockfd; + wsi->mode = LWS_CONNMODE_SERVER_LISTENER; + + insert_wsi_socket_into_fds(context, wsi); + + context->listen_service_modulo = LWS_LISTEN_SERVICE_MODULO; + context->listen_service_count = 0; + context->listen_service_fd = sockfd; + + listen(sockfd, LWS_SOMAXCONN); + lwsl_notice(" Listening on port %d\n", info->port); + + return 0; +} + +int +_libwebsocket_rx_flow_control(struct libwebsocket *wsi) +{ + struct libwebsocket_context *context = wsi->protocol->owning_server; + + /* there is no pending change */ + if (!(wsi->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)) + return 0; + + /* stuff is still buffered, not ready to really accept new input */ + if (wsi->rxflow_buffer) { + /* get ourselves called back to deal with stashed buffer */ + libwebsocket_callback_on_writable(context, wsi); + return 0; + } + + /* pending is cleared, we can change rxflow state */ + + wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE; + + lwsl_info("rxflow: wsi %p change_to %d\n", wsi, + wsi->rxflow_change_to & LWS_RXFLOW_ALLOW); + + /* adjust the pollfd for this wsi */ + + if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) { + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_info("%s: fail\n", __func__); + return -1; + } + } else + if (lws_change_pollfd(wsi, LWS_POLLIN, 0)) + return -1; + + return 0; +} + +int lws_http_action(struct libwebsocket_context *context, + struct libwebsocket *wsi) +{ + char *uri_ptr = NULL; + int uri_len = 0; + enum http_version request_version; + enum http_connection_type connection_type; + int http_version_len; + char content_length_str[32]; + char http_version_str[10]; + char http_conn_str[20]; + int n; + + /* it's not websocket.... shall we accept it as http? */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && + !lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) && +#ifdef LWS_USE_HTTP2 + !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH) && +#endif + !lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { + lwsl_warn("Missing URI in HTTP request\n"); + goto bail_nuke_ah; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) && + lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { + lwsl_warn("GET and POST methods?\n"); + goto bail_nuke_ah; + } + + if (libwebsocket_ensure_user_space(wsi)) + goto bail_nuke_ah; + +#ifdef LWS_USE_HTTP2 + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH)) { + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH); + lwsl_info("HTTP2 request for '%s'\n", uri_ptr); + goto got_uri; + } +#endif + if (lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI)) { + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_OPTIONS_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_OPTIONS_URI); + lwsl_info("HTTP OPTIONS request for '%s'\n", uri_ptr); + goto got_uri; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_POST_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI); + lwsl_info("HTTP POST request for '%s'\n", uri_ptr); + goto got_uri; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) { + uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); + uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); + lwsl_info("HTTP GET request for '%s'\n", uri_ptr); + } + +got_uri: + /* HTTP header had a content length? */ + + wsi->u.http.content_length = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) + wsi->u.http.content_length = 100 * 1024 * 1024; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + lws_hdr_copy(wsi, content_length_str, + sizeof(content_length_str) - 1, + WSI_TOKEN_HTTP_CONTENT_LENGTH); + wsi->u.http.content_length = atoi(content_length_str); + } + + /* http_version? Default to 1.0, override with token: */ + request_version = HTTP_VERSION_1_0; + + /* Works for single digit HTTP versions. : */ + http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); + if (http_version_len > 7) { + lws_hdr_copy(wsi, http_version_str, + sizeof(http_version_str) - 1, WSI_TOKEN_HTTP); + if (http_version_str[5] == '1' && http_version_str[7] == '1') + request_version = HTTP_VERSION_1_1; + } + wsi->u.http.request_version = request_version; + + /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ + if (request_version == HTTP_VERSION_1_1) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + else + connection_type = HTTP_CONNECTION_CLOSE; + + /* Override default if http "Connection:" header: */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { + lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1, + WSI_TOKEN_CONNECTION); + http_conn_str[sizeof(http_conn_str) - 1] = '\0'; + if (!strcasecmp(http_conn_str, "keep-alive")) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + else + if (strcasecmp(http_conn_str, "close")) + connection_type = HTTP_CONNECTION_CLOSE; + } + wsi->u.http.connection_type = connection_type; + + n = 0; + if (wsi->protocol->callback) + n = wsi->protocol->callback(context, wsi, + LWS_CALLBACK_FILTER_HTTP_CONNECTION, + wsi->user_space, uri_ptr, uri_len); + + if (!n) { + /* + * if there is content supposed to be coming, + * put a timeout on it having arrived + */ + libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + AWAITING_TIMEOUT); + + if (wsi->protocol->callback) + n = wsi->protocol->callback(context, wsi, + LWS_CALLBACK_HTTP, + wsi->user_space, uri_ptr, uri_len); + } + + /* now drop the header info we kept a pointer to */ + lws_free2(wsi->u.http.ah); + + if (n) { + lwsl_info("LWS_CALLBACK_HTTP closing\n"); + return 1; /* struct ah ptr already nuked */ } + + /* + * If we're not issuing a file, check for content_length or + * HTTP keep-alive. No keep-alive header allocation for + * ISSUING_FILE, as this uses HTTP/1.0. + * + * In any case, return 0 and let libwebsocket_read decide how to + * proceed based on state + */ + if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE) + /* Prepare to read body if we have a content length: */ + if (wsi->u.http.content_length > 0) + wsi->state = WSI_STATE_HTTP_BODY; + + return 0; + +bail_nuke_ah: + /* drop the header info */ + lws_free2(wsi->u.hdr.ah); + + return 1; +} + + +int lws_handshake_server(struct libwebsocket_context *context, + struct libwebsocket *wsi, unsigned char **buf, size_t len) +{ + struct allocated_headers *ah; + int protocol_len; + char protocol_list[128]; + char protocol_name[32]; + char *p; + int n, hit; + + /* LWS_CONNMODE_WS_SERVING */ + + while (len--) { + if (libwebsocket_parse(context, wsi, *(*buf)++)) { + lwsl_info("libwebsocket_parse failed\n"); + goto bail_nuke_ah; + } + + if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) + continue; + + lwsl_parser("libwebsocket_parse sees parsing complete\n"); + + wsi->mode = LWS_CONNMODE_PRE_WS_SERVING_ACCEPT; + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* is this websocket protocol or normal http 1.0? */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE) || + !lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { + + ah = wsi->u.hdr.ah; + + lws_union_transition(wsi, LWS_CONNMODE_HTTP_SERVING_ACCEPTED); + wsi->state = WSI_STATE_HTTP; + wsi->u.http.fd = LWS_INVALID_FILE; + + /* expose it at the same offset as u.hdr */ + wsi->u.http.ah = ah; + + n = lws_http_action(context, wsi); + + return n; + } + + if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), + "websocket")) + goto upgrade_ws; +#ifdef LWS_USE_HTTP2 + if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), + "h2c-14")) + goto upgrade_h2c; +#endif + /* dunno what he wanted to upgrade to */ + goto bail_nuke_ah; + +#ifdef LWS_USE_HTTP2 +upgrade_h2c: + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { + lwsl_err("missing http2_settings\n"); + goto bail_nuke_ah; + } + + lwsl_err("h2c upgrade...\n"); + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); + /* convert the peer's HTTP-Settings */ + n = lws_b64_decode_string(p, protocol_list, sizeof(protocol_list)); + if (n < 0) { + lwsl_parser("HTTP2_SETTINGS too long\n"); + return 1; + } + + /* adopt the header info */ + + ah = wsi->u.hdr.ah; + + lws_union_transition(wsi, LWS_CONNMODE_HTTP2_SERVING); + + /* http2 union member has http union struct at start */ + wsi->u.http.ah = ah; + + lws_http2_init(&wsi->u.http2.peer_settings); + lws_http2_init(&wsi->u.http2.my_settings); + + /* HTTP2 union */ + + lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, (unsigned char *)protocol_list, n); + + strcpy(protocol_list, + "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); + n = lws_issue_raw(wsi, (unsigned char *)protocol_list, + strlen(protocol_list)); + if (n != strlen(protocol_list)) { + lwsl_debug("http2 switch: ERROR writing to socket\n"); + return 1; + } + + wsi->state = WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE; + + return 0; +#endif + +upgrade_ws: + if (!wsi->protocol) + lwsl_err("NULL protocol at libwebsocket_read\n"); + + /* + * It's websocket + * + * Select the first protocol we support from the list + * the client sent us. + * + * Copy it to remove header fragmentation + */ + + if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, + WSI_TOKEN_PROTOCOL) < 0) { + lwsl_err("protocol list too long"); + goto bail_nuke_ah; + } + + protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + protocol_list[protocol_len] = '\0'; + p = protocol_list; + hit = 0; + + while (*p && !hit) { + n = 0; + while (n < sizeof(protocol_name) - 1 && *p && *p !=',') + protocol_name[n++] = *p++; + protocol_name[n] = '\0'; + if (*p) + p++; + + lwsl_info("checking %s\n", protocol_name); + + n = 0; + while (wsi->protocol && context->protocols[n].callback) { + if (!wsi->protocol->name) { + n++; + continue; + } + if (!strcmp(context->protocols[n].name, + protocol_name)) { + lwsl_info("prot match %d\n", n); + wsi->protocol = &context->protocols[n]; + hit = 1; + break; + } + + n++; + } + } + + /* we didn't find a protocol he wanted? */ + + if (!hit) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL) == + NULL) { + /* + * some clients only have one protocol and + * do not sent the protocol list header... + * allow it and match to protocol 0 + */ + lwsl_info("defaulting to prot 0 handler\n"); + wsi->protocol = &context->protocols[0]; + } else { + lwsl_err("No protocol from list \"%s\" supported\n", + protocol_list); + goto bail_nuke_ah; + } + } + + /* allocate wsi->user storage */ + if (libwebsocket_ensure_user_space(wsi)) + goto bail_nuke_ah; + + /* + * Give the user code a chance to study the request and + * have the opportunity to deny it + */ + + if ((wsi->protocol->callback)(wsi->protocol->owning_server, wsi, + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, + wsi->user_space, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { + lwsl_warn("User code denied connection\n"); + goto bail_nuke_ah; + } + + + /* + * Perform the handshake according to the protocol version the + * client announced + */ + + switch (wsi->ietf_spec_revision) { + case 13: + lwsl_parser("lws_parse calling handshake_04\n"); + if (handshake_0405(context, wsi)) { + lwsl_info("hs0405 has failed the connection\n"); + goto bail_nuke_ah; + } + break; + + default: + lwsl_warn("Unknown client spec version %d\n", + wsi->ietf_spec_revision); + goto bail_nuke_ah; + } + + /* drop the header info -- no bail_nuke_ah after this */ + lws_free_header_table(wsi); + + lws_union_transition(wsi, LWS_CONNMODE_WS_SERVING); + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, use + * a big default for compatibility + */ + + n = wsi->protocol->rx_buffer_size; + if (!n) + n = LWS_MAX_SOCKET_IO_BUF; + n += LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING; + wsi->u.ws.rx_user_buffer = lws_malloc(n); + if (!wsi->u.ws.rx_user_buffer) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + return 1; + } + lwsl_info("Allocating RX buffer %d\n", n); + + if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + return 1; + } + + lwsl_parser("accepted v%02d connection\n", + wsi->ietf_spec_revision); + } /* while all chars are handled */ + + return 0; + +bail_nuke_ah: + /* drop the header info */ + lws_free_header_table(wsi); + return 1; +} + +struct libwebsocket * +libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) +{ + struct libwebsocket *new_wsi; + + new_wsi = lws_zalloc(sizeof(struct libwebsocket)); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* intialize the instance struct */ + + new_wsi->state = WSI_STATE_HTTP; + new_wsi->mode = LWS_CONNMODE_HTTP_SERVING; + new_wsi->hdr_parsing_completed = 0; + + if (lws_allocate_header_table(new_wsi)) { + lws_free(new_wsi); + return NULL; + } + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = context->protocols; + new_wsi->user_space = NULL; + new_wsi->ietf_spec_revision = 0; + + /* + * outermost create notification for wsi + * no user_space because no protocol selection + */ + context->protocols[0].callback(context, new_wsi, + LWS_CALLBACK_WSI_CREATE, NULL, NULL, 0); + + return new_wsi; +} + +/** + * lws_http_transaction_completed() - wait for new http transaction or close + * @wsi: websocket connection + * + * Returns 1 if the HTTP connection must close now + * Returns 0 and resets connection to wait for new HTTP header / + * transaction if possible + */ + +LWS_VISIBLE +int lws_http_transaction_completed(struct libwebsocket *wsi) +{ + /* if we can't go back to accept new headers, drop the connection */ + if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + lwsl_info("%s: close connection\n", __func__); + return 1; + } + + /* otherwise set ourselves up ready to go again */ + wsi->state = WSI_STATE_HTTP; + + lwsl_info("%s: await new transaction\n", __func__); + + return 0; +} + +int lws_server_socket_service(struct libwebsocket_context *context, + struct libwebsocket *wsi, struct libwebsocket_pollfd *pollfd) +{ + struct libwebsocket *new_wsi = NULL; + int accept_fd = 0; + socklen_t clilen; + struct sockaddr_in cli_addr; + int n; + int len; + + switch (wsi->mode) { + + case LWS_CONNMODE_HTTP_SERVING: + case LWS_CONNMODE_HTTP_SERVING_ACCEPTED: + case LWS_CONNMODE_HTTP2_SERVING: + + /* handle http headers coming in */ + + /* pending truncated sends have uber priority */ + + if (wsi->truncated_send_len) { + if (pollfd->revents & LWS_POLLOUT) + if (lws_issue_raw(wsi, wsi->truncated_send_malloc + + wsi->truncated_send_offset, + wsi->truncated_send_len) < 0) { + lwsl_info("closing from socket service\n"); + return -1; + } + /* + * we can't afford to allow input processing send + * something new, so spin around he event loop until + * he doesn't have any partials + */ + break; + } + + /* any incoming data ready? */ + + if (pollfd->revents & LWS_POLLIN) { + len = lws_ssl_capable_read(context, wsi, + context->service_buffer, + sizeof(context->service_buffer)); + switch (len) { + case 0: + lwsl_info("lws_server_skt_srv: read 0 len\n"); + /* lwsl_info(" state=%d\n", wsi->state); */ + if (!wsi->hdr_parsing_completed) + lws_free_header_table(wsi); + /* fallthru */ + case LWS_SSL_CAPABLE_ERROR: + libwebsocket_close_and_free_session( + context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 0; + case LWS_SSL_CAPABLE_MORE_SERVICE: + goto try_pollout; + } + + /* just ignore incoming if waiting for close */ + if (wsi->state != WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE) { + + /* hm this may want to send (via HTTP callback for example) */ + n = libwebsocket_read(context, wsi, + context->service_buffer, len); + if (n < 0) + /* we closed wsi */ + return 0; + + /* hum he may have used up the writability above */ + break; + } + } + +try_pollout: + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + break; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + goto fail; + + lws_libev_io(context, wsi, LWS_EV_STOP | LWS_EV_WRITE); + + if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE) { + n = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi->protocol->owning_server, + wsi, LWS_CALLBACK_HTTP_WRITEABLE, + wsi->user_space, + NULL, + 0); + if (n < 0) + goto fail; + break; + } + + /* >0 == completion, <0 == error */ + n = libwebsockets_serve_http_file_fragment(context, wsi); + if (n < 0 || (n > 0 && lws_http_transaction_completed(wsi))) + goto fail; + break; + + case LWS_CONNMODE_SERVER_LISTENER: + + /* pollin means a client has connected to us then */ + + if (!(pollfd->revents & LWS_POLLIN)) + break; + + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + lws_latency_pre(context, wsi); + accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, + &clilen); + lws_latency(context, wsi, + "unencrypted accept LWS_CONNMODE_SERVER_LISTENER", + accept_fd, accept_fd >= 0); + if (accept_fd < 0) { + if (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK) { + lwsl_debug("accept asks to try again\n"); + break; + } + lwsl_warn("ERROR on accept: %s\n", strerror(LWS_ERRNO)); + break; + } + + lws_plat_set_socket_options(context, accept_fd); + + /* + * look at who we connected to and give user code a chance + * to reject based on client IP. There's no protocol selected + * yet so we issue this to protocols[0] + */ + + if ((context->protocols[0].callback)(context, wsi, + LWS_CALLBACK_FILTER_NETWORK_CONNECTION, + NULL, (void *)(long)accept_fd, 0)) { + lwsl_debug("Callback denied network connection\n"); + compatible_close(accept_fd); + break; + } + + new_wsi = libwebsocket_create_new_server_wsi(context); + if (new_wsi == NULL) { + compatible_close(accept_fd); + break; + } + + new_wsi->sock = accept_fd; + + /* the transport is accepted... give him time to negotiate */ + libwebsocket_set_timeout(new_wsi, + PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, + AWAITING_TIMEOUT); + + /* + * A new connection was accepted. Give the user a chance to + * set properties of the newly created wsi. There's no protocol + * selected yet so we issue this to protocols[0] + */ + + (context->protocols[0].callback)(context, new_wsi, + LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED, NULL, NULL, 0); + + lws_libev_accept(context, new_wsi, accept_fd); + + if (!LWS_SSL_ENABLED(context)) { + lwsl_debug("accepted new conn port %u on fd=%d\n", + ntohs(cli_addr.sin_port), accept_fd); + + insert_wsi_socket_into_fds(context, new_wsi); + } + break; + + default: + break; + } + + if (lws_server_socket_service_ssl(context, &wsi, new_wsi, + accept_fd, pollfd)) + goto fail; + + return 0; + +fail: + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 1; +} + +#include "lextable-strings.h" + +const unsigned char *lws_token_to_string(enum lws_token_indexes token) +{ + if ((unsigned int)token >= ARRAY_SIZE(set)) + return NULL; + + return (unsigned char *)set[token]; +} + +int lws_add_http_header_by_name(struct libwebsocket_context *context, + struct libwebsocket *wsi, + const unsigned char *name, + const unsigned char *value, + int length, + unsigned char **p, + unsigned char *end) +{ +#ifdef LWS_USE_HTTP2 + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) + return lws_add_http2_header_by_name(context, wsi, name, value, length, p, end); +#endif + if (name) { + while (*p < end && *name) + *((*p)++) = *name++; + + if (*p == end) + return 1; + + *((*p)++) = ' '; + } + if (*p + length + 3 >= end) + return 1; + + memcpy(*p, value, length); + *p += length; + + *((*p)++) = '\x0d'; + *((*p)++) = '\x0a'; + + return 0; +} + +int lws_finalize_http_header(struct libwebsocket_context *context, + struct libwebsocket *wsi, + unsigned char **p, + unsigned char *end) +{ +#ifdef LWS_USE_HTTP2 + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) + return 0; +#endif + + if ((long)(end - *p) < 3) + return 1; + + *((*p)++) = '\x0d'; + *((*p)++) = '\x0a'; + + return 0; +} + +int lws_add_http_header_by_token(struct libwebsocket_context *context, + struct libwebsocket *wsi, + enum lws_token_indexes token, + const unsigned char *value, + int length, + unsigned char **p, + unsigned char *end) +{ + const unsigned char *name; +#ifdef LWS_USE_HTTP2 + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) + return lws_add_http2_header_by_token(context, wsi, token, value, length, p, end); +#endif + name = lws_token_to_string(token); + if (!name) + return 1; + + return lws_add_http_header_by_name(context, wsi, name, value, length, p, end); +} + +int lws_add_http_header_content_length(struct libwebsocket_context *context, + struct libwebsocket *wsi, + unsigned long content_length, + unsigned char **p, + unsigned char *end) +{ + char b[24]; + int n; + + n = sprintf(b, "%lu", content_length); + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)b, n, p, end)) + return 1; + wsi->u.http.content_length = content_length; + wsi->u.http.content_remain = content_length; + + return 0; +} + +static const char *err400[] = { + "Bad Request", + "Unauthorized", + "Payment Required", + "Forbidden", + "Not Found", + "Method Not Allowed", + "Not Acceptable", + "Proxy Auth Required", + "Request Timeout", + "Conflict", + "Gone", + "Length Required", + "Precondition Failed", + "Request Entity Too Large", + "Request URI too Long", + "Unsupported Media Type", + "Requested Range Not Satisfiable", + "Expectation Failed" +}; + +static const char *err500[] = { + "Internal Server Error", + "Not Implemented", + "Bad Gateway", + "Service Unavailable", + "Gateway Timeout", + "HTTP Version Not Supported" +}; + +int lws_add_http_header_status(struct libwebsocket_context *context, + struct libwebsocket *wsi, + unsigned int code, + unsigned char **p, + unsigned char *end) +{ + unsigned char code_and_desc[60]; + const char *description = ""; + int n; + +#ifdef LWS_USE_HTTP2 + if (wsi->mode == LWS_CONNMODE_HTTP2_SERVING) + return lws_add_http2_header_status(context, wsi, code, p, end); +#endif + if (code >= 400 && code < (400 + ARRAY_SIZE(err400))) + description = err400[code - 400]; + if (code >= 500 && code < (500 + ARRAY_SIZE(err500))) + description = err500[code - 500]; + + n = sprintf((char *)code_and_desc, "HTTP/1.0 %u %s", code, description); + + return lws_add_http_header_by_name(context, wsi, NULL, code_and_desc, n, p, end); +} + +/** + * libwebsockets_return_http_status() - Return simple http status + * @context: libwebsockets context + * @wsi: Websocket instance (available from user callback) + * @code: Status index, eg, 404 + * @html_body: User-readable HTML description < 1KB, or NULL + * + * Helper to report HTTP errors back to the client cleanly and + * consistently + */ +LWS_VISIBLE int libwebsockets_return_http_status( + struct libwebsocket_context *context, struct libwebsocket *wsi, + unsigned int code, const char *html_body) +{ + int n, m; + + unsigned char *p = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING; + unsigned char *start = p; + unsigned char *end = p + sizeof(context->service_buffer) - + LWS_SEND_BUFFER_PRE_PADDING; + + if (!html_body) + html_body = ""; + + if (lws_add_http_header_status(context, wsi, code, &p, end)) + return 1; + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) + return 1; + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) + return 1; + if (lws_finalize_http_header(context, wsi, &p, end)) + return 1; + + m = libwebsocket_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + if (m != (int)(p - start)) + return 1; + + n = sprintf((char *)start, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); + m = libwebsocket_write(wsi, start, n, LWS_WRITE_HTTP); + + return m != n; +} + +/** + * libwebsockets_serve_http_file() - Send a file back to the client using http + * @context: libwebsockets context + * @wsi: Websocket instance (available from user callback) + * @file: The file to issue over http + * @content_type: The http content type, eg, text/html + * @other_headers: NULL or pointer to \0-terminated other header string + * + * This function is intended to be called from the callback in response + * to http requests from the client. It allows the callback to issue + * local files down the http link in a single step. + * + * Returning <0 indicates error and the wsi should be closed. Returning + * >0 indicates the file was completely sent and + * lws_http_transaction_completed() called on the wsi (and close if != 0) + * ==0 indicates the file transfer is started and needs more service later, + * the wsi should be left alone. + */ + +LWS_VISIBLE int libwebsockets_serve_http_file( + struct libwebsocket_context *context, + struct libwebsocket *wsi, const char *file, + const char *content_type, const char *other_headers, + int other_headers_len) +{ + unsigned char *response = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING; + unsigned char *p = response; + unsigned char *end = p + sizeof(context->service_buffer) - + LWS_SEND_BUFFER_PRE_PADDING; + int ret = 0; + + wsi->u.http.fd = lws_plat_open_file(file, &wsi->u.http.filelen); + + if (wsi->u.http.fd == LWS_INVALID_FILE) { + lwsl_err("Unable to open '%s'\n", file); + libwebsockets_return_http_status(context, wsi, + HTTP_STATUS_NOT_FOUND, NULL); + return -1; + } + + if (lws_add_http_header_status(context, wsi, 200, &p, end)) + return -1; + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) + return -1; + if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end)) + return -1; + if (lws_add_http_header_content_length(context, wsi, wsi->u.http.filelen, &p, end)) + return -1; + + if (other_headers) { + if ((end - p) < other_headers_len) + return -1; + memcpy(p, other_headers, other_headers_len); + p += other_headers_len; + } + + if (lws_finalize_http_header(context, wsi, &p, end)) + return -1; + + ret = libwebsocket_write(wsi, response, + p - response, LWS_WRITE_HTTP_HEADERS); + if (ret != (p - response)) { + lwsl_err("_write returned %d from %d\n", ret, (p - response)); + return -1; + } + + wsi->u.http.filepos = 0; + wsi->state = WSI_STATE_HTTP_ISSUING_FILE; + + return libwebsockets_serve_http_file_fragment(context, wsi); +} + + +int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, + unsigned char *buf, size_t len) +{ + size_t n = 0; + int m; + +#if 0 + lwsl_parser("received %d byte packet\n", (int)len); + lwsl_hexdump(buf, len); +#endif + + /* let the rx protocol state machine have as much as it needs */ + + while (n < len) { + /* + * we were accepting input but now we stopped doing so + */ + if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) { + lws_rxflow_cache(wsi, buf, n, len); + + return 1; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; + + /* process the byte */ + m = libwebsocket_rx_sm(wsi, buf[n++]); + if (m < 0) + return -1; + } + + return 0; +} + +LWS_VISIBLE void +lws_server_get_canonical_hostname(struct libwebsocket_context *context, + struct lws_context_creation_info *info) +{ + if (info->options & LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME) + return; + + /* find canonical hostname */ + gethostname((char *)context->canonical_hostname, + sizeof(context->canonical_hostname) - 1); + + lwsl_notice(" canonical_hostname = %s\n", context->canonical_hostname); +} |