diff --git a/components/esp_http_server/include/esp_http_server.h b/components/esp_http_server/include/esp_http_server.h index 7372003df3..5b4ee04dcb 100644 --- a/components/esp_http_server/include/esp_http_server.h +++ b/components/esp_http_server/include/esp_http_server.h @@ -412,6 +412,11 @@ typedef struct httpd_uri { * This is used if a custom processing of the control frames is needed */ bool handle_ws_control_frames; + + /** + * Pointer to subprotocol supported by URI + */ + const char *supported_subprotocol; #endif } httpd_uri_t; diff --git a/components/esp_http_server/src/esp_httpd_priv.h b/components/esp_http_server/src/esp_httpd_priv.h index 554f0e69bf..f7e1eff1bb 100644 --- a/components/esp_http_server/src/esp_httpd_priv.h +++ b/components/esp_http_server/src/esp_httpd_priv.h @@ -492,7 +492,8 @@ int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, /** * @brief This function is for responding a WebSocket handshake * - * @param[in] req Pointer to handshake request that will be handled + * @param[in] req Pointer to handshake request that will be handled + * @param[in] supported_subprotocol Pointer to the subprotocol supported by this URI * @return * - ESP_OK : When handshake is sucessful * - ESP_ERR_NOT_FOUND : When some headers (Sec-WebSocket-*) are not found @@ -501,7 +502,7 @@ int httpd_default_recv(httpd_handle_t hd, int sockfd, char *buf, size_t buf_len, * - ESP_ERR_INVALID_ARG : Argument is invalid (null or non-WebSocket) * - ESP_FAIL : Socket failures */ -esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req); +esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol); /** * @brief This function is for getting a frame type diff --git a/components/esp_http_server/src/httpd_uri.c b/components/esp_http_server/src/httpd_uri.c index cdc93df506..522ff3bb88 100644 --- a/components/esp_http_server/src/httpd_uri.c +++ b/components/esp_http_server/src/httpd_uri.c @@ -175,6 +175,11 @@ esp_err_t httpd_register_uri_handler(httpd_handle_t handle, #ifdef CONFIG_HTTPD_WS_SUPPORT hd->hd_calls[i]->is_websocket = uri_handler->is_websocket; hd->hd_calls[i]->handle_ws_control_frames = uri_handler->handle_ws_control_frames; + if (uri_handler->supported_subprotocol) { + hd->hd_calls[i]->supported_subprotocol = strdup(uri_handler->supported_subprotocol); + } else { + hd->hd_calls[i]->supported_subprotocol = NULL; + } #endif ESP_LOGD(TAG, LOG_FMT("[%d] installed %s"), i, uri_handler->uri); return ESP_OK; @@ -316,7 +321,7 @@ esp_err_t httpd_uri(struct httpd_data *hd) struct httpd_req_aux *aux = req->aux; if (uri->is_websocket && aux->ws_handshake_detect && uri->method == HTTP_GET) { ESP_LOGD(TAG, LOG_FMT("Responding WS handshake to sock %d"), aux->sd->fd); - esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req); + esp_err_t ret = httpd_ws_respond_server_handshake(&hd->hd_req, uri->supported_subprotocol); if (ret != ESP_OK) { return ret; } diff --git a/components/esp_http_server/src/httpd_ws.c b/components/esp_http_server/src/httpd_ws.c index 7675f263c3..53e870dabc 100644 --- a/components/esp_http_server/src/httpd_ws.c +++ b/components/esp_http_server/src/httpd_ws.c @@ -15,6 +15,7 @@ #include +#include #include #include #include @@ -44,7 +45,50 @@ static const char *TAG="httpd_ws"; */ static const char ws_magic_uuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; -esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req) +/* Checks if any subprotocols from the comma seperated list matches the supported one + * + * Returns true if the response should contain a protocol field +*/ + +/** + * @brief Checks if any subprotocols from the comma seperated list matches the supported one + * + * @param supported_subprotocol[in] The subprotocol supported by the URI + * @param subprotocol[in], [in]: A comma seperate list of subprotocols requested + * @param buf_len Length of the buffer + * @return true: found a matching subprotocol + * @return false + */ +static bool httpd_ws_get_response_subprotocol(const char *supported_subprotocol, char *subprotocol, size_t buf_len) +{ + /* Request didnt contain any subprotocols */ + if (strnlen(subprotocol, buf_len) == 0) { + return false; + } + + if (supported_subprotocol == NULL) { + ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, URI do not support any subprotocols", subprotocol); + return false; + } + + /* Get first subprotocol from comma seperated list */ + char *rest = NULL; + char *s = strtok_r(subprotocol, ", ", &rest); + do { + if (strncmp(s, supported_subprotocol, sizeof(subprotocol)) == 0) { + ESP_LOGD(TAG, "Requested subprotocol supported: %s", s); + return true; + } + } while ((s = strtok_r(NULL, ", ", &rest)) != NULL); + + ESP_LOGW(TAG, "Sec-WebSocket-Protocol %s not supported, supported subprotocol is %s", subprotocol, supported_subprotocol); + + /* No matches */ + return false; + +} + +esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req, const char *supported_subprotocol) { /* Probe if input parameters are valid or not */ if (!req || !req->aux) { @@ -101,18 +145,56 @@ esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *req) ESP_LOGD(TAG, LOG_FMT("Generated server key: %s"), server_key_encoded); + char subprotocol[50] = { '\0' }; + if (httpd_req_get_hdr_value_str(req, "Sec-WebSocket-Protocol", subprotocol, sizeof(subprotocol) - 1) == ESP_ERR_HTTPD_RESULT_TRUNC) { + ESP_LOGW(TAG, "Sec-WebSocket-Protocol length exceeded buffer size of %d, was trunctated", sizeof(subprotocol)); + } + + /* Prepare the Switching Protocol response */ char tx_buf[192] = { '\0' }; int fmt_len = snprintf(tx_buf, sizeof(tx_buf), "HTTP/1.1 101 Switching Protocols\r\n" "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" - "Sec-WebSocket-Accept: %s\r\n\r\n", server_key_encoded); + "Sec-WebSocket-Accept: %s\r\n", server_key_encoded); + if (fmt_len < 0 || fmt_len > sizeof(tx_buf)) { ESP_LOGW(TAG, LOG_FMT("Failed to prepare Tx buffer")); return ESP_FAIL; } + if ( httpd_ws_get_response_subprotocol(supported_subprotocol, subprotocol, sizeof(subprotocol))) { + ESP_LOGD(TAG, "subprotocol: %s", subprotocol); + int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "Sec-WebSocket-Protocol: %s\r\n", supported_subprotocol); + if (r <= 0) { + ESP_LOGE(TAG, "Error in response generation" + "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf)); + return ESP_FAIL; + } + + fmt_len += r; + + if (fmt_len >= sizeof(tx_buf)) { + ESP_LOGE(TAG, "Error in response generation" + "(snprintf of subprotocol returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf)); + return ESP_FAIL; + } + } + + int r = snprintf(tx_buf + fmt_len, sizeof(tx_buf) - fmt_len, "\r\n"); + if (r <= 0) { + ESP_LOGE(TAG, "Error in response generation" + "(snprintf of subprotocol returned %d, buffer size: %d", r, sizeof(tx_buf)); + return ESP_FAIL; + } + fmt_len += r; + if (fmt_len >= sizeof(tx_buf)) { + ESP_LOGE(TAG, "Error in response generation" + "(snprintf of header terminal returned %d, desired response len: %d, buffer size: %d", r, fmt_len, sizeof(tx_buf)); + return ESP_FAIL; + } + /* Send off the response */ if (httpd_send(req, tx_buf, fmt_len) < 0) { ESP_LOGW(TAG, LOG_FMT("Failed to send the response"));