summaryrefslogtreecommitdiffstats
path: root/Utilities/cmcurl/lib/vquic
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2022-04-28 19:20:13 (GMT)
committerBrad King <brad.king@kitware.com>2022-04-28 19:21:23 (GMT)
commit30aba1ce8cedec012cec9099217d4d83d7080a1c (patch)
treee920c2c714b2f0662de839b140797da3bdc7ff64 /Utilities/cmcurl/lib/vquic
parent5239672e64a85ad400ef1bdb4a9118aff2da0c3f (diff)
parent2a9bc9ebf09fbafa5378d143083434204e9f233e (diff)
downloadCMake-30aba1ce8cedec012cec9099217d4d83d7080a1c.zip
CMake-30aba1ce8cedec012cec9099217d4d83d7080a1c.tar.gz
CMake-30aba1ce8cedec012cec9099217d4d83d7080a1c.tar.bz2
Merge branch 'upstream-curl' into update-curl
* upstream-curl: curl 2022-04-27 (1669b17d)
Diffstat (limited to 'Utilities/cmcurl/lib/vquic')
-rw-r--r--Utilities/cmcurl/lib/vquic/msh3.c498
-rw-r--r--Utilities/cmcurl/lib/vquic/msh3.h38
-rw-r--r--Utilities/cmcurl/lib/vquic/ngtcp2.c263
-rw-r--r--Utilities/cmcurl/lib/vquic/quiche.c317
-rw-r--r--Utilities/cmcurl/lib/vquic/quiche.h6
-rw-r--r--Utilities/cmcurl/lib/vquic/vquic.c4
-rw-r--r--Utilities/cmcurl/lib/vquic/vquic.h2
7 files changed, 789 insertions, 339 deletions
diff --git a/Utilities/cmcurl/lib/vquic/msh3.c b/Utilities/cmcurl/lib/vquic/msh3.c
new file mode 100644
index 0000000..be18e6e
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/msh3.c
@@ -0,0 +1,498 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include "urldata.h"
+#include "curl_printf.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "sendf.h"
+#include "connect.h"
+#include "h2h3.h"
+#include "msh3.h"
+
+/* #define DEBUG_HTTP3 1 */
+#ifdef DEBUG_HTTP3
+#define H3BUGF(x) x
+#else
+#define H3BUGF(x) do { } while(0)
+#endif
+
+#define MSH3_REQ_INIT_BUF_LEN 8192
+
+static CURLcode msh3_do_it(struct Curl_easy *data, bool *done);
+static int msh3_getsock(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t *socks);
+static CURLcode msh3_disconnect(struct Curl_easy *data,
+ struct connectdata *conn,
+ bool dead_connection);
+static unsigned int msh3_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform);
+static Curl_recv msh3_stream_recv;
+static Curl_send msh3_stream_send;
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+ void *IfContext,
+ const MSH3_HEADER *Header);
+static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+ void *IfContext, uint32_t Length,
+ const uint8_t *Data);
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+ bool Aborted, uint64_t AbortError);
+static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext);
+
+static const struct Curl_handler msh3_curl_handler_http3 = {
+ "HTTPS", /* scheme */
+ ZERO_NULL, /* setup_connection */
+ msh3_do_it, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ ZERO_NULL, /* connect_it */
+ ZERO_NULL, /* connecting */
+ ZERO_NULL, /* doing */
+ msh3_getsock, /* proto_getsock */
+ msh3_getsock, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ msh3_getsock, /* perform_getsock */
+ msh3_disconnect, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ msh3_conncheck, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTP, /* defport */
+ CURLPROTO_HTTPS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_SSL | PROTOPT_STREAM /* flags */
+};
+
+static const MSH3_REQUEST_IF msh3_request_if = {
+ msh3_header_received,
+ msh3_data_received,
+ msh3_complete,
+ msh3_shutdown
+};
+
+void Curl_quic_ver(char *p, size_t len)
+{
+ (void)msnprintf(p, len, "msh3/%s", "0.0.1");
+}
+
+CURLcode Curl_quic_connect(struct Curl_easy *data,
+ struct connectdata *conn,
+ curl_socket_t sockfd,
+ int sockindex,
+ const struct sockaddr *addr,
+ socklen_t addrlen)
+{
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ bool unsecure = !conn->ssl_config.verifypeer;
+ memset(qs, 0, sizeof(*qs));
+
+ (void)sockfd;
+ (void)addr; /* TODO - Pass address along */
+ (void)addrlen;
+
+ H3BUGF(infof(data, "creating new api/connection"));
+
+ qs->api = MsH3ApiOpen();
+ if(!qs->api) {
+ failf(data, "can't create msh3 api");
+ return CURLE_FAILED_INIT;
+ }
+
+ qs->conn = MsH3ConnectionOpen(qs->api, conn->host.name, unsecure);
+ if(!qs->conn) {
+ failf(data, "can't create msh3 connection");
+ if(qs->api) {
+ MsH3ApiClose(qs->api);
+ }
+ return CURLE_FAILED_INIT;
+ }
+
+ return CURLE_OK;
+}
+
+CURLcode Curl_quic_is_connected(struct Curl_easy *data,
+ struct connectdata *conn,
+ int sockindex,
+ bool *connected)
+{
+ struct quicsocket *qs = &conn->hequic[sockindex];
+ MSH3_CONNECTION_STATE state;
+
+ state = MsH3ConnectionGetState(qs->conn, false);
+ if(state == MSH3_CONN_HANDSHAKE_FAILED || state == MSH3_CONN_DISCONNECTED) {
+ failf(data, "failed to connect, state=%u", (uint32_t)state);
+ return CURLE_COULDNT_CONNECT;
+ }
+
+ if(state == MSH3_CONN_CONNECTED) {
+ H3BUGF(infof(data, "connection connected"));
+ *connected = true;
+ conn->quic = qs;
+ conn->recv[sockindex] = msh3_stream_recv;
+ conn->send[sockindex] = msh3_stream_send;
+ conn->handler = &msh3_curl_handler_http3;
+ conn->bits.multiplex = TRUE; /* at least potentially multiplexed */
+ conn->httpversion = 30;
+ conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ /* TODO - Clean up other happy-eyeballs connection(s)? */
+ }
+
+ return CURLE_OK;
+}
+
+static int msh3_getsock(struct Curl_easy *data,
+ struct connectdata *conn, curl_socket_t *socks)
+{
+ struct HTTP *stream = data->req.p.http;
+ int bitmap = GETSOCK_BLANK;
+
+ socks[0] = conn->sock[FIRSTSOCKET];
+
+ if(stream->recv_error) {
+ bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ data->state.drain++;
+ }
+ else if(stream->recv_header_len || stream->recv_data_len) {
+ bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
+ data->state.drain++;
+ }
+
+ H3BUGF(infof(data, "msh3_getsock %u", (uint32_t)data->state.drain));
+
+ return bitmap;
+}
+
+static CURLcode msh3_do_it(struct Curl_easy *data, bool *done)
+{
+ struct HTTP *stream = data->req.p.http;
+ H3BUGF(infof(data, "msh3_do_it"));
+ stream->recv_buf = malloc(MSH3_REQ_INIT_BUF_LEN);
+ if(!stream->recv_buf) {
+ return CURLE_OUT_OF_MEMORY;
+ }
+ stream->req = ZERO_NULL;
+ msh3_lock_initialize(&stream->recv_lock);
+ stream->recv_buf_alloc = MSH3_REQ_INIT_BUF_LEN;
+ stream->recv_header_len = 0;
+ stream->recv_header_complete = false;
+ stream->recv_data_len = 0;
+ stream->recv_data_complete = false;
+ stream->recv_error = CURLE_OK;
+ return Curl_http(data, done);
+}
+
+static unsigned int msh3_conncheck(struct Curl_easy *data,
+ struct connectdata *conn,
+ unsigned int checks_to_perform)
+{
+ (void)data;
+ (void)conn;
+ (void)checks_to_perform;
+ H3BUGF(infof(data, "msh3_conncheck"));
+ return CONNRESULT_NONE;
+}
+
+static void msh3_cleanup(struct quicsocket *qs, struct HTTP *stream)
+{
+ if(stream && stream->recv_buf) {
+ free(stream->recv_buf);
+ stream->recv_buf = ZERO_NULL;
+ msh3_lock_uninitialize(&stream->recv_lock);
+ }
+ if(qs->conn) {
+ MsH3ConnectionClose(qs->conn);
+ qs->conn = ZERO_NULL;
+ }
+ if(qs->api) {
+ MsH3ApiClose(qs->api);
+ qs->api = ZERO_NULL;
+ }
+}
+
+static CURLcode msh3_disconnect(struct Curl_easy *data,
+ struct connectdata *conn, bool dead_connection)
+{
+ (void)dead_connection;
+ H3BUGF(infof(data, "disconnecting (msh3)"));
+ msh3_cleanup(conn->quic, data->req.p.http);
+ return CURLE_OK;
+}
+
+void Curl_quic_disconnect(struct Curl_easy *data, struct connectdata *conn,
+ int tempindex)
+{
+ if(conn->transport == TRNSPRT_QUIC) {
+ H3BUGF(infof(data, "disconnecting (curl)"));
+ msh3_cleanup(&conn->hequic[tempindex], data->req.p.http);
+ }
+}
+
+/* Requires stream->recv_lock to be held */
+static bool msh3request_ensure_room(struct HTTP *stream, size_t len)
+{
+ uint8_t *new_recv_buf;
+ const size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+ if(cur_recv_len + len > stream->recv_buf_alloc) {
+ size_t new_recv_buf_alloc_len = stream->recv_buf_alloc;
+ do {
+ new_recv_buf_alloc_len <<= 1; /* TODO - handle overflow */
+ } while(cur_recv_len + len > new_recv_buf_alloc_len);
+ new_recv_buf = malloc(new_recv_buf_alloc_len);
+ if(!new_recv_buf) {
+ return false;
+ }
+ if(cur_recv_len) {
+ memcpy(new_recv_buf, stream->recv_buf, cur_recv_len);
+ }
+ stream->recv_buf_alloc = new_recv_buf_alloc_len;
+ free(stream->recv_buf);
+ stream->recv_buf = new_recv_buf;
+ }
+ return true;
+}
+
+static void MSH3_CALL msh3_header_received(MSH3_REQUEST *Request,
+ void *IfContext,
+ const MSH3_HEADER *Header)
+{
+ struct HTTP *stream = IfContext;
+ size_t total_len;
+ (void)Request;
+ H3BUGF(printf("* msh3_header_received\n"));
+
+ if(stream->recv_header_complete) {
+ H3BUGF(printf("* ignoring header after data\n"));
+ return;
+ }
+
+ msh3_lock_acquire(&stream->recv_lock);
+
+ if((Header->NameLength == 7) &&
+ !strncmp(H2H3_PSEUDO_STATUS, (char *)Header->Name, 7)) {
+ total_len = 9 + Header->ValueLength;
+ if(!msh3request_ensure_room(stream, total_len)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+ stream->recv_buf_alloc - stream->recv_header_len,
+ "HTTP/3 %.*s\n", (int)Header->ValueLength, Header->Value);
+ }
+ else {
+ total_len = Header->NameLength + 4 + Header->ValueLength;
+ if(!msh3request_ensure_room(stream, total_len)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ msnprintf((char *)stream->recv_buf + stream->recv_header_len,
+ stream->recv_buf_alloc - stream->recv_header_len,
+ "%.*s: %.*s\n",
+ (int)Header->NameLength, Header->Name,
+ (int)Header->ValueLength, Header->Value);
+ }
+
+ stream->recv_header_len += total_len - 1; /* don't include null-terminator */
+
+release_lock:
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_data_received(MSH3_REQUEST *Request,
+ void *IfContext, uint32_t Length,
+ const uint8_t *Data)
+{
+ struct HTTP *stream = IfContext;
+ size_t cur_recv_len = stream->recv_header_len + stream->recv_data_len;
+ (void)Request;
+ H3BUGF(printf("* msh3_data_received %u. %zu buffered, %zu allocated\n",
+ Length, cur_recv_len, stream->recv_buf_alloc));
+ msh3_lock_acquire(&stream->recv_lock);
+ if(!stream->recv_header_complete) {
+ H3BUGF(printf("* Headers complete!\n"));
+ if(!msh3request_ensure_room(stream, 2)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ stream->recv_buf[stream->recv_header_len++] = '\r';
+ stream->recv_buf[stream->recv_header_len++] = '\n';
+ stream->recv_header_complete = true;
+ cur_recv_len += 2;
+ }
+ if(!msh3request_ensure_room(stream, Length)) {
+ /* TODO - handle error */
+ goto release_lock;
+ }
+ memcpy(stream->recv_buf + cur_recv_len, Data, Length);
+ stream->recv_data_len += (size_t)Length;
+release_lock:
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_complete(MSH3_REQUEST *Request, void *IfContext,
+ bool Aborted, uint64_t AbortError)
+{
+ struct HTTP *stream = IfContext;
+ (void)Request;
+ (void)AbortError;
+ H3BUGF(printf("* msh3_complete, aborted=%hhu\n", Aborted));
+ msh3_lock_acquire(&stream->recv_lock);
+ if(Aborted) {
+ stream->recv_error = CURLE_HTTP3; /* TODO - how do we pass AbortError? */
+ }
+ stream->recv_header_complete = true;
+ stream->recv_data_complete = true;
+ msh3_lock_release(&stream->recv_lock);
+}
+
+static void MSH3_CALL msh3_shutdown(MSH3_REQUEST *Request, void *IfContext)
+{
+ struct HTTP *stream = IfContext;
+ (void)Request;
+ (void)stream;
+}
+
+static_assert(sizeof(MSH3_HEADER) == sizeof(struct h2h3pseudo),
+ "Sizes must match for cast below to work");
+
+static ssize_t msh3_stream_send(struct Curl_easy *data,
+ int sockindex,
+ const void *mem,
+ size_t len,
+ CURLcode *curlcode)
+{
+ struct connectdata *conn = data->conn;
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
+ struct h2h3req *hreq;
+
+ (void)sockindex;
+ H3BUGF(infof(data, "msh3_stream_send %zu", len));
+
+ if(!stream->req) {
+ *curlcode = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(*curlcode) {
+ failf(data, "Curl_pseudo_headers failed");
+ return -1;
+ }
+ H3BUGF(infof(data, "starting request with %zu headers", hreq->entries));
+ stream->req = MsH3RequestOpen(qs->conn, &msh3_request_if, stream,
+ (MSH3_HEADER*)hreq->header, hreq->entries);
+ Curl_pseudo_free(hreq);
+ if(!stream->req) {
+ failf(data, "request open failed");
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+ }
+ *curlcode = CURLE_OK;
+ return len;
+ }
+ H3BUGF(infof(data, "send %zd body bytes on request %p", len,
+ (void *)stream->req));
+ *curlcode = CURLE_SEND_ERROR;
+ return -1;
+}
+
+static ssize_t msh3_stream_recv(struct Curl_easy *data,
+ int sockindex,
+ char *buf,
+ size_t buffersize,
+ CURLcode *curlcode)
+{
+ struct HTTP *stream = data->req.p.http;
+ size_t outsize = 0;
+ (void)sockindex;
+ H3BUGF(infof(data, "msh3_stream_recv %zu", buffersize));
+
+ if(stream->recv_error) {
+ failf(data, "request aborted");
+ *curlcode = stream->recv_error;
+ return -1;
+ }
+
+ msh3_lock_acquire(&stream->recv_lock);
+
+ if(stream->recv_header_len) {
+ outsize = buffersize;
+ if(stream->recv_header_len < outsize) {
+ outsize = stream->recv_header_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_header_len + stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_header_len + stream->recv_data_len - outsize);
+ }
+ stream->recv_header_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of headers", outsize));
+ }
+ else if(stream->recv_data_len) {
+ outsize = buffersize;
+ if(stream->recv_data_len < outsize) {
+ outsize = stream->recv_data_len;
+ }
+ memcpy(buf, stream->recv_buf, outsize);
+ if(outsize < stream->recv_data_len) {
+ memmove(stream->recv_buf, stream->recv_buf + outsize,
+ stream->recv_data_len - outsize);
+ }
+ stream->recv_data_len -= outsize;
+ H3BUGF(infof(data, "returned %zu bytes of data", outsize));
+ }
+ else if(stream->recv_data_complete) {
+ H3BUGF(infof(data, "receive complete"));
+ }
+
+ msh3_lock_release(&stream->recv_lock);
+
+ return (ssize_t)outsize;
+}
+
+CURLcode Curl_quic_done_sending(struct Curl_easy *data)
+{
+ struct connectdata *conn = data->conn;
+ H3BUGF(infof(data, "Curl_quic_done_sending"));
+ if(conn->handler == &msh3_curl_handler_http3) {
+ struct HTTP *stream = data->req.p.http;
+ stream->upload_done = TRUE;
+ }
+
+ return CURLE_OK;
+}
+
+void Curl_quic_done(struct Curl_easy *data, bool premature)
+{
+ (void)data;
+ (void)premature;
+ H3BUGF(infof(data, "Curl_quic_done"));
+}
+
+bool Curl_quic_data_pending(const struct Curl_easy *data)
+{
+ struct HTTP *stream = data->req.p.http;
+ H3BUGF(infof((struct Curl_easy *)data, "Curl_quic_data_pending"));
+ return stream->recv_header_len || stream->recv_data_len;
+}
+
+#endif /* USE_MSH3 */
diff --git a/Utilities/cmcurl/lib/vquic/msh3.h b/Utilities/cmcurl/lib/vquic/msh3.h
new file mode 100644
index 0000000..bacdcb1
--- /dev/null
+++ b/Utilities/cmcurl/lib/vquic/msh3.h
@@ -0,0 +1,38 @@
+#ifndef HEADER_CURL_VQUIC_MSH3_H
+#define HEADER_CURL_VQUIC_MSH3_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_MSH3
+
+#include <msh3.h>
+
+struct quicsocket {
+ MSH3_API* api;
+ MSH3_CONNECTION* conn;
+};
+
+#endif /* USE_MSQUIC */
+
+#endif /* HEADER_CURL_VQUIC_MSH3_H */
diff --git a/Utilities/cmcurl/lib/vquic/ngtcp2.c b/Utilities/cmcurl/lib/vquic/ngtcp2.c
index 1596049..abce631 100644
--- a/Utilities/cmcurl/lib/vquic/ngtcp2.c
+++ b/Utilities/cmcurl/lib/vquic/ngtcp2.c
@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -45,7 +45,9 @@
#include "strerror.h"
#include "dynbuf.h"
#include "vquic.h"
+#include "h2h3.h"
#include "vtls/keylog.h"
+#include "vtls/vtls.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -71,7 +73,7 @@
* the far end, then start over at index 0 again.
*/
-#define H3_SEND_SIZE (20*1024)
+#define H3_SEND_SIZE (256*1024)
struct h3out {
uint8_t buf[H3_SEND_SIZE];
size_t used; /* number of bytes used in the buffer */
@@ -81,7 +83,7 @@ struct h3out {
#define QUIC_MAX_STREAMS (256*1024)
#define QUIC_MAX_DATA (1*1024*1024)
-#define QUIC_IDLE_TIMEOUT 60000 /* milliseconds */
+#define QUIC_IDLE_TIMEOUT (60*NGTCP2_SECONDS)
#ifdef USE_OPENSSL
#define QUIC_CIPHERS \
@@ -313,6 +315,25 @@ static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
return ssl_ctx;
}
+static CURLcode quic_set_client_cert(struct Curl_easy *data,
+ struct quicsocket *qs)
+{
+ struct connectdata *conn = data->conn;
+ SSL_CTX *ssl_ctx = qs->sslctx;
+ char *const ssl_cert = SSL_SET_OPTION(primary.clientcert);
+ const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
+ const char *const ssl_cert_type = SSL_SET_OPTION(cert_type);
+
+ if(ssl_cert || ssl_cert_blob || ssl_cert_type) {
+ return Curl_ossl_set_client_cert(
+ data, ssl_ctx, ssl_cert, ssl_cert_blob, ssl_cert_type,
+ SSL_SET_OPTION(key), SSL_SET_OPTION(key_blob),
+ SSL_SET_OPTION(key_type), SSL_SET_OPTION(key_passwd));
+ }
+
+ return CURLE_OK;
+}
+
/** SSL callbacks ***/
static int quic_init_ssl(struct quicsocket *qs)
@@ -743,7 +764,8 @@ static ngtcp2_callbacks ng_callbacks = {
NULL, /* ack_datagram */
NULL, /* lost_datagram */
ngtcp2_crypto_get_path_challenge_data_cb,
- cb_stream_stop_sending
+ cb_stream_stop_sending,
+ NULL, /* version_negotiation */
};
/*
@@ -785,6 +807,10 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
qs->sslctx = quic_ssl_ctx(data);
if(!qs->sslctx)
return CURLE_QUIC_CONNECT_ERROR;
+
+ result = quic_set_client_cert(data, qs);
+ if(result)
+ return result;
#endif
if(quic_init_ssl(qs))
@@ -841,6 +867,8 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
{
struct SingleRequest *k = &data->req;
int bitmap = GETSOCK_BLANK;
+ struct HTTP *stream = data->req.p.http;
+ struct quicsocket *qs = conn->quic;
socks[0] = conn->sock[FIRSTSOCKET];
@@ -849,7 +877,11 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
bitmap |= GETSOCK_READSOCK(FIRSTSOCKET);
/* we're still uploading or the HTTP/2 layer wants to send data */
- if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND)
+ if((k->keepon & (KEEP_SEND|KEEP_SEND_PAUSE)) == KEEP_SEND &&
+ (!stream->h3out || stream->h3out->used < H3_SEND_SIZE) &&
+ ngtcp2_conn_get_cwnd_left(qs->qconn) &&
+ ngtcp2_conn_get_max_data_left(qs->qconn) &&
+ nghttp3_conn_is_stream_writable(qs->h3conn, stream->stream3_id))
bitmap |= GETSOCK_WRITESOCK(FIRSTSOCKET);
return bitmap;
@@ -857,8 +889,26 @@ static int ng_getsock(struct Curl_easy *data, struct connectdata *conn,
static void qs_disconnect(struct quicsocket *qs)
{
+ char buffer[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
+ ngtcp2_tstamp ts;
+ ngtcp2_ssize rc;
+ ngtcp2_connection_close_error errorcode;
+
if(!qs->conn) /* already closed */
return;
+ ngtcp2_connection_close_error_set_application_error(&errorcode,
+ NGHTTP3_H3_NO_ERROR,
+ NULL, 0);
+ ts = timestamp();
+ rc = ngtcp2_conn_write_connection_close(qs->qconn, NULL, /* path */
+ NULL, /* pkt_info */
+ (uint8_t *)buffer, sizeof(buffer),
+ &errorcode, ts);
+ if(rc > 0) {
+ while((send(qs->conn->sock[FIRSTSOCKET], buffer, rc, 0) == -1) &&
+ SOCKERRNO == EINTR);
+ }
+
qs->conn = NULL;
if(qs->qlogfd != -1) {
close(qs->qlogfd);
@@ -1043,7 +1093,7 @@ static int decode_status_code(const uint8_t *value, size_t len)
}
static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
- void *user_data, void *stream_user_data)
+ int fin, void *user_data, void *stream_user_data)
{
struct Curl_easy *data = stream_user_data;
struct HTTP *stream = data->req.p.http;
@@ -1051,6 +1101,7 @@ static int cb_h3_end_headers(nghttp3_conn *conn, int64_t stream_id,
(void)conn;
(void)stream_id;
(void)user_data;
+ (void)fin;
/* add a CRLF only if we've received some headers */
if(stream->firstheader) {
@@ -1078,8 +1129,7 @@ static int cb_h3_recv_header(nghttp3_conn *conn, int64_t stream_id,
(void)flags;
(void)user_data;
- if(h3name.len == sizeof(":status") - 1 &&
- !memcmp(":status", h3name.base, h3name.len)) {
+ if(token == NGHTTP3_QPACK_TOKEN__STATUS) {
char line[14]; /* status line is always 13 characters long */
size_t ncopy;
int status = decode_status_code(h3val.base, h3val.len);
@@ -1218,6 +1268,8 @@ static size_t drain_overflow_buffer(struct HTTP *stream)
if(ncopy != overlen)
/* make the buffer only keep the tail */
(void)Curl_dyn_tail(&stream->overflow, overlen - ncopy);
+ else
+ Curl_dyn_reset(&stream->overflow);
}
return ncopy;
}
@@ -1329,6 +1381,10 @@ static ssize_t cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
return 1;
}
+ if(stream->upload_len && H3_SEND_SIZE <= stream->h3out->used) {
+ return NGHTTP3_ERR_WOULDBLOCK;
+ }
+
nread = CURLMIN(stream->upload_len, H3_SEND_SIZE - stream->h3out->used);
if(nread > 0) {
/* nghttp3 wants us to hold on to the data until it tells us it is okay to
@@ -1363,7 +1419,7 @@ static ssize_t cb_h3_readfunction(nghttp3_conn *conn, int64_t stream_id,
}
if(stream->upload_done && !stream->upload_len &&
(stream->upload_left <= 0)) {
- H3BUGF(infof(data, "!!!!!!!!! cb_h3_readfunction sets EOF"));
+ H3BUGF(infof(data, "cb_h3_readfunction sets EOF"));
*pflags = NGHTTP3_DATA_FLAG_EOF;
return nread ? 1 : 0;
}
@@ -1383,16 +1439,13 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
struct connectdata *conn = data->conn;
struct HTTP *stream = data->req.p.http;
size_t nheader;
- size_t i;
- size_t authority_idx;
- char *hdbuf = (char *)mem;
- char *end, *line_end;
struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
nghttp3_nv *nva = NULL;
int64_t stream3_id;
int rc;
struct h3out *h3out = NULL;
+ struct h2h3req *hreq = NULL;
rc = ngtcp2_conn_open_bidi_stream(qs->qconn, &stream3_id, NULL);
if(rc) {
@@ -1405,158 +1458,23 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
stream->h3req = TRUE; /* senf off! */
Curl_dyn_init(&stream->overflow, CURL_MAX_READ_SIZE);
- /* Calculate number of headers contained in [mem, mem + len). Assumes a
- correctly generated HTTP header field block. */
- nheader = 0;
- for(i = 1; i < len; ++i) {
- if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
- ++nheader;
- ++i;
- }
- }
- if(nheader < 2)
+ result = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(result)
goto fail;
+ nheader = hreq->entries;
- /* We counted additional 2 \r\n in the first and last line. We need 3
- new headers: :method, :path and :scheme. Therefore we need one
- more space. */
- nheader += 1;
nva = malloc(sizeof(nghttp3_nv) * nheader);
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
-
- /* Extract :method, :path from request line
- We do line endings with CRLF so checking for CR is enough */
- line_end = memchr(hdbuf, '\r', len);
- if(!line_end) {
- result = CURLE_BAD_FUNCTION_ARGUMENT; /* internal error */
- goto fail;
- }
-
- /* Method does not contain spaces */
- end = memchr(hdbuf, ' ', line_end - hdbuf);
- if(!end || end == hdbuf)
- goto fail;
- nva[0].name = (unsigned char *)":method";
- nva[0].namelen = strlen((char *)nva[0].name);
- nva[0].value = (unsigned char *)hdbuf;
- nva[0].valuelen = (size_t)(end - hdbuf);
- nva[0].flags = NGHTTP3_NV_FLAG_NONE;
-
- hdbuf = end + 1;
-
- /* Path may contain spaces so scan backwards */
- end = NULL;
- for(i = (size_t)(line_end - hdbuf); i; --i) {
- if(hdbuf[i - 1] == ' ') {
- end = &hdbuf[i - 1];
- break;
- }
- }
- if(!end || end == hdbuf)
- goto fail;
- nva[1].name = (unsigned char *)":path";
- nva[1].namelen = strlen((char *)nva[1].name);
- nva[1].value = (unsigned char *)hdbuf;
- nva[1].valuelen = (size_t)(end - hdbuf);
- nva[1].flags = NGHTTP3_NV_FLAG_NONE;
-
- nva[2].name = (unsigned char *)":scheme";
- nva[2].namelen = strlen((char *)nva[2].name);
- if(conn->handler->flags & PROTOPT_SSL)
- nva[2].value = (unsigned char *)"https";
- else
- nva[2].value = (unsigned char *)"http";
- nva[2].valuelen = strlen((char *)nva[2].value);
- nva[2].flags = NGHTTP3_NV_FLAG_NONE;
-
-
- authority_idx = 0;
- i = 3;
- while(i < nheader) {
- size_t hlen;
-
- hdbuf = line_end + 2;
-
- /* check for next CR, but only within the piece of data left in the given
- buffer */
- line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
- if(!line_end || (line_end == hdbuf))
- goto fail;
-
- /* header continuation lines are not supported */
- if(*hdbuf == ' ' || *hdbuf == '\t')
- goto fail;
-
- for(end = hdbuf; end < line_end && *end != ':'; ++end)
- ;
- if(end == hdbuf || end == line_end)
- goto fail;
- hlen = end - hdbuf;
-
- if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
- authority_idx = i;
- nva[i].name = (unsigned char *)":authority";
- nva[i].namelen = strlen((char *)nva[i].name);
- }
- else {
- nva[i].namelen = (size_t)(end - hdbuf);
- /* Lower case the header name for HTTP/3 */
- Curl_strntolower((char *)hdbuf, hdbuf, nva[i].namelen);
- nva[i].name = (unsigned char *)hdbuf;
- }
- nva[i].flags = NGHTTP3_NV_FLAG_NONE;
- hdbuf = end + 1;
- while(*hdbuf == ' ' || *hdbuf == '\t')
- ++hdbuf;
- end = line_end;
-
-#if 0 /* This should probably go in more or less like this */
- switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
- end - hdbuf)) {
- case HEADERINST_IGNORE:
- /* skip header fields prohibited by HTTP/2 specification. */
- --nheader;
- continue;
- case HEADERINST_TE_TRAILERS:
- nva[i].value = (uint8_t*)"trailers";
- nva[i].value_len = sizeof("trailers") - 1;
- break;
- default:
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].value_len = (size_t)(end - hdbuf);
- }
-#endif
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].valuelen = (size_t)(end - hdbuf);
- nva[i].flags = NGHTTP3_NV_FLAG_NONE;
-
- ++i;
- }
-
- /* :authority must come before non-pseudo header fields */
- if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
- nghttp3_nv authority = nva[authority_idx];
- for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
- nva[i] = nva[i - 1];
- }
- nva[i] = authority;
- }
-
- /* Warn stream may be rejected if cumulative length of headers is too
- large. */
-#define MAX_ACC 60000 /* <64KB to account for some overhead */
- {
- size_t acc = 0;
- for(i = 0; i < nheader; ++i)
- acc += nva[i].namelen + nva[i].valuelen;
-
- if(acc > MAX_ACC) {
- infof(data, "http_request: Warning: The cumulative length of all "
- "headers exceeds %d bytes and that could cause the "
- "stream to be rejected.", MAX_ACC);
+ else {
+ unsigned int i;
+ for(i = 0; i < nheader; i++) {
+ nva[i].name = (unsigned char *)hreq->header[i].name;
+ nva[i].namelen = hreq->header[i].namelen;
+ nva[i].value = (unsigned char *)hreq->header[i].value;
+ nva[i].valuelen = hreq->header[i].valuelen;
}
}
@@ -1605,10 +1523,12 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)",
stream3_id, (void *)data);
+ Curl_pseudo_free(hreq);
return CURLE_OK;
fail:
free(nva);
+ Curl_pseudo_free(hreq);
return result;
}
static ssize_t ngh3_stream_send(struct Curl_easy *data,
@@ -1617,7 +1537,7 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
size_t len,
CURLcode *curlcode)
{
- ssize_t sent;
+ ssize_t sent = 0;
struct connectdata *conn = data->conn;
struct quicsocket *qs = conn->quic;
curl_socket_t sockfd = conn->sock[sockindex];
@@ -1629,6 +1549,9 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
*curlcode = CURLE_SEND_ERROR;
return -1;
}
+ /* Assume that mem of length len only includes HTTP/1.1 style
+ header fields. In other words, it does not contain request
+ body. */
sent = len;
}
else {
@@ -1638,7 +1561,6 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
stream->upload_mem = mem;
stream->upload_len = len;
(void)nghttp3_conn_resume_stream(qs->h3conn, stream->stream3_id);
- sent = len;
}
else {
*curlcode = CURLE_AGAIN;
@@ -1653,8 +1575,20 @@ static ssize_t ngh3_stream_send(struct Curl_easy *data,
/* Reset post upload buffer after resumed. */
if(stream->upload_mem) {
+ if(data->set.postfields) {
+ sent = len;
+ }
+ else {
+ sent = len - stream->upload_len;
+ }
+
stream->upload_mem = NULL;
stream->upload_len = 0;
+
+ if(sent == 0) {
+ *curlcode = CURLE_AGAIN;
+ return -1;
+ }
}
*curlcode = CURLE_OK;
@@ -1676,7 +1610,6 @@ static CURLcode ng_has_connected(struct Curl_easy *data,
if(conn->ssl_config.verifyhost) {
#ifdef USE_OPENSSL
X509 *server_cert;
- CURLcode result;
server_cert = SSL_get_peer_certificate(conn->quic->ssl);
if(!server_cert) {
return CURLE_PEER_FAILED_VERIFICATION;
@@ -1786,7 +1719,6 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
uint8_t out[NGTCP2_MAX_UDP_PAYLOAD_SIZE];
ngtcp2_path_storage ps;
ngtcp2_tstamp ts = timestamp();
- struct sockaddr_storage remote_addr;
ngtcp2_tstamp expiry;
ngtcp2_duration timeout;
int64_t stream_id;
@@ -1875,7 +1807,6 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
}
}
- memcpy(&remote_addr, ps.path.remote.addr, ps.path.remote.addrlen);
while((sent = send(sockfd, (const char *)out, outlen, 0)) == -1 &&
SOCKERRNO == EINTR)
;
@@ -1896,10 +1827,13 @@ static CURLcode ng_flush_egress(struct Curl_easy *data,
expiry = ngtcp2_conn_get_expiry(qs->qconn);
if(expiry != UINT64_MAX) {
if(expiry <= ts) {
- timeout = NGTCP2_MILLISECONDS;
+ timeout = 0;
}
else {
timeout = expiry - ts;
+ if(timeout % NGTCP2_MILLISECONDS) {
+ timeout += NGTCP2_MILLISECONDS;
+ }
}
Curl_expire(data, timeout / NGTCP2_MILLISECONDS, EXPIRE_QUIC);
}
@@ -1935,6 +1869,7 @@ void Curl_quic_done(struct Curl_easy *data, bool premature)
/* only for HTTP/3 transfers */
struct HTTP *stream = data->req.p.http;
Curl_dyn_free(&stream->overflow);
+ free(stream->h3out);
}
}
diff --git a/Utilities/cmcurl/lib/vquic/quiche.c b/Utilities/cmcurl/lib/vquic/quiche.c
index f757760..bfdc966 100644
--- a/Utilities/cmcurl/lib/vquic/quiche.c
+++ b/Utilities/cmcurl/lib/vquic/quiche.c
@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -25,6 +25,7 @@
#ifdef USE_QUICHE
#include <quiche.h>
#include <openssl/err.h>
+#include <openssl/ssl.h>
#include "urldata.h"
#include "sendf.h"
#include "strdup.h"
@@ -35,6 +36,10 @@
#include "connect.h"
#include "strerror.h"
#include "vquic.h"
+#include "transfer.h"
+#include "h2h3.h"
+#include "vtls/openssl.h"
+#include "vtls/keylog.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -172,6 +177,68 @@ static void quiche_debug_log(const char *line, void *argp)
}
#endif
+static void keylog_callback(const SSL *ssl, const char *line)
+{
+ (void)ssl;
+ Curl_tls_keylog_write_line(line);
+}
+
+static SSL_CTX *quic_ssl_ctx(struct Curl_easy *data)
+{
+ SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
+
+ SSL_CTX_set_alpn_protos(ssl_ctx,
+ (const uint8_t *)QUICHE_H3_APPLICATION_PROTOCOL,
+ sizeof(QUICHE_H3_APPLICATION_PROTOCOL) - 1);
+
+ SSL_CTX_set_default_verify_paths(ssl_ctx);
+
+ /* Open the file if a TLS or QUIC backend has not done this before. */
+ Curl_tls_keylog_open();
+ if(Curl_tls_keylog_enabled()) {
+ SSL_CTX_set_keylog_callback(ssl_ctx, keylog_callback);
+ }
+
+ {
+ struct connectdata *conn = data->conn;
+ const char * const ssl_cafile = conn->ssl_config.CAfile;
+ const char * const ssl_capath = conn->ssl_config.CApath;
+
+ if(conn->ssl_config.verifypeer) {
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ /* tell OpenSSL where to find CA certificates that are used to verify
+ the server's certificate. */
+ if(!SSL_CTX_load_verify_locations(ssl_ctx, ssl_cafile, ssl_capath)) {
+ /* Fail if we insist on successfully verifying the server. */
+ failf(data, "error setting certificate verify locations:"
+ " CAfile: %s CApath: %s",
+ ssl_cafile ? ssl_cafile : "none",
+ ssl_capath ? ssl_capath : "none");
+ return NULL;
+ }
+ infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
+ infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
+ }
+ }
+ return ssl_ctx;
+}
+
+static int quic_init_ssl(struct quicsocket *qs, struct connectdata *conn)
+{
+ /* this will need some attention when HTTPS proxy over QUIC get fixed */
+ const char * const hostname = conn->host.name;
+
+ DEBUGASSERT(!qs->ssl);
+ qs->ssl = SSL_new(qs->sslctx);
+
+ SSL_set_app_data(qs->ssl, qs);
+
+ /* set SNI */
+ SSL_set_tlsext_host_name(qs->ssl, hostname);
+ return 0;
+}
+
+
CURLcode Curl_quic_connect(struct Curl_easy *data,
struct connectdata *conn, curl_socket_t sockfd,
int sockindex,
@@ -179,7 +246,6 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
{
CURLcode result;
struct quicsocket *qs = &conn->hequic[sockindex];
- char *keylog_file = NULL;
char ipbuf[40];
int port;
@@ -216,25 +282,25 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
sizeof(QUICHE_H3_APPLICATION_PROTOCOL)
- 1);
+ qs->sslctx = quic_ssl_ctx(data);
+ if(!qs->sslctx)
+ return CURLE_QUIC_CONNECT_ERROR;
+
+ if(quic_init_ssl(qs, conn))
+ return CURLE_QUIC_CONNECT_ERROR;
+
result = Curl_rand(data, qs->scid, sizeof(qs->scid));
if(result)
return result;
- keylog_file = getenv("SSLKEYLOGFILE");
-
- if(keylog_file)
- quiche_config_log_keys(qs->cfg);
-
- qs->conn = quiche_connect(conn->host.name, (const uint8_t *) qs->scid,
- sizeof(qs->scid), addr, addrlen, qs->cfg);
+ qs->conn = quiche_conn_new_with_tls((const uint8_t *) qs->scid,
+ sizeof(qs->scid), NULL, 0, addr, addrlen,
+ qs->cfg, qs->ssl, false);
if(!qs->conn) {
failf(data, "can't create quiche connection");
return CURLE_OUT_OF_MEMORY;
}
- if(keylog_file)
- quiche_conn_set_keylog_path(qs->conn, keylog_file);
-
/* Known to not work on Windows */
#if !defined(WIN32) && defined(HAVE_QUICHE_CONN_SET_QLOG_FD)
{
@@ -284,7 +350,8 @@ CURLcode Curl_quic_connect(struct Curl_easy *data,
return CURLE_OK;
}
-static CURLcode quiche_has_connected(struct connectdata *conn,
+static CURLcode quiche_has_connected(struct Curl_easy *data,
+ struct connectdata *conn,
int sockindex,
int tempindex)
{
@@ -298,6 +365,21 @@ static CURLcode quiche_has_connected(struct connectdata *conn,
conn->httpversion = 30;
conn->bundle->multiuse = BUNDLE_MULTIPLEX;
+ if(conn->ssl_config.verifyhost) {
+ X509 *server_cert;
+ server_cert = SSL_get_peer_certificate(qs->ssl);
+ if(!server_cert) {
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ result = Curl_ossl_verifyhost(data, conn, server_cert);
+ X509_free(server_cert);
+ if(result)
+ return result;
+ infof(data, "Verified certificate just fine");
+ }
+ else
+ infof(data, "Skipped certificate verification");
+
qs->h3config = quiche_h3_config_new();
if(!qs->h3config)
return CURLE_OUT_OF_MEMORY;
@@ -344,8 +426,8 @@ CURLcode Curl_quic_is_connected(struct Curl_easy *data,
if(quiche_conn_is_established(qs->conn)) {
*done = TRUE;
- result = quiche_has_connected(conn, 0, sockindex);
- DEBUGF(infof(data, "quiche established connection!"));
+ result = quiche_has_connected(data, conn, 0, sockindex);
+ DEBUGF(infof(data, "quiche established connection"));
}
return result;
@@ -392,7 +474,18 @@ static CURLcode process_ingress(struct Curl_easy *data, int sockfd,
break;
if(recvd < 0) {
+ if(QUICHE_ERR_TLS_FAIL == recvd) {
+ long verify_ok = SSL_get_verify_result(qs->ssl);
+ if(verify_ok != X509_V_OK) {
+ failf(data, "SSL certificate problem: %s",
+ X509_verify_cert_error_string(verify_ok));
+
+ return CURLE_PEER_FAILED_VERIFICATION;
+ }
+ }
+
failf(data, "quiche_conn_recv() == %zd", recvd);
+
return CURLE_RECV_ERROR;
}
} while(1);
@@ -451,7 +544,7 @@ static int cb_each_header(uint8_t *name, size_t name_len,
struct h3h1header *headers = (struct h3h1header *)argp;
size_t olen = 0;
- if((name_len == 7) && !strncmp(":status", (char *)name, 7)) {
+ if((name_len == 7) && !strncmp(H2H3_PSEUDO_STATUS, (char *)name, 7)) {
msnprintf(headers->dest,
headers->destlen, "HTTP/3 %.*s\n",
(int) value_len, value);
@@ -496,6 +589,19 @@ static ssize_t h3_stream_recv(struct Curl_easy *data,
return -1;
}
+ if(qs->h3_recving) {
+ /* body receiving state */
+ rcode = quiche_h3_recv_body(qs->h3c, qs->conn, stream->stream3_id,
+ (unsigned char *)buf, buffersize);
+ if(rcode <= 0) {
+ recvd = -1;
+ qs->h3_recving = FALSE;
+ /* fall through into the while loop below */
+ }
+ else
+ recvd = rcode;
+ }
+
while(recvd < 0) {
int64_t s = quiche_h3_conn_poll(qs->h3c, qs->conn, &ev);
if(s < 0)
@@ -537,9 +643,15 @@ static ssize_t h3_stream_recv(struct Curl_easy *data,
recvd = -1;
break;
}
+ qs->h3_recving = TRUE;
recvd += rcode;
break;
+ case QUICHE_H3_EVENT_RESET:
+ streamclose(conn, "Stream reset");
+ *curlcode = CURLE_PARTIAL_FILE;
+ return -1;
+
case QUICHE_H3_EVENT_FINISHED:
streamclose(conn, "End of stream");
recvd = 0; /* end of stream */
@@ -585,10 +697,12 @@ static ssize_t h3_stream_send(struct Curl_easy *data,
sent = len;
}
else {
- H3BUGF(infof(data, "Pass on %zd body bytes to quiche", len));
sent = quiche_h3_send_body(qs->h3c, qs->conn, stream->stream3_id,
(uint8_t *)mem, len, FALSE);
- if(sent < 0) {
+ if(sent == QUICHE_H3_ERR_DONE) {
+ sent = 0;
+ }
+ else if(sent < 0) {
*curlcode = CURLE_SEND_ERROR;
return -1;
}
@@ -618,175 +732,34 @@ void Curl_quic_ver(char *p, size_t len)
static CURLcode http_request(struct Curl_easy *data, const void *mem,
size_t len)
{
- /*
- */
struct connectdata *conn = data->conn;
struct HTTP *stream = data->req.p.http;
size_t nheader;
- size_t i;
- size_t authority_idx;
- char *hdbuf = (char *)mem;
- char *end, *line_end;
int64_t stream3_id;
quiche_h3_header *nva = NULL;
struct quicsocket *qs = conn->quic;
CURLcode result = CURLE_OK;
+ struct h2h3req *hreq = NULL;
stream->h3req = TRUE; /* senf off! */
- /* Calculate number of headers contained in [mem, mem + len). Assumes a
- correctly generated HTTP header field block. */
- nheader = 0;
- for(i = 1; i < len; ++i) {
- if(hdbuf[i] == '\n' && hdbuf[i - 1] == '\r') {
- ++nheader;
- ++i;
- }
- }
- if(nheader < 2)
+ result = Curl_pseudo_headers(data, mem, len, &hreq);
+ if(result)
goto fail;
+ nheader = hreq->entries;
- /* We counted additional 2 \r\n in the first and last line. We need 3
- new headers: :method, :path and :scheme. Therefore we need one
- more space. */
- nheader += 1;
nva = malloc(sizeof(quiche_h3_header) * nheader);
if(!nva) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
-
- /* Extract :method, :path from request line
- We do line endings with CRLF so checking for CR is enough */
- line_end = memchr(hdbuf, '\r', len);
- if(!line_end) {
- result = CURLE_BAD_FUNCTION_ARGUMENT; /* internal error */
- goto fail;
- }
-
- /* Method does not contain spaces */
- end = memchr(hdbuf, ' ', line_end - hdbuf);
- if(!end || end == hdbuf)
- goto fail;
- nva[0].name = (unsigned char *)":method";
- nva[0].name_len = strlen((char *)nva[0].name);
- nva[0].value = (unsigned char *)hdbuf;
- nva[0].value_len = (size_t)(end - hdbuf);
-
- hdbuf = end + 1;
-
- /* Path may contain spaces so scan backwards */
- end = NULL;
- for(i = (size_t)(line_end - hdbuf); i; --i) {
- if(hdbuf[i - 1] == ' ') {
- end = &hdbuf[i - 1];
- break;
- }
- }
- if(!end || end == hdbuf)
- goto fail;
- nva[1].name = (unsigned char *)":path";
- nva[1].name_len = strlen((char *)nva[1].name);
- nva[1].value = (unsigned char *)hdbuf;
- nva[1].value_len = (size_t)(end - hdbuf);
-
- nva[2].name = (unsigned char *)":scheme";
- nva[2].name_len = strlen((char *)nva[2].name);
- if(conn->handler->flags & PROTOPT_SSL)
- nva[2].value = (unsigned char *)"https";
- else
- nva[2].value = (unsigned char *)"http";
- nva[2].value_len = strlen((char *)nva[2].value);
-
-
- authority_idx = 0;
- i = 3;
- while(i < nheader) {
- size_t hlen;
-
- hdbuf = line_end + 2;
-
- /* check for next CR, but only within the piece of data left in the given
- buffer */
- line_end = memchr(hdbuf, '\r', len - (hdbuf - (char *)mem));
- if(!line_end || (line_end == hdbuf))
- goto fail;
-
- /* header continuation lines are not supported */
- if(*hdbuf == ' ' || *hdbuf == '\t')
- goto fail;
-
- for(end = hdbuf; end < line_end && *end != ':'; ++end)
- ;
- if(end == hdbuf || end == line_end)
- goto fail;
- hlen = end - hdbuf;
-
- if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
- authority_idx = i;
- nva[i].name = (unsigned char *)":authority";
- nva[i].name_len = strlen((char *)nva[i].name);
- }
- else {
- nva[i].name_len = (size_t)(end - hdbuf);
- /* Lower case the header name for HTTP/3 */
- Curl_strntolower((char *)hdbuf, hdbuf, nva[i].name_len);
- nva[i].name = (unsigned char *)hdbuf;
- }
- hdbuf = end + 1;
- while(*hdbuf == ' ' || *hdbuf == '\t')
- ++hdbuf;
- end = line_end;
-
-#if 0 /* This should probably go in more or less like this */
- switch(inspect_header((const char *)nva[i].name, nva[i].namelen, hdbuf,
- end - hdbuf)) {
- case HEADERINST_IGNORE:
- /* skip header fields prohibited by HTTP/2 specification. */
- --nheader;
- continue;
- case HEADERINST_TE_TRAILERS:
- nva[i].value = (uint8_t*)"trailers";
- nva[i].value_len = sizeof("trailers") - 1;
- break;
- default:
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].value_len = (size_t)(end - hdbuf);
- }
-#endif
- nva[i].value = (unsigned char *)hdbuf;
- nva[i].value_len = (size_t)(end - hdbuf);
-
- ++i;
- }
-
- /* :authority must come before non-pseudo header fields */
- if(authority_idx && authority_idx != AUTHORITY_DST_IDX) {
- quiche_h3_header authority = nva[authority_idx];
- for(i = authority_idx; i > AUTHORITY_DST_IDX; --i) {
- nva[i] = nva[i - 1];
- }
- nva[i] = authority;
- }
-
- /* Warn stream may be rejected if cumulative length of headers is too
- large. */
-#define MAX_ACC 60000 /* <64KB to account for some overhead */
- {
- size_t acc = 0;
-
- for(i = 0; i < nheader; ++i) {
- acc += nva[i].name_len + nva[i].value_len;
-
- H3BUGF(infof(data, "h3 [%.*s: %.*s]",
- nva[i].name_len, nva[i].name,
- nva[i].value_len, nva[i].value));
- }
-
- if(acc > MAX_ACC) {
- infof(data, "http_request: Warning: The cumulative length of all "
- "headers exceeds %d bytes and that could cause the "
- "stream to be rejected.", MAX_ACC);
+ else {
+ unsigned int i;
+ for(i = 0; i < nheader; i++) {
+ nva[i].name = (unsigned char *)hreq->header[i].name;
+ nva[i].name_len = hreq->header[i].namelen;
+ nva[i].value = (unsigned char *)hreq->header[i].value;
+ nva[i].value_len = hreq->header[i].valuelen;
}
}
@@ -808,7 +781,7 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
(uint8_t *)data->set.postfields,
stream->upload_left, TRUE);
if(sent <= 0) {
- failf(data, "quiche_h3_send_body failed!");
+ failf(data, "quiche_h3_send_body failed");
result = CURLE_SEND_ERROR;
}
stream->upload_left = 0; /* nothing left to send */
@@ -833,10 +806,12 @@ static CURLcode http_request(struct Curl_easy *data, const void *mem,
stream3_id, (void *)data);
stream->stream3_id = stream3_id;
+ Curl_pseudo_free(hreq);
return CURLE_OK;
fail:
free(nva);
+ Curl_pseudo_free(hreq);
return result;
}
diff --git a/Utilities/cmcurl/lib/vquic/quiche.h b/Utilities/cmcurl/lib/vquic/quiche.h
index d311e99..759a20b 100644
--- a/Utilities/cmcurl/lib/vquic/quiche.h
+++ b/Utilities/cmcurl/lib/vquic/quiche.h
@@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -27,6 +27,7 @@
#ifdef USE_QUICHE
#include <quiche.h>
+#include <openssl/ssl.h>
struct quic_handshake {
char *buf; /* pointer to the buffer */
@@ -43,6 +44,9 @@ struct quicsocket {
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
curl_socket_t sockfd;
uint32_t version;
+ SSL_CTX *sslctx;
+ SSL *ssl;
+ bool h3_recving; /* TRUE when in h3-body-reading state */
};
#endif
diff --git a/Utilities/cmcurl/lib/vquic/vquic.c b/Utilities/cmcurl/lib/vquic/vquic.c
index 7c0cc6d..be2a65f 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.c
+++ b/Utilities/cmcurl/lib/vquic/vquic.c
@@ -5,7 +5,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
@@ -67,7 +67,7 @@ CURLcode Curl_qlogdir(struct Curl_easy *data,
result = Curl_dyn_add(&fname, hex);
}
if(!result)
- result = Curl_dyn_add(&fname, ".qlog");
+ result = Curl_dyn_add(&fname, ".sqlog");
if(!result) {
int qlogfd = open(Curl_dyn_ptr(&fname), QLOGMODE,
diff --git a/Utilities/cmcurl/lib/vquic/vquic.h b/Utilities/cmcurl/lib/vquic/vquic.h
index eb8a893..3df138f 100644
--- a/Utilities/cmcurl/lib/vquic/vquic.h
+++ b/Utilities/cmcurl/lib/vquic/vquic.h
@@ -7,7 +7,7 @@
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2020, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms