summaryrefslogtreecommitdiffstats
path: root/libcli/http/http.c
diff options
context:
space:
mode:
Diffstat (limited to 'libcli/http/http.c')
-rw-r--r--libcli/http/http.c309
1 files changed, 289 insertions, 20 deletions
diff --git a/libcli/http/http.c b/libcli/http/http.c
index 96c573a..6f22214 100644
--- a/libcli/http/http.c
+++ b/libcli/http/http.c
@@ -28,16 +28,28 @@
#undef strcasecmp
+enum http_body_type {
+ BODY_NONE = 0,
+ BODY_CONTENT_LENGTH,
+ BODY_CHUNKED,
+ BODY_ERROR = -1
+};
+
/**
* Determines if a response should have a body.
- * @return 1 if the response MUST have a body; 0 if the response MUST NOT have
- * a body. Returns -1 on error.
+ * @return 2 if response MUST use chunked encoding,
+ * 1 if the response MUST have a body;
+ * 0 if the response MUST NOT have a body.
+ * Returns -1 on error.
*/
-static int http_response_needs_body(struct http_request *req)
+static enum http_body_type http_response_needs_body(
+ struct http_request *req)
{
struct http_header *h = NULL;
- if (!req) return -1;
+ if (!req) {
+ return BODY_ERROR;
+ }
for (h = req->headers; h != NULL; h = h->next) {
int cmp;
@@ -45,6 +57,18 @@ 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");
+ if (cmp == 0) {
+ return BODY_CHUNKED;
+ }
+ /* unsupported Transfer-Encoding type */
+ DBG_ERR("Unsupported transfer encoding type %s\n",
+ h->value);
+ return BODY_ERROR;
+ }
+
cmp = strcasecmp(h->key, "Content-Length");
if (cmp != 0) {
continue;
@@ -52,20 +76,25 @@ static int http_response_needs_body(struct http_request *req)
n = sscanf(h->value, "%llu%c", &v, &c);
if (n != 1) {
- return -1;
+ return BODY_ERROR;
}
req->remaining_content_length = v;
if (v != 0) {
- return 1;
+ return BODY_CONTENT_LENGTH;
}
- return 0;
+ return BODY_NONE;
}
- return 0;
+ return BODY_NONE;
}
+struct http_chunk
+{
+ struct http_chunk *prev, *next;
+ DATA_BLOB blob;
+};
struct http_read_response_state {
enum http_parser_state parser_state;
@@ -73,6 +102,7 @@ struct http_read_response_state {
uint64_t max_content_length;
DATA_BLOB buffer;
struct http_request *response;
+ struct http_chunk *chunks;
};
/**
@@ -86,7 +116,7 @@ static enum http_read_status http_parse_headers(struct http_read_response_state
char *key = NULL;
char *value = NULL;
int n = 0;
- int ret;
+ enum http_body_type ret;
/* Sanity checks */
if (!state || !state->response) {
@@ -119,19 +149,24 @@ static enum http_read_status http_parse_headers(struct http_read_response_state
ret = http_response_needs_body(state->response);
switch (ret) {
- case 1:
+ case BODY_CHUNKED:
+ DEBUG(11, ("%s: need to process chunks... %d\n", __func__,
+ state->response->response_code));
+ state->parser_state = HTTP_READING_CHUNK_SIZE;
+ break;
+ case BODY_CONTENT_LENGTH:
if (state->response->remaining_content_length <= state->max_content_length) {
DEBUG(11, ("%s: Start of read body\n", __func__));
state->parser_state = HTTP_READING_BODY;
break;
}
FALL_THROUGH;
- case 0:
+ case BODY_NONE:
DEBUG(11, ("%s: Skipping body for code %d\n", __func__,
state->response->response_code));
state->parser_state = HTTP_READING_DONE;
break;
- case -1:
+ case BODY_ERROR:
DEBUG(0, ("%s_: Error in http_response_needs_body\n", __func__));
TALLOC_FREE(line);
return HTTP_DATA_CORRUPTED;
@@ -162,6 +197,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 +471,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 +542,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;
@@ -527,20 +756,60 @@ static int http_read_response_next_vector(struct tstream_context *stream,
*_count = 1;
}
break;
- case HTTP_MORE_DATA_EXPECTED:
- /* TODO Optimize, allocating byte by byte */
- state->buffer.data = talloc_realloc(state, state->buffer.data,
- uint8_t, state->buffer.length + 1);
+ case HTTP_MORE_DATA_EXPECTED: {
+ size_t toread = 1;
+ size_t total;
+ 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;
+ }
+
+ total = toread + state->buffer.length;
+
+ if (total < state->buffer.length) {
+ DBG_ERR("adding %zu to buf len %zu "
+ "will overflow\n",
+ toread,
+ state->buffer.length);
+ return -1;
+ }
+
+ /*
+ * test if content-length message exceeds the
+ * specified max_content_length
+ * 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
+ * code changes.
+ */
+ if (state->parser_state == HTTP_READING_BODY) {
+ if (total > state->max_content_length) {
+ DBG_ERR("content size %zu exceeds "
+ "max content len %"PRIu64"\n",
+ total,
+ state->max_content_length);
+ return -1;
+ }
+ }
+
+ state->buffer.data =
+ talloc_realloc(state, state->buffer.data,
+ uint8_t,
+ state->buffer.length + toread);
if (!state->buffer.data) {
return -1;
}
- state->buffer.length++;
+ state->buffer.length += toread;
vector[0].iov_base = (void *)(state->buffer.data +
- state->buffer.length - 1);
- vector[0].iov_len = 1;
+ state->buffer.length - toread);
+ vector[0].iov_len = toread;
*_vector = vector;
*_count = 1;
break;
+ }
case HTTP_DATA_CORRUPTED:
case HTTP_REQUEST_CANCELED:
case HTTP_DATA_TOO_LONG:
@@ -603,7 +872,7 @@ static void http_read_response_done(struct tevent_req *subreq)
{
NTSTATUS status;
struct tevent_req *req;
- int ret;
+ enum http_body_type ret;
int sys_errno;
if (!subreq) {