diff --git a/lib/handshake.c b/lib/handshake.c index dffd2d23..4d794c8d 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -46,6 +46,10 @@ * Sec-WebSocket-Protocol: chat */ +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + /* * We have to take care about parsing because the headers may be split * into multiple fragments. They may contain unknown headers with arbitrary @@ -58,97 +62,86 @@ libwebsocket_read(struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned char *buf, size_t len) { size_t n; + int body_chunk_len; + int content_remain = 0; + unsigned char *last_char; switch (wsi->state) { - - case WSI_STATE_HTTP_BODY: -http_postbody: - while (len--) { - - if (wsi->u.http.content_length_seen >= wsi->u.http.content_length) - break; - - wsi->u.http.post_buffer[wsi->u.http.body_index++] = *buf++; - wsi->u.http.content_length_seen++; - n = wsi->protocol->rx_buffer_size; - if (!n) - n = LWS_MAX_SOCKET_IO_BUF; - - if (wsi->u.http.body_index != n && - wsi->u.http.content_length_seen != wsi->u.http.content_length) - continue; - - if (wsi->protocol->callback) { - n = wsi->protocol->callback( - wsi->protocol->owning_server, wsi, - LWS_CALLBACK_HTTP_BODY, - wsi->user_space, wsi->u.http.post_buffer, - wsi->u.http.body_index); - wsi->u.http.body_index = 0; - if (n) - goto bail; - } - - if (wsi->u.http.content_length_seen == wsi->u.http.content_length) { - /* he sent the content in time */ - libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); - n = wsi->protocol->callback( - wsi->protocol->owning_server, wsi, - LWS_CALLBACK_HTTP_BODY_COMPLETION, - wsi->user_space, NULL, 0); - wsi->u.http.body_index = 0; - if (n) - goto bail; - } - - } - - /* - * we need to spill here so everything is seen in the case - * there is no content-length - */ - if (wsi->u.http.body_index && wsi->protocol->callback) { - n = wsi->protocol->callback( - wsi->protocol->owning_server, wsi, - LWS_CALLBACK_HTTP_BODY, - wsi->user_space, wsi->u.http.post_buffer, - wsi->u.http.body_index); - wsi->u.http.body_index = 0; - if (n) - goto bail; - } - break; - - case WSI_STATE_HTTP_ISSUING_FILE: +http_new: case WSI_STATE_HTTP: + case WSI_STATE_HTTP_ISSUING_FILE: wsi->state = WSI_STATE_HTTP_HEADERS; wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.hdr.lextable_pos = 0; /* fallthru */ case WSI_STATE_HTTP_HEADERS: - lwsl_parser("issuing %d bytes to parser\n", (int)len); if (lws_handshake_client(wsi, &buf, len)) goto bail; + last_char = buf; + if (lws_handshake_server(context, wsi, &buf, len)) + /* Handshake indicates this session is done. */ + goto bail; + /* It's possible that we've exhausted our data already, but * lws_handshake_server doesn't update len for us. Figure out how * much was read, so that we can proceed appropriately: */ - { - unsigned char *last_char = buf; - switch (lws_handshake_server(context, wsi, &buf, len)) { - case 1: - goto bail; - case 2: - len -= (buf - last_char); + len -= (buf - last_char); + switch (wsi->state) { + case WSI_STATE_HTTP: + case WSI_STATE_HTTP_HEADERS: + goto http_complete; + case WSI_STATE_HTTP_ISSUING_FILE: + goto read_ok; + case WSI_STATE_HTTP_BODY: + wsi->u.http.content_remain = wsi->u.http.content_length; goto http_postbody; + default: + break; + } + break; + + case WSI_STATE_HTTP_BODY: +http_postbody: + while (len && wsi->u.http.content_remain) { + /* Copy as much as possible, up to the limit of: + * what we have in the read buffer (len) + * remaining portion of the POST body (content_remain) + */ + body_chunk_len = min(wsi->u.http.content_remain,len); + wsi->u.http.content_remain -= body_chunk_len; + len -= body_chunk_len; + + if (wsi->protocol->callback) { + n = wsi->protocol->callback( + wsi->protocol->owning_server, wsi, + LWS_CALLBACK_HTTP_BODY, wsi->user_space, + buf, body_chunk_len); + if (n) + goto bail; + } + buf += body_chunk_len; + + if (!wsi->u.http.content_remain) { + /* he sent the content in time */ + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + if (wsi->protocol->callback) { + n = wsi->protocol->callback( + wsi->protocol->owning_server, wsi, + LWS_CALLBACK_HTTP_BODY_COMPLETION, + wsi->user_space, NULL, 0); + if (n) + goto bail; + } + goto http_complete; } } break; - case WSI_STATE_AWAITING_CLOSE_ACK: case WSI_STATE_ESTABLISHED: + case WSI_STATE_AWAITING_CLOSE_ACK: if (lws_handshake_client(wsi, &buf, len)) goto bail; switch (wsi->mode) { @@ -166,8 +159,30 @@ http_postbody: break; } +read_ok: + /* Nothing more to do for now. */ + lwsl_debug("libwebsocket_read: read_ok\n"); + return 0; +http_complete: + lwsl_debug("libwebsocket_read: http_complete\n"); + /* Handle keep-alives, by preparing for a new request: */ + if (wsi->u.http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) { + wsi->state = WSI_STATE_HTTP; + wsi->mode = LWS_CONNMODE_HTTP_SERVING; + lwsl_debug("libwebsocket_read: keep-alive\n"); + + if (lws_allocate_header_table(wsi)) + goto bail; + + /* If we have more data, loop back around: */ + if (len) + goto http_new; + + return 0; + } + bail: lwsl_debug("closing connection at libwebsocket_read bail:\n"); diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6bebc605..ee88e2e1 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -90,10 +90,6 @@ libwebsocket_close_and_free_session(struct libwebsocket_context *context, } if (wsi->mode == LWS_CONNMODE_HTTP_SERVING_ACCEPTED) { - if (wsi->u.http.post_buffer) { - free(wsi->u.http.post_buffer); - wsi->u.http.post_buffer = NULL; - } if (wsi->u.http.fd != LWS_INVALID_FILE) { lwsl_debug("closing http file\n"); compatible_file_close(wsi->u.http.fd); diff --git a/lib/parsers.c b/lib/parsers.c index a8d0a5ea..9626cd20 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -248,9 +248,12 @@ int libwebsocket_parse( if (!wsi->u.hdr.ah->frags[wsi->u.hdr.ah->next_frag_index].len) if (issue_char(wsi, '/') < 0) return -1; - c = '\0'; - wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; - goto spill; + + /* begin parsing HTTP version: */ + if (issue_char(wsi, '\0') < 0) + return -1; + wsi->u.hdr.parser_state = WSI_TOKEN_HTTP; + goto start_fragment; } /* special URI processing... convert %xx */ @@ -394,7 +397,6 @@ check_eol: goto swallow; } -spill: { int issue_result = issue_char(wsi, c); if (issue_result < 0) { diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 6fae8898..4194498e 100755 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -291,6 +291,16 @@ enum lws_connection_states { WSI_STATE_FLUSHING_STORED_SEND_BEFORE_CLOSE, }; +enum http_version { + HTTP_VERSION_1_0, + HTTP_VERSION_1_1, +}; + +enum http_connection_type { + HTTP_CONNECTION_CLOSE, + HTTP_CONNECTION_KEEP_ALIVE +}; + enum lws_rx_parse_state { LWS_RXPS_NEW, @@ -520,10 +530,10 @@ struct _lws_http_mode_related { unsigned long filepos; unsigned long filelen; + enum http_version request_version; + enum http_connection_type connection_type; int content_length; - int content_length_seen; - int body_index; - unsigned char *post_buffer; + int content_remain; }; struct _lws_header_related { diff --git a/lib/server.c b/lib/server.c index 6afd570e..9ffb18c2 100644 --- a/lib/server.c +++ b/lib/server.c @@ -173,6 +173,9 @@ int lws_handshake_server(struct libwebsocket_context *context, struct allocated_headers *ah; 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]; int n; @@ -265,18 +268,44 @@ int lws_handshake_server(struct libwebsocket_context *context, wsi->u.http.content_length = atoi(content_length_str); } - if (wsi->u.http.content_length > 0) { - wsi->u.http.body_index = 0; - n = wsi->protocol->rx_buffer_size; - if (!n) - n = LWS_MAX_SOCKET_IO_BUF; - wsi->u.http.post_buffer = malloc(n); - if (!wsi->u.http.post_buffer) { - lwsl_err("Unable to allocate post buffer\n"); - n = -1; - goto cleanup; + /* 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) { + char http_version_str[10]; + 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)) { + char http_conn_str[20]; + 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") == 0) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + + else if (strcasecmp(http_conn_str,"close") == 0) + connection_type = HTTP_CONNECTION_CLOSE; + + } + wsi->u.http.connection_type = connection_type; n = 0; if (wsi->protocol->callback) @@ -311,15 +340,19 @@ cleanup: return 1; /* struct ah ptr already nuked */ } - /* - * (if callback didn't start sending a file) - * deal with anything else as body, whether - * there was a content-length or not - */ - + /* 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) - wsi->state = WSI_STATE_HTTP_BODY; - return 2; /* goto http_postbody; */ + + /* 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; /* don't bail out of libwebsocket_read, just yet */ } if (!wsi->protocol) @@ -550,7 +583,6 @@ int lws_server_socket_service(struct libwebsocket_context *context, 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)