The branch, v4-20-test has been updated
       via  077f39baf7c libcli/http: Detect unsupported Transfer-encoding type
       via  2fb1bf0205f selftest: Add new test for testing non-chunk transfer 
encoding
       via  30bf3d1430f selftest: fix potential reference before assigned error
       via  a70e3a36c82 libcli/http: Handle http chunked transfer encoding
       via  7e17e4809d5 tests: add test for chunked encoding with http cli 
library
       via  26206392153 libcli/http: Optimise reading for content-length
       via  71eac5a065f selftest: Add basic content-lenght http tests
       via  19250e13ab6 Add simple http_client for use in black box tests (in 
following commits)
      from  eaefe50327d VERSION: Bump version up to Samba 4.20.1...

https://git.samba.org/?p=samba.git;a=shortlog;h=v4-20-test


- Log -----------------------------------------------------------------
commit 077f39baf7cc7f4e4ee8709d48b1cb23b8736c1c
Author: Noel Power <noel.po...@suse.com>
Date:   Thu Mar 28 10:48:58 2024 +0000

    libcli/http: Detect unsupported Transfer-encoding type
    
    Also removes knownfail for test that now passes
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    (cherry picked from commit a18c53a9b98e2e8dea08cf0ef08efc59e58ec137)
    
    Autobuild-User(v4-20-test): Jule Anger <jan...@samba.org>
    Autobuild-Date(v4-20-test): Thu Apr 11 12:24:08 UTC 2024 on atb-devel-224

commit 2fb1bf0205f9b5f72d8e1f51e55cf86997639a46
Author: Noel Power <noel.po...@suse.com>
Date:   Thu Mar 28 09:16:33 2024 +0000

    selftest: Add new test for testing non-chunk transfer encoding
    
    And add a known fail because there is a bug :-(
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    (cherry picked from commit 93709d31590d4ca25fbac813b9e499755b81ddb5)

commit 30bf3d1430f96a42c7b90ef215daa33b427da8b9
Author: Noel Power <noel.po...@suse.com>
Date:   Thu Mar 28 09:09:02 2024 +0000

    selftest: fix potential reference before assigned error
    
    This would only happen if the test failed (but the message would be
    incorrect as 'e' the exception to be stringified doesn't exist.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    (cherry picked from commit efdbf0511e0a89f865210170001fbebf17a45278)

commit a70e3a36c8244a324f5e8fa7b138dae5684055e0
Author: Noel Power <noel.po...@suse.com>
Date:   Mon Mar 25 19:44:10 2024 +0000

    libcli/http: Handle http chunked transfer encoding
    
    Also removes the knownfail for the chunked transfer test
    
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    (cherry picked from commit 03240c91fb6ffcf5afe47c14a1ba7a8bc12f2348)

commit 7e17e4809d593e1ce2d51583a351b38300a20e2a
Author: Noel Power <noel.po...@suse.com>
Date:   Thu Sep 23 12:18:22 2021 +0100

    tests: add test for chunked encoding with http cli library
    
    Adds http test client to excercise the http client library
    and a blackbox test to run the client. This client is built
    only with selftest
    
    also adds a knownfail for the test
    
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    (cherry picked from commit 30acd609f560352d3edb0c931b9a864110025b2c)

commit 26206392153248fb2be1ec95a2e3ac14f9356125
Author: Noel Power <noel.po...@suse.com>
Date:   Fri Mar 22 08:55:49 2024 +0000

    libcli/http: Optimise reading for content-length
    
    Instead of reading byte-by-byte we know the content length we
    want to read so lets use it.
    
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    (cherry picked from commit 5f03d84e3b52bf5a31a0f885cb83bdcb48ec96f7)

commit 71eac5a065fac4023601b067b850d209a7dec149
Author: Noel Power <noel.po...@suse.com>
Date:   Mon Mar 25 16:25:55 2024 +0000

    selftest: Add basic content-lenght http tests
    
    very simple test of basic http request/response plus some checks to
    ensure http response doesn't exceed the response max length set by
    the client call.
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    (cherry picked from commit 74cdebeae3d1bc35eea96b51b9491f6c52844b10)

commit 19250e13ab6c654405baf7c7d9c18f514ceade0f
Author: Noel Power <noel.po...@suse.com>
Date:   Mon Mar 25 19:21:54 2024 +0000

    Add simple http_client for use in black box tests (in following commits)
    
    BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
    Signed-off-by: Noel Power <noel.po...@suse.com>
    Reviewed-by: Andrew Bartlett <abart...@samba.org>
    (cherry picked from commit cd6c075476c820b4fe8bdc10a24d8fc8ac74e9c9)

-----------------------------------------------------------------------

Summary of changes:
 libcli/http/http.c                          | 309 +++++++++++++++++++--
 libcli/http/http_internal.h                 |   4 +
 python/samba/tests/blackbox/http_chunk.py   | 129 +++++++++
 python/samba/tests/blackbox/http_content.py |  95 +++++++
 selftest/tests.py                           |   2 +
 source4/client/http_test.c                  | 401 ++++++++++++++++++++++++++++
 source4/wscript_build                       |   5 +
 7 files changed, 925 insertions(+), 20 deletions(-)
 create mode 100644 python/samba/tests/blackbox/http_chunk.py
 create mode 100644 python/samba/tests/blackbox/http_content.py
 create mode 100644 source4/client/http_test.c


Changeset truncated at 500 lines:

diff --git a/libcli/http/http.c b/libcli/http/http.c
index 96c573af137..6f22214f706 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) {
diff --git a/libcli/http/http_internal.h b/libcli/http/http_internal.h
index ec17f7e2850..786ace62d84 100644
--- a/libcli/http/http_internal.h
+++ b/libcli/http/http_internal.h
@@ -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 {
diff --git a/python/samba/tests/blackbox/http_chunk.py 
b/python/samba/tests/blackbox/http_chunk.py
new file mode 100644
index 00000000000..6745c8cb392
--- /dev/null
+++ b/python/samba/tests/blackbox/http_chunk.py
@@ -0,0 +1,129 @@
+# Blackbox tests for http_test
+#
+# Copyright (C) Noel Power noel.po...@suse.com
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+import threading
+import logging
+import json
+from http.server import HTTPServer, BaseHTTPRequestHandler
+from samba.logger import get_samba_logger
+from samba.tests import BlackboxTestCase, BlackboxProcessError
+
+logger = get_samba_logger(name=__name__)
+COMMAND = "bin/http_test"
+def make_chunks(msg, chunk_size):
+    chunks = []
+    while len(msg) > chunk_size:
+        chunk = msg[:chunk_size]
+        chunks.append(chunk)
+        msg = msg[chunk_size:]
+    if len(msg):
+        chunks.append(msg)
+    return chunks
+
+# simple handler, spits back the 'path' passed in
+# GET or POST and a chunked encoded http response
+# where the chunk size is 10 octets
+class ChunkHTTPRequestHandler(BaseHTTPRequestHandler):
+    def handle_req(self):
+        msg = bytes(self.path, encoding="utf-8")
+        chunks = make_chunks(msg, 10)
+
+        self.send_response(200)
+        self.send_header('content-type', 'application/json; charset=UTF-8')
+        if self.path == "usegziptransferencoding":
+            self.send_header('Transfer-Encoding', 'gzip')
+        else:
+            self.send_header('Transfer-Encoding', 'chunked')
+        self.end_headers()
+        resp = bytes()
+        for chunk in chunks:
+            resp = resp + ("%x" % len(chunk)).encode("utf-8") + b'\r\n' + 
chunk + b'\r\n'


-- 
Samba Shared Repository

Reply via email to