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