mirror of
https://github.com/apache/httpd.git
synced 2025-08-16 16:17:23 +00:00
*) mod_http: genereate HEADERS buckets for trailers
mod_proxy: forward trailers on chunked request encoding test: add http/1.x test cases in pytest git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1899552 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
@ -1312,6 +1312,15 @@ AP_DECLARE_NONSTD(int) ap_send_http_trace(request_rec *r)
|
||||
return DONE;
|
||||
}
|
||||
|
||||
static apr_bucket *create_trailers_bucket(request_rec *r, apr_bucket_alloc_t *bucket_alloc)
|
||||
{
|
||||
if (r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, "sending trailers");
|
||||
return ap_bucket_headers_create(r->trailers_out, r->pool, bucket_alloc);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct header_filter_ctx {
|
||||
int headers_sent;
|
||||
} header_filter_ctx;
|
||||
@ -1323,7 +1332,7 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
|
||||
conn_rec *c = r->connection;
|
||||
int header_only = (r->header_only || AP_STATUS_IS_HEADER_ONLY(r->status));
|
||||
const char *protocol = NULL;
|
||||
apr_bucket *e;
|
||||
apr_bucket *e, *eos = NULL;
|
||||
apr_bucket_brigade *b2;
|
||||
header_struct h;
|
||||
header_filter_ctx *ctx = f->ctx;
|
||||
@ -1364,6 +1373,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
|
||||
eb = e->data;
|
||||
continue;
|
||||
}
|
||||
if (APR_BUCKET_IS_EOS(e)) {
|
||||
if (!eos) eos = e;
|
||||
continue;
|
||||
}
|
||||
/*
|
||||
* If we see an EOC bucket it is a signal that we should get out
|
||||
* of the way doing nothing.
|
||||
@ -1416,6 +1429,22 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (eos) {
|
||||
/* on having seen EOS and added possible trailers, we
|
||||
* can remove this filter.
|
||||
*/
|
||||
e = create_trailers_bucket(r, b->bucket_alloc);
|
||||
if (e) {
|
||||
APR_BUCKET_INSERT_BEFORE(eos, e);
|
||||
}
|
||||
ap_remove_output_filter(f);
|
||||
}
|
||||
|
||||
if (ctx->headers_sent) {
|
||||
/* we did already the stuff below, just pass on */
|
||||
return ap_pass_brigade(f->next, b);
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that we are ready to send a response, we need to combine the two
|
||||
* header field tables into a single table. If we don't do this, our
|
||||
@ -1545,11 +1574,6 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_header_filter(ap_filter_t *f,
|
||||
ap_add_output_filter("CHUNK", NULL, r, r->connection);
|
||||
}
|
||||
|
||||
/* Don't remove this filter until after we have added the CHUNK filter.
|
||||
* Otherwise, f->next won't be the CHUNK filter and thus the first
|
||||
* brigade won't be chunked properly.
|
||||
*/
|
||||
ap_remove_output_filter(f);
|
||||
rv = ap_pass_brigade(f->next, b);
|
||||
out:
|
||||
if (recursive_error) {
|
||||
|
@ -455,14 +455,7 @@ static int stream_reqbody(proxy_http_req_t *req)
|
||||
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
|
||||
}
|
||||
if (seen_eos) {
|
||||
/*
|
||||
* Append the tailing 0-size chunk
|
||||
*/
|
||||
e = apr_bucket_immortal_create(ZERO_ASCII CRLF_ASCII
|
||||
/* <trailers> */
|
||||
CRLF_ASCII,
|
||||
5, bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(input_brigade, e);
|
||||
ap_h1_add_end_chunk(input_brigade, NULL, r, r->trailers_in);
|
||||
}
|
||||
}
|
||||
else if (rb_method == RB_STREAM_CL
|
||||
|
0
test/modules/http1/__init__.py
Normal file
0
test/modules/http1/__init__.py
Normal file
47
test/modules/http1/conftest.py
Normal file
47
test/modules/http1/conftest.py
Normal file
@ -0,0 +1,47 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
|
||||
|
||||
from .env import H1TestEnv
|
||||
|
||||
|
||||
def pytest_report_header(config, startdir):
|
||||
env = H1TestEnv()
|
||||
return f"mod_http [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if "repeat" in metafunc.fixturenames:
|
||||
count = int(metafunc.config.getoption("repeat"))
|
||||
metafunc.fixturenames.append('tmp_ct')
|
||||
metafunc.parametrize('repeat', range(count))
|
||||
|
||||
|
||||
@pytest.fixture(scope="package")
|
||||
def env(pytestconfig) -> H1TestEnv:
|
||||
level = logging.INFO
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(level)
|
||||
console.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
|
||||
logging.getLogger('').addHandler(console)
|
||||
logging.getLogger('').setLevel(level=level)
|
||||
env = H1TestEnv(pytestconfig=pytestconfig)
|
||||
env.setup_httpd()
|
||||
env.apache_access_log_clear()
|
||||
env.httpd_error_log.clear_log()
|
||||
return env
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="package")
|
||||
def _session_scope(env):
|
||||
yield
|
||||
assert env.apache_stop() == 0
|
||||
errors, warnings = env.httpd_error_log.get_missed()
|
||||
assert (len(errors), len(warnings)) == (0, 0),\
|
||||
f"apache logged {len(errors)} errors and {len(warnings)} warnings: \n"\
|
||||
"{0}\n{1}\n".format("\n".join(errors), "\n".join(warnings))
|
||||
|
88
test/modules/http1/env.py
Normal file
88
test/modules/http1/env.py
Normal file
@ -0,0 +1,88 @@
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from typing import Dict, Any
|
||||
|
||||
from pyhttpd.certs import CertificateSpec
|
||||
from pyhttpd.conf import HttpdConf
|
||||
from pyhttpd.env import HttpdTestEnv, HttpdTestSetup
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class H1TestSetup(HttpdTestSetup):
|
||||
|
||||
def __init__(self, env: 'HttpdTestEnv'):
|
||||
super().__init__(env=env)
|
||||
self.add_source_dir(os.path.dirname(inspect.getfile(H1TestSetup)))
|
||||
self.add_modules(["cgid", "autoindex", "ssl"])
|
||||
|
||||
def make(self):
|
||||
super().make()
|
||||
self._add_h1test()
|
||||
self._setup_data_1k_1m()
|
||||
|
||||
def _add_h1test(self):
|
||||
local_dir = os.path.dirname(inspect.getfile(H1TestSetup))
|
||||
p = subprocess.run([self.env.apxs, '-c', 'mod_h1test.c'],
|
||||
capture_output=True,
|
||||
cwd=os.path.join(local_dir, 'mod_h1test'))
|
||||
rv = p.returncode
|
||||
if rv != 0:
|
||||
log.error(f"compiling md_h1test failed: {p.stderr}")
|
||||
raise Exception(f"compiling md_h1test failed: {p.stderr}")
|
||||
|
||||
modules_conf = os.path.join(self.env.server_dir, 'conf/modules.conf')
|
||||
with open(modules_conf, 'a') as fd:
|
||||
# load our test module which is not installed
|
||||
fd.write(f"LoadModule h1test_module \"{local_dir}/mod_h1test/.libs/mod_h1test.so\"\n")
|
||||
|
||||
def _setup_data_1k_1m(self):
|
||||
s90 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678\n"
|
||||
with open(os.path.join(self.env.gen_dir, "data-1k"), 'w') as f:
|
||||
for i in range(10):
|
||||
f.write(f"{i:09d}-{s90}")
|
||||
with open(os.path.join(self.env.gen_dir, "data-10k"), 'w') as f:
|
||||
for i in range(100):
|
||||
f.write(f"{i:09d}-{s90}")
|
||||
with open(os.path.join(self.env.gen_dir, "data-100k"), 'w') as f:
|
||||
for i in range(1000):
|
||||
f.write(f"{i:09d}-{s90}")
|
||||
with open(os.path.join(self.env.gen_dir, "data-1m"), 'w') as f:
|
||||
for i in range(10000):
|
||||
f.write(f"{i:09d}-{s90}")
|
||||
|
||||
|
||||
class H1TestEnv(HttpdTestEnv):
|
||||
|
||||
def __init__(self, pytestconfig=None):
|
||||
super().__init__(pytestconfig=pytestconfig)
|
||||
self.add_httpd_log_modules(["http", "core"])
|
||||
|
||||
self.httpd_error_log.set_ignored_lognos([
|
||||
'AH00135', # unsafe/strict tests send invalid methods
|
||||
])
|
||||
self.httpd_error_log.add_ignored_patterns([
|
||||
])
|
||||
|
||||
def setup_httpd(self, setup: HttpdTestSetup = None):
|
||||
super().setup_httpd(setup=H1TestSetup(env=self))
|
||||
|
||||
|
||||
class H1Conf(HttpdConf):
|
||||
|
||||
def __init__(self, env: HttpdTestEnv, extras: Dict[str, Any] = None):
|
||||
super().__init__(env=env, extras=HttpdConf.merge_extras(extras, {
|
||||
"base": [
|
||||
"LogLevel http:trace4",
|
||||
],
|
||||
f"cgi.{env.http_tld}": [
|
||||
"SSLOptions +StdEnvVars",
|
||||
"AddHandler cgi-script .py",
|
||||
"<Location \"/h1test/echo\">",
|
||||
" SetHandler h1test-echo",
|
||||
"</Location>",
|
||||
]
|
||||
}))
|
0
test/modules/http1/htdocs/cgi/files/empty.txt
Normal file
0
test/modules/http1/htdocs/cgi/files/empty.txt
Normal file
15
test/modules/http1/htdocs/cgi/hello.py
Executable file
15
test/modules/http1/htdocs/cgi/hello.py
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
|
||||
print("Content-Type: application/json")
|
||||
print()
|
||||
print("{")
|
||||
print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', '')))
|
||||
print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', '')))
|
||||
print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', '')))
|
||||
print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', '')))
|
||||
print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', '')))
|
||||
print(" \"h2push\" : \"%s\"" % (os.getenv('H2PUSH', '')))
|
||||
print("}")
|
||||
|
64
test/modules/http1/htdocs/cgi/upload.py
Executable file
64
test/modules/http1/htdocs/cgi/upload.py
Executable file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
import cgi, os
|
||||
import cgitb
|
||||
|
||||
cgitb.enable()
|
||||
|
||||
status = '200 Ok'
|
||||
|
||||
try: # Windows needs stdio set for binary mode.
|
||||
import msvcrt
|
||||
|
||||
msvcrt.setmode(0, os.O_BINARY) # stdin = 0
|
||||
msvcrt.setmode(1, os.O_BINARY) # stdout = 1
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
form = cgi.FieldStorage()
|
||||
|
||||
# Test if the file was uploaded
|
||||
if 'file' in form:
|
||||
fileitem = form['file']
|
||||
# strip leading path from file name to avoid directory traversal attacks
|
||||
fn = os.path.basename(fileitem.filename)
|
||||
f = open(('%s/files/%s' % (os.environ["DOCUMENT_ROOT"], fn)), 'wb');
|
||||
f.write(fileitem.file.read())
|
||||
f.close()
|
||||
message = "The file %s was uploaded successfully" % (fn)
|
||||
print("Status: 201 Created")
|
||||
print("Content-Type: text/html")
|
||||
print("Location: %s://%s/files/%s" % (os.environ["REQUEST_SCHEME"], os.environ["HTTP_HOST"], fn))
|
||||
print("")
|
||||
print("<html><body><p>%s</p></body></html>" % (message))
|
||||
|
||||
elif 'remove' in form:
|
||||
remove = form['remove'].value
|
||||
try:
|
||||
fn = os.path.basename(remove)
|
||||
os.remove('./files/' + fn)
|
||||
message = 'The file "' + fn + '" was removed successfully'
|
||||
except OSError as e:
|
||||
message = 'Error removing ' + fn + ': ' + e.strerror
|
||||
status = '404 File Not Found'
|
||||
print("Status: %s" % (status))
|
||||
print("""
|
||||
Content-Type: text/html
|
||||
|
||||
<html><body>
|
||||
<p>%s</p>
|
||||
</body></html>""" % (message))
|
||||
|
||||
else:
|
||||
message = '''\
|
||||
Upload File<form method="POST" enctype="multipart/form-data">
|
||||
<input type="file" name="file">
|
||||
<button type="submit">Upload</button></form>
|
||||
'''
|
||||
print("Status: %s" % (status))
|
||||
print("""\
|
||||
Content-Type: text/html
|
||||
|
||||
<html><body>
|
||||
<p>%s</p>
|
||||
</body></html>""" % (message))
|
||||
|
129
test/modules/http1/mod_h1test/mod_h1test.c
Normal file
129
test/modules/http1/mod_h1test/mod_h1test.c
Normal file
@ -0,0 +1,129 @@
|
||||
/* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include <apr_optional.h>
|
||||
#include <apr_optional_hooks.h>
|
||||
#include <apr_strings.h>
|
||||
#include <apr_cstr.h>
|
||||
#include <apr_time.h>
|
||||
#include <apr_want.h>
|
||||
|
||||
#include <httpd.h>
|
||||
#include <http_protocol.h>
|
||||
#include <http_request.h>
|
||||
#include <http_log.h>
|
||||
|
||||
static void h1test_hooks(apr_pool_t *pool);
|
||||
|
||||
AP_DECLARE_MODULE(h1test) = {
|
||||
STANDARD20_MODULE_STUFF,
|
||||
NULL, /* func to create per dir config */
|
||||
NULL, /* func to merge per dir config */
|
||||
NULL, /* func to create per server config */
|
||||
NULL, /* func to merge per server config */
|
||||
NULL, /* command handlers */
|
||||
h1test_hooks,
|
||||
#if defined(AP_MODULE_FLAG_NONE)
|
||||
AP_MODULE_FLAG_ALWAYS_MERGE
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
static int h1test_echo_handler(request_rec *r)
|
||||
{
|
||||
conn_rec *c = r->connection;
|
||||
apr_bucket_brigade *bb;
|
||||
apr_bucket *b;
|
||||
apr_status_t rv;
|
||||
char buffer[8192];
|
||||
const char *ct;
|
||||
long l;
|
||||
|
||||
if (strcmp(r->handler, "h1test-echo")) {
|
||||
return DECLINED;
|
||||
}
|
||||
if (r->method_number != M_GET && r->method_number != M_POST) {
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: processing request");
|
||||
r->status = 200;
|
||||
r->clength = -1;
|
||||
r->chunked = 1;
|
||||
ct = apr_table_get(r->headers_in, "content-type");
|
||||
ap_set_content_type(r, ct? ct : "application/octet-stream");
|
||||
|
||||
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
||||
/* copy any request body into the response */
|
||||
if ((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
|
||||
if (ap_should_client_block(r)) {
|
||||
while (0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
||||
"echo_handler: copying %ld bytes from request body", l);
|
||||
rv = apr_brigade_write(bb, NULL, NULL, buffer, l);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
rv = ap_pass_brigade(r->output_filters, bb);
|
||||
if (APR_SUCCESS != rv) goto cleanup;
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
|
||||
"echo_handler: passed %ld bytes from request body", l);
|
||||
}
|
||||
}
|
||||
/* we are done */
|
||||
b = apr_bucket_eos_create(c->bucket_alloc);
|
||||
APR_BRIGADE_INSERT_TAIL(bb, b);
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "echo_handler: request read");
|
||||
|
||||
if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"echo_handler: seeing incoming trailers");
|
||||
apr_table_setn(r->trailers_out, "h1test-trailers-in",
|
||||
apr_itoa(r->pool, 1));
|
||||
}
|
||||
if (apr_table_get(r->headers_in, "Add-Trailer")) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
|
||||
"echo_handler: seeing incoming Add-Trailer header");
|
||||
apr_table_setn(r->trailers_out, "h1test-add-trailer",
|
||||
apr_table_get(r->headers_in, "Add-Trailer"));
|
||||
}
|
||||
|
||||
rv = ap_pass_brigade(r->output_filters, bb);
|
||||
|
||||
cleanup:
|
||||
if (rv == APR_SUCCESS
|
||||
|| r->status != HTTP_OK
|
||||
|| c->aborted) {
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "echo_handler: request handled");
|
||||
return OK;
|
||||
}
|
||||
else {
|
||||
/* no way to know what type of error occurred */
|
||||
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "h1test_echo_handler failed");
|
||||
return AP_FILTER_ERROR;
|
||||
}
|
||||
return DECLINED;
|
||||
}
|
||||
|
||||
|
||||
/* Install this module into the apache2 infrastructure.
|
||||
*/
|
||||
static void h1test_hooks(apr_pool_t *pool)
|
||||
{
|
||||
ap_log_perror(APLOG_MARK, APLOG_TRACE1, 0, pool, "installing hooks and handlers");
|
||||
|
||||
/* test h1 handlers */
|
||||
ap_hook_handler(h1test_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
|
||||
}
|
||||
|
0
test/modules/http1/mod_h1test/mod_h1test.slo
Normal file
0
test/modules/http1/mod_h1test/mod_h1test.slo
Normal file
20
test/modules/http1/test_001_alive.py
Normal file
20
test/modules/http1/test_001_alive.py
Normal file
@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
|
||||
class TestBasicAlive:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
H1Conf(env).add_vhost_test1().install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
# we expect to see the document from the generic server
|
||||
def test_h1_001_01(self, env):
|
||||
url = env.mkurl("https", "test1", "/alive.json")
|
||||
r = env.curl_get(url, 5)
|
||||
assert r.exit_code == 0, r.stderr + r.stdout
|
||||
assert r.response["json"]
|
||||
assert r.response["json"]["alive"] is True
|
||||
assert r.response["json"]["host"] == "test1"
|
28
test/modules/http1/test_003_get.py
Normal file
28
test/modules/http1/test_003_get.py
Normal file
@ -0,0 +1,28 @@
|
||||
import re
|
||||
import socket
|
||||
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
|
||||
class TestGet:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
H1Conf(env).add_vhost_cgi(
|
||||
proxy_self=True
|
||||
).add_vhost_test1(
|
||||
proxy_self=True
|
||||
).install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
# check SSL environment variables from CGI script
|
||||
def test_h1_003_01(self, env):
|
||||
url = env.mkurl("https", "cgi", "/hello.py")
|
||||
r = env.curl_get(url)
|
||||
assert r.response["status"] == 200
|
||||
assert r.response["json"]["protocol"] == "HTTP/1.1"
|
||||
assert r.response["json"]["https"] == "on"
|
||||
tls_version = r.response["json"]["ssl_protocol"]
|
||||
assert tls_version in ["TLSv1.2", "TLSv1.3"]
|
54
test/modules/http1/test_004_post.py
Normal file
54
test/modules/http1/test_004_post.py
Normal file
@ -0,0 +1,54 @@
|
||||
import difflib
|
||||
import email.parser
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
|
||||
class TestPost:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
TestPost._local_dir = os.path.dirname(inspect.getfile(TestPost))
|
||||
H1Conf(env).add_vhost_cgi().install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
def local_src(self, fname):
|
||||
return os.path.join(TestPost._local_dir, fname)
|
||||
|
||||
# upload and GET again using curl, compare to original content
|
||||
def curl_upload_and_verify(self, env, fname, options=None):
|
||||
url = env.mkurl("https", "cgi", "/upload.py")
|
||||
fpath = os.path.join(env.gen_dir, fname)
|
||||
r = env.curl_upload(url, fpath, options=options)
|
||||
assert r.exit_code == 0, f"{r}"
|
||||
assert 200 <= r.response["status"] < 300
|
||||
|
||||
r2 = env.curl_get(r.response["header"]["location"])
|
||||
assert r2.exit_code == 0
|
||||
assert r2.response["status"] == 200
|
||||
with open(self.local_src(fpath), mode='rb') as file:
|
||||
src = file.read()
|
||||
assert src == r2.response["body"]
|
||||
return r
|
||||
|
||||
def test_h1_004_01(self, env):
|
||||
self.curl_upload_and_verify(env, "data-1k", ["-vvv"])
|
||||
|
||||
def test_h1_004_02(self, env):
|
||||
self.curl_upload_and_verify(env, "data-10k", [])
|
||||
|
||||
def test_h1_004_03(self, env):
|
||||
self.curl_upload_and_verify(env, "data-100k", [])
|
||||
|
||||
def test_h1_004_04(self, env):
|
||||
self.curl_upload_and_verify(env, "data-1m", [])
|
||||
|
||||
def test_h1_004_05(self, env):
|
||||
r = self.curl_upload_and_verify(env, "data-1k", ["-vvv", "-H", "Expect: 100-continue"])
|
38
test/modules/http1/test_005_trailers.py
Normal file
38
test/modules/http1/test_005_trailers.py
Normal file
@ -0,0 +1,38 @@
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
|
||||
# The trailer tests depend on "nghttp" as no other client seems to be able to send those
|
||||
# rare things.
|
||||
class TestTrailers:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
H1Conf(env).add_vhost_cgi(proxy_self=True).install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
# check that we get a trailer out when telling the handler to add one
|
||||
def test_h1_005_01(self, env):
|
||||
url = env.mkurl("https", "cgi", "/h1test/echo")
|
||||
host = f"cgi.{env.http_tld}"
|
||||
fpath = os.path.join(env.gen_dir, "data-1k")
|
||||
r = env.curl_upload(url, fpath, options=["--header", "Add-Trailer: 005_01"])
|
||||
assert r.exit_code == 0, f"{r}"
|
||||
assert 200 <= r.response["status"] < 300
|
||||
assert r.response["trailer"], f"no trailers received: {r}"
|
||||
assert "h1test-add-trailer" in r.response["trailer"]
|
||||
assert r.response["trailer"]["h1test-add-trailer"] == "005_01"
|
||||
|
||||
# check that we get out trailers through the proxy
|
||||
def test_h1_005_02(self, env):
|
||||
url = env.mkurl("https", "cgi", "/proxy/h1test/echo")
|
||||
host = f"cgi.{env.http_tld}"
|
||||
fpath = os.path.join(env.gen_dir, "data-1k")
|
||||
r = env.curl_upload(url, fpath, options=["--header", "Add-Trailer: 005_01"])
|
||||
assert r.exit_code == 0, f"{r}"
|
||||
assert 200 <= r.response["status"] < 300
|
||||
assert r.response["trailer"], f"no trailers received: {r}"
|
||||
assert "h1test-add-trailer" in r.response["trailer"]
|
||||
assert r.response["trailer"]["h1test-add-trailer"] == "005_01"
|
132
test/modules/http1/test_006_unsafe.py
Normal file
132
test/modules/http1/test_006_unsafe.py
Normal file
@ -0,0 +1,132 @@
|
||||
import re
|
||||
import socket
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
class TestRequestUnsafe:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
conf = H1Conf(env)
|
||||
conf.add([
|
||||
"HttpProtocolOptions Unsafe",
|
||||
])
|
||||
conf.install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
# unsafe tests from t/apache/http_strict.t
|
||||
# possible expected results:
|
||||
# 0: any HTTP error
|
||||
# 1: any HTTP success
|
||||
# 200-500: specific HTTP status code
|
||||
# None: HTTPD should drop connection without error message
|
||||
@pytest.mark.parametrize(["intext", "status"], [
|
||||
["GET / HTTP/1.0\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\n\n", 1],
|
||||
["get / HTTP/1.0\r\n\r\n", 501],
|
||||
["G ET / HTTP/1.0\r\n\r\n", 400],
|
||||
["G\0ET / HTTP/1.0\r\n\r\n", 400],
|
||||
["G/T / HTTP/1.0\r\n\r\n", 501],
|
||||
["GET /\0 HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\0\r\n\r\n", 400],
|
||||
["GET\f/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET\r/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET\t/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTT/1.0\r\n\r\n", 0],
|
||||
["GET / HTTP/1.0\r\nHost: localhost\r\n\r\n", 1],
|
||||
["GET / HTTP/2.0\r\nHost: localhost\r\n\r\n", 1],
|
||||
["GET / HTTP/1.2\r\nHost: localhost\r\n\r\n", 1],
|
||||
["GET / HTTP/1.11\r\nHost: localhost\r\n\r\n", 400],
|
||||
["GET / HTTP/10.0\r\nHost: localhost\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0 \r\nHost: localhost\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0 x\r\nHost: localhost\r\n\r\n", 400],
|
||||
["GET / HTTP/\r\nHost: localhost\r\n\r\n", 0],
|
||||
["GET / HTTP/0.9\r\n\r\n", 0],
|
||||
["GET / HTTP/0.8\r\n\r\n", 0],
|
||||
["GET /\x01 HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFoo: bar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nFoo:bar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nFoo: b\0ar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFoo: b\x01ar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nFoo\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFoo bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\n: bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nX: bar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nFoo bar:bash\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFoo :bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\n Foo:bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nF\x01o: bar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nF\ro: bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nF\to: bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFo: b\tar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nFo: bar\r\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\r", None],
|
||||
["GET /\r\n", 90],
|
||||
["GET /#frag HTTP/1.0\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: localhost\r\nHost: localhost\r\n\r\n", 200],
|
||||
["GET http://017700000001/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://0x7f.1/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://127.0.0.1/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://127.01.0.1/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://%3127.0.0.1/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: localhost:80\r\nHost: localhost:80\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: localhost:80 x\r\n\r", 400],
|
||||
["GET http://localhost:80/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://localhost:80x/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://localhost:80:80/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://localhost::80/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://foo@localhost:80/ HTTP/1.0\r\n\r\n", 200],
|
||||
["GET http://[::1]/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://[::1:2]/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://[4712::abcd]/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://[4712::abcd:1]/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://[4712::abcd::]/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://[4712:abcd::]/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://[4712::abcd]:8000/ HTTP/1.0\r\n\r\n", 1],
|
||||
["GET http://4713::abcd:8001/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: [::1]\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: [::1:2]\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: [4711::abcd]\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: [4711::abcd:1]\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: [4711:abcd::]\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: [4711::abcd]:8000\r\n\r\n", 1],
|
||||
["GET / HTTP/1.0\r\nHost: 4714::abcd:8001\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: abc\xa0\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: abc\\foo\r\n\r\n", 400],
|
||||
["GET http://foo/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200],
|
||||
["GET http://foo:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200],
|
||||
["GET http://[::1]:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200],
|
||||
["GET http://10.0.0.1:81/ HTTP/1.0\r\nHost: bar\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: foo-bar.example.com\r\n\r\n", 200],
|
||||
["GET / HTTP/1.0\r\nHost: foo_bar.example.com\r\n\r\n", 200],
|
||||
["GET http://foo_bar/ HTTP/1.0\r\n\r\n", 200],
|
||||
])
|
||||
def test_h1_006_01(self, env, intext, status: Optional[int]):
|
||||
with socket.create_connection(('localhost', int(env.http_port))) as sock:
|
||||
# on some OS, the server does not see our connection until there is
|
||||
# something incoming
|
||||
sock.sendall(intext.encode())
|
||||
sock.shutdown(socket.SHUT_WR)
|
||||
buff = sock.recv(1024)
|
||||
msg = buff.decode()
|
||||
if status is None:
|
||||
assert len(msg) == 0, f"unexpected answer: {msg}"
|
||||
else:
|
||||
assert len(msg) > 0, "no answer from server"
|
||||
rlines = msg.splitlines()
|
||||
response = rlines[0]
|
||||
m = re.match(r'^HTTP/1.1 (\d+)\s+(\S+)', response)
|
||||
assert m or status == 90, f"unrecognized response: {rlines}"
|
||||
if status == 1:
|
||||
assert int(m.group(1)) >= 200
|
||||
elif status == 90:
|
||||
# headerless 0.9 response, yuk
|
||||
assert len(rlines) >= 1, f"{rlines}"
|
||||
elif status > 0:
|
||||
assert int(m.group(1)) == status, f"{rlines}"
|
||||
else:
|
||||
assert int(m.group(1)) >= 400, f"{rlines}"
|
||||
|
69
test/modules/http1/test_007_strict.py
Normal file
69
test/modules/http1/test_007_strict.py
Normal file
@ -0,0 +1,69 @@
|
||||
import re
|
||||
import socket
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from .env import H1Conf
|
||||
|
||||
|
||||
class TestRequestStrict:
|
||||
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def _class_scope(self, env):
|
||||
conf = H1Conf(env)
|
||||
conf.add([
|
||||
"HttpProtocolOptions Strict",
|
||||
])
|
||||
conf.install()
|
||||
assert env.apache_restart() == 0
|
||||
|
||||
# strict tests from t/apache/http_strict.t
|
||||
# possible expected results:
|
||||
# 0: any HTTP error
|
||||
# 1: any HTTP success
|
||||
# 200-500: specific HTTP status code
|
||||
# undef: HTTPD should drop connection without error message
|
||||
@pytest.mark.parametrize(["intext", "status"], [
|
||||
["GET / HTTP/1.0\n\n", 400],
|
||||
["G/T / HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0 \r\nHost: localhost\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nFoo: b\x01ar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nF\x01o: bar\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\r", None],
|
||||
["GET / HTTP/1.0\r\nHost: localhost\r\nHost: localhost\r\n\r\n", 400],
|
||||
["GET http://017700000001/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://0x7f.1/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://127.01.0.1/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET http://%3127.0.0.1/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: localhost:80\r\nHost: localhost:80\r\n\r\n", 400],
|
||||
["GET http://foo@localhost:80/ HTTP/1.0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: 4714::abcd:8001\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: abc\xa0\r\n\r\n", 400],
|
||||
["GET / HTTP/1.0\r\nHost: foo_bar.example.com\r\n\r\n", 200],
|
||||
["GET http://foo_bar/ HTTP/1.0\r\n\r\n", 200],
|
||||
])
|
||||
def test_h1_007_01(self, env, intext, status: Optional[int]):
|
||||
with socket.create_connection(('localhost', int(env.http_port))) as sock:
|
||||
# on some OS, the server does not see our connection until there is
|
||||
# something incoming
|
||||
sock.sendall(intext.encode())
|
||||
sock.shutdown(socket.SHUT_WR)
|
||||
buff = sock.recv(1024)
|
||||
msg = buff.decode()
|
||||
if status is None:
|
||||
assert len(msg) == 0, f"unexpected answer: {msg}"
|
||||
else:
|
||||
assert len(msg) > 0, "no answer from server"
|
||||
rlines = msg.splitlines()
|
||||
response = rlines[0]
|
||||
m = re.match(r'^HTTP/1.1 (\d+)\s+(\S+)', response)
|
||||
assert m, f"unrecognized response: {rlines}"
|
||||
if status == 1:
|
||||
assert int(m.group(1)) >= 200
|
||||
elif status == 90:
|
||||
assert len(rlines) >= 1, f"{rlines}"
|
||||
elif status > 0:
|
||||
assert int(m.group(1)) == status, f"{rlines}"
|
||||
else:
|
||||
assert int(m.group(1)) >= 400, f"{rlines}"
|
@ -61,7 +61,7 @@ class H2TestEnv(HttpdTestEnv):
|
||||
@property
|
||||
def is_unsupported(cls):
|
||||
mpm_module = f"mpm_{os.environ['MPM']}" if 'MPM' in os.environ else 'mpm_event'
|
||||
return mpm_module in ['mpm_prefork']
|
||||
return mpm_module == 'mpm_prefork'
|
||||
|
||||
def __init__(self, pytestconfig=None):
|
||||
super().__init__(pytestconfig=pytestconfig)
|
||||
@ -115,6 +115,12 @@ class H2Conf(HttpdConf):
|
||||
f"cgi.{env.http_tld}": [
|
||||
"SSLOptions +StdEnvVars",
|
||||
"AddHandler cgi-script .py",
|
||||
"<Location \"/h2test/echo\">",
|
||||
" SetHandler h2test-echo",
|
||||
"</Location>",
|
||||
"<Location \"/h2test/delay\">",
|
||||
" SetHandler h2test-delay",
|
||||
"</Location>",
|
||||
]
|
||||
}))
|
||||
|
||||
|
@ -157,12 +157,6 @@ class HttpdConf(object):
|
||||
self.start_vhost(domains=[domain, f"cgi-alias.{self.env.http_tld}"],
|
||||
port=self.env.https_port, doc_root="htdocs/cgi")
|
||||
self.add_proxies("cgi", proxy_self=proxy_self, h2proxy_self=h2proxy_self)
|
||||
self.add("<Location \"/h2test/echo\">")
|
||||
self.add(" SetHandler h2test-echo")
|
||||
self.add("</Location>")
|
||||
self.add("<Location \"/h2test/delay\">")
|
||||
self.add(" SetHandler h2test-delay")
|
||||
self.add("</Location>")
|
||||
if domain in self._extras:
|
||||
self.add(self._extras[domain])
|
||||
self.end_vhost()
|
||||
|
@ -1,3 +1,4 @@
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import re
|
||||
@ -68,6 +69,7 @@ class HttpdTestSetup:
|
||||
self.env = env
|
||||
self._source_dirs = [os.path.dirname(inspect.getfile(HttpdTestSetup))]
|
||||
self._modules = HttpdTestSetup.MODULES.copy()
|
||||
self._optional_modules = []
|
||||
|
||||
def add_source_dir(self, source_dir):
|
||||
self._source_dirs.append(source_dir)
|
||||
@ -75,6 +77,9 @@ class HttpdTestSetup:
|
||||
def add_modules(self, modules: List[str]):
|
||||
self._modules.extend(modules)
|
||||
|
||||
def add_optional_modules(self, modules: List[str]):
|
||||
self._optional_modules.extend(modules)
|
||||
|
||||
def make(self):
|
||||
self._make_dirs()
|
||||
self._make_conf()
|
||||
@ -141,6 +146,16 @@ class HttpdTestSetup:
|
||||
else:
|
||||
fd.write(f"#built static: LoadModule {m}_module \"{mod_path}\"\n")
|
||||
loaded.add(m)
|
||||
for m in self._optional_modules:
|
||||
match = re.match(r'^mod_(.+)$', m)
|
||||
if match:
|
||||
m = match.group(1)
|
||||
if m in loaded:
|
||||
continue
|
||||
mod_path = os.path.join(self.env.libexec_dir, f"mod_{m}.so")
|
||||
if os.path.isfile(mod_path):
|
||||
fd.write(f"LoadModule {m}_module \"{mod_path}\"\n")
|
||||
loaded.add(m)
|
||||
if len(missing_mods) > 0:
|
||||
raise Exception(f"Unable to find modules: {missing_mods} "
|
||||
f"DSOs: {self.env.dso_modules}")
|
||||
@ -167,10 +182,32 @@ class HttpdTestSetup:
|
||||
|
||||
class HttpdTestEnv:
|
||||
|
||||
LIBEXEC_DIR = None
|
||||
|
||||
@classmethod
|
||||
def has_python_package(cls, name: str) -> bool:
|
||||
if name in sys.modules:
|
||||
# already loaded
|
||||
return True
|
||||
elif (spec := importlib.util.find_spec(name)) is not None:
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
sys.modules[name] = module
|
||||
spec.loader.exec_module(module)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_ssl_module(cls):
|
||||
return os.environ['SSL'] if 'SSL' in os.environ else 'mod_ssl'
|
||||
|
||||
@classmethod
|
||||
def has_shared_module(cls, name):
|
||||
if cls.LIBEXEC_DIR is None:
|
||||
env = HttpdTestEnv() # will initialized it
|
||||
path = os.path.join(cls.LIBEXEC_DIR, f"mod_{name}.so")
|
||||
return os.path.isfile(path)
|
||||
|
||||
def __init__(self, pytestconfig=None):
|
||||
self._our_dir = os.path.dirname(inspect.getfile(Dummy))
|
||||
self.config = ConfigParser(interpolation=ExtendedInterpolation())
|
||||
@ -180,8 +217,8 @@ class HttpdTestEnv:
|
||||
self._apxs = self.config.get('global', 'apxs')
|
||||
self._prefix = self.config.get('global', 'prefix')
|
||||
self._apachectl = self.config.get('global', 'apachectl')
|
||||
self._libexec_dir = self.get_apxs_var('LIBEXECDIR')
|
||||
|
||||
if HttpdTestEnv.LIBEXEC_DIR is None:
|
||||
HttpdTestEnv.LIBEXEC_DIR = self._libexec_dir = self.get_apxs_var('LIBEXECDIR')
|
||||
self._curl = self.config.get('global', 'curl_bin')
|
||||
self._nghttp = self.config.get('global', 'nghttp')
|
||||
if self._nghttp is None:
|
||||
@ -332,7 +369,7 @@ class HttpdTestEnv:
|
||||
|
||||
@property
|
||||
def libexec_dir(self) -> str:
|
||||
return self._libexec_dir
|
||||
return HttpdTestEnv.LIBEXEC_DIR
|
||||
|
||||
@property
|
||||
def dso_modules(self) -> List[str]:
|
||||
@ -604,32 +641,56 @@ class HttpdTestEnv:
|
||||
|
||||
def curl_parse_headerfile(self, headerfile: str, r: ExecResult = None) -> ExecResult:
|
||||
lines = open(headerfile).readlines()
|
||||
exp_stat = True
|
||||
if r is None:
|
||||
r = ExecResult(args=[], exit_code=0, stdout=b'', stderr=b'')
|
||||
header = {}
|
||||
|
||||
response = None
|
||||
def fin_response(response):
|
||||
if response:
|
||||
r.add_response(response)
|
||||
|
||||
expected = ['status']
|
||||
for line in lines:
|
||||
if exp_stat:
|
||||
if re.match(r'^$', line):
|
||||
if 'trailer' in expected:
|
||||
# end of trailers
|
||||
fin_response(response)
|
||||
response = None
|
||||
expected = ['status']
|
||||
elif 'header' in expected:
|
||||
# end of header, another status or trailers might follow
|
||||
expected = ['status', 'trailer']
|
||||
else:
|
||||
assert False, f"unexpected line: {line}"
|
||||
continue
|
||||
if 'status' in expected:
|
||||
log.debug("reading 1st response line: %s", line)
|
||||
m = re.match(r'^(\S+) (\d+) (.*)$', line)
|
||||
assert m
|
||||
r.add_response({
|
||||
if m:
|
||||
fin_response(response)
|
||||
response = {
|
||||
"protocol": m.group(1),
|
||||
"status": int(m.group(2)),
|
||||
"description": m.group(3),
|
||||
"header": {},
|
||||
"trailer": {},
|
||||
"body": r.outraw
|
||||
})
|
||||
exp_stat = False
|
||||
header = {}
|
||||
elif re.match(r'^$', line):
|
||||
exp_stat = True
|
||||
else:
|
||||
log.debug("reading header line: %s", line)
|
||||
}
|
||||
expected = ['header']
|
||||
continue
|
||||
if 'trailer' in expected:
|
||||
m = re.match(r'^([^:]+):\s*(.*)$', line)
|
||||
assert m
|
||||
header[m.group(1).lower()] = m.group(2)
|
||||
if r.response:
|
||||
r.response["header"] = header
|
||||
if m:
|
||||
response['trailer'][m.group(1).lower()] = m.group(2)
|
||||
continue
|
||||
if 'header' in expected:
|
||||
m = re.match(r'^([^:]+):\s*(.*)$', line)
|
||||
if m:
|
||||
response['header'][m.group(1).lower()] = m.group(2)
|
||||
continue
|
||||
assert False, f"unexpected line: {line}"
|
||||
|
||||
fin_response(response)
|
||||
return r
|
||||
|
||||
def curl_raw(self, urls, timeout=10, options=None, insecure=False,
|
||||
|
Reference in New Issue
Block a user