if(strlen(sel) < 1)
break;
- result = Curl_write(data, sockfd, sel, k, &amount);
+ result = Curl_nwrite(data, FIRSTSOCKET, sel, k, &amount);
if(!result) { /* Which may not have written it all! */
result = Curl_client_write(data, CLIENTWRITE_HEADER, sel, amount);
if(result)
free(sel_org);
if(!result)
- result = Curl_write(data, sockfd, "\r\n", 2, &amount);
+ result = Curl_nwrite(data, FIRSTSOCKET, "\r\n", 2, &amount);
if(result) {
failf(data, "Failed sending Gopher request");
return result;
curl_off_t *bytes_written,
/* how much of the buffer contains body data */
curl_off_t included_body_bytes,
- int socketindex)
+ int sockindex)
{
ssize_t amount;
CURLcode result;
size_t size;
struct connectdata *conn = data->conn;
size_t sendsize;
- curl_socket_t sockfd;
size_t headersize;
- DEBUGASSERT(socketindex <= SECONDARYSOCKET);
-
- sockfd = Curl_conn_get_socket(data, socketindex);
+ DEBUGASSERT(sockindex <= SECONDARYSOCKET && sockindex >= 0);
/* The looping below is required since we use non-blocking sockets, but due
to the circumstances we will just loop and try again and again etc */
else
sendsize = size;
}
+
+ /* We currently cannot send more that this for http here:
+ * - if sending blocks, it return 0 as amount
+ * - we then whisk aside the `in` into the `http` struct
+ * and install our own `data->state.fread_func` that
+ * on subsequent calls reads `in` empty.
+ * - when the whisked away `in` is empty, the `fread_func`
+ * is restored ot its original state.
+ * The problem is that `fread_func` can only return
+ * `upload_buffer_size` lengths. If the send we do here
+ * is larger and blocks, we do re-sending with smaller
+ * amounts of data and connection filters do not like
+ * that.
+ */
+ if(http && (sendsize > (size_t)data->set.upload_buffer_size))
+ sendsize = (size_t)data->set.upload_buffer_size;
}
- result = Curl_write(data, sockfd, ptr, sendsize, &amount);
+ result = Curl_nwrite(data, sockindex, ptr, sendsize, &amount);
if(!result) {
/*
#include "urlapi-int.h"
#include "cfilters.h"
#include "connect.h"
+#include "rand.h"
#include "strtoofft.h"
#include "strdup.h"
#include "transfer.h"
int32_t id; /* HTTP/2 protocol identifier for stream */
struct bufq recvbuf; /* response buffer */
struct bufq sendbuf; /* request buffer */
+ struct h1_req_parser h1; /* parsing the request */
struct dynhds resp_trailers; /* response trailer fields */
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
size_t upload_blocked_len;
H2_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
H2_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
+ Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
Curl_dynhds_init(&stream->resp_trailers, 0, DYN_HTTP_REQUEST);
stream->resp_hds_len = 0;
stream->bodystarted = FALSE;
Curl_bufq_free(&stream->sendbuf);
Curl_bufq_free(&stream->recvbuf);
+ Curl_h1_req_parse_free(&stream->h1);
Curl_dynhds_free(&stream->resp_trailers);
if(stream->push_headers) {
/* if they weren't used and then freed before */
{
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = NULL;
- struct h1_req_parser h1;
struct dynhds h2_headers;
nghttp2_nv *nva = NULL;
const void *body = NULL;
nghttp2_priority_spec pri_spec;
ssize_t nwritten;
- Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
*err = http2_data_setup(cf, data, &stream);
goto out;
}
- nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+ nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
if(nwritten < 0)
goto out;
- DEBUGASSERT(h1.done);
- DEBUGASSERT(h1.req);
+ if(!stream->h1.done) {
+ /* need more data */
+ goto out;
+ }
+ DEBUGASSERT(stream->h1.req);
- *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+ *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
if(*err) {
nwritten = -1;
goto out;
}
+ /* no longer needed */
+ Curl_h1_req_parse_free(&stream->h1);
nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(nghttp2_nv) * nheader);
stream? stream->id : -1, nwritten, *err);
Curl_safefree(nva);
*pstream = stream;
- Curl_h1_req_parse_free(&h1);
Curl_dynhds_free(&h2_headers);
return nwritten;
}
if(len < stream->upload_blocked_len) {
/* Did we get called again with a smaller `len`? This should not
* happen. We are not prepared to handle that. */
- failf(data, "HTTP/2 send again with decreased length");
+ failf(data, "HTTP/2 send again with decreased length (%zd vs %zd)",
+ len, stream->upload_blocked_len);
*err = CURLE_HTTP2;
nwritten = -1;
goto out;
* optionally request body, and now are going to send or sending
* more request body in DATA frame */
nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err);
- if(nwritten < 0) {
- if(*err != CURLE_AGAIN)
- goto out;
- nwritten = 0;
- }
+ if(nwritten < 0 && *err != CURLE_AGAIN)
+ goto out;
}
if(!Curl_bufq_is_empty(&stream->sendbuf)) {
blocked = 1;
}
- if(stream && blocked) {
+ if(stream && blocked && nwritten > 0) {
/* Unable to send all data, due to connection blocked or H2 window
* exhaustion. Data is left in our stream buffer, or nghttp2's internal
* frame buffer or our network out buffer. */
#ifdef HAVE_GSSAPI
conn->data_prot = PROT_CMD;
#endif
- result = Curl_write(data, conn->sock[FIRSTSOCKET], sptr, write_len,
+ result = Curl_nwrite(data, FIRSTSOCKET, sptr, write_len,
&bytes_written);
#ifdef HAVE_GSSAPI
DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
char *buf, size_t len)
{
CURLcode result = CURLE_OK;
- struct connectdata *conn = data->conn;
- curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
struct MQTT *mq = data->req.p.mqtt;
ssize_t n;
- result = Curl_write(data, sockfd, buf, len, &n);
+ result = Curl_nwrite(data, FIRSTSOCKET, buf, len, &n);
if(result)
return result;
Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
#ifdef HAVE_GSSAPI
conn->data_prot = PROT_CMD;
#endif
- result = Curl_write(data, conn->sock[FIRSTSOCKET], s, write_len,
- &bytes_written);
+ result = Curl_nwrite(data, FIRSTSOCKET, s, write_len, &bytes_written);
if(result)
return result;
#ifdef HAVE_GSSAPI
struct pingpong *pp)
{
/* we have a piece of a command still left to send */
- struct connectdata *conn = data->conn;
ssize_t written;
- curl_socket_t sock = conn->sock[FIRSTSOCKET];
- CURLcode result = Curl_write(data, sock, pp->sendthis + pp->sendsize -
- pp->sendleft, pp->sendleft, &written);
+ CURLcode result = Curl_nwrite(data, FIRSTSOCKET,
+ pp->sendthis + pp->sendsize - pp->sendleft,
+ pp->sendleft, &written);
if(result)
return result;
#endif /* CURL_DO_LINEEND_CONV && !CURL_DISABLE_FTP */
/*
- * Curl_write() is an internal write function that sends data to the
- * server. Works with plain sockets, SCP, SSL or kerberos.
+ * Curl_nwrite() is an internal write function that sends data to the
+ * server. Works with a socket index for the connection.
*
- * If the write would block (CURLE_AGAIN), we return CURLE_OK and
- * (*written == 0). Otherwise we return regular CURLcode value.
+ * If the write would block (CURLE_AGAIN), it returns CURLE_OK and
+ * (*nwritten == 0). Otherwise we return regular CURLcode value.
*/
-CURLcode Curl_write(struct Curl_easy *data,
- curl_socket_t sockfd,
- const void *mem,
- size_t len,
- ssize_t *written)
+CURLcode Curl_nwrite(struct Curl_easy *data,
+ int sockindex,
+ const void *buf,
+ size_t blen,
+ ssize_t *pnwritten)
{
- ssize_t bytes_written;
+ ssize_t nwritten;
CURLcode result = CURLE_OK;
struct connectdata *conn;
- int num;
+
+ DEBUGASSERT(sockindex >= 0 && sockindex < 2);
+ DEBUGASSERT(pnwritten);
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
conn = data->conn;
- num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
-
#ifdef CURLDEBUG
{
/* Allow debug builds to override this logic to force short sends
if(p) {
size_t altsize = (size_t)strtoul(p, NULL, 10);
if(altsize)
- len = CURLMIN(len, altsize);
+ blen = CURLMIN(blen, altsize);
}
}
#endif
- bytes_written = conn->send[num](data, num, mem, len, &result);
-
- *written = bytes_written;
- if(bytes_written >= 0)
- /* we completely ignore the curlcode value when subzero is not returned */
- return CURLE_OK;
+ nwritten = conn->send[sockindex](data, sockindex, buf, blen, &result);
+ if(result == CURLE_AGAIN) {
+ nwritten = 0;
+ result = CURLE_OK;
+ }
+ else if(result) {
+ nwritten = -1; /* make sure */
+ }
+ else {
+ DEBUGASSERT(nwritten >= 0);
+ }
- /* handle CURLE_AGAIN or a send failure */
- switch(result) {
- case CURLE_AGAIN:
- *written = 0;
- return CURLE_OK;
+ *pnwritten = nwritten;
+ return result;
+}
- case CURLE_OK:
- /* general send failure */
- return CURLE_SEND_ERROR;
+/*
+ * Curl_write() is an internal write function that sends data to the
+ * server. Works with plain sockets, SCP, SSL or kerberos.
+ *
+ * If the write would block (CURLE_AGAIN), we return CURLE_OK and
+ * (*written == 0). Otherwise we return regular CURLcode value.
+ */
+CURLcode Curl_write(struct Curl_easy *data,
+ curl_socket_t sockfd,
+ const void *mem,
+ size_t len,
+ ssize_t *written)
+{
+ struct connectdata *conn;
+ int num;
- default:
- /* we got a specific curlcode, forward it */
- return result;
- }
+ DEBUGASSERT(data);
+ DEBUGASSERT(data->conn);
+ conn = data->conn;
+ num = (sockfd != CURL_SOCKET_BAD && sockfd == conn->sock[SECONDARYSOCKET]);
+ return Curl_nwrite(data, num, mem, len, written);
}
static CURLcode pausewrite(struct Curl_easy *data,
const void *mem, size_t len,
ssize_t *written);
+/* internal write-function, using sockindex for connection destination */
+CURLcode Curl_nwrite(struct Curl_easy *data,
+ int sockindex,
+ const void *buf,
+ size_t blen,
+ ssize_t *pnwritten);
+
#endif /* HEADER_CURL_SENDF_H */
size_t upload_size)
{
struct connectdata *conn = data->conn;
- curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
struct smb_conn *smbc = &conn->proto.smbc;
ssize_t bytes_written;
CURLcode result;
- result = Curl_write(data, sockfd, data->state.ulbuf,
+ result = Curl_nwrite(data, FIRSTSOCKET, data->state.ulbuf,
len, &bytes_written);
if(result)
return result;
static CURLcode smb_flush(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
- curl_socket_t sockfd = conn->sock[FIRSTSOCKET];
struct smb_conn *smbc = &conn->proto.smbc;
ssize_t bytes_written;
ssize_t len = smbc->send_size - smbc->sent;
if(!smbc->send_size)
return CURLE_OK;
- result = Curl_write(data, sockfd,
- data->state.ulbuf + smbc->sent,
- len, &bytes_written);
+ result = Curl_nwrite(data, FIRSTSOCKET,
+ data->state.ulbuf + smbc->sent,
+ len, &bytes_written);
if(result)
return result;
break;
default: /* write! */
bytes_written = 0;
- result = Curl_write(data, conn->sock[FIRSTSOCKET],
- outbuf + total_written,
- outlen - total_written,
- &bytes_written);
+ result = Curl_nwrite(data, FIRSTSOCKET,
+ outbuf + total_written,
+ outlen - total_written,
+ &bytes_written);
total_written += bytes_written;
break;
}
int64_t id; /* HTTP/3 protocol identifier */
struct bufq sendbuf; /* h3 request body */
struct bufq recvbuf; /* h3 response body */
+ struct h1_req_parser h1; /* h1 request parsing */
size_t sendbuf_len_in_flight; /* sendbuf amount "in flight" */
size_t upload_blocked_len; /* the amount written last and EGAINed */
size_t recv_buf_nonflow; /* buffered bytes, not counting for flow control */
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
stream->recv_buf_nonflow = 0;
+ Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
H3_STREAM_LCTX(data) = stream;
return CURLE_OK;
CURL_TRC_CF(data, cf, "[%"PRId64"] easy handle is done", stream->id);
Curl_bufq_free(&stream->sendbuf);
Curl_bufq_free(&stream->recvbuf);
+ Curl_h1_req_parse_free(&stream->h1);
free(stream);
H3_STREAM_LCTX(data) = NULL;
}
{
struct cf_ngtcp2_ctx *ctx = cf->ctx;
struct h3_stream_ctx *stream = NULL;
- struct h1_req_parser h1;
struct dynhds h2_headers;
size_t nheader;
nghttp3_nv *nva = NULL;
nghttp3_data_reader reader;
nghttp3_data_reader *preader = NULL;
- Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
*err = h3_data_setup(cf, data);
goto out;
}
- nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+ nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
if(nwritten < 0)
goto out;
- DEBUGASSERT(h1.done);
- DEBUGASSERT(h1.req);
+ if(!stream->h1.done) {
+ /* need more data */
+ goto out;
+ }
+ DEBUGASSERT(stream->h1.req);
- *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+ *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
if(*err) {
nwritten = -1;
goto out;
}
+ /* no longer needed */
+ Curl_h1_req_parse_free(&stream->h1);
nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(nghttp3_nv) * nheader);
out:
free(nva);
- Curl_h1_req_parse_free(&h1);
Curl_dynhds_free(&h2_headers);
return nwritten;
}
struct stream_ctx {
int64_t id; /* HTTP/3 protocol stream identifier */
struct bufq recvbuf; /* h3 response */
+ struct h1_req_parser h1; /* h1 request parsing */
uint64_t error3; /* HTTP/3 stream error code */
curl_off_t upload_left; /* number of request bytes left to upload */
bool closed; /* TRUE on stream close */
stream->id = -1;
Curl_bufq_initp(&stream->recvbuf, &ctx->stream_bufcp,
H3_STREAM_RECV_CHUNKS, BUFQ_OPT_SOFT_LIMIT);
+ Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
return CURLE_OK;
}
--ctx->sends_on_hold;
}
Curl_bufq_free(&stream->recvbuf);
+ Curl_h1_req_parse_free(&stream->h1);
free(stream);
H3_STREAM_LCTX(data) = NULL;
}
struct stream_ctx *stream = H3_STREAM_CTX(data);
size_t nheader, i;
int64_t stream3_id;
- struct h1_req_parser h1;
struct dynhds h2_headers;
quiche_h3_header *nva = NULL;
ssize_t nwritten;
DEBUGASSERT(stream);
}
- Curl_h1_req_parse_init(&h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
DEBUGASSERT(stream);
- nwritten = Curl_h1_req_parse_read(&h1, buf, len, NULL, 0, err);
+ nwritten = Curl_h1_req_parse_read(&stream->h1, buf, len, NULL, 0, err);
if(nwritten < 0)
goto out;
- DEBUGASSERT(h1.done);
- DEBUGASSERT(h1.req);
+ if(!stream->h1.done) {
+ /* need more data */
+ goto out;
+ }
+ DEBUGASSERT(stream->h1.req);
- *err = Curl_http_req_to_h2(&h2_headers, h1.req, data);
+ *err = Curl_http_req_to_h2(&h2_headers, stream->h1.req, data);
if(*err) {
nwritten = -1;
goto out;
}
+ /* no longer needed */
+ Curl_h1_req_parse_free(&stream->h1);
nheader = Curl_dynhds_count(&h2_headers);
nva = malloc(sizeof(quiche_h3_header) * nheader);
out:
free(nva);
- Curl_h1_req_parse_free(&h1);
Curl_dynhds_free(&h2_headers);
return nwritten;
}
respdata = open(curl.response_file(0)).readlines()
assert respdata == indata
+ # POST data urlencoded, small enough to be sent with request headers
+ # and request headers are so large that the first send is larger
+ # than our default upload buffer length (64KB).
+ # Unfixed, this will fail when run with CURL_DBG_SOCK_WBLOCK=80 most
+ # of the time
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_07_41_post_urlenc_small(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")
+ if proto == 'h3' and env.curl_uses_lib('quiche'):
+ pytest.skip("quiche has CWND issues with large requests")
+ fdata = os.path.join(env.gen_dir, 'data-63k')
+ curl = CurlClient(env=env)
+ extra_args = ['--trace-config', 'http/2,http/3']
+ # add enough headers so that the first send chunk is > 64KB
+ for i in range(63):
+ extra_args.extend(['-H', f'x{i:02d}: {"y"*1019}'])
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/echo?id=[0-0]'
+ r = curl.http_upload(urls=[url], data=f'@{fdata}', alpn_proto=proto, extra_args=extra_args)
+ r.check_stats(count=1, http_status=200, exitcode=0)
+ indata = open(fdata).readlines()
+ respdata = open(curl.response_file(0)).readlines()
+ assert respdata == indata
+
def check_download(self, count, srcfile, curl):
for i in range(count):
dfile = curl.download_file(i)