diff --git a/docs/c-api/mg_http_server.h/intro.md b/docs/c-api/mg_http_server.h/intro.md index 03b026d9fc786deced5c669a8e20a12296264d98..1a6055be5c8f1101d64b372a8cd7b3dba785ac05 100644 --- a/docs/c-api/mg_http_server.h/intro.md +++ b/docs/c-api/mg_http_server.h/intro.md @@ -10,6 +10,7 @@ items: - { name: mg_get_http_var.md } - { name: mg_http_check_digest_auth.md } - { name: mg_http_parse_header.md } + - { name: mg_http_parse_header2.md } - { name: mg_http_reverse_proxy.md } - { name: mg_http_send_error.md } - { name: mg_http_send_redirect.md } diff --git a/docs/c-api/mg_http_server.h/mg_http_parse_header.md b/docs/c-api/mg_http_server.h/mg_http_parse_header.md index f86564536ab146012c4b3582eba071812a00d0ff..969a13c94fc0fbf600b7bb98c61d65529c8f0559 100644 --- a/docs/c-api/mg_http_server.h/mg_http_parse_header.md +++ b/docs/c-api/mg_http_server.h/mg_http_parse_header.md @@ -7,17 +7,9 @@ signature: | size_t buf_size); --- -Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value -in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero -otherwise. +DEPRECATED: use mg_http_parse_header2() instead. -This function is supposed to parse cookies, authentication headers, etc. -Example (error handling omitted): - - char user[20]; - struct mg_str *hdr = mg_get_http_header(hm, "Authorization"); - mg_http_parse_header(hdr, "username", user, sizeof(user)); - -Returns the length of the variable's value. If buffer is not large enough, -or variable not found, 0 is returned. +Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of +`char **`), and thus it cannot allocate a new buffer if the provided one +is not enough, and just returns 0 in that case. diff --git a/docs/c-api/mg_http_server.h/mg_http_parse_header2.md b/docs/c-api/mg_http_server.h/mg_http_parse_header2.md new file mode 100644 index 0000000000000000000000000000000000000000..4f1e62ed50d5e603923ba024f43869ef8a7696e9 --- /dev/null +++ b/docs/c-api/mg_http_server.h/mg_http_parse_header2.md @@ -0,0 +1,30 @@ +--- +title: "mg_http_parse_header2()" +decl_name: "mg_http_parse_header2" +symbol_kind: "func" +signature: | + int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, + size_t buf_size); +--- + +Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value +in the buffer `*buf`, `buf_size`. If the buffer size is not enough, +allocates a buffer of required size and writes it to `*buf`, similar to +asprintf(). The caller should always check whether the buffer was updated, +and free it if so. + +This function is supposed to parse cookies, authentication headers, etc. +Example (error handling omitted): + + char user_buf[20]; + char *user = user_buf; + struct mg_str *hdr = mg_get_http_header(hm, "Authorization"); + mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf)); + // ... do something useful with user + if (user != user_buf) { + free(user); + } + +Returns the length of the variable's value. If variable is not found, 0 is +returned. + diff --git a/examples/cookie_auth/cookie_auth.c b/examples/cookie_auth/cookie_auth.c index d0bc672ce8198e191ea5d168c696c6d48cdda354..6111e62aa4cf05cd4cd59e566af5cc59178e9c8d 100644 --- a/examples/cookie_auth/cookie_auth.c +++ b/examples/cookie_auth/cookie_auth.c @@ -59,20 +59,29 @@ static int check_pass(const char *user, const char *pass) { */ static struct session *get_session(struct http_message *hm) { struct mg_str *cookie_header = mg_get_http_header(hm, "cookie"); - if (cookie_header == NULL) return NULL; - char ssid[21]; - if (!mg_http_parse_header(cookie_header, SESSION_COOKIE_NAME, ssid, - sizeof(ssid))) { - return NULL; + if (cookie_header == NULL) goto clean; + char ssid_buf[21]; + char *ssid = ssid_buf; + struct session *ret = NULL; + if (!mg_http_parse_header2(cookie_header, SESSION_COOKIE_NAME, &ssid, + sizeof(ssid_buf))) { + goto clean; } uint64_t sid = strtoull(ssid, NULL, 16); - for (int i = 0; i < NUM_SESSIONS; i++) { + int i; + for (i = 0; i < NUM_SESSIONS; i++) { if (s_sessions[i].id == sid) { s_sessions[i].last_used = mg_time(); - return &s_sessions[i]; + ret = &s_sessions[i]; + goto clean; } } - return NULL; + +clean: + if (ssid != ssid_buf) { + free(ssid); + } + return ret; } /* @@ -91,7 +100,8 @@ static struct session *create_session(const char *user, /* Find first available slot or use the oldest one. */ struct session *s = NULL; struct session *oldest_s = s_sessions; - for (int i = 0; i < NUM_SESSIONS; i++) { + int i; + for (i = 0; i < NUM_SESSIONS; i++) { if (s_sessions[i].id == 0) { s = &s_sessions[i]; break; @@ -176,7 +186,8 @@ static void logout_handler(struct mg_connection *nc, int ev, void *p) { /* Cleans up sessions that have been idle for too long. */ void check_sessions(void) { double threshold = mg_time() - SESSION_TTL; - for (int i = 0; i < NUM_SESSIONS; i++) { + int i; + for (i = 0; i < NUM_SESSIONS; i++) { struct session *s = &s_sessions[i]; if (s->id != 0 && s->last_used < threshold) { fprintf(stderr, "Session %" INT64_X_FMT " (%s) closed due to idleness.\n", diff --git a/mongoose.c b/mongoose.c index 3de8b98d1c1357ca0a5dd8a72ac7fac59bb06bd6..141e501762054b8de40e3253276b99b8260bc207 100644 --- a/mongoose.c +++ b/mongoose.c @@ -5373,6 +5373,86 @@ out: /* Amalgamated: #include "mg_internal.h" */ /* Amalgamated: #include "mg_util.h" */ +/* altbuf {{{ */ + +/* + * Alternate buffer: fills the client-provided buffer with data; and if it's + * not large enough, allocates another buffer (via mbuf), similar to asprintf. + */ +struct altbuf { + struct mbuf m; + char *user_buf; + size_t len; + size_t user_buf_size; +}; + +/* + * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer. + */ +MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) { + mbuf_init(&ab->m, 0); + ab->user_buf = buf; + ab->user_buf_size = buf_size; + ab->len = 0; +} + +/* + * Appends a single char to the altbuf. + */ +MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) { + if (ab->len < ab->user_buf_size) { + /* The data fits into the original buffer */ + ab->user_buf[ab->len++] = c; + } else { + /* The data can't fit into the original buffer, so write it to mbuf. */ + + /* + * First of all, see if that's the first byte which overflows the original + * buffer: if so, copy the existing data from there to a newly allocated + * mbuf. + */ + if (ab->len > 0 && ab->m.len == 0) { + mbuf_append(&ab->m, ab->user_buf, ab->len); + } + + mbuf_append(&ab->m, &c, 1); + ab->len = ab->m.len; + } +} + +/* + * Resets any data previously appended to altbuf. + */ +MG_INTERNAL void altbuf_reset(struct altbuf *ab) { + mbuf_free(&ab->m); + ab->len = 0; +} + +/* + * Returns whether the additional buffer was allocated (and thus the data + * is in the mbuf, not the client-provided buffer) + */ +MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) { + return ab->len > ab->user_buf_size; +} + +/* + * Returns the actual buffer with data, either the client-provided or a newly + * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first. + */ +MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) { + if (altbuf_reallocated(ab)) { + if (trim) { + mbuf_trim(&ab->m); + } + return ab->m.buf; + } else { + return ab->user_buf; + } +} + +/* }}} */ + static const char *mg_version_header = "Mongoose/" MG_VERSION; enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT }; @@ -6182,7 +6262,8 @@ static void mg_http_multipart_begin(struct mg_connection *nc, struct mg_str *ct; struct mbuf *io = &nc->recv_mbuf; - char boundary[100]; + char boundary_buf[100]; + char *boundary = boundary_buf; int boundary_len; ct = mg_get_http_header(hm, "Content-Type"); @@ -6197,7 +6278,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc, } boundary_len = - mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); + mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf)); if (boundary_len == 0) { /* * Content type is multipart, but there is no boundary, @@ -6234,7 +6315,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc, mbuf_remove(io, req_len); } exit_mp: - ; + if (boundary != boundary_buf) MG_FREE(boundary); } #define CONTENT_DISPOSITION "Content-Disposition: " @@ -6316,17 +6397,24 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { return 1; } +static void mg_http_parse_header_internal(struct mg_str *hdr, + const char *var_name, + struct altbuf *ab); + static int mg_http_multipart_process_boundary(struct mg_connection *c) { int data_size; const char *boundary, *block_begin; struct mbuf *io = &c->recv_mbuf; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - char file_name[100], var_name[100]; + struct altbuf ab_file_name, ab_var_name; int line_len; boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); block_begin = boundary + pd->mp_stream.boundary_len + 2; data_size = io->len - (block_begin - io->buf); + altbuf_init(&ab_file_name, NULL, 0); + altbuf_init(&ab_var_name, NULL, 0); + while (data_size > 0 && (line_len = mg_get_line_len(block_begin, data_size)) != 0) { if (line_len > (int) sizeof(CONTENT_DISPOSITION) && @@ -6336,11 +6424,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1; header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1; - mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2); - mg_http_parse_header(&header, "filename", file_name, - sizeof(file_name) - 2); + + altbuf_reset(&ab_var_name); + mg_http_parse_header_internal(&header, "name", &ab_var_name); + + altbuf_reset(&ab_file_name); + mg_http_parse_header_internal(&header, "filename", &ab_file_name); + block_begin += line_len; data_size -= line_len; + continue; } @@ -6351,10 +6444,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); } + /* Reserve 2 bytes for "\r\n" in file_name and var_name */ + altbuf_append(&ab_file_name, '\0'); + altbuf_append(&ab_file_name, '\0'); + altbuf_append(&ab_var_name, '\0'); + altbuf_append(&ab_var_name, '\0'); + MG_FREE((void *) pd->mp_stream.file_name); - pd->mp_stream.file_name = strdup(file_name); + pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */); MG_FREE((void *) pd->mp_stream.var_name); - pd->mp_stream.var_name = strdup(var_name); + pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */); mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0); pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; @@ -6367,6 +6466,9 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; + altbuf_reset(&ab_var_name); + altbuf_reset(&ab_file_name); + return 0; } @@ -6921,14 +7023,12 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) { /* LCOV_EXCL_STOP */ } -int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, - size_t buf_size) { - int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name); +static void mg_http_parse_header_internal(struct mg_str *hdr, + const char *var_name, + struct altbuf *ab) { + int ch = ' ', ch1 = ',', n = strlen(var_name); const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL; - if (buf != NULL && buf_size > 0) buf[0] = '\0'; - if (hdr == NULL) return 0; - /* Find where variable starts */ for (s = hdr->p; s != NULL && s + n < end; s++) { if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') && @@ -6942,19 +7042,51 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, ch = ch1 = *s++; } p = s; - while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) { + while (p < end && p[0] != ch && p[0] != ch1) { if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++; - buf[len++] = *p++; + altbuf_append(ab, *p++); } - if (len >= (int) buf_size || (ch != ' ' && *p != ch)) { - len = 0; - } else { - if (len > 0 && s[len - 1] == ',') len--; - if (len > 0 && s[len - 1] == ';') len--; - buf[len] = '\0'; + + if (ch != ' ' && *p != ch) { + altbuf_reset(ab); } } + /* If there is some data, append a NUL. */ + if (ab->len > 0) { + altbuf_append(ab, '\0'); + } +} + +int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, + size_t buf_size) { + struct altbuf ab; + altbuf_init(&ab, *buf, buf_size); + if (hdr == NULL) return 0; + if (*buf != NULL && buf_size > 0) *buf[0] = '\0'; + + mg_http_parse_header_internal(hdr, var_name, &ab); + + /* + * Get a (trimmed) buffer, and return a len without a NUL byte which might + * have been added. + */ + *buf = altbuf_get_buf(&ab, 1 /* trim */); + return ab.len > 0 ? ab.len - 1 : 0; +} + +int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, + size_t buf_size) { + char *buf2 = buf; + + int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size); + + if (buf2 != buf) { + /* Buffer was not enough and was reallocated: free it and just return 0 */ + MG_FREE(buf2); + return 0; + } + return len; } @@ -7092,27 +7224,34 @@ static int mg_check_nonce(const char *nonce) { int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain, FILE *fp) { + int ret = 0; struct mg_str *hdr; - char username[50], cnonce[64], response[40], uri[200], qop[20], nc[20], - nonce[30]; + char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200], + qop_buf[20], nc_buf[20], nonce_buf[16]; + + char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf, + *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf; /* Parse "Authorization:" header, fail fast on parse error */ if (hm == NULL || fp == NULL || (hdr = mg_get_http_header(hm, "Authorization")) == NULL || - mg_http_parse_header(hdr, "username", username, sizeof(username)) == 0 || - mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 || - mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 || - mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 || - mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 || - mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 || - mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 || + mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) == + 0 || + mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 || + mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) == + 0 || + mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 || + mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 || + mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 || + mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 || mg_check_nonce(nonce) == 0) { - return 0; + ret = 0; + goto clean; } /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */ - return mg_check_digest_auth( + ret = mg_check_digest_auth( hm->method, mg_mk_str_n( hm->uri.p, @@ -7120,6 +7259,17 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain, mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response), mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain), fp); + +clean: + if (username != username_buf) MG_FREE(username); + if (cnonce != cnonce_buf) MG_FREE(cnonce); + if (response != response_buf) MG_FREE(response); + if (uri != uri_buf) MG_FREE(uri); + if (qop != qop_buf) MG_FREE(qop); + if (nc != nc_buf) MG_FREE(nc); + if (nonce != nonce_buf) MG_FREE(nonce); + + return ret; } int mg_check_digest_auth(struct mg_str method, struct mg_str uri, @@ -8189,8 +8339,24 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name, struct mg_str header; header.p = buf + n + cdl; header.len = ll - (cdl + 2); - mg_http_parse_header(&header, "name", var_name, var_name_len); - mg_http_parse_header(&header, "filename", file_name, file_name_len); + { + char *var_name2 = var_name; + mg_http_parse_header2(&header, "name", &var_name2, var_name_len); + /* TODO: handle reallocated buffer correctly */ + if (var_name2 != var_name) { + MG_FREE(var_name2); + var_name[0] = '\0'; + } + } + { + char *file_name2 = file_name; + mg_http_parse_header2(&header, "filename", &file_name2, file_name_len); + /* TODO: handle reallocated buffer correctly */ + if (file_name2 != file_name) { + MG_FREE(file_name2); + file_name[0] = '\0'; + } + } } } diff --git a/mongoose.h b/mongoose.h index 15d54fe637e21e20c3eb01c9c7b749da1f6b2fb2..3104507f47a132eb137be07812b8dec3f6f6b391 100644 --- a/mongoose.h +++ b/mongoose.h @@ -4647,21 +4647,42 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name); /* * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value - * in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero - * otherwise. + * in the buffer `*buf`, `buf_size`. If the buffer size is not enough, + * allocates a buffer of required size and writes it to `*buf`, similar to + * asprintf(). The caller should always check whether the buffer was updated, + * and free it if so. * * This function is supposed to parse cookies, authentication headers, etc. * Example (error handling omitted): * - * char user[20]; + * char user_buf[20]; + * char *user = user_buf; * struct mg_str *hdr = mg_get_http_header(hm, "Authorization"); - * mg_http_parse_header(hdr, "username", user, sizeof(user)); + * mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf)); + * // ... do something useful with user + * if (user != user_buf) { + * free(user); + * } * - * Returns the length of the variable's value. If buffer is not large enough, - * or variable not found, 0 is returned. + * Returns the length of the variable's value. If variable is not found, 0 is + * returned. + */ +int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, + size_t buf_size); + +/* + * DEPRECATED: use mg_http_parse_header2() instead. + * + * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of + * `char **`), and thus it cannot allocate a new buffer if the provided one + * is not enough, and just returns 0 in that case. */ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, - size_t buf_size); + size_t buf_size) +#ifdef __GNUC__ + __attribute__((deprecated)); +#endif +; /* * Gets and parses the Authorization: Basic header diff --git a/src/mg_http.c b/src/mg_http.c index 547b5d13eb2eb4fa44e2ecf151dbc79992bc6936..068d2083077942b5a5f340d90c0f9901a9b2ace7 100644 --- a/src/mg_http.c +++ b/src/mg_http.c @@ -9,6 +9,86 @@ #include "mg_internal.h" #include "mg_util.h" +/* altbuf {{{ */ + +/* + * Alternate buffer: fills the client-provided buffer with data; and if it's + * not large enough, allocates another buffer (via mbuf), similar to asprintf. + */ +struct altbuf { + struct mbuf m; + char *user_buf; + size_t len; + size_t user_buf_size; +}; + +/* + * Initializes altbuf; `buf`, `buf_size` is the client-provided buffer. + */ +MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) { + mbuf_init(&ab->m, 0); + ab->user_buf = buf; + ab->user_buf_size = buf_size; + ab->len = 0; +} + +/* + * Appends a single char to the altbuf. + */ +MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) { + if (ab->len < ab->user_buf_size) { + /* The data fits into the original buffer */ + ab->user_buf[ab->len++] = c; + } else { + /* The data can't fit into the original buffer, so write it to mbuf. */ + + /* + * First of all, see if that's the first byte which overflows the original + * buffer: if so, copy the existing data from there to a newly allocated + * mbuf. + */ + if (ab->len > 0 && ab->m.len == 0) { + mbuf_append(&ab->m, ab->user_buf, ab->len); + } + + mbuf_append(&ab->m, &c, 1); + ab->len = ab->m.len; + } +} + +/* + * Resets any data previously appended to altbuf. + */ +MG_INTERNAL void altbuf_reset(struct altbuf *ab) { + mbuf_free(&ab->m); + ab->len = 0; +} + +/* + * Returns whether the additional buffer was allocated (and thus the data + * is in the mbuf, not the client-provided buffer) + */ +MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) { + return ab->len > ab->user_buf_size; +} + +/* + * Returns the actual buffer with data, either the client-provided or a newly + * allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first. + */ +MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) { + if (altbuf_reallocated(ab)) { + if (trim) { + mbuf_trim(&ab->m); + } + return ab->m.buf; + } else { + return ab->user_buf; + } +} + +/* }}} */ + static const char *mg_version_header = "Mongoose/" MG_VERSION; enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT }; @@ -818,7 +898,8 @@ static void mg_http_multipart_begin(struct mg_connection *nc, struct mg_str *ct; struct mbuf *io = &nc->recv_mbuf; - char boundary[100]; + char boundary_buf[100]; + char *boundary = boundary_buf; int boundary_len; ct = mg_get_http_header(hm, "Content-Type"); @@ -833,7 +914,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc, } boundary_len = - mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); + mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf)); if (boundary_len == 0) { /* * Content type is multipart, but there is no boundary, @@ -870,7 +951,7 @@ static void mg_http_multipart_begin(struct mg_connection *nc, mbuf_remove(io, req_len); } exit_mp: - ; + if (boundary != boundary_buf) MG_FREE(boundary); } #define CONTENT_DISPOSITION "Content-Disposition: " @@ -952,17 +1033,24 @@ static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { return 1; } +static void mg_http_parse_header_internal(struct mg_str *hdr, + const char *var_name, + struct altbuf *ab); + static int mg_http_multipart_process_boundary(struct mg_connection *c) { int data_size; const char *boundary, *block_begin; struct mbuf *io = &c->recv_mbuf; struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - char file_name[100], var_name[100]; + struct altbuf ab_file_name, ab_var_name; int line_len; boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); block_begin = boundary + pd->mp_stream.boundary_len + 2; data_size = io->len - (block_begin - io->buf); + altbuf_init(&ab_file_name, NULL, 0); + altbuf_init(&ab_var_name, NULL, 0); + while (data_size > 0 && (line_len = mg_get_line_len(block_begin, data_size)) != 0) { if (line_len > (int) sizeof(CONTENT_DISPOSITION) && @@ -972,11 +1060,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1; header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1; - mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2); - mg_http_parse_header(&header, "filename", file_name, - sizeof(file_name) - 2); + + altbuf_reset(&ab_var_name); + mg_http_parse_header_internal(&header, "name", &ab_var_name); + + altbuf_reset(&ab_file_name); + mg_http_parse_header_internal(&header, "filename", &ab_file_name); + block_begin += line_len; data_size -= line_len; + continue; } @@ -987,10 +1080,16 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); } + /* Reserve 2 bytes for "\r\n" in file_name and var_name */ + altbuf_append(&ab_file_name, '\0'); + altbuf_append(&ab_file_name, '\0'); + altbuf_append(&ab_var_name, '\0'); + altbuf_append(&ab_var_name, '\0'); + MG_FREE((void *) pd->mp_stream.file_name); - pd->mp_stream.file_name = strdup(file_name); + pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */); MG_FREE((void *) pd->mp_stream.var_name); - pd->mp_stream.var_name = strdup(var_name); + pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */); mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0); pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; @@ -1003,6 +1102,9 @@ static int mg_http_multipart_process_boundary(struct mg_connection *c) { pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; + altbuf_reset(&ab_var_name); + altbuf_reset(&ab_file_name); + return 0; } @@ -1557,14 +1659,12 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) { /* LCOV_EXCL_STOP */ } -int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, - size_t buf_size) { - int ch = ' ', ch1 = ',', len = 0, n = strlen(var_name); +static void mg_http_parse_header_internal(struct mg_str *hdr, + const char *var_name, + struct altbuf *ab) { + int ch = ' ', ch1 = ',', n = strlen(var_name); const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL; - if (buf != NULL && buf_size > 0) buf[0] = '\0'; - if (hdr == NULL) return 0; - /* Find where variable starts */ for (s = hdr->p; s != NULL && s + n < end; s++) { if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') && @@ -1578,19 +1678,51 @@ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, ch = ch1 = *s++; } p = s; - while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) { + while (p < end && p[0] != ch && p[0] != ch1) { if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++; - buf[len++] = *p++; + altbuf_append(ab, *p++); } - if (len >= (int) buf_size || (ch != ' ' && *p != ch)) { - len = 0; - } else { - if (len > 0 && s[len - 1] == ',') len--; - if (len > 0 && s[len - 1] == ';') len--; - buf[len] = '\0'; + + if (ch != ' ' && *p != ch) { + altbuf_reset(ab); } } + /* If there is some data, append a NUL. */ + if (ab->len > 0) { + altbuf_append(ab, '\0'); + } +} + +int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, + size_t buf_size) { + struct altbuf ab; + altbuf_init(&ab, *buf, buf_size); + if (hdr == NULL) return 0; + if (*buf != NULL && buf_size > 0) *buf[0] = '\0'; + + mg_http_parse_header_internal(hdr, var_name, &ab); + + /* + * Get a (trimmed) buffer, and return a len without a NUL byte which might + * have been added. + */ + *buf = altbuf_get_buf(&ab, 1 /* trim */); + return ab.len > 0 ? ab.len - 1 : 0; +} + +int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, + size_t buf_size) { + char *buf2 = buf; + + int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size); + + if (buf2 != buf) { + /* Buffer was not enough and was reallocated: free it and just return 0 */ + MG_FREE(buf2); + return 0; + } + return len; } @@ -1728,27 +1860,34 @@ static int mg_check_nonce(const char *nonce) { int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain, FILE *fp) { + int ret = 0; struct mg_str *hdr; - char username[50], cnonce[64], response[40], uri[200], qop[20], nc[20], - nonce[30]; + char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200], + qop_buf[20], nc_buf[20], nonce_buf[16]; + + char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf, + *uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf; /* Parse "Authorization:" header, fail fast on parse error */ if (hm == NULL || fp == NULL || (hdr = mg_get_http_header(hm, "Authorization")) == NULL || - mg_http_parse_header(hdr, "username", username, sizeof(username)) == 0 || - mg_http_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce)) == 0 || - mg_http_parse_header(hdr, "response", response, sizeof(response)) == 0 || - mg_http_parse_header(hdr, "uri", uri, sizeof(uri)) == 0 || - mg_http_parse_header(hdr, "qop", qop, sizeof(qop)) == 0 || - mg_http_parse_header(hdr, "nc", nc, sizeof(nc)) == 0 || - mg_http_parse_header(hdr, "nonce", nonce, sizeof(nonce)) == 0 || + mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) == + 0 || + mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 || + mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) == + 0 || + mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 || + mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 || + mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 || + mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 || mg_check_nonce(nonce) == 0) { - return 0; + ret = 0; + goto clean; } /* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */ - return mg_check_digest_auth( + ret = mg_check_digest_auth( hm->method, mg_mk_str_n( hm->uri.p, @@ -1756,6 +1895,17 @@ int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain, mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response), mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain), fp); + +clean: + if (username != username_buf) MG_FREE(username); + if (cnonce != cnonce_buf) MG_FREE(cnonce); + if (response != response_buf) MG_FREE(response); + if (uri != uri_buf) MG_FREE(uri); + if (qop != qop_buf) MG_FREE(qop); + if (nc != nc_buf) MG_FREE(nc); + if (nonce != nonce_buf) MG_FREE(nonce); + + return ret; } int mg_check_digest_auth(struct mg_str method, struct mg_str uri, @@ -2825,8 +2975,24 @@ size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name, struct mg_str header; header.p = buf + n + cdl; header.len = ll - (cdl + 2); - mg_http_parse_header(&header, "name", var_name, var_name_len); - mg_http_parse_header(&header, "filename", file_name, file_name_len); + { + char *var_name2 = var_name; + mg_http_parse_header2(&header, "name", &var_name2, var_name_len); + /* TODO: handle reallocated buffer correctly */ + if (var_name2 != var_name) { + MG_FREE(var_name2); + var_name[0] = '\0'; + } + } + { + char *file_name2 = file_name; + mg_http_parse_header2(&header, "filename", &file_name2, file_name_len); + /* TODO: handle reallocated buffer correctly */ + if (file_name2 != file_name) { + MG_FREE(file_name2); + file_name[0] = '\0'; + } + } } } diff --git a/src/mg_http_server.h b/src/mg_http_server.h index 62f298f86409dbb3bb5c5b0f915d725477d26155..c4dfee4da21b55f0e1656c3bddb7e5b95ad67a78 100644 --- a/src/mg_http_server.h +++ b/src/mg_http_server.h @@ -31,21 +31,42 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name); /* * Parses the HTTP header `hdr`. Finds variable `var_name` and stores its value - * in the buffer `buf`, `buf_size`. Returns 0 if variable not found, non-zero - * otherwise. + * in the buffer `*buf`, `buf_size`. If the buffer size is not enough, + * allocates a buffer of required size and writes it to `*buf`, similar to + * asprintf(). The caller should always check whether the buffer was updated, + * and free it if so. * * This function is supposed to parse cookies, authentication headers, etc. * Example (error handling omitted): * - * char user[20]; + * char user_buf[20]; + * char *user = user_buf; * struct mg_str *hdr = mg_get_http_header(hm, "Authorization"); - * mg_http_parse_header(hdr, "username", user, sizeof(user)); + * mg_http_parse_header2(hdr, "username", &user, sizeof(user_buf)); + * // ... do something useful with user + * if (user != user_buf) { + * free(user); + * } + * + * Returns the length of the variable's value. If variable is not found, 0 is + * returned. + */ +int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf, + size_t buf_size); + +/* + * DEPRECATED: use mg_http_parse_header2() instead. * - * Returns the length of the variable's value. If buffer is not large enough, - * or variable not found, 0 is returned. + * Same as mg_http_parse_header2(), but takes buffer as a `char *` (instead of + * `char **`), and thus it cannot allocate a new buffer if the provided one + * is not enough, and just returns 0 in that case. */ int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf, - size_t buf_size); + size_t buf_size) +#ifdef __GNUC__ + __attribute__((deprecated)); +#endif +; /* * Gets and parses the Authorization: Basic header diff --git a/test/Makefile b/test/Makefile index 4795243d01635c37f83b88b799df511c2b6cf539..89223ab842205789216a24353bfdd3a17f4d92ae 100644 --- a/test/Makefile +++ b/test/Makefile @@ -45,7 +45,9 @@ COMMON_FEATURE_FLAGS = \ -DMG_ENABLE_SNTP -DMG_SNTP_NO_DELAY_CORRECTION \ -DMG_ENABLE_HTTP_STREAMING_MULTIPART UNIX_FEATURE_FLAGS=-DMG_ENABLE_IPV6 -DMG_ENABLE_SSL -CFLAGS = -W -Wall -Wundef -Werror -g -O0 -Wno-multichar -D__USE_MISC \ +# TODO: remove -Wno-deprecated-declarations once deprecated +# `mg_http_parse_header()` is removed from mongoose. +CFLAGS = -W -Wall -Wundef -Werror -Wno-deprecated-declarations -g -O0 -Wno-multichar -D__USE_MISC \ $(COMMON_FEATURE_FLAGS) $(UNIX_FEATURE_FLAGS) \ $(patsubst %,-I%,$(subst :, ,$(VPATH))) \ -include unit_test.h -pthread $(CFLAGS_EXTRA) diff --git a/test/unit_test.c b/test/unit_test.c index 9c7f284cd00000bddcc39d25d65cf8bcf6e3a6ca..ff080c58a6ec0063bcf4ec2068b3b294efe54712 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -4941,12 +4941,34 @@ static const char *test_http_parse_header(void) { "xx=1 kl yy, ert=234 kl=123, " "uri=\"/?naii=x,y\";ii=\"12\\\"34\" zz='aa bb',tt=2,gf=\"xx d=1234"); char buf[20]; + char *buf2; ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, sizeof(buf)), 3); ASSERT_STREQ(buf, "234"); + ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 2), 0); ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 3), 0); ASSERT_EQ(mg_http_parse_header(&h, "ert", buf, 4), 3); + + buf2 = buf; + ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 2), 3); + ASSERT(buf2 != buf); + free(buf2); + + buf2 = buf; + ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 3), 3); + ASSERT(buf2 != buf); + free(buf2); + + buf2 = buf; + ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 4), 3); + ASSERT(buf2 == buf); + + buf2 = NULL; + ASSERT_EQ(mg_http_parse_header2(&h, "ert", &buf2, 0), 3); + ASSERT_STREQ(buf2, "234"); + free(buf2); + ASSERT_EQ(mg_http_parse_header(&h, "gf", buf, sizeof(buf)), 0); ASSERT_EQ(mg_http_parse_header(&h, "zz", buf, sizeof(buf)), 5); ASSERT_STREQ(buf, "aa bb");