summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2019-11-08 18:06:48 +0100
committerDaniel Stenberg <daniel@haxx.se>2019-11-08 18:13:54 +0100
commit199976f938b844043a107580695077f58e8c55de (patch)
treeaa4431ee616d8ce3c79b85d51441f4feafec946e
parent1f73138ce1272ea52c9b0119f5ef67aed28af836 (diff)
downloadcurl-bagder/quiche-h3-headers.tar.gz
quiche: handle psuedo headers in another orderbagder/quiche-h3-headers
:status doesn't have to be the first header from quiche to arrive so this code now makes sure to handle such events. HTTP/3 (and HTTP/2) mandates: "All pseudo-header fields MUST appear in the header block before regular header fields" Fixes #4571
-rw-r--r--lib/http.c1
-rw-r--r--lib/http.h4
-rw-r--r--lib/quic.h2
-rw-r--r--lib/vquic/ngtcp2.c4
-rw-r--r--lib/vquic/quiche.c127
5 files changed, 99 insertions, 39 deletions
diff --git a/lib/http.c b/lib/http.c
index 4631a7f36..ff329b6ad 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -1618,6 +1618,7 @@ CURLcode Curl_http_done(struct connectdata *conn,
}
Curl_http2_done(conn, premature);
+ Curl_http3_done(data);
Curl_mime_cleanpart(&http->form);
diff --git a/lib/http.h b/lib/http.h
index a3a275702..2c229b109 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -163,8 +163,6 @@ struct HTTP {
int32_t stream_id; /* stream we are interested in */
bool bodystarted;
- /* We store non-final and final response headers here, per-stream */
- Curl_send_buffer *header_recvbuf;
size_t nread_header_recvbuf; /* number of bytes in header_recvbuf fed into
upper layer */
Curl_send_buffer *trailer_recvbuf;
@@ -184,6 +182,8 @@ struct HTTP {
size_t memlen; /* size of data copied to mem */
#endif
#if defined(USE_NGHTTP2) || defined(ENABLE_QUIC)
+ /* We store non-final and final response headers here, per-stream */
+ Curl_send_buffer *header_recvbuf;
/* fields used by both HTTP/2 and HTTP/3 */
const uint8_t *upload_mem; /* points to a buffer to read from */
size_t upload_len; /* size of the buffer 'upload_mem' points to */
diff --git a/lib/quic.h b/lib/quic.h
index 6c132a324..906c351a8 100644
--- a/lib/quic.h
+++ b/lib/quic.h
@@ -45,9 +45,11 @@ CURLcode Curl_quic_is_connected(struct connectdata *conn,
bool *connected);
int Curl_quic_ver(char *p, size_t len);
CURLcode Curl_quic_done_sending(struct connectdata *conn);
+void Curl_http3_done(struct Curl_easy *data);
#else /* ENABLE_QUIC */
#define Curl_quic_done_sending(x)
+#define Curl_http3_done(x);
#endif /* !ENABLE_QUIC */
#endif /* HEADER_CURL_QUIC_H */
diff --git a/lib/vquic/ngtcp2.c b/lib/vquic/ngtcp2.c
index c0f9b16e3..5732b759b 100644
--- a/lib/vquic/ngtcp2.c
+++ b/lib/vquic/ngtcp2.c
@@ -1575,6 +1575,10 @@ static CURLcode ng_flush_egress(struct connectdata *conn, int sockfd,
return CURLE_OK;
}
+void Curl_http3_done(struct Curl_easy *data)
+{
+ (void)data;
+}
/*
* Called from transfer.c:done_sending when we stop HTTP/3 uploading.
*/
diff --git a/lib/vquic/quiche.c b/lib/vquic/quiche.c
index 0ee360d07..27ae95a6d 100644
--- a/lib/vquic/quiche.c
+++ b/lib/vquic/quiche.c
@@ -361,36 +361,62 @@ static CURLcode flush_egress(struct connectdata *conn, int sockfd,
return CURLE_OK;
}
-struct h3h1header {
- char *dest;
- size_t destlen; /* left to use */
- size_t nlen; /* used */
-};
-
-static int cb_each_header(uint8_t *name, size_t name_len,
- uint8_t *value, size_t value_len,
+/* Pseudo headers come before the real ones but :status doesn't necessarily
+ have to be the first to arrive */
+static int cb_each_header(uint8_t *name, size_t namelen,
+ uint8_t *value, size_t valuelen,
void *argp)
{
- struct h3h1header *headers = (struct h3h1header *)argp;
- size_t olen = 0;
+ struct HTTP *stream = (struct HTTP *)argp;
+ CURLcode result;
- if((name_len == 7) && !strncmp(":status", (char *)name, 7)) {
- msnprintf(headers->dest,
- headers->destlen, "HTTP/3 %.*s\n",
- (int) value_len, value);
+ if((namelen == 7) && !strncmp(":status", (char *)name, 7)) {
+ /* "HTTP/3 NNN" is already a placeholder, so just replace the number with
+ the incoming value */
+ if(valuelen == 3)
+ memcpy(&stream->header_recvbuf->buffer[7], value, 3);
}
- else {
- msnprintf(headers->dest,
- headers->destlen, "%.*s: %.*s\n",
- (int)name_len, name, (int) value_len, value);
+ else if(':' != name[0]) {
+ /* Non-psuedo headers get converted a HTTP1-style headers */
+ result = Curl_add_buffer(&stream->header_recvbuf, name, namelen);
+ if(result)
+ return result;
+ result = Curl_add_buffer(&stream->header_recvbuf, ": ", 2);
+ if(result)
+ return result;
+ result = Curl_add_buffer(&stream->header_recvbuf, value, valuelen);
+ if(result)
+ return result;
+ result = Curl_add_buffer(&stream->header_recvbuf, "\r\n", 2);
+ if(result)
+ return result;
}
- olen = strlen(headers->dest);
- headers->destlen -= olen;
- headers->nlen += olen;
- headers->dest += olen;
return 0;
}
+static CURLcode h3_headers2buffer(struct HTTP *stream,
+ char *buf,
+ size_t *buflen,
+ size_t *added)
+{
+ Curl_send_buffer *recvb = stream->header_recvbuf;
+ CURLcode result;
+ *added = 0;
+ /* add a header-body separator CRLF */
+ result = Curl_add_buffer(&recvb, "\r\n", 2);
+ if(result)
+ return result;
+ stream->firstbody = TRUE;
+ DEBUGASSERT(*buflen >= recvb->size_used);
+ if(*buflen >= recvb->size_used) {
+ memcpy(buf, recvb->buffer, recvb->size_used);
+ *buflen -= recvb->size_used;
+ *added = recvb->size_used;
+ Curl_add_buffer_free(&stream->header_recvbuf);
+ }
+ return CURLE_OK;
+}
+
static ssize_t h3_stream_recv(struct connectdata *conn,
int sockindex,
char *buf,
@@ -403,12 +429,8 @@ static ssize_t h3_stream_recv(struct connectdata *conn,
curl_socket_t sockfd = conn->sock[sockindex];
quiche_h3_event *ev;
int rc;
- struct h3h1header headers;
struct Curl_easy *data = conn->data;
struct HTTP *stream = data->req.protop;
- headers.dest = buf;
- headers.destlen = buffersize;
- headers.nlen = 0;
if(process_ingress(conn, sockfd, qs)) {
infof(data, "h3_stream_recv returns on ingress\n");
@@ -431,21 +453,24 @@ static ssize_t h3_stream_recv(struct connectdata *conn,
switch(quiche_h3_event_type(ev)) {
case QUICHE_H3_EVENT_HEADERS:
- rc = quiche_h3_event_for_each_header(ev, cb_each_header, &headers);
+ rc = quiche_h3_event_for_each_header(ev, cb_each_header, stream);
if(rc) {
- /* what do we do about this? */
+ *curlcode = rc;
+ return -1;
}
- recvd = headers.nlen;
break;
case QUICHE_H3_EVENT_DATA:
if(!stream->firstbody) {
- /* add a header-body separator CRLF */
- buf[0] = '\r';
- buf[1] = '\n';
- buf += 2;
- buffersize -= 2;
- stream->firstbody = TRUE;
- recvd = 2; /* two bytes already */
+ size_t added;
+ CURLcode result = h3_headers2buffer(stream, buf, &buffersize, &added);
+ if(result) {
+ *curlcode = result;
+ break;
+ }
+ recvd = added;
+ if(!buffersize)
+ break;
+ buf += added;
}
else
recvd = 0;
@@ -460,7 +485,18 @@ static ssize_t h3_stream_recv(struct connectdata *conn,
case QUICHE_H3_EVENT_FINISHED:
streamclose(conn, "End of stream");
- recvd = 0; /* end of stream */
+ if(!stream->firstbody) {
+ /* No body, only headers have been received */
+ size_t added;
+ CURLcode result = h3_headers2buffer(stream, buf, &buffersize, &added);
+ if(result) {
+ *curlcode = result;
+ break;
+ }
+ recvd = added;
+ }
+ else
+ recvd = 0; /* end of stream */
break;
default:
break;
@@ -748,6 +784,17 @@ static CURLcode http_request(struct connectdata *conn, const void *mem,
goto fail;
}
+ if(!stream->header_recvbuf) {
+ stream->header_recvbuf = Curl_add_buffer_init();
+ if(!stream->header_recvbuf)
+ result = CURLE_OUT_OF_MEMORY;
+ else
+ /* add response code place holder first in the receive header buffer */
+ result = Curl_add_buffer(&stream->header_recvbuf, "HTTP/3 000\r\n", 12);
+ if(result)
+ goto fail;
+ }
+
infof(data, "Using HTTP/3 Stream ID: %x (easy handle %p)\n",
stream3_id, (void *)data);
stream->stream3_id = stream3_id;
@@ -759,6 +806,12 @@ fail:
return result;
}
+void Curl_http3_done(struct Curl_easy *data)
+{
+ struct HTTP *http = data->req.protop;
+ Curl_add_buffer_free(&http->header_recvbuf);
+}
+
/*
* Called from transfer.c:done_sending when we stop HTTP/3 uploading.
*/