|
@@ -1628,6 +1628,7 @@ struct mg_workerTLS {
|
|
|
HANDLE pthread_cond_helper_mutex;
|
|
|
struct mg_workerTLS *next_waiting_thread;
|
|
|
#endif
|
|
|
+ const char *alpn_proto;
|
|
|
#if defined(MG_ALLOW_USING_GET_REQUEST_INFO_FOR_RESPONSE)
|
|
|
char txtbuf[4];
|
|
|
#endif
|
|
@@ -2856,8 +2857,15 @@ enum {
|
|
|
CONNECTION_TYPE_RESPONSE
|
|
|
};
|
|
|
|
|
|
+enum {
|
|
|
+ PROTOCOL_TYPE_HTTP1 = 0,
|
|
|
+ PROTOCOL_TYPE_WEBSOCKET = 1,
|
|
|
+ PROTOCOL_TYPE_HTTP2 = 2
|
|
|
+};
|
|
|
+
|
|
|
struct mg_connection {
|
|
|
int connection_type; /* see CONNECTION_TYPE_* above */
|
|
|
+ int protocol_type; /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */
|
|
|
|
|
|
struct mg_request_info request_info;
|
|
|
struct mg_response_info response_info;
|
|
@@ -2937,13 +2945,6 @@ struct de {
|
|
|
};
|
|
|
|
|
|
|
|
|
-#if defined(USE_WEBSOCKET)
|
|
|
-static int is_websocket_protocol(const struct mg_connection *conn);
|
|
|
-#else
|
|
|
-#define is_websocket_protocol(conn) (0)
|
|
|
-#endif
|
|
|
-
|
|
|
-
|
|
|
#define mg_cry_internal(conn, fmt, ...) \
|
|
|
mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
|
|
|
|
|
@@ -4026,9 +4027,9 @@ get_proto_name(const struct mg_connection *conn)
|
|
|
|
|
|
const struct mg_request_info *ri = &conn->request_info;
|
|
|
|
|
|
- const char *proto =
|
|
|
- (is_websocket_protocol(conn) ? (ri->is_ssl ? "wss" : "ws")
|
|
|
- : (ri->is_ssl ? "https" : "http"));
|
|
|
+ const char *proto = ((conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET)
|
|
|
+ ? (ri->is_ssl ? "wss" : "ws")
|
|
|
+ : (ri->is_ssl ? "https" : "http"));
|
|
|
|
|
|
return proto;
|
|
|
|
|
@@ -7748,7 +7749,7 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
|
|
|
/* Step 3: Check if it is a websocket request, and modify the document
|
|
|
* root if required */
|
|
|
#if defined(USE_WEBSOCKET)
|
|
|
- *is_websocket_request = is_websocket_protocol(conn);
|
|
|
+ *is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
|
|
|
#if !defined(NO_FILES)
|
|
|
if (*is_websocket_request && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
|
|
|
root = conn->dom_ctx->config[WEBSOCKET_ROOT];
|
|
@@ -10806,7 +10807,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
|
|
|
{
|
|
|
int request_length;
|
|
|
int init_skip = 0;
|
|
|
- int is_http2 = 0;
|
|
|
|
|
|
/* Reset attributes. DO NOT TOUCH is_ssl, remote_addr,
|
|
|
* remote_port */
|
|
@@ -10844,13 +10844,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
-#if defined(USE_HTTP2)
|
|
|
- static int is_http2_header(const char *buf, size_t buflen);
|
|
|
- if (is_http2_header(buf, request_length)) {
|
|
|
- is_http2 = 1;
|
|
|
- }
|
|
|
-#endif
|
|
|
-
|
|
|
/* The first word has to be the HTTP method */
|
|
|
ri->request_method = buf;
|
|
|
|
|
@@ -10858,14 +10851,6 @@ parse_http_request(char *buf, int len, struct mg_request_info *ri)
|
|
|
return -1;
|
|
|
}
|
|
|
|
|
|
- /* Check for a valid http method */
|
|
|
- if (!is_valid_http_method(ri->request_method)) {
|
|
|
- /* "PRI" is valid for HTTP/2 connection initialization */
|
|
|
- if (!is_http2) {
|
|
|
- return -1;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/* The second word is the URI */
|
|
|
ri->request_uri = buf;
|
|
|
|
|
@@ -13311,10 +13296,17 @@ handle_websocket_request(struct mg_connection *conn,
|
|
|
ws_close_handler(conn, cbData);
|
|
|
}
|
|
|
}
|
|
|
+#endif /* !USE_WEBSOCKET */
|
|
|
|
|
|
|
|
|
+/* Is upgrade request:
|
|
|
+ * 0 = regular HTTP/1.0 or HTTP/1.1 request
|
|
|
+ * 1 = upgrade to websocket
|
|
|
+ * 2 = upgrade to HTTP/2
|
|
|
+ * -1 = upgrade to unknown protocol
|
|
|
+ */
|
|
|
static int
|
|
|
-is_websocket_protocol(const struct mg_connection *conn)
|
|
|
+should_switch_to_protocol(const struct mg_connection *conn)
|
|
|
{
|
|
|
const char *upgrade, *connection;
|
|
|
|
|
@@ -13324,35 +13316,37 @@ is_websocket_protocol(const struct mg_connection *conn)
|
|
|
* Upgrade: Websocket
|
|
|
*/
|
|
|
|
|
|
- upgrade = mg_get_header(conn, "Upgrade");
|
|
|
- if (upgrade == NULL) {
|
|
|
- return 0; /* fail early, don't waste time checking other header
|
|
|
- * fields
|
|
|
- */
|
|
|
- }
|
|
|
- DEBUG_TRACE("Upgrade: %s", upgrade);
|
|
|
- if (!mg_strcasestr(upgrade, "websocket")) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
connection = mg_get_header(conn, "Connection");
|
|
|
if (connection == NULL) {
|
|
|
- return 0;
|
|
|
+ return PROTOCOL_TYPE_HTTP1;
|
|
|
}
|
|
|
if (!mg_strcasestr(connection, "upgrade")) {
|
|
|
- return 0;
|
|
|
+ return PROTOCOL_TYPE_HTTP1;
|
|
|
}
|
|
|
|
|
|
- /* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and
|
|
|
- * "Sec-WebSocket-Version" are also required.
|
|
|
- * Don't check them here, since even an unsupported websocket protocol
|
|
|
- * request still IS a websocket request (in contrast to a standard HTTP
|
|
|
- * request). It will fail later in handle_websocket_request.
|
|
|
- */
|
|
|
+ upgrade = mg_get_header(conn, "Upgrade");
|
|
|
+ if (upgrade == NULL) {
|
|
|
+ /* "Connection: Upgrade" without "Upgrade" Header --> Error */
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
|
|
|
- return 1;
|
|
|
+ /* Upgrade to ... */
|
|
|
+ if (0 != mg_strcasestr(upgrade, "websocket")) {
|
|
|
+ /* The headers "Host", "Sec-WebSocket-Key", "Sec-WebSocket-Protocol" and
|
|
|
+ * "Sec-WebSocket-Version" are also required.
|
|
|
+ * Don't check them here, since even an unsupported websocket protocol
|
|
|
+ * request still IS a websocket request (in contrast to a standard HTTP
|
|
|
+ * request). It will fail later in handle_websocket_request.
|
|
|
+ */
|
|
|
+ return PROTOCOL_TYPE_WEBSOCKET; /* Websocket */
|
|
|
+ }
|
|
|
+ if (0 != mg_strcasestr(upgrade, "h2")) {
|
|
|
+ return PROTOCOL_TYPE_HTTP2; /* Websocket */
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Upgrade to another protocol */
|
|
|
+ return -1;
|
|
|
}
|
|
|
-#endif /* !USE_WEBSOCKET */
|
|
|
|
|
|
|
|
|
static int
|
|
@@ -14382,7 +14376,7 @@ handle_request(struct mg_connection *conn)
|
|
|
/* 5.1. first test, if the request targets the regular http(s)://
|
|
|
* protocol namespace or the websocket ws(s):// protocol namespace.
|
|
|
*/
|
|
|
- is_websocket_request = is_websocket_protocol(conn);
|
|
|
+ is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
|
|
|
#if defined(USE_WEBSOCKET)
|
|
|
handler_type = is_websocket_request ? WEBSOCKET_HANDLER : REQUEST_HANDLER;
|
|
|
#else
|
|
@@ -16376,9 +16370,19 @@ alpn_select_cb(SSL *ssl,
|
|
|
struct mg_domain_context *dom_ctx = (struct mg_domain_context *)arg;
|
|
|
unsigned int i, j;
|
|
|
|
|
|
+ struct mg_workerTLS *tls =
|
|
|
+ (struct mg_workerTLS *)pthread_getspecific(sTlsKey);
|
|
|
+
|
|
|
(void)ssl;
|
|
|
(void)dom_ctx;
|
|
|
|
|
|
+
|
|
|
+ if (tls == NULL) {
|
|
|
+ /* Need to store protocol in Thread Local Storage */
|
|
|
+ /* If there is no Thread Local Storage, don't use ALPN */
|
|
|
+ return SSL_TLSEXT_ERR_NOACK;
|
|
|
+ }
|
|
|
+
|
|
|
for (j = 0; alpn_proto_order[j] != NULL; j++) {
|
|
|
/* check all accepted protocols in this order */
|
|
|
const char *alpn_proto = alpn_proto_order[j];
|
|
@@ -16387,6 +16391,7 @@ alpn_select_cb(SSL *ssl,
|
|
|
if (!memcmp(in + i, alpn_proto, (unsigned char)alpn_proto[0])) {
|
|
|
*out = in + i + 1;
|
|
|
*outlen = in[i];
|
|
|
+ tls->alpn_proto = alpn_proto;
|
|
|
return SSL_TLSEXT_ERR_OK;
|
|
|
}
|
|
|
}
|
|
@@ -18384,7 +18389,6 @@ process_new_connection(struct mg_connection *conn)
|
|
|
char ebuf[100];
|
|
|
const char *hostend;
|
|
|
int reqerr, uri_type;
|
|
|
- int is_http2 = 0;
|
|
|
|
|
|
#if defined(USE_SERVER_STATS)
|
|
|
int mcon = mg_atomic_inc(&(conn->phys_ctx->active_connections));
|
|
@@ -18421,16 +18425,9 @@ process_new_connection(struct mg_connection *conn)
|
|
|
mg_send_http_error(conn, reqerr, "%s", ebuf);
|
|
|
}
|
|
|
|
|
|
-#if defined(USE_HTTP2)
|
|
|
- } else if ((0 == strcmp(ri->http_version, "2.0"))
|
|
|
- && (conn->handled_requests == 0)) {
|
|
|
- /* Initialize */
|
|
|
- is_http2 = 1;
|
|
|
- conn->content_len = -1; /* content len is unknown */
|
|
|
-#endif
|
|
|
-
|
|
|
} else if (strcmp(ri->http_version, "1.0")
|
|
|
&& strcmp(ri->http_version, "1.1")) {
|
|
|
+ /* HTTP/2 is not allowed here */
|
|
|
mg_snprintf(conn,
|
|
|
NULL, /* No truncation check for ebuf */
|
|
|
ebuf,
|
|
@@ -18440,7 +18437,7 @@ process_new_connection(struct mg_connection *conn)
|
|
|
mg_send_http_error(conn, 505, "%s", ebuf);
|
|
|
}
|
|
|
|
|
|
- if ((ebuf[0] == '\0') && (!is_http2)) {
|
|
|
+ if (ebuf[0] == '\0') {
|
|
|
uri_type = get_uri_type(conn->request_info.request_uri);
|
|
|
switch (uri_type) {
|
|
|
case 1:
|
|
@@ -18480,27 +18477,29 @@ process_new_connection(struct mg_connection *conn)
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
+ if (ebuf[0] != '\0') {
|
|
|
+ conn->protocol_type = -1;
|
|
|
+
|
|
|
+ } else if (conn->protocol_type == PROTOCOL_TYPE_HTTP1) {
|
|
|
+ /* HTTP/1 allows protocol upgrade */
|
|
|
+ conn->protocol_type = should_switch_to_protocol(conn);
|
|
|
+
|
|
|
+ if (conn->protocol_type == PROTOCOL_TYPE_HTTP2) {
|
|
|
+ /* This will occur, if a HTTP/1.1 request should be upgraded
|
|
|
+ * to HTTP/2 - but not if HTTP/2 is negotiated using ALPN.
|
|
|
+ * Since most (all?) major browsers only support HTTP/2 using
|
|
|
+ * ALPN, this is hard to test and very low priority.
|
|
|
+ * Deactivate it (at least for now).
|
|
|
+ */
|
|
|
+ conn->protocol_type = PROTOCOL_TYPE_HTTP1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
DEBUG_TRACE("http: %s, error: %s",
|
|
|
(ri->http_version ? ri->http_version : "none"),
|
|
|
(ebuf[0] ? ebuf : "none"));
|
|
|
-#if defined(USE_HTTP2)
|
|
|
- if (is_http2) {
|
|
|
- if (!is_valid_http2_primer(conn)) {
|
|
|
- /* Primer does not match expectation from RFC.
|
|
|
- * See https://tools.ietf.org/html/rfc7540#section-3.5 */
|
|
|
- mg_snprintf(conn,
|
|
|
- NULL, /* No truncation check for ebuf */
|
|
|
- ebuf,
|
|
|
- sizeof(ebuf),
|
|
|
- "Invalid HTTP/2 primer");
|
|
|
- mg_send_http_error(conn, 400, "%s", ebuf);
|
|
|
- } else {
|
|
|
- /* Valid HTTP/2 primer received */
|
|
|
- handle_http2(conn);
|
|
|
- }
|
|
|
- } else
|
|
|
-#endif
|
|
|
- if (ebuf[0] == '\0') {
|
|
|
+
|
|
|
+ if (ebuf[0] == '\0') {
|
|
|
if (conn->request_info.local_uri) {
|
|
|
|
|
|
/* handle request to local server */
|
|
@@ -18555,7 +18554,7 @@ process_new_connection(struct mg_connection *conn)
|
|
|
&& ((conn->consumed_content == conn->content_len)
|
|
|
|| ((conn->request_len + conn->content_len)
|
|
|
<= conn->data_len))))
|
|
|
- && (!is_http2);
|
|
|
+ && (conn->protocol_type == PROTOCOL_TYPE_HTTP1);
|
|
|
|
|
|
if (keep_alive) {
|
|
|
/* Discard all buffered data for this request */
|
|
@@ -18822,6 +18821,9 @@ worker_thread_run(struct mg_connection *conn)
|
|
|
* produce_socket() */
|
|
|
while (consume_socket(ctx, &conn->client, thread_index)) {
|
|
|
|
|
|
+ /* New connections must start with new protocol negotiation */
|
|
|
+ tls.alpn_proto = NULL;
|
|
|
+
|
|
|
#if defined(USE_SERVER_STATS)
|
|
|
conn->conn_close_time = 0;
|
|
|
#endif
|
|
@@ -18865,7 +18867,17 @@ worker_thread_run(struct mg_connection *conn)
|
|
|
ssl_get_client_cert_info(conn);
|
|
|
|
|
|
/* process HTTPS connection */
|
|
|
- process_new_connection(conn);
|
|
|
+ if (!memcmp(tls.alpn_proto, "\x02h2", 3)) {
|
|
|
+ /* process HTTPS/2 connection */
|
|
|
+ conn->protocol_type = PROTOCOL_TYPE_HTTP2;
|
|
|
+ init_connection(conn);
|
|
|
+#ifdef USE_HTTP2
|
|
|
+ process_new_http2_connection(conn);
|
|
|
+#endif
|
|
|
+ } else {
|
|
|
+ /* process HTTPS/1.x or WEBSOCKET-SECURE connection */
|
|
|
+ process_new_connection(conn);
|
|
|
+ }
|
|
|
|
|
|
/* Free client certificate info */
|
|
|
if (conn->request_info.client_cert) {
|