1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00

libcli/http: Handle http chunked transfer encoding

Also removes the knownfail for the chunked transfer test

Signed-off-by: Noel Power <noel.power@suse.com>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
(cherry picked from commit 03240c91fb)
This commit is contained in:
Noel Power 2024-03-25 19:44:10 +00:00 committed by Jule Anger
parent 7e17e4809d
commit a70e3a36c8
4 changed files with 220 additions and 9 deletions

View File

@ -45,6 +45,12 @@ static int http_response_needs_body(struct http_request *req)
char c;
unsigned long long v;
cmp = strcasecmp(h->key, "Transfer-Encoding");
if (cmp == 0) {
cmp = strcasecmp(h->value, "chunked");
return 2;
}
cmp = strcasecmp(h->key, "Content-Length");
if (cmp != 0) {
continue;
@ -66,6 +72,11 @@ static int http_response_needs_body(struct http_request *req)
return 0;
}
struct http_chunk
{
struct http_chunk *prev, *next;
DATA_BLOB blob;
};
struct http_read_response_state {
enum http_parser_state parser_state;
@ -73,6 +84,7 @@ struct http_read_response_state {
uint64_t max_content_length;
DATA_BLOB buffer;
struct http_request *response;
struct http_chunk *chunks;
};
/**
@ -119,6 +131,11 @@ static enum http_read_status http_parse_headers(struct http_read_response_state
ret = http_response_needs_body(state->response);
switch (ret) {
case 2:
DEBUG(11, ("%s: need to process chunks... %d\n", __func__,
state->response->response_code));
state->parser_state = HTTP_READING_CHUNK_SIZE;
break;
case 1:
if (state->response->remaining_content_length <= state->max_content_length) {
DEBUG(11, ("%s: Start of read body\n", __func__));
@ -162,6 +179,141 @@ error:
return status;
}
static bool http_response_process_chunks(struct http_read_response_state *state)
{
struct http_chunk *chunk = NULL;
struct http_request *resp = state->response;
for (chunk = state->chunks; chunk; chunk = chunk->next) {
DBG_DEBUG("processing chunk of size %zi\n",
chunk->blob.length);
if (resp->body.data == NULL) {
resp->body = chunk->blob;
chunk->blob = data_blob_null;
talloc_steal(resp, resp->body.data);
continue;
}
resp->body.data =
talloc_realloc(resp,
resp->body.data,
uint8_t,
resp->body.length + chunk->blob.length);
if (!resp->body.data) {
return false;
}
memcpy(resp->body.data + resp->body.length,
chunk->blob.data,
chunk->blob.length);
resp->body.length += chunk->blob.length;
TALLOC_FREE(chunk->blob.data);
chunk->blob = data_blob_null;
}
return true;
}
static enum http_read_status http_read_chunk_term(struct http_read_response_state *state)
{
enum http_read_status status = HTTP_ALL_DATA_READ;
char *ptr = NULL;
char *line = NULL;
/* Sanity checks */
if (!state || !state->response) {
DBG_ERR("%s: Invalid Parameter\n", __func__);
return HTTP_DATA_CORRUPTED;
}
line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length);
if (!line) {
DBG_ERR("%s: Memory error\n", __func__);
return HTTP_DATA_CORRUPTED;
}
ptr = strstr(line, "\r\n");
if (ptr == NULL) {
TALLOC_FREE(line);
return HTTP_MORE_DATA_EXPECTED;
}
if (strncmp(line, "\r\n", 2) == 0) {
/* chunk terminator */
if (state->parser_state == HTTP_READING_FINAL_CHUNK_TERM) {
if (http_response_process_chunks(state) == false) {
status = HTTP_DATA_CORRUPTED;
goto out;
}
state->parser_state = HTTP_READING_DONE;
} else {
state->parser_state = HTTP_READING_CHUNK_SIZE;
}
status = HTTP_ALL_DATA_READ;
goto out;
}
status = HTTP_DATA_CORRUPTED;
out:
TALLOC_FREE(line);
return status;
}
static enum http_read_status http_read_chunk_size(struct http_read_response_state *state)
{
enum http_read_status status = HTTP_ALL_DATA_READ;
char *ptr = NULL;
char *line = NULL;
char *value = NULL;
int n = 0;
unsigned long long v;
/* Sanity checks */
if (!state || !state->response) {
DBG_ERR("%s: Invalid Parameter\n", __func__);
return HTTP_DATA_CORRUPTED;
}
line = talloc_strndup(state, (char *)state->buffer.data, state->buffer.length);
if (!line) {
DBG_ERR("%s: Memory error\n", __func__);
return HTTP_DATA_CORRUPTED;
}
ptr = strstr(line, "\r\n");
if (ptr == NULL) {
TALLOC_FREE(line);
return HTTP_MORE_DATA_EXPECTED;
}
n = sscanf(line, "%m[^\r\n]\r\n", &value);
if (n != 1) {
DBG_ERR("%s: Error parsing chunk size '%s'\n", __func__, line);
status = HTTP_DATA_CORRUPTED;
goto out;
}
DBG_DEBUG("Got chunk size string %s\n", value);
n = sscanf(value, "%llx", &v);
if (n != 1) {
DBG_ERR("%s: Error parsing chunk size '%s'\n", __func__, line);
status = HTTP_DATA_CORRUPTED;
goto out;
}
DBG_DEBUG("Got chunk size %llu 0x%llx\n", v, v);
if (v == 0) {
state->parser_state = HTTP_READING_FINAL_CHUNK_TERM;
} else {
state->parser_state = HTTP_READING_CHUNK;
}
state->response->remaining_content_length = v;
status = HTTP_ALL_DATA_READ;
out:
if (value) {
free(value);
}
TALLOC_FREE(line);
return status;
}
/**
* Parses the first line of a HTTP response
*/
@ -301,6 +453,55 @@ static enum http_read_status http_read_body(struct http_read_response_state *sta
return HTTP_ALL_DATA_READ;
}
static enum http_read_status http_read_chunk(struct http_read_response_state *state)
{
struct http_request *resp = state->response;
struct http_chunk *chunk = NULL;
size_t total = 0;
size_t prev = 0;
if (state->buffer.length < resp->remaining_content_length) {
return HTTP_MORE_DATA_EXPECTED;
}
for (chunk = state->chunks; chunk; chunk = chunk->next) {
total += chunk->blob.length;
}
prev = total;
total = total + state->buffer.length;
if (total < prev) {
DBG_ERR("adding chunklen %zu to buf len %zu "
"will overflow\n",
state->buffer.length,
prev);
return HTTP_DATA_CORRUPTED;
}
if (total > state->max_content_length) {
DBG_DEBUG("size %zu exceeds "
"max content len %"PRIu64" skipping body\n",
total,
state->max_content_length);
state->parser_state = HTTP_READING_DONE;
goto out;
}
/* chunk read */
chunk = talloc_zero(state, struct http_chunk);
if (chunk == NULL) {
DBG_ERR("%s: Memory error\n", __func__);
return HTTP_DATA_CORRUPTED;
}
chunk->blob = state->buffer;
talloc_steal(chunk, chunk->blob.data);
DLIST_ADD_END(state->chunks, chunk);
state->parser_state = HTTP_READING_CHUNK_TERM;
out:
state->buffer = data_blob_null;
resp->remaining_content_length = 0;
return HTTP_ALL_DATA_READ;
}
static enum http_read_status http_read_trailer(struct http_read_response_state *state)
{
enum http_read_status status = HTTP_DATA_CORRUPTED;
@ -323,6 +524,16 @@ static enum http_read_status http_parse_buffer(struct http_read_response_state *
case HTTP_READING_BODY:
return http_read_body(state);
break;
case HTTP_READING_FINAL_CHUNK_TERM:
case HTTP_READING_CHUNK_TERM:
return http_read_chunk_term(state);
break;
case HTTP_READING_CHUNK_SIZE:
return http_read_chunk_size(state);
break;
case HTTP_READING_CHUNK:
return http_read_chunk(state);
break;
case HTTP_READING_TRAILER:
return http_read_trailer(state);
break;
@ -530,7 +741,8 @@ static int http_read_response_next_vector(struct tstream_context *stream,
case HTTP_MORE_DATA_EXPECTED: {
size_t toread = 1;
size_t total;
if (state->parser_state == HTTP_READING_BODY) {
if (state->parser_state == HTTP_READING_BODY ||
state->parser_state == HTTP_READING_CHUNK) {
struct http_request *resp = state->response;
toread = resp->remaining_content_length -
state->buffer.length;
@ -549,7 +761,7 @@ static int http_read_response_next_vector(struct tstream_context *stream,
/*
* test if content-length message exceeds the
* specified max_content_length
* Note: This check wont be hit at the moment
* Note: This check won't be hit at the moment
* due to an existing check in parse_headers
* which will skip the body. Check is here
* for completeness and to cater for future
@ -558,7 +770,7 @@ static int http_read_response_next_vector(struct tstream_context *stream,
if (state->parser_state == HTTP_READING_BODY) {
if (total > state->max_content_length) {
DBG_ERR("content size %zu exceeds "
"max content len %zu\n",
"max content len %"PRIu64"\n",
total,
state->max_content_length);
return -1;

View File

@ -28,6 +28,10 @@ enum http_parser_state {
HTTP_READING_BODY,
HTTP_READING_TRAILER,
HTTP_READING_DONE,
HTTP_READING_CHUNK_SIZE,
HTTP_READING_CHUNK,
HTTP_READING_CHUNK_TERM,
HTTP_READING_FINAL_CHUNK_TERM,
};
enum http_read_status {

View File

@ -81,7 +81,7 @@ class HttpChunkBlackboxTests(BlackboxTestCase):
try:
msg = "one_chunk"
resp = self.check_output("%s -U%% -I%s --uri %s" % (COMMAND, os.getenv("SERVER_IP", "localhost"), msg))
self.assertEqual(msg, resp.decode('utf-8'))
self.assertEqual(msg,resp.decode('utf-8'))
except BlackboxProcessError as e:
print("Failed with: %s" % e)
self.fail(str(e))

View File

@ -1,5 +0,0 @@
^samba.tests.blackbox.http_chunk.samba.tests.blackbox.http_chunk.HttpChunkBlackboxTests.test_multi_chunks
^samba.tests.blackbox.http_chunk.samba.tests.blackbox.http_chunk.HttpChunkBlackboxTests.test_single_chunk
^samba.tests.blackbox.http_chunk.samba.tests.blackbox.http_chunk.HttpChunkBlackboxTests.test_exact_request_size
^samba.tests.blackbox.http_chunk.samba.tests.blackbox.http_chunk.HttpChunkBlackboxTests.test_exceed_request_size