* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 1998 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2017, 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
#include "http.h"
#include "sendf.h"
#include "curl_base64.h"
-#include "rawstr.h"
+#include "strcase.h"
#include "multiif.h"
#include "conncache.h"
#include "url.h"
#include "connect.h"
#include "strtoofft.h"
-
+#include "strdup.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
#include "curl_memory.h"
#define nghttp2_session_callbacks_set_error_callback(x,y)
#endif
+#if (NGHTTP2_VERSION_NUM >= 0x010c00)
+#define NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE 1
+#endif
+
+#define HTTP2_HUGE_WINDOW_SIZE (1 << 30)
+
/*
* Curl_http2_init_state() is called when the easy handle is created and
* allows for HTTP/2 specific init of state.
return http2_perform_getsock(conn, sock, numsocks);
}
-static CURLcode http2_disconnect(struct connectdata *conn,
- bool dead_connection)
+/*
+ * http2_stream_free() free HTTP2 stream related data
+ */
+static void http2_stream_free(struct HTTP *http)
{
- struct HTTP *http = conn->data->req.protop;
- struct http_conn *c = &conn->proto.httpc;
- (void)dead_connection;
-
- DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n"));
-
- nghttp2_session_del(c->h2);
- Curl_safefree(c->inbuf);
-
if(http) {
Curl_add_buffer_free(http->header_recvbuf);
http->header_recvbuf = NULL; /* clear the pointer */
free(http->push_headers);
http->push_headers = NULL;
}
+}
+
+static CURLcode http2_disconnect(struct connectdata *conn,
+ bool dead_connection)
+{
+ struct http_conn *c = &conn->proto.httpc;
+ (void)dead_connection;
+
+ DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT starts now\n"));
+
+ nghttp2_session_del(c->h2);
+ Curl_safefree(c->inbuf);
+ http2_stream_free(conn->data->req.protop);
DEBUGF(infof(conn->data, "HTTP/2 DISCONNECT done\n"));
https://tools.ietf.org/html/rfc7540#page-77
nghttp2_error_code enums are identical.
*/
-const char *Curl_http2_strerror(uint32_t err) {
+const char *Curl_http2_strerror(uint32_t err)
+{
#ifndef NGHTTP2_HAS_HTTP2_STRERROR
const char *str[] = {
"NO_ERROR", /* 0x0 */
the middle of header, it could be matched in middle of the value,
this is because we do prefix match.*/
if(!h || !GOOD_EASY_HANDLE(h->data) || !header || !header[0] ||
- Curl_raw_equal(header, ":") || strchr(header + 1, ':'))
+ !strcmp(header, ":") || strchr(header + 1, ':'))
return NULL;
else {
struct HTTP *stream = h->data->req.protop;
free(stream->push_headers[i]);
free(stream->push_headers);
stream->push_headers = NULL;
+ stream->push_headers_used = 0;
if(rv) {
/* denied, kill off the new handle again */
+ http2_stream_free(newhandle->req.protop);
(void)Curl_close(newhandle);
goto fail;
}
rc = Curl_multi_add_perform(data->multi, newhandle, conn);
if(rc) {
infof(data, "failed to add handle to multi\n");
+ http2_stream_free(newhandle->req.protop);
Curl_close(newhandle);
rv = 1;
goto fail;
stream->push_headers_alloc) {
char **headp;
stream->push_headers_alloc *= 2;
- headp = realloc(stream->push_headers,
- stream->push_headers_alloc * sizeof(char *));
+ headp = Curl_saferealloc(stream->push_headers,
+ stream->push_headers_alloc * sizeof(char *));
if(!headp) {
- free(stream->push_headers);
stream->push_headers = NULL;
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
memcpy(buf, stream->upload_mem, nread);
stream->upload_mem += nread;
stream->upload_len -= nread;
- stream->upload_left -= nread;
+ if(data_s->state.infilesize != -1)
+ stream->upload_left -= nread;
}
if(stream->upload_left == 0)
return nread;
}
-/*
- * The HTTP2 settings we send in the Upgrade request
- */
-static nghttp2_settings_entry settings[] = {
- { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100 },
- { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, NGHTTP2_INITIAL_WINDOW_SIZE },
-};
-
#define H2_BUFSIZE 32768
#ifdef NGHTTP2_HAS_ERROR_CALLBACK
}
#endif
+static void populate_settings(struct connectdata *conn,
+ struct http_conn *httpc)
+{
+ nghttp2_settings_entry *iv = httpc->local_settings;
+
+ iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+ iv[0].value = 100;
+
+ iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+ iv[1].value = HTTP2_HUGE_WINDOW_SIZE;
+
+ iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+ iv[2].value = conn->data->multi->push_cb != NULL;
+
+ httpc->local_settings_num = 3;
+}
+
void Curl_http2_done(struct connectdata *conn, bool premature)
{
struct Curl_easy *data = conn->data;
size_t blen;
struct SingleRequest *k = &conn->data->req;
uint8_t *binsettings = conn->proto.httpc.binsettings;
+ struct http_conn *httpc = &conn->proto.httpc;
- /* As long as we have a fixed set of settings, we don't have to dynamically
- * figure out the base64 strings since it'll always be the same. However,
- * the settings will likely not be fixed every time in the future.
- */
+ populate_settings(conn, httpc);
/* this returns number of bytes it wrote */
binlen = nghttp2_pack_settings_payload(binsettings, H2_BINSETTINGS_LEN,
- settings,
- sizeof(settings)/sizeof(settings[0]));
+ httpc->local_settings,
+ httpc->local_settings_num);
if(!binlen) {
failf(conn->data, "nghttp2 unexpectedly failed on pack_settings_payload");
return CURLE_FAILED_INIT;
#define HEADER_OVERFLOW(x) \
(x.namelen > (uint16_t)-1 || x.valuelen > (uint16_t)-1 - x.namelen)
+/*
+ * Check header memory for the token "trailers".
+ * Parse the tokens as separated by comma and surrounded by whitespace.
+ * Returns TRUE if found or FALSE if not.
+ */
+static bool contains_trailers(const char *p, size_t len)
+{
+ const char *end = p + len;
+ for(;;) {
+ for(; p != end && (*p == ' ' || *p == '\t'); ++p)
+ ;
+ if(p == end || (size_t)(end - p) < sizeof("trailers") - 1)
+ return FALSE;
+ if(strncasecompare("trailers", p, sizeof("trailers") - 1)) {
+ p += sizeof("trailers") - 1;
+ for(; p != end && (*p == ' ' || *p == '\t'); ++p)
+ ;
+ if(p == end || *p == ',')
+ return TRUE;
+ }
+ /* skip to next token */
+ for(; p != end && *p != ','; ++p)
+ ;
+ if(p == end)
+ return FALSE;
+ ++p;
+ }
+}
+
+typedef enum {
+ /* Send header to server */
+ HEADERINST_FORWARD,
+ /* Don't send header to server */
+ HEADERINST_IGNORE,
+ /* Discard header, and replace it with "te: trailers" */
+ HEADERINST_TE_TRAILERS
+} header_instruction;
+
+/* Decides how to treat given header field. */
+static header_instruction inspect_header(const char *name, size_t namelen,
+ const char *value, size_t valuelen) {
+ switch(namelen) {
+ case 2:
+ if(!strncasecompare("te", name, namelen))
+ return HEADERINST_FORWARD;
+
+ return contains_trailers(value, valuelen) ?
+ HEADERINST_TE_TRAILERS : HEADERINST_IGNORE;
+ case 7:
+ return strncasecompare("upgrade", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 10:
+ return (strncasecompare("connection", name, namelen) ||
+ strncasecompare("keep-alive", name, namelen)) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 16:
+ return strncasecompare("proxy-connection", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ case 17:
+ return strncasecompare("transfer-encoding", name, namelen) ?
+ HEADERINST_IGNORE : HEADERINST_FORWARD;
+ default:
+ return HEADERINST_FORWARD;
+ }
+}
+
static ssize_t http2_send(struct connectdata *conn, int sockindex,
const void *mem, size_t len, CURLcode *err)
{
size_t nheader;
size_t i;
size_t authority_idx;
- char *hdbuf = (char*)mem;
+ char *hdbuf = (char *)mem;
char *end, *line_end;
nghttp2_data_provider data_prd;
int32_t stream_id;
i = 3;
while(i < nheader) {
size_t hlen;
- int skip = 0;
hdbuf = line_end + 2;
goto fail;
hlen = end - hdbuf;
- if(hlen == 10 && Curl_raw_nequal("connection", hdbuf, 10)) {
- /* skip Connection: headers! */
- skip = 1;
- --nheader;
- }
- else if(hlen == 4 && Curl_raw_nequal("host", hdbuf, 4)) {
+ if(hlen == 4 && strncasecompare("host", hdbuf, 4)) {
authority_idx = i;
nva[i].name = (unsigned char *)":authority";
nva[i].namelen = strlen((char *)nva[i].name);
while(*hdbuf == ' ' || *hdbuf == '\t')
++hdbuf;
end = line_end;
- if(!skip) {
+
+ 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].valuelen = sizeof("trailers") - 1;
+ break;
+ default:
nva[i].value = (unsigned char *)hdbuf;
nva[i].valuelen = (size_t)(end - hdbuf);
- nva[i].flags = NGHTTP2_NV_FLAG_NONE;
- if(HEADER_OVERFLOW(nva[i])) {
- failf(conn->data, "Failed sending HTTP request: Header overflow");
- goto fail;
- }
- ++i;
}
+
+ nva[i].flags = NGHTTP2_NV_FLAG_NONE;
+ if(HEADER_OVERFLOW(nva[i])) {
+ failf(conn->data, "Failed sending HTTP request: Header overflow");
+ goto fail;
+ }
+ ++i;
}
/* :authority must come before non-pseudo header fields */
/* Warn stream may be rejected if cumulative length of headers is too large.
It appears nghttp2 will not send a header frame larger than 64KB. */
+#define MAX_ACC 60000 /* <64KB to account for some overhead */
{
size_t acc = 0;
- const size_t max_acc = 60000; /* <64KB to account for some overhead */
for(i = 0; i < nheader; ++i) {
- if(nva[i].namelen > max_acc - acc)
- break;
- acc += nva[i].namelen;
+ acc += nva[i].namelen + nva[i].valuelen;
- if(nva[i].valuelen > max_acc - acc)
- break;
- acc += nva[i].valuelen;
+ DEBUGF(infof(conn->data, "h2 header: %.*s:%.*s\n",
+ nva[i].namelen, nva[i].name,
+ nva[i].valuelen, nva[i].value));
}
- if(i != nheader) {
+ if(acc > MAX_ACC) {
infof(conn->data, "http2_send: Warning: The cumulative length of all "
- "headers exceeds %zu bytes and that could cause the "
- "stream to be rejected.\n", max_acc);
+ "headers exceeds %zu bytes and that could cause the "
+ "stream to be rejected.\n", MAX_ACC);
}
}
conn->data);
}
else {
+ populate_settings(conn, httpc);
+
/* stream ID is unknown at this point */
stream->stream_id = -1;
- rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE, NULL, 0);
+ rv = nghttp2_submit_settings(httpc->h2, NGHTTP2_FLAG_NONE,
+ httpc->local_settings,
+ httpc->local_settings_num);
if(rv != 0) {
failf(data, "nghttp2_submit_settings() failed: %s(%d)",
nghttp2_strerror(rv), rv);
}
}
+#ifdef NGHTTP2_HAS_SET_LOCAL_WINDOW_SIZE
+ rv = nghttp2_session_set_local_window_size(httpc->h2, NGHTTP2_FLAG_NONE, 0,
+ HTTP2_HUGE_WINDOW_SIZE);
+ if(rv != 0) {
+ failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
+ nghttp2_strerror(rv), rv);
+ return CURLE_HTTP2;
+ }
+#endif
+
/* we are going to copy mem to httpc->inbuf. This is required since
mem is part of buffer pointed by stream->mem, and callbacks
called by nghttp2_session_mem_recv() will write stream specific
" after upgrade: len=%zu\n",
nread);
- memcpy(httpc->inbuf, mem, nread);
+ if(nread)
+ memcpy(httpc->inbuf, mem, nread);
httpc->inbuflen = nread;
nproc = nghttp2_session_mem_recv(httpc->h2, (const uint8_t *)httpc->inbuf,
return CURLE_OK;
}
+void Curl_http2_add_child(struct Curl_easy *parent, struct Curl_easy *child,
+ bool exclusive)
+{
+ struct Curl_http2_dep **tail;
+ struct Curl_http2_dep *dep = calloc(1, sizeof(struct Curl_http2_dep));
+ dep->data = child;
+
+ if(parent->set.stream_dependents && exclusive) {
+ struct Curl_http2_dep *node = parent->set.stream_dependents;
+ while(node) {
+ node->data->set.stream_depends_on = child;
+ node = node->next;
+ }
+
+ tail = &child->set.stream_dependents;
+ while(*tail)
+ tail = &(*tail)->next;
+
+ DEBUGASSERT(!*tail);
+ *tail = parent->set.stream_dependents;
+ parent->set.stream_dependents = 0;
+ }
+
+ tail = &parent->set.stream_dependents;
+ while(*tail) {
+ (*tail)->data->set.stream_depends_e = FALSE;
+ tail = &(*tail)->next;
+ }
+
+ DEBUGASSERT(!*tail);
+ *tail = dep;
+
+ child->set.stream_depends_on = parent;
+ child->set.stream_depends_e = exclusive;
+}
+
+void Curl_http2_remove_child(struct Curl_easy *parent, struct Curl_easy *child)
+{
+ struct Curl_http2_dep *last = 0;
+ struct Curl_http2_dep *data = parent->set.stream_dependents;
+ DEBUGASSERT(child->set.stream_depends_on == parent);
+
+ while(data && data->data != child) {
+ last = data;
+ data = data->next;
+ }
+
+ DEBUGASSERT(data);
+
+ if(data) {
+ if(last) {
+ last->next = data->next;
+ }
+ else {
+ parent->set.stream_dependents = data->next;
+ }
+ free(data);
+ }
+
+ child->set.stream_depends_on = 0;
+ child->set.stream_depends_e = FALSE;
+}
+
+void Curl_http2_cleanup_dependencies(struct Curl_easy *data)
+{
+ while(data->set.stream_dependents) {
+ struct Curl_easy *tmp = data->set.stream_dependents->data;
+ Curl_http2_remove_child(data, tmp);
+ if(data->set.stream_depends_on)
+ Curl_http2_add_child(data->set.stream_depends_on, tmp, FALSE);
+ }
+
+ if(data->set.stream_depends_on)
+ Curl_http2_remove_child(data->set.stream_depends_on, data);
+}
+
#else /* !USE_NGHTTP2 */
/* Satisfy external references even if http2 is not compiled in. */