Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/tests/http/test_01_basic.py
2066 views
1
#!/usr/bin/env python3
2
# -*- coding: utf-8 -*-
3
#***************************************************************************
4
# _ _ ____ _
5
# Project ___| | | | _ \| |
6
# / __| | | | |_) | |
7
# | (__| |_| | _ <| |___
8
# \___|\___/|_| \_\_____|
9
#
10
# Copyright (C) Daniel Stenberg, <[email protected]>, et al.
11
#
12
# This software is licensed as described in the file COPYING, which
13
# you should have received as part of this distribution. The terms
14
# are also available at https://curl.se/docs/copyright.html.
15
#
16
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
17
# copies of the Software, and permit persons to whom the Software is
18
# furnished to do so, under the terms of the COPYING file.
19
#
20
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21
# KIND, either express or implied.
22
#
23
# SPDX-License-Identifier: curl
24
#
25
###########################################################################
26
#
27
import logging
28
import pytest
29
30
from testenv import Env
31
from testenv import CurlClient
32
33
34
log = logging.getLogger(__name__)
35
36
37
class TestBasic:
38
39
# simple http: GET
40
def test_01_01_http_get(self, env: Env, httpd):
41
curl = CurlClient(env=env)
42
url = f'http://{env.domain1}:{env.http_port}/data.json'
43
r = curl.http_get(url=url)
44
r.check_response(http_status=200)
45
assert r.json['server'] == env.domain1
46
47
# simple https: GET, any http version
48
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
49
def test_01_02_https_get(self, env: Env, httpd):
50
curl = CurlClient(env=env)
51
url = f'https://{env.domain1}:{env.https_port}/data.json'
52
r = curl.http_get(url=url)
53
r.check_response(http_status=200)
54
assert r.json['server'] == env.domain1
55
56
# simple https: GET, h2 wanted and got
57
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
58
def test_01_03_h2_get(self, env: Env, httpd):
59
curl = CurlClient(env=env)
60
url = f'https://{env.domain1}:{env.https_port}/data.json'
61
r = curl.http_get(url=url, extra_args=['--http2'])
62
r.check_response(http_status=200, protocol='HTTP/2')
63
assert r.json['server'] == env.domain1
64
65
# simple https: GET, h2 unsupported, fallback to h1
66
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
67
def test_01_04_h2_unsupported(self, env: Env, httpd):
68
curl = CurlClient(env=env)
69
url = f'https://{env.domain2}:{env.https_port}/data.json'
70
r = curl.http_get(url=url, extra_args=['--http2'])
71
r.check_response(http_status=200, protocol='HTTP/1.1')
72
assert r.json['server'] == env.domain2
73
74
# simple h3: GET, want h3 and get it
75
@pytest.mark.skipif(condition=not Env.have_h3(), reason="h3 not supported")
76
def test_01_05_h3_get(self, env: Env, httpd, nghttpx):
77
curl = CurlClient(env=env)
78
url = f'https://{env.domain1}:{env.h3_port}/data.json'
79
r = curl.http_get(url=url, extra_args=['--http3-only'])
80
r.check_response(http_status=200, protocol='HTTP/3')
81
assert r.json['server'] == env.domain1
82
83
# simple download, check connect/handshake timings
84
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
85
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
86
def test_01_06_timings(self, env: Env, httpd, nghttpx, proto):
87
if proto == 'h3' and not env.have_h3():
88
pytest.skip("h3 not supported")
89
curl = CurlClient(env=env)
90
url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
91
r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True)
92
r.check_stats(http_status=200, count=1,
93
remote_port=env.port_for(alpn_proto=proto),
94
remote_ip='127.0.0.1')
95
# there are cases where time_connect is reported as 0
96
assert r.stats[0]['time_connect'] >= 0, f'{r.stats[0]}'
97
assert r.stats[0]['time_appconnect'] > 0, f'{r.stats[0]}'
98
99
# simple https: HEAD
100
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
101
@pytest.mark.skipif(condition=not Env.have_ssl_curl(), reason="curl without SSL")
102
def test_01_07_head(self, env: Env, httpd, nghttpx, proto):
103
if proto == 'h3' and not env.have_h3():
104
pytest.skip("h3 not supported")
105
curl = CurlClient(env=env)
106
url = f'https://{env.authority_for(env.domain1, proto)}/data.json'
107
r = curl.http_download(urls=[url], with_stats=True, with_headers=True,
108
extra_args=['-I'])
109
r.check_stats(http_status=200, count=1, exitcode=0,
110
remote_port=env.port_for(alpn_proto=proto),
111
remote_ip='127.0.0.1')
112
# got the Content-Length: header, but did not download anything
113
assert r.responses[0]['header']['content-length'] == '30', f'{r.responses[0]}'
114
assert r.stats[0]['size_download'] == 0, f'{r.stats[0]}'
115
116
# http: GET for HTTP/2, see Upgrade:, 101 switch
117
def test_01_08_h2_upgrade(self, env: Env, httpd):
118
curl = CurlClient(env=env)
119
url = f'http://{env.domain1}:{env.http_port}/data.json'
120
r = curl.http_get(url=url, extra_args=['--http2'])
121
r.check_exit_code(0)
122
assert len(r.responses) == 2, f'{r.responses}'
123
assert r.responses[0]['status'] == 101, f'{r.responses[0]}'
124
assert r.responses[1]['status'] == 200, f'{r.responses[1]}'
125
assert r.responses[1]['protocol'] == 'HTTP/2', f'{r.responses[1]}'
126
assert r.json['server'] == env.domain1
127
128
# http: GET for HTTP/2 with prior knowledge
129
def test_01_09_h2_prior_knowledge(self, env: Env, httpd):
130
curl = CurlClient(env=env)
131
url = f'http://{env.domain1}:{env.http_port}/data.json'
132
r = curl.http_get(url=url, extra_args=['--http2-prior-knowledge'])
133
r.check_exit_code(0)
134
assert len(r.responses) == 1, f'{r.responses}'
135
assert r.response['status'] == 200, f'{r.responsw}'
136
assert r.response['protocol'] == 'HTTP/2', f'{r.response}'
137
assert r.json['server'] == env.domain1
138
139
# http: strip TE header in HTTP/2 requests
140
def test_01_10_te_strip(self, env: Env, httpd):
141
curl = CurlClient(env=env)
142
url = f'https://{env.authority_for(env.domain1, "h2")}/data.json'
143
r = curl.http_get(url=url, extra_args=['--http2', '-H', 'TE: gzip'])
144
r.check_exit_code(0)
145
assert len(r.responses) == 1, f'{r.responses}'
146
assert r.responses[0]['status'] == 200, f'{r.responses[1]}'
147
assert r.responses[0]['protocol'] == 'HTTP/2', f'{r.responses[1]}'
148
149
# http: large response headers
150
# send 48KB+ sized response headers to check we handle that correctly
151
# larger than 64KB headers expose a bug in Apache HTTP/2 that is not
152
# RSTing the stream correctly when its internal limits are exceeded.
153
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
154
def test_01_11_large_resp_headers(self, env: Env, httpd, proto):
155
if proto == 'h3' and not env.have_h3():
156
pytest.skip("h3 not supported")
157
curl = CurlClient(env=env)
158
url = f'https://{env.authority_for(env.domain1, proto)}' \
159
f'/curltest/tweak?x-hd={48 * 1024}'
160
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
161
r.check_exit_code(0)
162
assert len(r.responses) == 1, f'{r.responses}'
163
assert r.responses[0]['status'] == 200, f'{r.responses}'
164
165
# http: response headers larger than what curl buffers for
166
@pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
167
reason='httpd must be at least 2.4.64')
168
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
169
def test_01_12_xlarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
170
httpd.set_extra_config('base', [
171
f'H2MaxHeaderBlockLen {130 * 1024}',
172
])
173
httpd.reload_if_config_changed()
174
curl = CurlClient(env=env)
175
url = f'https://{env.authority_for(env.domain1, proto)}' \
176
f'/curltest/tweak?x-hd={128 * 1024}'
177
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
178
r.check_exit_code(0)
179
assert len(r.responses) == 1, f'{r.responses}'
180
assert r.responses[0]['status'] == 200, f'{r.responses}'
181
182
# http: 1 response header larger than what curl buffers for
183
@pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
184
reason='httpd must be at least 2.4.64')
185
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
186
def test_01_13_megalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
187
httpd.set_extra_config('base', [
188
'LogLevel http2:trace2',
189
f'H2MaxHeaderBlockLen {130 * 1024}',
190
])
191
httpd.reload_if_config_changed()
192
curl = CurlClient(env=env)
193
url = f'https://{env.authority_for(env.domain1, proto)}' \
194
f'/curltest/tweak?x-hd1={128 * 1024}'
195
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
196
if proto == 'h2':
197
r.check_exit_code(16) # CURLE_HTTP2
198
else:
199
r.check_exit_code(100) # CURLE_TOO_LARGE
200
201
# http: several response headers, together > 256 KB
202
# nghttp2 error -905: Too many CONTINUATION frames following a HEADER frame
203
@pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
204
reason='httpd must be at least 2.4.64')
205
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
206
def test_01_14_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
207
httpd.set_extra_config('base', [
208
'LogLevel http2:trace2',
209
f'H2MaxHeaderBlockLen {1024 * 1024}',
210
])
211
httpd.reload_if_config_changed()
212
curl = CurlClient(env=env)
213
url = f'https://{env.authority_for(env.domain1, proto)}' \
214
f'/curltest/tweak?x-hd={256 * 1024}'
215
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
216
if proto == 'h2':
217
r.check_exit_code(16) # CURLE_HTTP2
218
else:
219
r.check_exit_code(0) # 1.1 can do
220
221
# http: one response header > 256 KB
222
@pytest.mark.skipif(condition=not Env.httpd_is_at_least('2.4.64'),
223
reason='httpd must be at least 2.4.64')
224
@pytest.mark.parametrize("proto", ['http/1.1', 'h2'])
225
def test_01_15_gigalarge_resp_headers(self, env: Env, httpd, configures_httpd, proto):
226
httpd.set_extra_config('base', [
227
'LogLevel http2:trace2',
228
f'H2MaxHeaderBlockLen {1024 * 1024}',
229
])
230
httpd.reload_if_config_changed()
231
curl = CurlClient(env=env)
232
url = f'https://{env.authority_for(env.domain1, proto)}' \
233
f'/curltest/tweak?x-hd1={256 * 1024}'
234
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[])
235
if proto == 'h2':
236
r.check_exit_code(16) # CURLE_HTTP2
237
else:
238
r.check_exit_code(100) # CURLE_TOO_LARGE
239
240
# http: invalid request headers, GET, issue #16998
241
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
242
def test_01_16_inv_req_get(self, env: Env, httpd, nghttpx, proto):
243
if proto == 'h3' and not env.have_h3():
244
pytest.skip("h3 not supported")
245
curl = CurlClient(env=env)
246
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
247
r = curl.http_get(url=url, alpn_proto=proto, extra_args=[
248
'-H', "a: a\x0ab"
249
])
250
# on h1, request is sent, h2/h3 reject
251
if proto == 'http/1.1':
252
r.check_exit_code(0)
253
else:
254
r.check_exit_code(43)
255
256
# http: special handling of TE request header
257
@pytest.mark.parametrize("te_in, te_out", [
258
pytest.param('trailers', 'trailers', id='trailers'),
259
pytest.param('chunked', None, id='chunked'),
260
pytest.param('gzip, trailers', 'trailers', id='gzip+trailers'),
261
pytest.param('gzip ;q=0.2;x="y,x", trailers', 'trailers', id='gzip+q+x+trailers'),
262
pytest.param('gzip ;x="trailers", chunks', None, id='gzip+x+chunks'),
263
])
264
def test_01_17_TE(self, env: Env, httpd, te_in, te_out):
265
proto = 'h2'
266
curl = CurlClient(env=env)
267
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo'
268
r = curl.http_download(urls=[url], alpn_proto=proto, with_stats=True,
269
with_headers=True,
270
extra_args=['-H', f'TE: {te_in}'])
271
r.check_response(200)
272
if te_out is not None:
273
assert r.responses[0]['header']['request-te'] == te_out, f'{r.responses[0]}'
274
else:
275
assert 'request-te' not in r.responses[0]['header'], f'{r.responses[0]}'
276
277