diff --git a/mongoose.c b/mongoose.c index bc070da72a27d9df28e5faf0f4fd4392f17968dc..5a581c596c83e88f9d690b5746c5dcc4d1c0a9ca 100644 --- a/mongoose.c +++ b/mongoose.c @@ -4159,6 +4159,10 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) { /* Amalgamated: #include "common/sha1.h" */ /* Amalgamated: #include "common/md5.h" */ +#ifndef MG_DISABLE_HTTP_WEBSOCKET +#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1) +#endif + enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT }; struct mg_http_proto_data_file { @@ -5365,9 +5369,11 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) { #ifndef MG_DISABLE_HTTP_WEBSOCKET -void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri, - const char *extra_headers) { - unsigned long random = (unsigned long) uri; +void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path, + const char *host, const char *protocol, + const char *extra_headers) { + /* pretty poor source of randomness, TODO fix */ + unsigned long random = (unsigned long) path; char key[sizeof(random) * 3]; mg_base64_encode((unsigned char *) &random, sizeof(random), key); @@ -5376,9 +5382,26 @@ void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri, "Upgrade: websocket\r\n" "Connection: Upgrade\r\n" "Sec-WebSocket-Version: 13\r\n" - "Sec-WebSocket-Key: %s\r\n" - "%s\r\n", - uri, key, extra_headers == NULL ? "" : extra_headers); + "Sec-WebSocket-Key: %s\r\n", + path, key); + + /* TODO(mkm): take default hostname from http proto data if host == NULL */ + if (host != MG_WS_NO_HOST_HEADER_MAGIC) { + mg_printf(nc, "Host: %s\r\n", host); + } + if (protocol != NULL) { + mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol); + } + if (extra_headers != NULL) { + mg_printf(nc, "%s", extra_headers); + } + mg_printf(nc, "\r\n"); +} + +void mg_send_websocket_handshake(struct mg_connection *nc, const char *path, + const char *extra_headers) { + mg_send_websocket_handshake2(nc, path, MG_WS_NO_HOST_HEADER_MAGIC, NULL, + extra_headers); } #endif /* MG_DISABLE_HTTP_WEBSOCKET */ @@ -7357,52 +7380,110 @@ void mg_serve_http(struct mg_connection *nc, struct http_message *hm, #endif /* MG_DISABLE_FILESYSTEM */ -struct mg_connection *mg_connect_http(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - const char *url, - const char *extra_headers, - const char *post_data) { - struct mg_connection *nc = NULL; - char *addr = NULL; - const char *path = NULL; - int use_ssl = 0, addr_len = 0, port_i = -1; - - if (memcmp(url, "http://", 7) == 0) { - url += 7; - } else if (memcmp(url, "https://", 8) == 0) { - url += 8; - use_ssl = 1; +/* returns 0 on success, -1 on error */ +static int mg_http_common_url_parse(const char *url, const char *schema, + const char *schema_tls, int *use_ssl, + char **addr, int *port_i, + const char **path) { + int addr_len = 0; + + if (memcmp(url, schema, strlen(schema)) == 0) { + url += strlen(schema); + } else if (memcmp(url, schema_tls, strlen(schema_tls)) == 0) { + url += strlen(schema_tls); + *use_ssl = 1; #ifndef MG_ENABLE_SSL - return NULL; /* SSL is not enabled, cannot do HTTPS URLs */ + return -1; /* SSL is not enabled, cannot do HTTPS URLs */ #endif } while (*url != '\0') { - addr = (char *) MG_REALLOC(addr, addr_len + 5 /* space for port too. */); - if (addr == NULL) { + *addr = (char *) MG_REALLOC(*addr, addr_len + 5 /* space for port too. */); + if (*addr == NULL) { DBG(("OOM")); - return NULL; + return -1; } if (*url == '/') { url++; break; } - if (*url == ':') port_i = addr_len; - addr[addr_len++] = *url; - addr[addr_len] = '\0'; + if (*url == ':') *port_i = addr_len; + (*addr)[addr_len++] = *url; + (*addr)[addr_len] = '\0'; url++; } if (addr_len == 0) goto cleanup; - if (port_i < 0) { - port_i = addr_len; - strcpy(addr + port_i, use_ssl ? ":443" : ":80"); + if (*port_i < 0) { + *port_i = addr_len; + strcpy(*addr + *port_i, *use_ssl ? ":443" : ":80"); } else { - port_i = -1; + *port_i = -1; + } + + if (*path == NULL) *path = url; + + if (**path == '\0') *path = "/"; + + DBG(("%s %s", *addr, *path)); + + return 0; + +cleanup: + MG_FREE(*addr); + return -1; +} + +struct mg_connection *mg_connect_ws(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + const char *url, const char *protocol, + const char *extra_headers) { + struct mg_connection *nc = NULL; + char *addr = NULL; + int port_i = -1; + const char *path = NULL; + int use_ssl = 0; + + if (mg_http_common_url_parse(url, "ws://", "wss://", &use_ssl, &addr, &port_i, + &path) < 0) { + DBG(("%p: error parsing wss url: %s", (void *) nc, url)); + return NULL; } - if (path == NULL) path = url; + if ((nc = mg_connect(mgr, addr, ev_handler)) != NULL) { + mg_set_protocol_http_websocket(nc); + + if (use_ssl) { +#ifdef MG_ENABLE_SSL + mg_set_ssl(nc, NULL, NULL); +#endif + } + + /* If the port was addred by us, restore the original host. */ + if (port_i >= 0) addr[port_i] = '\0'; + + mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers); + } + + MG_FREE(addr); + return nc; +} + +struct mg_connection *mg_connect_http(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + const char *url, + const char *extra_headers, + const char *post_data) { + struct mg_connection *nc = NULL; + char *addr = NULL; + int port_i = -1; + const char *path = NULL; + int use_ssl = 0; + + if (mg_http_common_url_parse(url, "http://", "https://", &use_ssl, &addr, + &port_i, &path) < 0) { + return NULL; + } - DBG(("%s %s", addr, path)); if ((nc = mg_connect(mgr, addr, ev_handler)) != NULL) { mg_set_protocol_http_websocket(nc); @@ -7414,6 +7495,7 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr, /* If the port was addred by us, restore the original host. */ if (port_i >= 0) addr[port_i] = '\0'; + mg_printf(nc, "%s /%s HTTP/1.1\r\nHost: %s\r\nContent-Length: %" SIZE_T_FMT "\r\n%s\r\n%s", post_data == NULL ? "GET" : "POST", path, addr, @@ -7422,7 +7504,6 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr, post_data == NULL ? "" : post_data); } -cleanup: MG_FREE(addr); return nc; } diff --git a/mongoose.h b/mongoose.h index 242c5c1ea72d697b1adf553be79ed5a43090d399..22553c16f14fd7bc7dc01e1be23060e6798888e8 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1990,10 +1990,56 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc); * to fetch, extra_headers` is extra HTTP headers to send or `NULL`. * * This function is intended to be used by websocket client. + * + * Note that the Host header is mandatory in HTTP/1.1 and must be + * included in `extra_headers`. `mg_send_websocket_handshake2` offers + * a better API for that. + * + * Deprecated in favour of `mg_send_websocket_handshake2` */ void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri, const char *extra_headers); +/* + * Send websocket handshake to the server. + * + * `nc` must be a valid connection, connected to a server. `uri` is an URI + * to fetch, `host` goes into the `Host` header, `protocol` goes into the + * `Sec-WebSocket-Proto` header (NULL to omit), extra_headers` is extra HTTP + * headers to send or `NULL`. + * + * This function is intended to be used by websocket client. + */ +void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path, + const char *host, const char *protocol, + const char *extra_headers); + +/* + * Helper function that creates an outbound WebSocket connection. + * + * `url` is a URL to connect to. It must be properly URL-encoded, e.g. have + * no spaces, etc. By default, `mg_connect_ws()` sends Connection and + * Host headers. `extra_headers` is an extra HTTP headers to send, e.g. + * `"User-Agent: my-app\r\n"`. + * If `protocol` is not NULL, then a `Sec-WebSocket-Protocol` header is sent. + * + * Examples: + * + * [source,c] + * ---- + * nc1 = mg_connect_ws(mgr, ev_handler_1, "ws://echo.websocket.org", NULL, + * NULL); + * nc2 = mg_connect_ws(mgr, ev_handler_1, "wss://echo.websocket.org", NULL, + * NULL); + * nc3 = mg_connect_ws(mgr, ev_handler_1, "ws://api.cesanta.com", + * "clubby.cesanta.com", NULL); + * ---- + */ +struct mg_connection *mg_connect_ws(struct mg_mgr *mgr, + mg_event_handler_t event_handler, + const char *url, const char *protocol, + const char *extra_headers); + /* * Send websocket frame to the remote end. *