if(nwrite == wmax) {
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
return CURLE_READ_ERROR;
}
/* 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 */
*pnread = 0;
*peos = FALSE;
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
else
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 */
break;
}
return 0;
}
-#define PAUSE_READ_AFTER 10
+#define PAUSE_READ_AFTER 1
static size_t total_read = 0;
static size_t read_callback(char *ptr, size_t size, size_t nmemb,
(void)nmemb;
(void)userdata;
if(total_read >= PAUSE_READ_AFTER) {
+ fprintf(stderr, "read_callback, return PAUSE\n");
return CURL_READFUNC_PAUSE;
}
else {
ptr[0] = '\n';
++total_read;
+ fprintf(stderr, "read_callback, return 1 byte\n");
return 1;
}
}
double ultotal,
double ulnow)
{
- CURL *curl;
(void)dltotal;
(void)dlnow;
(void)ultotal;
(void)ulnow;
- curl = (CURL *)clientp;
- curl_easy_pause(curl, CURLPAUSE_CONT);
+ (void)clientp;
+#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;
}
n=1))
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
@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():
pytest.skip("h3 not supported")
if proto == 'h3' and env.curl_uses_lib('msh3'):
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=10'
+ url = f'http://{env.domain1}:{env.http_port}/curltest/echo?id=[0-0]&die_after=0'
r = client.run([url])
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
@pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
def test_07_50_put_speed_limit(self, env: Env, httpd, nghttpx, proto, repeat):
char buffer[8192];
const char *ct;
apr_off_t die_after_len = -1, total_read_len = 0;
+ int just_die = 0, die_after_100 = 0;
long l;
if(strcmp(r->handler, "curltest-echo")) {
val = s + 1;
if(!strcmp("die_after", arg)) {
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;
if(die_after_len >= 0) {
r->clength = die_after_len + 1;
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 {
r->clength = -1;
bb = apr_brigade_create(r->pool, c->bucket_alloc);
/* copy any request body into the response */
if((rv = ap_setup_client_block(r, REQUEST_CHUNKED_DECHUNK))) goto cleanup;
+ if(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)) {
while(0 < (l = ap_get_client_block(r, &buffer[0], sizeof(buffer)))) {
total_read_len += l;
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"echo_handler: dying after %ld bytes as requested",
(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;
return DONE;
}