OVMS3-idf/components/esp_http_server/src/httpd_parse.c
Chinmay 38b1c93764 esp_http_server : Bugfix in parsing of empty header values
This MR is intended to fix incorrect parsing of HTTP requests when empty header values are present.

The issue is was due to asymmetric behavior of `http_parser` library, which in case of:

    non-empty header values : invokes callbacks with the pointer to the start of a value
    empty header values : invokes callbacks with pointer to the start of next header or section

Since HTTP server relies on this pointer (along with length of the value) to locate the end of a value, and replace the line terminators (CRLFs) with null characters, the second case needed to be handled correctly.

Closes IDFGH-1539

Closes https://github.com/espressif/esp-idf/issues/3803
2019-09-09 08:21:13 +00:00

1003 lines
34 KiB
C

// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <stdlib.h>
#include <sys/param.h>
#include <esp_log.h>
#include <esp_err.h>
#include <http_parser.h>
#include <esp_http_server.h>
#include "esp_httpd_priv.h"
#include "osal.h"
static const char *TAG = "httpd_parse";
typedef struct {
/* Parser settings for http_parser_execute() */
http_parser_settings settings;
/* Request being parsed */
struct httpd_req *req;
/* Status of the parser describes the part of the
* HTTP request packet being processed at any moment.
*/
enum {
PARSING_IDLE = 0,
PARSING_URL,
PARSING_HDR_FIELD,
PARSING_HDR_VALUE,
PARSING_BODY,
PARSING_COMPLETE,
PARSING_FAILED
} status;
/* Response error code in case of PARSING_FAILED */
httpd_err_code_t error;
/* For storing last callback parameters */
struct {
const char *at;
size_t length;
} last;
/* State variables */
bool paused; /*!< Parser is paused */
size_t pre_parsed; /*!< Length of data to be skipped while parsing */
size_t raw_datalen; /*!< Full length of the raw data in scratch buffer */
} parser_data_t;
static esp_err_t verify_url (http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Get previous values of the parser callback arguments */
const char *at = parser_data->last.at;
size_t length = parser_data->last.length;
if ((r->method = parser->method) < 0) {
ESP_LOGW(TAG, LOG_FMT("HTTP Operation not supported"));
parser_data->error = HTTPD_501_METHOD_NOT_IMPLEMENTED;
return ESP_FAIL;
}
if (sizeof(r->uri) < (length + 1)) {
ESP_LOGW(TAG, LOG_FMT("URI length (%d) greater than supported (%d)"),
length, sizeof(r->uri));
parser_data->error = HTTPD_414_URI_TOO_LONG;
return ESP_FAIL;
}
/* Keep URI with terminating null character. Note URI string pointed
* by 'at' is not NULL terminated, therefore use length provided by
* parser while copying the URI to buffer */
strlcpy((char *)r->uri, at, (length + 1));
ESP_LOGD(TAG, LOG_FMT("received URI = %s"), r->uri);
/* Make sure version is HTTP/1.1 */
if ((parser->http_major != 1) && (parser->http_minor != 1)) {
ESP_LOGW(TAG, LOG_FMT("unsupported HTTP version = %d.%d"),
parser->http_major, parser->http_minor);
parser_data->error = HTTPD_505_VERSION_NOT_SUPPORTED;
return ESP_FAIL;
}
/* Parse URL and keep result for later */
http_parser_url_init(res);
if (http_parser_parse_url(r->uri, strlen(r->uri),
r->method == HTTP_CONNECT, res)) {
ESP_LOGW(TAG, LOG_FMT("http_parser_parse_url failed with errno = %d"),
parser->http_errno);
parser_data->error = HTTPD_400_BAD_REQUEST;
return ESP_FAIL;
}
return ESP_OK;
}
/* http_parser callback on finding url in HTTP request
* Will be invoked ATLEAST once every packet
*/
static esp_err_t cb_url(http_parser *parser,
const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
if (parser_data->status == PARSING_IDLE) {
ESP_LOGD(TAG, LOG_FMT("message begin"));
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_URL;
} else if (parser_data->status != PARSING_URL) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing url = %.*s"), length, at);
/* Update length of URL string */
if ((parser_data->last.length += length) > HTTPD_MAX_URI_LEN) {
ESP_LOGW(TAG, LOG_FMT("URI length (%d) greater than supported (%d)"),
parser_data->last.length, HTTPD_MAX_URI_LEN);
parser_data->error = HTTPD_414_URI_TOO_LONG;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
return ESP_OK;
}
static esp_err_t pause_parsing(http_parser *parser, const char* at)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* The length of data that was not parsed due to interruption
* and hence needs to be read again later for parsing */
ssize_t unparsed = parser_data->raw_datalen - (at - ra->scratch);
if (unparsed < 0) {
ESP_LOGE(TAG, LOG_FMT("parsing beyond valid data = %d"), -unparsed);
return ESP_ERR_INVALID_STATE;
}
/* Push back the un-parsed data into pending buffer for
* receiving again with httpd_recv_with_opt() later when
* read_block() executes */
if (unparsed && (unparsed != httpd_unrecv(r, at, unparsed))) {
ESP_LOGE(TAG, LOG_FMT("data too large for un-recv = %d"), unparsed);
return ESP_FAIL;
}
/* Signal http_parser to pause execution and save the maximum
* possible length, of the yet un-parsed data, that may get
* parsed before http_parser_execute() returns. This pre_parsed
* length will be updated then to reflect the actual length
* that got parsed, and must be skipped when parsing resumes */
parser_data->pre_parsed = unparsed;
http_parser_pause(parser, 1);
parser_data->paused = true;
ESP_LOGD(TAG, LOG_FMT("paused"));
return ESP_OK;
}
static size_t continue_parsing(http_parser *parser, size_t length)
{
parser_data_t *data = (parser_data_t *) parser->data;
/* Part of the received data may have been parsed earlier
* so we must skip that before parsing resumes */
length = MIN(length, data->pre_parsed);
data->pre_parsed -= length;
ESP_LOGD(TAG, LOG_FMT("skip pre-parsed data of size = %d"), length);
http_parser_pause(parser, 0);
data->paused = false;
ESP_LOGD(TAG, LOG_FMT("un-paused"));
return length;
}
/* http_parser callback on header field in HTTP request
* May be invoked ATLEAST once every header field
*/
static esp_err_t cb_header_field(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("headers begin"));
/* Last at is set to start of scratch where headers
* will be received next */
parser_data->last.at = ra->scratch;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_FIELD;
/* Stop parsing for now and give control to process */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status == PARSING_HDR_VALUE) {
/* Overwrite terminator (CRLFs) following last header
* (key: value) pair with null characters */
char *term_start = (char *)parser_data->last.at + parser_data->last.length;
memset(term_start, '\0', at - term_start);
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_FIELD;
/* Increment header count */
ra->req_hdrs_count++;
} else if (parser_data->status != PARSING_HDR_FIELD) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing field = %.*s"), length, at);
/* Update length of header string */
parser_data->last.length += length;
return ESP_OK;
}
/* http_parser callback on header value in HTTP request.
* May be invoked ATLEAST once every header value
*/
static esp_err_t cb_header_value(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
/* Check previous status */
if (parser_data->status == PARSING_HDR_FIELD) {
/* Store current values of the parser callback arguments */
parser_data->last.at = at;
parser_data->last.length = 0;
parser_data->status = PARSING_HDR_VALUE;
if (length == 0) {
/* As per behavior of http_parser, when length > 0,
* `at` points to the start of CRLF. But, in the
* case when header value is empty (zero length),
* then `at` points to the position right after
* the CRLF. Since for our purpose we need `last.at`
* to point to exactly where the CRLF starts, it
* needs to be adjusted by the right offset */
char *at_adj = (char *)parser_data->last.at;
/* Find the end of header field string */
while (*(--at_adj) != ':');
/* Now skip leading spaces' */
while (*(++at_adj) == ' ');
/* Now we are at the right position */
parser_data->last.at = at_adj;
}
} else if (parser_data->status != PARSING_HDR_VALUE) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("processing value = %.*s"), length, at);
/* Update length of header string */
parser_data->last.length += length;
return ESP_OK;
}
/* http_parser callback on completing headers in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_headers_complete(http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
struct httpd_req *r = parser_data->req;
struct httpd_req_aux *ra = r->aux;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status == PARSING_HDR_VALUE) {
/* Locate end of last header */
char *at = (char *)parser_data->last.at + parser_data->last.length;
/* Check if there is data left to parse. This value should
* at least be equal to the number of line terminators, i.e. 2 */
ssize_t remaining_length = parser_data->raw_datalen - (at - ra->scratch);
if (remaining_length < 2) {
ESP_LOGE(TAG, LOG_FMT("invalid length of data remaining to be parsed"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Locate end of headers section by skipping the remaining
* two line terminators. No assumption is made here about the
* termination sequence used apart from the necessity that it
* must end with an LF, because:
* 1) some clients may send non standard LFs instead of
* CRLFs for indicating termination.
* 2) it is the responsibility of http_parser to check
* that the termination is either CRLF or LF and
* not any other sequence */
unsigned short remaining_terminators = 2;
while (remaining_length-- && remaining_terminators) {
if (*at == '\n') {
remaining_terminators--;
}
/* Overwrite termination characters with null */
*(at++) = '\0';
}
if (remaining_terminators) {
ESP_LOGE(TAG, LOG_FMT("incomplete termination of headers"));
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Place the parser ptr right after the end of headers section */
parser_data->last.at = at;
/* Increment header count */
ra->req_hdrs_count++;
} else {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* In absence of body/chunked encoding, http_parser sets content_len to -1 */
r->content_len = ((int)parser->content_length != -1 ?
parser->content_length : 0);
ESP_LOGD(TAG, LOG_FMT("bytes read = %d"), parser->nread);
ESP_LOGD(TAG, LOG_FMT("content length = %zu"), r->content_len);
if (parser->upgrade) {
ESP_LOGW(TAG, LOG_FMT("upgrade from HTTP not supported"));
/* There is no specific HTTP error code to notify the client that
* upgrade is not supported, thus sending 400 Bad Request */
parser_data->error = HTTPD_400_BAD_REQUEST;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->status = PARSING_BODY;
ra->remaining_len = r->content_len;
return ESP_OK;
}
/* Last http_parser callback if body present in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_on_body(http_parser *parser, const char *at, size_t length)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
/* Check previous status */
if (parser_data->status != PARSING_BODY) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Pause parsing so that if part of another packet
* is in queue then it doesn't get parsed, which
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->last.at = 0;
parser_data->last.length = 0;
parser_data->status = PARSING_COMPLETE;
ESP_LOGD(TAG, LOG_FMT("body begins"));
return ESP_OK;
}
/* Last http_parser callback if body absent in HTTP request.
* Will be invoked ONLY once every packet
*/
static esp_err_t cb_no_body(http_parser *parser)
{
parser_data_t *parser_data = (parser_data_t *) parser->data;
/* Check previous status */
if (parser_data->status == PARSING_URL) {
ESP_LOGD(TAG, LOG_FMT("no headers"));
if (verify_url(parser) != ESP_OK) {
/* verify_url would already have set the
* error field of parser data, so only setting
* status to failed */
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
} else if (parser_data->status != PARSING_BODY) {
ESP_LOGE(TAG, LOG_FMT("unexpected state transition"));
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
/* Pause parsing so that if part of another packet
* is in queue then it doesn't get parsed, which
* may reset the parser state and cause current
* request packet to be lost */
if (pause_parsing(parser, parser_data->last.at) != ESP_OK) {
parser_data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
parser_data->status = PARSING_FAILED;
return ESP_FAIL;
}
parser_data->last.at = 0;
parser_data->last.length = 0;
parser_data->status = PARSING_COMPLETE;
ESP_LOGD(TAG, LOG_FMT("message complete"));
return ESP_OK;
}
static int read_block(httpd_req_t *req, size_t offset, size_t length)
{
struct httpd_req_aux *raux = req->aux;
/* Limits the read to scratch buffer size */
ssize_t buf_len = MIN(length, (sizeof(raux->scratch) - offset));
if (buf_len <= 0) {
return 0;
}
/* Receive data into buffer. If data is pending (from unrecv) then return
* immediately after receiving pending data, as pending data may just complete
* this request packet. */
int nbytes = httpd_recv_with_opt(req, raux->scratch + offset, buf_len, true);
if (nbytes < 0) {
ESP_LOGD(TAG, LOG_FMT("error in httpd_recv"));
/* If timeout occurred allow the
* situation to be handled */
if (nbytes == HTTPD_SOCK_ERR_TIMEOUT) {
/* Invoke error handler which may return ESP_OK
* to signal for retrying call to recv(), else it may
* return ESP_FAIL to signal for closure of socket */
return (httpd_req_handle_err(req, HTTPD_408_REQ_TIMEOUT) == ESP_OK) ?
HTTPD_SOCK_ERR_TIMEOUT : HTTPD_SOCK_ERR_FAIL;
}
/* Some socket error occurred. Return failure
* to force closure of underlying socket.
* Error message is not sent as socket may not
* be valid anymore */
return HTTPD_SOCK_ERR_FAIL;
} else if (nbytes == 0) {
ESP_LOGD(TAG, LOG_FMT("connection closed"));
/* Connection closed by client so no
* need to send error response */
return HTTPD_SOCK_ERR_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("received HTTP request block size = %d"), nbytes);
return nbytes;
}
static int parse_block(http_parser *parser, size_t offset, size_t length)
{
parser_data_t *data = (parser_data_t *)(parser->data);
httpd_req_t *req = data->req;
struct httpd_req_aux *raux = req->aux;
size_t nparsed = 0;
if (!length) {
/* Parsing is still happening but nothing to
* parse means no more space left on buffer,
* therefore it can be inferred that the
* request URI/header must be too long */
ESP_LOGW(TAG, LOG_FMT("request URI/header too long"));
switch (data->status) {
case PARSING_URL:
data->error = HTTPD_414_URI_TOO_LONG;
break;
case PARSING_HDR_FIELD:
case PARSING_HDR_VALUE:
data->error = HTTPD_431_REQ_HDR_FIELDS_TOO_LARGE;
break;
default:
ESP_LOGE(TAG, LOG_FMT("unexpected state"));
data->error = HTTPD_500_INTERNAL_SERVER_ERROR;
break;
}
data->status = PARSING_FAILED;
return -1;
}
/* Un-pause the parsing if paused */
if (data->paused) {
nparsed = continue_parsing(parser, length);
length -= nparsed;
offset += nparsed;
if (!length) {
return nparsed;
}
}
/* Execute http_parser */
nparsed = http_parser_execute(parser, &data->settings,
raux->scratch + offset, length);
/* Check state */
if (data->status == PARSING_FAILED) {
/* It is expected that the error field of
* parser data should have been set by now */
ESP_LOGW(TAG, LOG_FMT("parsing failed"));
return -1;
} else if (data->paused) {
/* Update the value of pre_parsed which was set when
* pause_parsing() was called. (length - nparsed) is
* the length of the data that will need to be parsed
* again later and hence must be deducted from the
* pre_parsed length */
data->pre_parsed -= (length - nparsed);
return 0;
} else if (nparsed != length) {
/* http_parser error */
data->error = HTTPD_400_BAD_REQUEST;
data->status = PARSING_FAILED;
ESP_LOGW(TAG, LOG_FMT("incomplete (%d/%d) with parser error = %d"),
nparsed, length, parser->http_errno);
return -1;
}
/* Return with the total length of the request packet
* that has been parsed till now */
ESP_LOGD(TAG, LOG_FMT("parsed block size = %d"), offset + nparsed);
return offset + nparsed;
}
static void parse_init(httpd_req_t *r, http_parser *parser, parser_data_t *data)
{
/* Initialize parser data */
memset(data, 0, sizeof(parser_data_t));
data->req = r;
/* Initialize parser */
http_parser_init(parser, HTTP_REQUEST);
parser->data = (void *)data;
/* Initialize parser settings */
http_parser_settings_init(&data->settings);
/* Set parser callbacks */
data->settings.on_url = cb_url;
data->settings.on_header_field = cb_header_field;
data->settings.on_header_value = cb_header_value;
data->settings.on_headers_complete = cb_headers_complete;
data->settings.on_body = cb_on_body;
data->settings.on_message_complete = cb_no_body;
}
/* Function that receives TCP data and runs parser on it
*/
static esp_err_t httpd_parse_req(struct httpd_data *hd)
{
httpd_req_t *r = &hd->hd_req;
int blk_len, offset;
http_parser parser;
parser_data_t parser_data;
/* Initialize parser */
parse_init(r, &parser, &parser_data);
/* Set offset to start of scratch buffer */
offset = 0;
do {
/* Read block into scratch buffer */
if ((blk_len = read_block(r, offset, PARSER_BLOCK_SIZE)) < 0) {
if (blk_len == HTTPD_SOCK_ERR_TIMEOUT) {
/* Retry read in case of non-fatal timeout error.
* read_block() ensures that the timeout error is
* handled properly so that this doesn't get stuck
* in an infinite loop */
continue;
}
/* If not HTTPD_SOCK_ERR_TIMEOUT, returned error must
* be HTTPD_SOCK_ERR_FAIL which means we need to return
* failure and thereby close the underlying socket */
return ESP_FAIL;
}
/* This is used by the callbacks to track
* data usage of the buffer */
parser_data.raw_datalen = blk_len + offset;
/* Parse data block from buffer */
if ((offset = parse_block(&parser, offset, blk_len)) < 0) {
/* HTTP error occurred.
* Send error code as response status and
* invoke error handler */
return httpd_req_handle_err(r, parser_data.error);
}
} while (parser_data.status != PARSING_COMPLETE);
ESP_LOGD(TAG, LOG_FMT("parsing complete"));
return httpd_uri(hd);
}
static void init_req(httpd_req_t *r, httpd_config_t *config)
{
r->handle = 0;
r->method = 0;
memset((char*)r->uri, 0, sizeof(r->uri));
r->content_len = 0;
r->aux = 0;
r->user_ctx = 0;
r->sess_ctx = 0;
r->free_ctx = 0;
r->ignore_sess_ctx_changes = 0;
}
static void init_req_aux(struct httpd_req_aux *ra, httpd_config_t *config)
{
ra->sd = 0;
memset(ra->scratch, 0, sizeof(ra->scratch));
ra->remaining_len = 0;
ra->status = 0;
ra->content_type = 0;
ra->first_chunk_sent = 0;
ra->req_hdrs_count = 0;
ra->resp_hdrs_count = 0;
memset(ra->resp_hdrs, 0, config->max_resp_headers * sizeof(struct resp_hdr));
}
static void httpd_req_cleanup(httpd_req_t *r)
{
struct httpd_req_aux *ra = r->aux;
/* Check if the context has changed and needs to be cleared */
if ((r->ignore_sess_ctx_changes == false) && (ra->sd->ctx != r->sess_ctx)) {
httpd_sess_free_ctx(ra->sd->ctx, ra->sd->free_ctx);
}
/* Retrieve session info from the request into the socket database. */
ra->sd->ctx = r->sess_ctx;
ra->sd->free_ctx = r->free_ctx;
ra->sd->ignore_sess_ctx_changes = r->ignore_sess_ctx_changes;
/* Clear out the request and request_aux structures */
ra->sd = NULL;
r->handle = NULL;
r->aux = NULL;
}
/* Function that processes incoming TCP data and
* updates the http request data httpd_req_t
*/
esp_err_t httpd_req_new(struct httpd_data *hd, struct sock_db *sd)
{
httpd_req_t *r = &hd->hd_req;
init_req(r, &hd->config);
init_req_aux(&hd->hd_req_aux, &hd->config);
r->handle = hd;
r->aux = &hd->hd_req_aux;
/* Associate the request to the socket */
struct httpd_req_aux *ra = r->aux;
ra->sd = sd;
/* Set defaults */
ra->status = (char *)HTTPD_200;
ra->content_type = (char *)HTTPD_TYPE_TEXT;
ra->first_chunk_sent = false;
/* Copy session info to the request */
r->sess_ctx = sd->ctx;
r->free_ctx = sd->free_ctx;
r->ignore_sess_ctx_changes = sd->ignore_sess_ctx_changes;
/* Parse request */
esp_err_t err = httpd_parse_req(hd);
if (err != ESP_OK) {
httpd_req_cleanup(r);
}
return err;
}
/* Function that resets the http request data
*/
esp_err_t httpd_req_delete(struct httpd_data *hd)
{
httpd_req_t *r = &hd->hd_req;
struct httpd_req_aux *ra = r->aux;
/* Finish off reading any pending/leftover data */
while (ra->remaining_len) {
/* Any length small enough not to overload the stack, but large
* enough to finish off the buffers fast */
char dummy[CONFIG_HTTPD_PURGE_BUF_LEN];
int recv_len = MIN(sizeof(dummy), ra->remaining_len);
recv_len = httpd_req_recv(r, dummy, recv_len);
if (recv_len < 0) {
httpd_req_cleanup(r);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("purging data size : %d bytes"), recv_len);
#ifdef CONFIG_HTTPD_LOG_PURGE_DATA
/* Enabling this will log discarded binary HTTP content data at
* Debug level. For large content data this may not be desirable
* as it will clutter the log */
ESP_LOGD(TAG, "================= PURGED DATA =================");
ESP_LOG_BUFFER_HEX_LEVEL(TAG, dummy, recv_len, ESP_LOG_DEBUG);
ESP_LOGD(TAG, "===============================================");
#endif
}
httpd_req_cleanup(r);
return ESP_OK;
}
/* Validates the request to prevent users from calling APIs, that are to
* be called only inside URI handler, outside the handler context
*/
bool httpd_validate_req_ptr(httpd_req_t *r)
{
if (r) {
struct httpd_data *hd = (struct httpd_data *) r->handle;
if (hd) {
/* Check if this function is running in the context of
* the correct httpd server thread */
if (httpd_os_thread_handle() == hd->hd_td.handle) {
return true;
}
}
}
return false;
}
/* Helper function to get a URL query tag from a query string of the type param1=val1&param2=val2 */
esp_err_t httpd_query_key_value(const char *qry_str, const char *key, char *val, size_t val_size)
{
if (qry_str == NULL || key == NULL || val == NULL) {
return ESP_ERR_INVALID_ARG;
}
const char *qry_ptr = qry_str;
const size_t buf_len = val_size;
while (strlen(qry_ptr)) {
/* Search for the '=' character. Else, it would mean
* that the parameter is invalid */
const char *val_ptr = strchr(qry_ptr, '=');
if (!val_ptr) {
break;
}
size_t offset = val_ptr - qry_ptr;
/* If the key, does not match, continue searching.
* Compare lengths first as key from url is not
* null terminated (has '=' in the end) */
if ((offset != strlen(key)) ||
(strncasecmp(qry_ptr, key, offset))) {
/* Get the name=val string. Multiple name=value pairs
* are separated by '&' */
qry_ptr = strchr(val_ptr, '&');
if (!qry_ptr) {
break;
}
qry_ptr++;
continue;
}
/* Locate start of next query */
qry_ptr = strchr(++val_ptr, '&');
/* Or this could be the last query, in which
* case get to the end of query string */
if (!qry_ptr) {
qry_ptr = val_ptr + strlen(val_ptr);
}
/* Update value length, including one byte for null */
val_size = qry_ptr - val_ptr + 1;
/* Copy value to the caller's buffer. */
strlcpy(val, val_ptr, MIN(val_size, buf_len));
/* If buffer length is smaller than needed, return truncation error */
if (buf_len < val_size) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
ESP_LOGD(TAG, LOG_FMT("key %s not found"), key);
return ESP_ERR_NOT_FOUND;
}
size_t httpd_req_get_url_query_len(httpd_req_t *r)
{
if (r == NULL) {
return 0;
}
if (!httpd_valid_req(r)) {
return 0;
}
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Check if query field is present in the URL */
if (res->field_set & (1 << UF_QUERY)) {
return res->field_data[UF_QUERY].len;
}
return 0;
}
esp_err_t httpd_req_get_url_query_str(httpd_req_t *r, char *buf, size_t buf_len)
{
if (r == NULL || buf == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
struct http_parser_url *res = &ra->url_parse_res;
/* Check if query field is present in the URL */
if (res->field_set & (1 << UF_QUERY)) {
const char *qry = r->uri + res->field_data[UF_QUERY].off;
/* Minimum required buffer len for keeping
* null terminated query string */
size_t min_buf_len = res->field_data[UF_QUERY].len + 1;
strlcpy(buf, qry, MIN(buf_len, min_buf_len));
if (buf_len < min_buf_len) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
/* Get the length of the value string of a header request field */
size_t httpd_req_get_hdr_value_len(httpd_req_t *r, const char *field)
{
if (r == NULL || field == NULL) {
return 0;
}
if (!httpd_valid_req(r)) {
return 0;
}
struct httpd_req_aux *ra = r->aux;
const char *hdr_ptr = ra->scratch; /*!< Request headers are kept in scratch buffer */
unsigned count = ra->req_hdrs_count; /*!< Count set during parsing */
while (count--) {
/* Search for the ':' character. Else, it would mean
* that the field is invalid
*/
const char *val_ptr = strchr(hdr_ptr, ':');
if (!val_ptr) {
break;
}
/* If the field, does not match, continue searching.
* Compare lengths first as field from header is not
* null terminated (has ':' in the end).
*/
if ((val_ptr - hdr_ptr != strlen(field)) ||
(strncasecmp(hdr_ptr, field, strlen(field)))) {
if (count) {
/* Jump to end of header field-value string */
hdr_ptr = 1 + strchr(hdr_ptr, '\0');
/* Skip all null characters (with which the line
* terminators had been overwritten) */
while (*hdr_ptr == '\0') {
hdr_ptr++;
}
}
continue;
}
/* Skip ':' */
val_ptr++;
/* Skip preceding space */
while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
val_ptr++;
}
return strlen(val_ptr);
}
return 0;
}
/* Get the value of a field from the request headers */
esp_err_t httpd_req_get_hdr_value_str(httpd_req_t *r, const char *field, char *val, size_t val_size)
{
if (r == NULL || field == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (!httpd_valid_req(r)) {
return ESP_ERR_HTTPD_INVALID_REQ;
}
struct httpd_req_aux *ra = r->aux;
const char *hdr_ptr = ra->scratch; /*!< Request headers are kept in scratch buffer */
unsigned count = ra->req_hdrs_count; /*!< Count set during parsing */
const size_t buf_len = val_size;
while (count--) {
/* Search for the ':' character. Else, it would mean
* that the field is invalid
*/
const char *val_ptr = strchr(hdr_ptr, ':');
if (!val_ptr) {
break;
}
/* If the field, does not match, continue searching.
* Compare lengths first as field from header is not
* null terminated (has ':' in the end).
*/
if ((val_ptr - hdr_ptr != strlen(field)) ||
(strncasecmp(hdr_ptr, field, strlen(field)))) {
if (count) {
/* Jump to end of header field-value string */
hdr_ptr = 1 + strchr(hdr_ptr, '\0');
/* Skip all null characters (with which the line
* terminators had been overwritten) */
while (*hdr_ptr == '\0') {
hdr_ptr++;
}
}
continue;
}
/* Skip ':' */
val_ptr++;
/* Skip preceding space */
while ((*val_ptr != '\0') && (*val_ptr == ' ')) {
val_ptr++;
}
/* Get the NULL terminated value and copy it to the caller's buffer. */
strlcpy(val, val_ptr, buf_len);
/* Update value length, including one byte for null */
val_size = strlen(val_ptr) + 1;
/* If buffer length is smaller than needed, return truncation error */
if (buf_len < val_size) {
return ESP_ERR_HTTPD_RESULT_TRUNC;
}
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}