Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/pkg
Path: blob/main/external/curl/tests/http/test_07_upload.py
2649 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 difflib
28
import filecmp
29
import logging
30
import os
31
import re
32
import sys
33
import pytest
34
from typing import List, Union
35
36
from testenv import Env, CurlClient, LocalClient, ExecResult
37
38
39
log = logging.getLogger(__name__)
40
41
42
class TestUpload:
43
44
@pytest.fixture(autouse=True, scope='class')
45
def _class_scope(self, env, httpd, nghttpx):
46
env.make_data_file(indir=env.gen_dir, fname="data-10k", fsize=10*1024)
47
env.make_data_file(indir=env.gen_dir, fname="data-63k", fsize=63*1024)
48
env.make_data_file(indir=env.gen_dir, fname="data-64k", fsize=64*1024)
49
env.make_data_file(indir=env.gen_dir, fname="data-100k", fsize=100*1024)
50
env.make_data_file(indir=env.gen_dir, fname="data-1m+", fsize=(1024*1024)+1)
51
env.make_data_file(indir=env.gen_dir, fname="data-10m", fsize=10*1024*1024)
52
53
# upload small data, check that this is what was echoed
54
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
55
def test_07_01_upload_1_small(self, env: Env, httpd, nghttpx, proto):
56
if proto == 'h3' and not env.have_h3():
57
pytest.skip("h3 not supported")
58
data = '0123456789'
59
curl = CurlClient(env=env)
60
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
61
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
62
r.check_stats(count=1, http_status=200, exitcode=0)
63
respdata = open(curl.response_file(0)).readlines()
64
assert respdata == [data]
65
66
# upload large data, check that this is what was echoed
67
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
68
def test_07_02_upload_1_large(self, env: Env, httpd, nghttpx, proto):
69
if proto == 'h3' and not env.have_h3():
70
pytest.skip("h3 not supported")
71
fdata = os.path.join(env.gen_dir, 'data-100k')
72
curl = CurlClient(env=env)
73
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
74
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
75
r.check_stats(count=1, http_status=200, exitcode=0)
76
indata = open(fdata).readlines()
77
respdata = open(curl.response_file(0)).readlines()
78
assert respdata == indata
79
80
# upload data sequentially, check that they were echoed
81
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
82
def test_07_10_upload_sequential(self, env: Env, httpd, nghttpx, proto):
83
if proto == 'h3' and not env.have_h3():
84
pytest.skip("h3 not supported")
85
count = 20
86
data = '0123456789'
87
curl = CurlClient(env=env)
88
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
89
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto)
90
r.check_stats(count=count, http_status=200, exitcode=0)
91
for i in range(count):
92
respdata = open(curl.response_file(i)).readlines()
93
assert respdata == [data]
94
95
# upload data parallel, check that they were echoed
96
@pytest.mark.parametrize("proto", ['h2', 'h3'])
97
def test_07_11_upload_parallel(self, env: Env, httpd, nghttpx, proto):
98
if proto == 'h3' and not env.have_h3():
99
pytest.skip("h3 not supported")
100
# limit since we use a separate connection in h1
101
count = 20
102
data = '0123456789'
103
curl = CurlClient(env=env)
104
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
105
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
106
extra_args=['--parallel'])
107
r.check_stats(count=count, http_status=200, exitcode=0)
108
for i in range(count):
109
respdata = open(curl.response_file(i)).readlines()
110
assert respdata == [data]
111
112
# upload large data sequentially, check that this is what was echoed
113
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
114
def test_07_12_upload_seq_large(self, env: Env, httpd, nghttpx, proto):
115
if proto == 'h3' and not env.have_h3():
116
pytest.skip("h3 not supported")
117
fdata = os.path.join(env.gen_dir, 'data-100k')
118
count = 10
119
curl = CurlClient(env=env)
120
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
121
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
122
r.check_response(count=count, http_status=200)
123
indata = open(fdata).readlines()
124
r.check_stats(count=count, http_status=200, exitcode=0)
125
for i in range(count):
126
respdata = open(curl.response_file(i)).readlines()
127
assert respdata == indata
128
129
# upload very large data sequentially, check that this is what was echoed
130
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
131
def test_07_13_upload_seq_large(self, env: Env, httpd, nghttpx, proto):
132
if proto == 'h3' and not env.have_h3():
133
pytest.skip("h3 not supported")
134
fdata = os.path.join(env.gen_dir, 'data-10m')
135
count = 2
136
curl = CurlClient(env=env)
137
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
138
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto)
139
r.check_stats(count=count, http_status=200, exitcode=0)
140
indata = open(fdata).readlines()
141
for i in range(count):
142
respdata = open(curl.response_file(i)).readlines()
143
assert respdata == indata
144
145
# upload from stdin, issue #14870
146
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
147
@pytest.mark.parametrize("indata", [
148
'', '1', '123\n456andsomething\n\n'
149
])
150
def test_07_14_upload_stdin(self, env: Env, httpd, nghttpx, proto, indata):
151
if proto == 'h3' and not env.have_h3():
152
pytest.skip("h3 not supported")
153
count = 1
154
curl = CurlClient(env=env)
155
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
156
r = curl.http_put(urls=[url], data=indata, alpn_proto=proto)
157
r.check_stats(count=count, http_status=200, exitcode=0)
158
for i in range(count):
159
respdata = open(curl.response_file(i)).readlines()
160
assert respdata == [f'{len(indata)}']
161
162
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
163
def test_07_15_hx_put(self, env: Env, httpd, nghttpx, proto):
164
if proto == 'h3' and not env.have_h3():
165
pytest.skip("h3 not supported")
166
count = 2
167
upload_size = 128*1024
168
url = f'https://localhost:{env.https_port}/curltest/put'
169
client = LocalClient(name='cli_hx_upload', env=env)
170
if not client.exists():
171
pytest.skip(f'example client not built: {client.name}')
172
r = client.run(args=[
173
'-n', f'{count}', '-S', f'{upload_size}', '-V', proto, url
174
])
175
r.check_exit_code(0)
176
self.check_downloads(client, r, [f"{upload_size}"], count)
177
178
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
179
def test_07_16_hx_put_reuse(self, env: Env, httpd, nghttpx, proto):
180
if proto == 'h3' and not env.have_h3():
181
pytest.skip("h3 not supported")
182
count = 2
183
upload_size = 128*1024
184
url = f'https://localhost:{env.https_port}/curltest/put'
185
client = LocalClient(name='cli_hx_upload', env=env)
186
if not client.exists():
187
pytest.skip(f'example client not built: {client.name}')
188
r = client.run(args=[
189
'-n', f'{count}', '-S', f'{upload_size}', '-R', '-V', proto, url
190
])
191
r.check_exit_code(0)
192
self.check_downloads(client, r, [f"{upload_size}"], count)
193
194
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
195
def test_07_17_hx_post_reuse(self, env: Env, httpd, nghttpx, proto):
196
if proto == 'h3' and not env.have_h3():
197
pytest.skip("h3 not supported")
198
count = 2
199
upload_size = 128*1024
200
url = f'https://localhost:{env.https_port}/curltest/echo'
201
client = LocalClient(name='cli_hx_upload', env=env)
202
if not client.exists():
203
pytest.skip(f'example client not built: {client.name}')
204
r = client.run(args=[
205
'-n', f'{count}', '-M', 'POST', '-S', f'{upload_size}', '-R', '-V', proto, url
206
])
207
r.check_exit_code(0)
208
self.check_downloads(client, r, ["x" * upload_size], count)
209
210
# upload data parallel, check that they were echoed
211
@pytest.mark.parametrize("proto", ['h2', 'h3'])
212
def test_07_20_upload_parallel(self, env: Env, httpd, nghttpx, proto):
213
if proto == 'h3' and not env.have_h3():
214
pytest.skip("h3 not supported")
215
# limit since we use a separate connection in h1
216
count = 10
217
data = '0123456789'
218
curl = CurlClient(env=env)
219
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
220
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto,
221
extra_args=['--parallel'])
222
r.check_stats(count=count, http_status=200, exitcode=0)
223
for i in range(count):
224
respdata = open(curl.response_file(i)).readlines()
225
assert respdata == [data]
226
227
# upload large data parallel, check that this is what was echoed
228
@pytest.mark.parametrize("proto", ['h2', 'h3'])
229
def test_07_21_upload_parallel_large(self, env: Env, httpd, nghttpx, proto):
230
if proto == 'h3' and not env.have_h3():
231
pytest.skip("h3 not supported")
232
fdata = os.path.join(env.gen_dir, 'data-100k')
233
# limit since we use a separate connection in h1
234
count = 10
235
curl = CurlClient(env=env)
236
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-{count-1}]'
237
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
238
extra_args=['--parallel'])
239
r.check_response(count=count, http_status=200)
240
self.check_download(r, count, fdata, curl)
241
242
# upload large data parallel to a URL that denies uploads
243
@pytest.mark.parametrize("proto", ['h2', 'h3'])
244
def test_07_22_upload_parallel_fail(self, env: Env, httpd, nghttpx, proto):
245
if proto == 'h3' and not env.have_h3():
246
pytest.skip("h3 not supported")
247
fdata = os.path.join(env.gen_dir, 'data-10m')
248
count = 20
249
curl = CurlClient(env=env)
250
url = f'https://{env.authority_for(env.domain1, proto)}'\
251
f'/curltest/tweak?status=400&delay=5ms&chunks=1&body_error=reset&id=[0-{count-1}]'
252
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
253
extra_args=['--parallel'])
254
# depending on timing and protocol, we might get CURLE_PARTIAL_FILE or
255
# CURLE_SEND_ERROR or CURLE_HTTP3 or CURLE_HTTP2_STREAM
256
r.check_stats(count=count, exitcode=[18, 55, 56, 92, 95])
257
258
# PUT 100k
259
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
260
def test_07_30_put_100k(self, env: Env, httpd, nghttpx, proto):
261
if proto == 'h3' and not env.have_h3():
262
pytest.skip("h3 not supported")
263
fdata = os.path.join(env.gen_dir, 'data-100k')
264
count = 1
265
curl = CurlClient(env=env)
266
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
267
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
268
extra_args=['--parallel'])
269
r.check_stats(count=count, http_status=200, exitcode=0)
270
exp_data = [f'{os.path.getsize(fdata)}']
271
r.check_response(count=count, http_status=200)
272
for i in range(count):
273
respdata = open(curl.response_file(i)).readlines()
274
assert respdata == exp_data
275
276
# PUT 10m
277
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
278
def test_07_31_put_10m(self, env: Env, httpd, nghttpx, proto):
279
if proto == 'h3' and not env.have_h3():
280
pytest.skip("h3 not supported")
281
fdata = os.path.join(env.gen_dir, 'data-10m')
282
count = 1
283
curl = CurlClient(env=env)
284
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]&chunk_delay=2ms'
285
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
286
extra_args=['--parallel'])
287
r.check_stats(count=count, http_status=200, exitcode=0)
288
exp_data = [f'{os.path.getsize(fdata)}']
289
r.check_response(count=count, http_status=200)
290
for i in range(count):
291
respdata = open(curl.response_file(i)).readlines()
292
assert respdata == exp_data
293
294
# issue #10591
295
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
296
def test_07_32_issue_10591(self, env: Env, httpd, nghttpx, proto):
297
if proto == 'h3' and not env.have_h3():
298
pytest.skip("h3 not supported")
299
fdata = os.path.join(env.gen_dir, 'data-10m')
300
count = 1
301
curl = CurlClient(env=env)
302
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-{count-1}]'
303
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto)
304
r.check_stats(count=count, http_status=200, exitcode=0)
305
306
# issue #11157, upload that is 404'ed by server, needs to terminate
307
# correctly and not time out on sending
308
def test_07_33_issue_11157a(self, env: Env, httpd, nghttpx):
309
proto = 'h2'
310
fdata = os.path.join(env.gen_dir, 'data-10m')
311
# send a POST to our PUT handler which will send immediately a 404 back
312
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
313
curl = CurlClient(env=env)
314
r = curl.run_direct(with_stats=True, args=[
315
'--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
316
'--cacert', env.ca.cert_file,
317
'--request', 'POST',
318
'--max-time', '5', '-v',
319
'--url', url,
320
'--form', 'idList=12345678',
321
'--form', 'pos=top',
322
'--form', 'name=mr_test',
323
'--form', f'fileSource=@{fdata};type=application/pdf',
324
])
325
assert r.exit_code == 0, f'{r}'
326
r.check_stats(1, 404)
327
328
# issue #11157, send upload that is slowly read in
329
def test_07_33_issue_11157b(self, env: Env, httpd, nghttpx):
330
proto = 'h2'
331
fdata = os.path.join(env.gen_dir, 'data-10m')
332
# tell our test PUT handler to read the upload more slowly, so
333
# that the send buffering and transfer loop needs to wait
334
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?chunk_delay=2ms'
335
curl = CurlClient(env=env)
336
r = curl.run_direct(with_stats=True, args=[
337
'--verbose', '--trace-config', 'ids,time',
338
'--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
339
'--cacert', env.ca.cert_file,
340
'--request', 'PUT',
341
'--max-time', '10', '-v',
342
'--url', url,
343
'--form', 'idList=12345678',
344
'--form', 'pos=top',
345
'--form', 'name=mr_test',
346
'--form', f'fileSource=@{fdata};type=application/pdf',
347
])
348
assert r.exit_code == 0, r.dump_logs()
349
r.check_stats(1, 200)
350
351
def test_07_34_issue_11194(self, env: Env, httpd, nghttpx):
352
proto = 'h2'
353
# tell our test PUT handler to read the upload more slowly, so
354
# that the send buffering and transfer loop needs to wait
355
fdata = os.path.join(env.gen_dir, 'data-100k')
356
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
357
curl = CurlClient(env=env)
358
r = curl.run_direct(with_stats=True, args=[
359
'--verbose', '--trace-config', 'ids,time',
360
'--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
361
'--cacert', env.ca.cert_file,
362
'--request', 'PUT',
363
'--digest', '--user', 'test:test',
364
'--data-binary', f'@{fdata}',
365
'--url', url,
366
])
367
assert r.exit_code == 0, r.dump_logs()
368
r.check_stats(1, 200)
369
370
# upload large data on a h1 to h2 upgrade
371
def test_07_35_h1_h2_upgrade_upload(self, env: Env, httpd, nghttpx):
372
fdata = os.path.join(env.gen_dir, 'data-100k')
373
curl = CurlClient(env=env)
374
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]'
375
r = curl.http_upload(urls=[url], data=f'@{fdata}', extra_args=[
376
'--http2'
377
])
378
r.check_response(count=1, http_status=200)
379
# apache does not Upgrade on request with a body
380
assert r.stats[0]['http_version'] == '1.1', f'{r}'
381
indata = open(fdata).readlines()
382
respdata = open(curl.response_file(0)).readlines()
383
assert respdata == indata
384
385
# upload to a 301,302,303 response
386
@pytest.mark.parametrize("redir", ['301', '302', '303'])
387
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
388
def test_07_36_upload_30x(self, env: Env, httpd, nghttpx, redir, proto):
389
if proto == 'h3' and not env.have_h3():
390
pytest.skip("h3 not supported")
391
if proto == 'h3' and env.curl_uses_ossl_quic():
392
pytest.skip("OpenSSL's own QUIC is flaky here")
393
data = '0123456789' * 10
394
curl = CurlClient(env=env)
395
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo{redir}?id=[0-0]'
396
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[
397
'-L', '--trace-config', 'http/2,http/3'
398
])
399
r.check_response(count=1, http_status=200)
400
respdata = open(curl.response_file(0)).readlines()
401
assert respdata == [] # was transformed to a GET
402
403
# upload to a 307 response
404
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
405
def test_07_37_upload_307(self, env: Env, httpd, nghttpx, proto):
406
if proto == 'h3' and not env.have_h3():
407
pytest.skip("h3 not supported")
408
if proto == 'h3' and env.curl_uses_ossl_quic():
409
pytest.skip("OpenSSL's own QUIC is flaky here")
410
data = '0123456789' * 10
411
curl = CurlClient(env=env)
412
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo307?id=[0-0]'
413
r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[
414
'-L', '--trace-config', 'http/2,http/3'
415
])
416
r.check_response(count=1, http_status=200)
417
respdata = open(curl.response_file(0)).readlines()
418
assert respdata == [data] # was POST again
419
420
# POST form data, yet another code path in transfer
421
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
422
def test_07_38_form_small(self, env: Env, httpd, nghttpx, proto):
423
if proto == 'h3' and not env.have_h3():
424
pytest.skip("h3 not supported")
425
curl = CurlClient(env=env)
426
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
427
r = curl.http_form(urls=[url], alpn_proto=proto, form={
428
'name1': 'value1',
429
})
430
r.check_stats(count=1, http_status=200, exitcode=0)
431
432
# POST data urlencoded, small enough to be sent with request headers
433
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
434
def test_07_39_post_urlenc_small(self, env: Env, httpd, nghttpx, proto):
435
if proto == 'h3' and not env.have_h3():
436
pytest.skip("h3 not supported")
437
fdata = os.path.join(env.gen_dir, 'data-63k')
438
curl = CurlClient(env=env)
439
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
440
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[
441
'--trace-config', 'http/2,http/3'
442
])
443
r.check_stats(count=1, http_status=200, exitcode=0)
444
indata = open(fdata).readlines()
445
respdata = open(curl.response_file(0)).readlines()
446
assert respdata == indata
447
448
# POST data urlencoded, large enough to be sent separate from request headers
449
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
450
def test_07_40_post_urlenc_large(self, env: Env, httpd, nghttpx, proto):
451
if proto == 'h3' and not env.have_h3():
452
pytest.skip("h3 not supported")
453
fdata = os.path.join(env.gen_dir, 'data-64k')
454
curl = CurlClient(env=env)
455
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
456
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=[
457
'--trace-config', 'http/2,http/3'
458
])
459
r.check_stats(count=1, http_status=200, exitcode=0)
460
indata = open(fdata).readlines()
461
respdata = open(curl.response_file(0)).readlines()
462
assert respdata == indata
463
464
# POST data urlencoded, small enough to be sent with request headers
465
# and request headers are so large that the first send is larger
466
# than our default upload buffer length (64KB).
467
# Unfixed, this will fail when run with CURL_DBG_SOCK_WBLOCK=80 most
468
# of the time
469
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
470
def test_07_41_post_urlenc_small(self, env: Env, httpd, nghttpx, proto):
471
if proto == 'h3' and not env.have_h3():
472
pytest.skip("h3 not supported")
473
if proto == 'h3' and env.curl_uses_lib('quiche'):
474
pytest.skip("quiche has CWND issues with large requests")
475
fdata = os.path.join(env.gen_dir, 'data-63k')
476
curl = CurlClient(env=env)
477
extra_args = ['--trace-config', 'http/2,http/3']
478
# add enough headers so that the first send chunk is > 64KB
479
for i in range(63):
480
extra_args.extend(['-H', f'x{i:02d}: {"y"*1019}'])
481
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
482
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args)
483
r.check_stats(count=1, http_status=200, exitcode=0)
484
indata = open(fdata).readlines()
485
respdata = open(curl.response_file(0)).readlines()
486
assert respdata == indata
487
488
def check_download(self, r: ExecResult, count: int, srcfile: Union[str, os.PathLike], curl: CurlClient):
489
for i in range(count):
490
dfile = curl.download_file(i)
491
assert os.path.exists(dfile), f'download {dfile} missing\n{r.dump_logs()}'
492
if not filecmp.cmp(srcfile, dfile, shallow=False):
493
diff = "".join(difflib.unified_diff(a=open(srcfile).readlines(),
494
b=open(dfile).readlines(),
495
fromfile=srcfile,
496
tofile=dfile,
497
n=1))
498
assert False, f'download {dfile} differs:\n{diff}\n{r.dump_logs()}'
499
500
# upload data, pause, let connection die with an incomplete response
501
# issues #11769 #13260
502
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
503
def test_07_42a_upload_disconnect(self, env: Env, httpd, nghttpx, proto):
504
if proto == 'h3' and not env.have_h3():
505
pytest.skip("h3 not supported")
506
client = LocalClient(name='cli_upload_pausing', env=env, timeout=60)
507
if not client.exists():
508
pytest.skip(f'example client not built: {client.name}')
509
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]&die_after=0'
510
r = client.run(['-V', proto, url])
511
if r.exit_code == 18: # PARTIAL_FILE is always ok
512
pass
513
elif proto == 'h2':
514
# CURLE_HTTP2, CURLE_HTTP2_STREAM
515
assert r.exit_code in [16, 92], f'unexpected exit code\n{r.dump_logs()}'
516
elif proto == 'h3':
517
r.check_exit_code(95) # CURLE_HTTP3 also ok
518
else:
519
r.check_exit_code(18) # will fail as it should
520
521
# upload data, pause, let connection die without any response at all
522
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
523
def test_07_42b_upload_disconnect(self, env: Env, httpd, nghttpx, proto):
524
if proto == 'h3' and not env.have_h3():
525
pytest.skip("h3 not supported")
526
client = LocalClient(name='cli_upload_pausing', env=env, timeout=60)
527
if not client.exists():
528
pytest.skip(f'example client not built: {client.name}')
529
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=0&just_die=1'
530
r = client.run(['-V', proto, url])
531
exp_code = 52 # GOT_NOTHING
532
if proto == 'h2' or proto == 'h3':
533
exp_code = 0 # we get a 500 from the server
534
r.check_exit_code(exp_code) # GOT_NOTHING
535
536
# upload data, pause, let connection die after 100 continue
537
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
538
def test_07_42c_upload_disconnect(self, env: Env, httpd, nghttpx, proto):
539
if proto == 'h3' and not env.have_h3():
540
pytest.skip("h3 not supported")
541
client = LocalClient(name='cli_upload_pausing', env=env, timeout=60)
542
if not client.exists():
543
pytest.skip(f'example client not built: {client.name}')
544
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=0&die_after_100=1'
545
r = client.run(['-V', proto, url])
546
exp_code = 52 # GOT_NOTHING
547
if proto == 'h2' or proto == 'h3':
548
exp_code = 0 # we get a 500 from the server
549
r.check_exit_code(exp_code) # GOT_NOTHING
550
551
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
552
def test_07_43_upload_denied(self, env: Env, httpd, nghttpx, proto):
553
if proto == 'h3' and not env.have_h3():
554
pytest.skip("h3 not supported")
555
if proto == 'h3' and env.curl_uses_ossl_quic():
556
pytest.skip("openssl-quic is flaky in filed PUTs")
557
fdata = os.path.join(env.gen_dir, 'data-10m')
558
count = 1
559
max_upload = 128 * 1024
560
curl = CurlClient(env=env)
561
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?'\
562
f'id=[0-{count-1}]&max_upload={max_upload}'
563
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
564
extra_args=['--trace-config', 'all'])
565
r.check_stats(count=count, http_status=413, exitcode=0)
566
567
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
568
@pytest.mark.parametrize("httpcode", [301, 302, 307, 308])
569
def test_07_44_put_redir(self, env: Env, httpd, nghttpx, proto, httpcode):
570
if proto == 'h3' and not env.have_h3():
571
pytest.skip("h3 not supported")
572
count = 1
573
upload_size = 128*1024
574
url = f'https://localhost:{env.https_port}/curltest/put-redir-{httpcode}'
575
client = LocalClient(name='cli_hx_upload', env=env)
576
if not client.exists():
577
pytest.skip(f'example client not built: {client.name}')
578
r = client.run(args=[
579
'-n', f'{count}', '-l', '-S', f'{upload_size}', '-V', proto, url
580
])
581
r.check_exit_code(0)
582
results = [int(m.group(1)) for line in r.trace_lines
583
if (m := re.match(r'.* FINISHED, result=(\d+), response=(\d+)', line))]
584
httpcodes = [int(m.group(2)) for line in r.trace_lines
585
if (m := re.match(r'.* FINISHED, result=(\d+), response=(\d+)', line))]
586
if httpcode == 308:
587
assert results[0] == 65, f'{r}' # could not rewind input
588
else:
589
assert httpcodes[0] == httpcode, f'{r}'
590
591
# speed limited on put handler
592
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
593
def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto):
594
if proto == 'h3' and not env.have_h3():
595
pytest.skip("h3 not supported")
596
count = 1
597
fdata = os.path.join(env.gen_dir, 'data-100k')
598
up_len = 100 * 1024
599
speed_limit = 50 * 1024
600
curl = CurlClient(env=env)
601
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'
602
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto,
603
with_headers=True, extra_args=[
604
'--limit-rate', f'{speed_limit}'
605
])
606
r.check_response(count=count, http_status=200)
607
assert r.responses[0]['header']['received-length'] == f'{up_len}', f'{r.responses[0]}'
608
up_speed = r.stats[0]['speed_upload']
609
assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}'
610
611
# speed limited on echo handler
612
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
613
def test_07_51_echo_speed_limit(self, env: Env, httpd, nghttpx, proto):
614
if proto == 'h3' and not env.have_h3():
615
pytest.skip("h3 not supported")
616
count = 1
617
fdata = os.path.join(env.gen_dir, 'data-100k')
618
speed_limit = 50 * 1024
619
curl = CurlClient(env=env)
620
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
621
r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto,
622
with_headers=True, extra_args=[
623
'--limit-rate', f'{speed_limit}'
624
])
625
r.check_response(count=count, http_status=200)
626
up_speed = r.stats[0]['speed_upload']
627
assert (speed_limit * 0.5) <= up_speed <= (speed_limit * 1.5), f'{r.stats[0]}'
628
629
# upload larger data, triggering "Expect: 100-continue" code paths
630
@pytest.mark.parametrize("proto", ['http/1.1'])
631
def test_07_60_upload_exp100(self, env: Env, httpd, nghttpx, proto):
632
fdata = os.path.join(env.gen_dir, 'data-1m+')
633
read_delay = 1
634
curl = CurlClient(env=env)
635
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\
636
f'&read_delay={read_delay}s'
637
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[
638
'--expect100-timeout', f'{read_delay+1}'
639
])
640
r.check_stats(count=1, http_status=200, exitcode=0)
641
642
# upload larger data, triggering "Expect: 100-continue" code paths
643
@pytest.mark.parametrize("proto", ['http/1.1'])
644
def test_07_61_upload_exp100_timeout(self, env: Env, httpd, nghttpx, proto):
645
fdata = os.path.join(env.gen_dir, 'data-1m+')
646
read_delay = 2
647
curl = CurlClient(env=env)
648
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put?id=[0-0]'\
649
f'&read_delay={read_delay}s'
650
r = curl.http_put(urls=[url], fdata=fdata, alpn_proto=proto, extra_args=[
651
'--expect100-timeout', f'{read_delay-1}'
652
])
653
r.check_stats(count=1, http_status=200, exitcode=0)
654
655
# issue #15688 when posting a form and cr_mime_read() is called with
656
# length < 4, we did not progress
657
@pytest.mark.parametrize("proto", ['http/1.1'])
658
def test_07_62_upload_issue_15688(self, env: Env, httpd, proto):
659
# this length leads to (including multipart formatting) to a
660
# client reader invocation with length 1.
661
upload_len = 196169
662
fname = f'data-{upload_len}'
663
env.make_data_file(indir=env.gen_dir, fname=fname, fsize=upload_len)
664
fdata = os.path.join(env.gen_dir, fname)
665
curl = CurlClient(env=env)
666
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
667
r = curl.http_form(urls=[url], form={
668
'file': f'@{fdata}',
669
}, alpn_proto=proto, extra_args=[
670
'--max-time', '10'
671
])
672
r.check_stats(count=1, http_status=200, exitcode=0)
673
674
@pytest.mark.parametrize("proto", ['http/1.1'])
675
def test_07_63_upload_exp100_paused(self, env: Env, httpd, nghttpx, proto):
676
read_delay = 1
677
url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'\
678
f'&read_delay={read_delay}s'
679
upload_size = 128 * 1024
680
client = LocalClient(name='cli_hx_upload', env=env)
681
if not client.exists():
682
pytest.skip(f'example client not built: {client.name}')
683
r = client.run(args=[
684
'-n', '1',
685
'-S', f'{upload_size}',
686
'-P', '1',
687
'-M', 'MIME',
688
'-r', f'{env.domain1}:{env.port_for(proto)}:127.0.0.1',
689
'-V', proto, url
690
])
691
r.check_exit_code(0)
692
693
# nghttpx is the only server we have that supports TLS early data and
694
# has a limit of 16k it announces
695
@pytest.mark.skipif(condition=not Env.have_nghttpx(), reason="no nghttpx")
696
@pytest.mark.parametrize("proto,upload_size,exp_early", [
697
pytest.param('http/1.1', 100, 203, id='h1-small-body'),
698
pytest.param('http/1.1', 10*1024, 10345, id='h1-medium-body'),
699
pytest.param('http/1.1', 32*1024, 16384, id='h1-limited-body'),
700
pytest.param('h2', 10*1024, 10378, id='h2-medium-body'),
701
pytest.param('h2', 32*1024, 16384, id='h2-limited-body'),
702
pytest.param('h3', 1024, 1126, id='h3-small-body'),
703
pytest.param('h3', 1024 * 1024, 131177, id='h3-limited-body'),
704
# h3: limited+body (long app data). The 0RTT size is limited by
705
# our sendbuf size of 128K.
706
])
707
def test_07_70_put_earlydata(self, env: Env, httpd, nghttpx, proto, upload_size, exp_early):
708
if not env.curl_can_early_data():
709
pytest.skip('TLS earlydata not implemented')
710
if proto == 'h3' and \
711
(not env.have_h3() or not env.curl_can_h3_early_data()):
712
pytest.skip("h3 not supported")
713
if proto != 'h3' and sys.platform.startswith('darwin') and env.ci_run:
714
pytest.skip('failing on macOS CI runners')
715
if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('wolfssl'):
716
pytest.skip('h3 wolfssl early data failing on macOS')
717
if proto == 'h3' and sys.platform.startswith('darwin') and env.curl_uses_lib('gnutls'):
718
pytest.skip('h3 gnutls early data failing on macOS')
719
count = 2
720
# we want this test to always connect to nghttpx, since it is
721
# the only server we have that supports TLS earlydata
722
port = env.port_for(proto)
723
if proto != 'h3':
724
port = env.nghttpx_https_port
725
url = f'https://{env.domain1}:{port}/curltest/put'
726
client = LocalClient(name='cli_hx_upload', env=env)
727
if not client.exists():
728
pytest.skip(f'example client not built: {client.name}')
729
r = client.run(args=[
730
'-n', f'{count}',
731
'-e', # use TLS earlydata
732
'-f', # forbid reuse of connections
733
'-l', # announce upload length, no 'Expect: 100'
734
'-S', f'{upload_size}',
735
'-r', f'{env.domain1}:{port}:127.0.0.1',
736
'-V', proto, url
737
])
738
r.check_exit_code(0)
739
self.check_downloads(client, r, [f"{upload_size}"], count)
740
earlydata = {}
741
for line in r.trace_lines:
742
m = re.match(r'^\[t-(\d+)] EarlyData: (-?\d+)', line)
743
if m:
744
earlydata[int(m.group(1))] = int(m.group(2))
745
assert earlydata[0] == 0, f'{earlydata}\n{r.dump_logs()}'
746
# depending on cpu load, curl might not upload as much before
747
# the handshake starts and early data stops.
748
assert 0 < earlydata[1] <= exp_early, f'{earlydata}\n{r.dump_logs()}'
749
750
def check_downloads(self, client, r, source: List[str], count: int,
751
complete: bool = True):
752
for i in range(count):
753
dfile = client.download_file(i)
754
assert os.path.exists(dfile), f'download {dfile} missing\n{r.dump_logs()}'
755
if complete:
756
diff = "".join(difflib.unified_diff(a=source,
757
b=open(dfile).readlines(),
758
fromfile='-',
759
tofile=dfile,
760
n=1))
761
assert not diff, f'download {dfile} differs:\n{diff}\n{r.dump_logs()}'
762
763