mirror of
https://github.com/curl/curl.git
synced 2026-08-02 14:10:28 +03:00
transfer: conn close on paused upload
- add 2 variations on test_07_42 which PAUSEs uploads and response connections terminating either right away or after the 100-continue response - when detecting the connection being closed in transfer.c readwrite_data(), clear ALL send bits in data->req.keepon. It no longer makes send to wait for a KEEP_SEND_PAUSE or HOLD. - in the protocol client writer add the check for incomplete response bodies. When an EOS is seen and the length is known, check that and fail if bytes are missing. Reported-by: Sergey Bronnikov Fixes #13740 Closes #13750
This commit is contained in:
parent
c5e322fb50
commit
30de937bda
5 changed files with 86 additions and 9 deletions
|
|
@ -289,6 +289,13 @@ static CURLcode cw_download_write(struct Curl_easy *data,
|
||||||
if(nwrite == wmax) {
|
if(nwrite == wmax) {
|
||||||
data->req.download_done = TRUE;
|
data->req.download_done = TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if((type & CLIENTWRITE_EOS) && !data->req.no_body &&
|
||||||
|
(data->req.maxdownload > data->req.bytecount)) {
|
||||||
|
failf(data, "end of response with %" CURL_FORMAT_CURL_OFF_T
|
||||||
|
" bytes missing", data->req.maxdownload - data->req.bytecount);
|
||||||
|
return CURLE_PARTIAL_FILE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Error on too large filesize is handled below, after writing
|
/* Error on too large filesize is handled below, after writing
|
||||||
|
|
@ -694,6 +701,7 @@ static CURLcode cr_in_read(struct Curl_easy *data,
|
||||||
return CURLE_READ_ERROR;
|
return CURLE_READ_ERROR;
|
||||||
}
|
}
|
||||||
/* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */
|
/* CURL_READFUNC_PAUSE pauses read callbacks that feed socket writes */
|
||||||
|
CURL_TRC_READ(data, "cr_in_read, callback returned CURL_READFUNC_PAUSE");
|
||||||
data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */
|
data->req.keepon |= KEEP_SEND_PAUSE; /* mark socket send as paused */
|
||||||
*pnread = 0;
|
*pnread = 0;
|
||||||
*peos = FALSE;
|
*peos = FALSE;
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,10 @@ static CURLcode readwrite_data(struct Curl_easy *data,
|
||||||
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
|
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
|
||||||
else
|
else
|
||||||
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
|
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
|
||||||
k->keepon &= ~(KEEP_RECV|KEEP_SEND); /* stop sending as well */
|
/* stop receiving and ALL sending as well, including PAUSE and HOLD.
|
||||||
|
* We might still be paused on receive client writes though, so
|
||||||
|
* keep those bits around. */
|
||||||
|
k->keepon &= ~(KEEP_RECV|KEEP_SENDBITS);
|
||||||
if(k->eos_written) /* already did write this to client, leave */
|
if(k->eos_written) /* already did write this to client, leave */
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ static int debug_cb(CURL *handle, curl_infotype type,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define PAUSE_READ_AFTER 10
|
#define PAUSE_READ_AFTER 1
|
||||||
static size_t total_read = 0;
|
static size_t total_read = 0;
|
||||||
|
|
||||||
static size_t read_callback(char *ptr, size_t size, size_t nmemb,
|
static size_t read_callback(char *ptr, size_t size, size_t nmemb,
|
||||||
|
|
@ -143,11 +143,13 @@ static size_t read_callback(char *ptr, size_t size, size_t nmemb,
|
||||||
(void)nmemb;
|
(void)nmemb;
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
if(total_read >= PAUSE_READ_AFTER) {
|
if(total_read >= PAUSE_READ_AFTER) {
|
||||||
|
fprintf(stderr, "read_callback, return PAUSE\n");
|
||||||
return CURL_READFUNC_PAUSE;
|
return CURL_READFUNC_PAUSE;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
ptr[0] = '\n';
|
ptr[0] = '\n';
|
||||||
++total_read;
|
++total_read;
|
||||||
|
fprintf(stderr, "read_callback, return 1 byte\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -158,13 +160,19 @@ static int progress_callback(void *clientp,
|
||||||
double ultotal,
|
double ultotal,
|
||||||
double ulnow)
|
double ulnow)
|
||||||
{
|
{
|
||||||
CURL *curl;
|
|
||||||
(void)dltotal;
|
(void)dltotal;
|
||||||
(void)dlnow;
|
(void)dlnow;
|
||||||
(void)ultotal;
|
(void)ultotal;
|
||||||
(void)ulnow;
|
(void)ulnow;
|
||||||
curl = (CURL *)clientp;
|
(void)clientp;
|
||||||
curl_easy_pause(curl, CURLPAUSE_CONT);
|
#if 0
|
||||||
|
/* Used to unpause on progress, but keeping for now. */
|
||||||
|
{
|
||||||
|
CURL *curl = (CURL *)clientp;
|
||||||
|
curl_easy_pause(curl, CURLPAUSE_CONT);
|
||||||
|
/* curl_easy_pause(curl, CURLPAUSE_RECV_CONT); */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -464,10 +464,10 @@ class TestUpload:
|
||||||
n=1))
|
n=1))
|
||||||
assert False, f'download {dfile} differs:\n{diff}'
|
assert False, f'download {dfile} differs:\n{diff}'
|
||||||
|
|
||||||
# upload large data, let connection die while doing it
|
# upload data, pause, let connection die with an incomplete response
|
||||||
# issues #11769 #13260
|
# issues #11769 #13260
|
||||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||||
def test_07_42_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
|
def test_07_42a_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
|
||||||
if proto == 'h3' and not env.have_h3():
|
if proto == 'h3' and not env.have_h3():
|
||||||
pytest.skip("h3 not supported")
|
pytest.skip("h3 not supported")
|
||||||
if proto == 'h3' and env.curl_uses_lib('msh3'):
|
if proto == 'h3' and env.curl_uses_lib('msh3'):
|
||||||
|
|
@ -475,10 +475,38 @@ class TestUpload:
|
||||||
client = LocalClient(name='upload-pausing', env=env, timeout=60)
|
client = LocalClient(name='upload-pausing', env=env, timeout=60)
|
||||||
if not client.exists():
|
if not client.exists():
|
||||||
pytest.skip(f'example client not built: {client.name}')
|
pytest.skip(f'example client not built: {client.name}')
|
||||||
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=10'
|
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=0'
|
||||||
r = client.run([url])
|
r = client.run([url])
|
||||||
r.check_exit_code(18) # PARTIAL_FILE
|
r.check_exit_code(18) # PARTIAL_FILE
|
||||||
|
|
||||||
|
# upload data, pause, let connection die without any response at all
|
||||||
|
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||||
|
def test_07_42b_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
|
||||||
|
if proto == 'h3' and not env.have_h3():
|
||||||
|
pytest.skip("h3 not supported")
|
||||||
|
if proto == 'h3' and env.curl_uses_lib('msh3'):
|
||||||
|
pytest.skip("msh3 fails here")
|
||||||
|
client = LocalClient(name='upload-pausing', env=env, timeout=60)
|
||||||
|
if not client.exists():
|
||||||
|
pytest.skip(f'example client not built: {client.name}')
|
||||||
|
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&just_die=1'
|
||||||
|
r = client.run([url])
|
||||||
|
r.check_exit_code(52) # GOT_NOTHING
|
||||||
|
|
||||||
|
# upload data, pause, let connection die after 100 continue
|
||||||
|
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||||
|
def test_07_42c_upload_disconnect(self, env: Env, httpd, nghttpx, repeat, proto):
|
||||||
|
if proto == 'h3' and not env.have_h3():
|
||||||
|
pytest.skip("h3 not supported")
|
||||||
|
if proto == 'h3' and env.curl_uses_lib('msh3'):
|
||||||
|
pytest.skip("msh3 fails here")
|
||||||
|
client = LocalClient(name='upload-pausing', env=env, timeout=60)
|
||||||
|
if not client.exists():
|
||||||
|
pytest.skip(f'example client not built: {client.name}')
|
||||||
|
url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after_100=1'
|
||||||
|
r = client.run([url])
|
||||||
|
r.check_exit_code(52) # GOT_NOTHING
|
||||||
|
|
||||||
# speed limited on put handler
|
# speed limited on put handler
|
||||||
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
|
||||||
def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
|
def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ static int curltest_echo_handler(request_rec *r)
|
||||||
char buffer[8192];
|
char buffer[8192];
|
||||||
const char *ct;
|
const char *ct;
|
||||||
apr_off_t die_after_len = -1, total_read_len = 0;
|
apr_off_t die_after_len = -1, total_read_len = 0;
|
||||||
|
int just_die = 0, die_after_100 = 0;
|
||||||
long l;
|
long l;
|
||||||
|
|
||||||
if(strcmp(r->handler, "curltest-echo")) {
|
if(strcmp(r->handler, "curltest-echo")) {
|
||||||
|
|
@ -208,16 +209,35 @@ static int curltest_echo_handler(request_rec *r)
|
||||||
val = s + 1;
|
val = s + 1;
|
||||||
if(!strcmp("die_after", arg)) {
|
if(!strcmp("die_after", arg)) {
|
||||||
die_after_len = (apr_off_t)apr_atoi64(val);
|
die_after_len = (apr_off_t)apr_atoi64(val);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(!strcmp("just_die", arg)) {
|
||||||
|
just_die = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if(!strcmp("die_after_100", arg)) {
|
||||||
|
die_after_100 = 1;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(just_die) {
|
||||||
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
|
||||||
|
"echo_handler: dying right away");
|
||||||
|
/* Generate no HTTP response at all. */
|
||||||
|
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||||
|
r->connection->keepalive = AP_CONN_CLOSE;
|
||||||
|
return AP_FILTER_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
r->status = 200;
|
r->status = 200;
|
||||||
if(die_after_len >= 0) {
|
if(die_after_len >= 0) {
|
||||||
r->clength = die_after_len + 1;
|
r->clength = die_after_len + 1;
|
||||||
r->chunked = 0;
|
r->chunked = 0;
|
||||||
apr_table_set(r->headers_out, "Content-Length", apr_ltoa(r->pool, (long)r->clength));
|
apr_table_set(r->headers_out, "Content-Length",
|
||||||
|
apr_ltoa(r->pool, (long)r->clength));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
r->clength = -1;
|
r->clength = -1;
|
||||||
|
|
@ -235,6 +255,14 @@ static int curltest_echo_handler(request_rec *r)
|
||||||
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
bb = apr_brigade_create(r->pool, c->bucket_alloc);
|
||||||
/* copy any request body into the response */
|
/* copy any request body into the response */
|
||||||
if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
|
if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
|
||||||
|
if(die_after_100) {
|
||||||
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
|
||||||
|
"echo_handler: dying after 100-continue");
|
||||||
|
/* Generate no HTTP response at all. */
|
||||||
|
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||||
|
r->connection->keepalive = AP_CONN_CLOSE;
|
||||||
|
return AP_FILTER_ERROR;
|
||||||
|
}
|
||||||
if(ap_should_client_block(r)) {
|
if(ap_should_client_block(r)) {
|
||||||
while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
|
while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
|
||||||
total_read_len += l;
|
total_read_len += l;
|
||||||
|
|
@ -242,6 +270,8 @@ static int curltest_echo_handler(request_rec *r)
|
||||||
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
|
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
|
||||||
"echo_handler: dying after %ld bytes as requested",
|
"echo_handler: dying after %ld bytes as requested",
|
||||||
(long)total_read_len);
|
(long)total_read_len);
|
||||||
|
ap_pass_brigade(r->output_filters, bb);
|
||||||
|
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
|
||||||
r->connection->keepalive = AP_CONN_CLOSE;
|
r->connection->keepalive = AP_CONN_CLOSE;
|
||||||
return DONE;
|
return DONE;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue