[SCM] Samba Shared Repository - branch master updated
Andrew Bartlett
abartlet at samba.org
Wed Mar 27 02:16:02 UTC 2024
The branch, master has been updated
via 9550d37f2f9 winbind: Log NOT_IMPLEMENTED as debug
via 03240c91fb6 libcli/http: Handle http chunked transfer encoding
via 30acd609f56 tests: add test for chunked encoding with http cli library
via 5f03d84e3b5 libcli/http: Optimise reading for content-length
via 74cdebeae3d selftest: Add basic content-lenght http tests
via cd6c075476c Add simple http_client for use in black box tests (in following commits)
from 6fb98f70c62 ndr: always attempt ACE coda pull if ACE type suggests a coda
https://git.samba.org/?p=samba.git;a=shortlog;h=master
- Log -----------------------------------------------------------------
commit 9550d37f2f9170c183d5a5e8f666cbbf21c5f444
Author: David Mulder <dmulder at samba.org>
Date: Mon Mar 11 07:36:58 2024 -0600
winbind: Log NOT_IMPLEMENTED as debug
This message happens frequently when running a
PDC/NT4 mode w/out winbind.
Signed-off-by: David Mulder <dmulder at samba.org>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
Autobuild-User(master): Andrew Bartlett <abartlet at samba.org>
Autobuild-Date(master): Wed Mar 27 02:15:31 UTC 2024 on atb-devel-224
commit 03240c91fb6ffcf5afe47c14a1ba7a8bc12f2348
Author: Noel Power <noel.power at 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.power at suse.com>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
commit 30acd609f560352d3edb0c931b9a864110025b2c
Author: Noel Power <noel.power at 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.power at suse.com>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
commit 5f03d84e3b52bf5a31a0f885cb83bdcb48ec96f7
Author: Noel Power <noel.power at 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.power at suse.com>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
BUG: https://bugzilla.samba.org/show_bug.cgi?id=15611
commit 74cdebeae3d1bc35eea96b51b9491f6c52844b10
Author: Noel Power <noel.power at 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.power at suse.com>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
commit cd6c075476c820b4fe8bdc10a24d8fc8ac74e9c9
Author: Noel Power <noel.power at 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.power at suse.com>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
-----------------------------------------------------------------------
Summary of changes:
libcli/http/http.c | 265 +++++++++++++++++-
libcli/http/http_internal.h | 4 +
python/samba/tests/blackbox/http_chunk.py | 116 ++++++++
python/samba/tests/blackbox/http_content.py | 95 +++++++
selftest/tests.py | 2 +
source3/auth/auth_winbind.c | 6 +-
source4/client/http_test.c | 401 ++++++++++++++++++++++++++++
source4/wscript_build | 5 +
8 files changed, 886 insertions(+), 8 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..3681500f194 100644
--- a/libcli/http/http.c
+++ b/libcli/http/http.c
@@ -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;
@@ -527,20 +738,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:
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..175c60d98a2
--- /dev/null
+++ b/python/samba/tests/blackbox/http_chunk.py
@@ -0,0 +1,116 @@
+# Blackbox tests for http_test
+#
+# Copyright (C) Noel Power noel.power at 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')
+ 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'
+ resp += b'0\r\n\r\n'
+ self.wfile.write(resp)
+
+ def do_POST(self):
+ self.handle_req()
+ def do_GET(self):
+ self.handle_req()
+
+class HttpChunkBlackboxTests(BlackboxTestCase):
+ def setUp(self):
+ self.server = HTTPServer((os.getenv("SERVER_IP", "localhost"), 8080),
+ ChunkHTTPRequestHandler,
+ bind_and_activate=False)
+ self.t = threading.Thread(target=HttpChunkBlackboxTests.http_server, args=(self,))
+ self.t.setDaemon(True)
+ self.t.start()
+ time.sleep(1)
+
+ def tearDown(self):
+ super().tearDown()
+
+ def http_server(self):
+ self.server.server_bind()
+ self.server.server_activate()
+ self.server.serve_forever()
+
+ def test_single_chunk(self):
+ 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'))
+ except BlackboxProcessError as e:
+ print("Failed with: %s" % e)
+ self.fail(str(e))
+
+ def test_multi_chunks(self):
+ try:
+ msg = "snglechunksnglechunksnglechunksnglechunksnglechunk"
+ resp = self.check_output("%s -U%% -I%s --uri %s" % (COMMAND, os.getenv("SERVER_IP", "localhost"), msg))
+ self.assertEqual(msg, resp.decode('utf-8'))
+ except BlackboxProcessError as e:
+ print("Failed with: %s" % e)
+ self.fail(str(e))
+
+ def test_exceed_request_size(self):
+ try:
+ msg = "snglechunksnglechunksnglechunksnglechunksnglechunk"
+ resp = self.check_output("%s -d11 -U%% -I%s --rsize 49 --uri %s" % (COMMAND, os.getenv("SERVER_IP", "localhost"), msg))
+ self.fail(str(e))
+ except BlackboxProcessError as e:
+ if "http_read_chunk: size 50 exceeds max content len 49 skipping body" not in e.stderr.decode('utf-8'):
+ self.fail(str(e))
+ if "unexpected 0 len response" not in e.stdout.decode('utf-8'):
+ self.fail(str(e))
+
+ def test_exact_request_size(self):
+ try:
+ msg = "snglechunksnglechunksnglechunksnglechunksnglechunk"
+ resp = self.check_output("%s -U%% -I%s --rsize 50 --uri %s" % (COMMAND, os.getenv("SERVER_IP", "localhost"), msg))
+ self.assertEqual(msg, resp.decode('utf-8'))
+ except BlackboxProcessError as e:
+ print("Failed with: %s" % e)
+ self.fail(str(e))
diff --git a/python/samba/tests/blackbox/http_content.py b/python/samba/tests/blackbox/http_content.py
new file mode 100644
index 00000000000..9ecb6ffe279
--- /dev/null
+++ b/python/samba/tests/blackbox/http_content.py
@@ -0,0 +1,95 @@
+# Blackbox tests for http_test
+#
+# Copyright (C) Noel Power noel.power at 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
--
Samba Shared Repository
More information about the samba-cvs
mailing list