diff --git a/docs/overview/build-options/disabling-flags.md b/docs/overview/build-options/disabling-flags.md index 961cd071208f71bb7b68fa34530d2be16fdfda6e..ac5efee3a67d069867592fcb42db5f1186855d5e 100644 --- a/docs/overview/build-options/disabling-flags.md +++ b/docs/overview/build-options/disabling-flags.md @@ -2,7 +2,6 @@ title: Disabling flags --- -- `MG_DISABLE_HTTP_WEBSOCKET` disable HTTP + WebSocket protocol support - `MG_DISABLE_HTTP_DIGEST_AUTH` disable HTTP Digest (MD5) authorisation support - `MG_DISABLE_SHA1` disable SHA1 support (used by WebSocket) - `MG_DISABLE_MD5` disable MD5 support (used by HTTP auth) diff --git a/docs/overview/build-options/enabling-flags.md b/docs/overview/build-options/enabling-flags.md index a1858a285cdb55975f7eb8920271f5098dc08a89..f3cca7c00701e6f1764d78a1e4c24bbfe6342a11 100644 --- a/docs/overview/build-options/enabling-flags.md +++ b/docs/overview/build-options/enabling-flags.md @@ -2,13 +2,16 @@ title: Enabling flags --- -- `MG_ENABLE_CGI` Enable CGI support -- `MG_ENABLE_SSL` Enable SSL/TLS support (OpenSSL API) +- `MG_ENABLE_SSL` Enable [SSL/TLS support](https://docs.cesanta.com/mongoose/master/#/http/ssl.md/) (OpenSSL API) - `MG_ENABLE_IPV6` Enable IPV6 support -- `MG_ENABLE_MQTT` enable MQTT client -- `MG_ENABLE_MQTT_BROKER` enable MQTT broker +- `MG_ENABLE_MQTT` enable [MQTT client](https://docs.cesanta.com/mongoose/master/#/mqtt/client_example.md/) +- `MG_ENABLE_MQTT_BROKER` enable [MQTT broker](https://docs.cesanta.com/mongoose/master/#/mqtt/server_example.md/) - `MG_ENABLE_DNS_SERVER` enable DNS server - `MG_ENABLE_COAP` enable CoAP protocol +- `MG_ENABLE_HTTP` Enable HTTP protocol support (on by default, set to 0 to disable) +- `MG_ENABLE_HTTP_CGI` Enable [CGI](https://docs.cesanta.com/mongoose/master/#/http/cgi.md/) support +- `MG_ENABLE_HTTP_SSI` Enable [Server SIde Includes](https://docs.cesanta.com/mongoose/master/#/http/ssi.md/) support - `MG_ENABLE_HTTP_WEBDAV` enable WebDAV extensions to HTTP +- `MG_ENABLE_HTTP_WEBSOCKET` enable WebSocket extension to HTTP (on by default, =0 to disable) - `MG_ENABLE_GETADDRINFO` enable `getaddrinfo()` in `mg_resolve2()` - `MG_ENABLE_THREADS` enable `mg_start_thread()` API diff --git a/examples/mqtt_broker/Makefile b/examples/mqtt_broker/Makefile index 0028d1af4986b06de2d1fe464474166491aa693a..f0c17a0e0daaed2a74d8425a185f4953a76e6f12 100644 --- a/examples/mqtt_broker/Makefile +++ b/examples/mqtt_broker/Makefile @@ -1,4 +1,4 @@ PROG = mqtt_broker -MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER +MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER -DMG_ENABLE_HTTP=0 SSL_LIB=openssl include ../examples.mk diff --git a/examples/mqtt_client/Makefile b/examples/mqtt_client/Makefile index 900fb580445844808ed93d42b0de7cde52f78d6d..a06c0ad4afb5753081cf145913e5a3d44e4ccb46 100644 --- a/examples/mqtt_client/Makefile +++ b/examples/mqtt_client/Makefile @@ -1,4 +1,4 @@ PROG = mqtt_client -MODULE_CFLAGS = -DMG_ENABLE_MQTT +MODULE_CFLAGS = -DMG_ENABLE_MQTT -DMG_ENABLE_HTTP=0 SSL_LIB=openssl include ../examples.mk diff --git a/examples/restful_server/Makefile b/examples/restful_server/Makefile index f481ef3048280654559432ce20159614ca3a3186..6961bbd0128cb9f99571b6960b056f3e695424ea 100644 --- a/examples/restful_server/Makefile +++ b/examples/restful_server/Makefile @@ -1,3 +1,3 @@ PROG = restful_server -MODULE_CFLAGS=-DMG_ENABLE_THREADS -DMG_DISABLE_HTTP_WEBSOCKET +MODULE_CFLAGS=-DMG_ENABLE_THREADS -DMG_ENABLE_HTTP_WEBSOCKET=0 include ../examples.mk diff --git a/examples/restful_server/restful_server.c b/examples/restful_server/restful_server.c index 9419755a8e9025b31db3c47287a2bf36f73baf66..264989e4ba138dfc72168bad17e40a04c1b4f452 100644 --- a/examples/restful_server/restful_server.c +++ b/examples/restful_server/restful_server.c @@ -86,7 +86,7 @@ int main(int argc, char *argv[]) { s_http_server_opts.per_directory_auth_file = argv[++i]; } else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) { s_http_server_opts.url_rewrites = argv[++i]; -#if !MG_DISABLE_CGI +#if MG_ENABLE_HTTP_CGI } else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) { s_http_server_opts.cgi_interpreter = argv[++i]; #endif diff --git a/mongoose.c b/mongoose.c index 10ff08dc4496641fd25a0bf31bb5f6b208023ec0..74e9b9feaf5dd2a818d1b61239b6f9604b34aaf4 100644 --- a/mongoose.c +++ b/mongoose.c @@ -71,34 +71,11 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *c); MG_INTERNAL struct mg_connection *mg_create_connection( struct mg_mgr *mgr, mg_event_handler_t callback, struct mg_add_sock_opts opts); -#if MG_ENABLE_FILESYSTEM -MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm, - const struct mg_serve_http_opts *opts, - char **local_path, - struct mg_str *remainder); -MG_INTERNAL time_t mg_parse_date_string(const char *datetime); -MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st); -#endif #ifdef _WIN32 /* Retur value is the same as for MultiByteToWideChar. */ int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len); #endif -/* - * Reassemble the content of the buffer (buf, blen) which should be - * in the HTTP chunked encoding, by collapsing data chunks to the - * beginning of the buffer. - * - * If chunks get reassembled, modify hm->body to point to the reassembled - * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK - * in nc->flags, delete reassembled body from the mbuf. - * - * Return reassembled body size. - */ -MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc, - struct http_message *hm, char *buf, - size_t blen); - struct ctl_msg { mg_event_handler_t callback; char message[MG_CTL_MSG_MESSAGE_SIZE]; @@ -117,7 +94,31 @@ extern void *(*test_calloc)(size_t count, size_t size); #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif -#if !MG_DISABLE_HTTP && MG_ENABLE_CGI +#if MG_ENABLE_HTTP +/* + * Reassemble the content of the buffer (buf, blen) which should be + * in the HTTP chunked encoding, by collapsing data chunks to the + * beginning of the buffer. + * + * If chunks get reassembled, modify hm->body to point to the reassembled + * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK + * in nc->flags, delete reassembled body from the mbuf. + * + * Return reassembled body size. + */ +MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc, + struct http_message *hm, char *buf, + size_t blen); + +#if MG_ENABLE_FILESYSTEM +MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm, + const struct mg_serve_http_opts *opts, + char **local_path, + struct mg_str *remainder); +MG_INTERNAL time_t mg_parse_date_string(const char *datetime); +MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st); +#endif +#if MG_ENABLE_HTTP_CGI MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, const struct mg_str *path_info, const struct http_message *hm, @@ -125,7 +126,13 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, struct mg_http_proto_data_cgi; MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d); #endif -#if !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV +#if MG_ENABLE_HTTP_SSI +MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc, + struct http_message *hm, + const char *path, + const struct mg_serve_http_opts *opts); +#endif +#if MG_ENABLE_HTTP_WEBDAV MG_INTERNAL int mg_is_dav_request(const struct mg_str *s); MG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path, cs_stat_t *stp, struct http_message *hm, @@ -142,6 +149,12 @@ MG_INTERNAL void mg_handle_delete(struct mg_connection *nc, MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path, struct http_message *hm); #endif +#if MG_ENABLE_HTTP_WEBSOCKET +MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev, void *ev_data); +MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc, + const struct mg_str *key); +#endif +#endif /* MG_ENABLE_HTTP */ MG_INTERNAL int mg_get_errno(); @@ -3818,17 +3831,13 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) { * All rights reserved */ -#if !MG_DISABLE_HTTP +#if MG_ENABLE_HTTP /* Amalgamated: #include "mongoose/src/internal.h" */ /* Amalgamated: #include "mongoose/src/util.h" */ /* Amalgamated: #include "common/sha1.h" */ /* Amalgamated: #include "common/md5.h" */ -#if !MG_DISABLE_HTTP_WEBSOCKET -#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1) -#endif - static const char *mg_version_header = "Mongoose/" MG_VERSION; enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT }; @@ -3841,7 +3850,7 @@ struct mg_http_proto_data_file { enum mg_http_proto_data_type type; }; -#if MG_ENABLE_CGI +#if MG_ENABLE_HTTP_CGI struct mg_http_proto_data_cgi { struct mg_connection *cgi_nc; }; @@ -3883,7 +3892,7 @@ struct mg_http_proto_data { #if MG_ENABLE_FILESYSTEM struct mg_http_proto_data_file file; #endif -#if MG_ENABLE_CGI +#if MG_ENABLE_HTTP_CGI struct mg_http_proto_data_cgi cgi; #endif #if MG_ENABLE_HTTP_STREAMING_MULTIPART @@ -3945,7 +3954,7 @@ static void mg_http_conn_destructor(void *proto_data) { #if MG_ENABLE_FILESYSTEM mg_http_free_proto_data_file(&pd->file); #endif -#if MG_ENABLE_CGI +#if MG_ENABLE_HTTP_CGI mg_http_free_proto_data_cgi(&pd->cgi); #endif #if MG_ENABLE_HTTP_STREAMING_MULTIPART @@ -4185,877 +4194,580 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name) { return NULL; } -#if !MG_DISABLE_HTTP_WEBSOCKET +#if MG_ENABLE_FILESYSTEM +static void mg_http_transfer_file_data(struct mg_connection *nc) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); + char buf[MG_MAX_HTTP_SEND_MBUF]; + size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent); -static int mg_is_ws_fragment(unsigned char flags) { - return (flags & 0x80) == 0 || (flags & 0x0f) == 0; -} + if (pd->file.type == DATA_FILE) { + struct mbuf *io = &nc->send_mbuf; + if (io->len < sizeof(buf)) { + to_read = sizeof(buf) - io->len; + } -static int mg_is_ws_first_fragment(unsigned char flags) { - return (flags & 0x80) == 0 && (flags & 0x0f) != 0; -} + if (left > 0 && to_read > left) { + to_read = left; + } -static void mg_handle_incoming_websocket_frame(struct mg_connection *nc, - struct websocket_message *wsm) { - if (wsm->flags & 0x8) { - mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm); - } else { - mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm); + if (to_read == 0) { + /* Rate limiting. send_mbuf is too full, wait until it's drained. */ + } else if (pd->file.sent < pd->file.cl && + (n = fread(buf, 1, to_read, pd->file.fp)) > 0) { + mg_send(nc, buf, n); + pd->file.sent += n; + } else { + if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE; + mg_http_free_proto_data_file(&pd->file); + } + } else if (pd->file.type == DATA_PUT) { + struct mbuf *io = &nc->recv_mbuf; + size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len; + size_t n = fwrite(io->buf, 1, to_write, pd->file.fp); + if (n > 0) { + mbuf_remove(io, n); + pd->file.sent += n; + } + if (n == 0 || pd->file.sent >= pd->file.cl) { + if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE; + mg_http_free_proto_data_file(&pd->file); + } + } +#if MG_ENABLE_HTTP_CGI + else if (pd->cgi.cgi_nc != NULL) { + /* This is POST data that needs to be forwarded to the CGI process */ + if (pd->cgi.cgi_nc != NULL) { + mg_forward(nc, pd->cgi.cgi_nc); + } else { + nc->flags |= MG_F_SEND_AND_CLOSE; + } } +#endif } +#endif /* MG_ENABLE_FILESYSTEM */ -static int mg_deliver_websocket_data(struct mg_connection *nc) { - /* Using unsigned char *, cause of integer arithmetic below */ - uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len, - mask_len = 0, header_len = 0; - unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p, - *e = p + buf_len; - unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */ - int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) && - !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG); +/* + * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or + * if it's incomplete. If the chunk is fully buffered, return total number of + * bytes in a chunk, and store data in `data`, `data_len`. + */ +static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data, + size_t *chunk_len) { + unsigned char *s = (unsigned char *) buf; + size_t n = 0; /* scanned chunk length */ + size_t i = 0; /* index in s */ - /* If that's a continuation frame that must be reassembled, handle it */ - if (reass && !mg_is_ws_first_fragment(p[0]) && - buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) { - buf += 1 + sizeof(*sizep) + *sizep; - buf_len -= 1 + sizeof(*sizep) + *sizep; + /* Scan chunk length. That should be a hexadecimal number. */ + while (i < len && isxdigit(s[i])) { + n *= 16; + n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10; + i++; } - if (buf_len >= 2) { - len = buf[1] & 127; - mask_len = buf[1] & 128 ? 4 : 0; - if (len < 126 && buf_len >= mask_len) { - data_len = len; - header_len = 2 + mask_len; - } else if (len == 126 && buf_len >= 4 + mask_len) { - header_len = 4 + mask_len; - data_len = ntohs(*(uint16_t *) &buf[2]); - } else if (buf_len >= 10 + mask_len) { - header_len = 10 + mask_len; - data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) + - ntohl(*(uint32_t *) &buf[6]); - } + /* Skip new line */ + if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') { + return 0; } + i += 2; - frame_len = header_len + data_len; - ok = frame_len > 0 && frame_len <= buf_len; + /* Record where the data is */ + *chunk_data = (char *) s + i; + *chunk_len = n; - if (ok) { - struct websocket_message wsm; + /* Skip data */ + i += n; - wsm.size = (size_t) data_len; - wsm.data = buf + header_len; - wsm.flags = buf[0]; + /* Skip new line */ + if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') { + return 0; + } + return i + 2; +} - /* Apply mask if necessary */ - if (mask_len > 0) { - for (i = 0; i < data_len; i++) { - buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4]; - } +MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc, + struct http_message *hm, char *buf, + size_t blen) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); + char *data; + size_t i, n, data_len, body_len, zero_chunk_received = 0; + /* Find out piece of received data that is not yet reassembled */ + body_len = (size_t) pd->chunk.body_len; + assert(blen >= body_len); + + /* Traverse all fully buffered chunks */ + for (i = body_len; + (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0; + i += n) { + /* Collapse chunk data to the rest of HTTP body */ + memmove(buf + body_len, data, data_len); + body_len += data_len; + hm->body.len = body_len; + + if (data_len == 0) { + zero_chunk_received = 1; + i += n; + break; } + } - if (reass) { - /* On first fragmented frame, nullify size */ - if (mg_is_ws_first_fragment(wsm.flags)) { - mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep)); - p[0] &= ~0x0f; /* Next frames will be treated as continuation */ - buf = p + 1 + sizeof(*sizep); - *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */ - } + if (i > body_len) { + /* Shift unparsed content to the parsed body */ + assert(i <= blen); + memmove(buf + body_len, buf + i, blen - i); + memset(buf + body_len + blen - i, 0, i - body_len); + nc->recv_mbuf.len -= i - body_len; + pd->chunk.body_len = body_len; - /* Append this frame to the reassembled buffer */ - memmove(buf, wsm.data, e - wsm.data); - (*sizep) += wsm.size; - nc->recv_mbuf.len -= wsm.data - buf; + /* Send MG_EV_HTTP_CHUNK event */ + nc->flags &= ~MG_F_DELETE_CHUNK; + mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm); - /* On last fragmented frame - call user handler and remove data */ - if (wsm.flags & 0x80) { - wsm.data = p + 1 + sizeof(*sizep); - wsm.size = *sizep; - mg_handle_incoming_websocket_frame(nc, &wsm); - mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep); - } - } else { - /* TODO(lsm): properly handle OOB control frames during defragmentation */ - mg_handle_incoming_websocket_frame(nc, &wsm); - mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */ + /* Delete processed data if user set MG_F_DELETE_CHUNK flag */ + if (nc->flags & MG_F_DELETE_CHUNK) { + memset(buf, 0, body_len); + memmove(buf, buf + body_len, blen - i); + nc->recv_mbuf.len -= body_len; + hm->body.len = 0; + pd->chunk.body_len = 0; } - /* If client closes, close too */ - if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) { - nc->flags |= MG_F_SEND_AND_CLOSE; + if (zero_chunk_received) { + hm->message.len = (size_t) pd->chunk.body_len + blen - i; } } - return ok; + return body_len; } -struct ws_mask_ctx { - size_t pos; /* zero means unmasked */ - uint32_t mask; -}; - -static uint32_t mg_ws_random_mask(void) { - uint32_t mask; -/* - * The spec requires WS client to generate hard to - * guess mask keys. From RFC6455, Section 5.3: - * - * The unpredictability of the masking key is essential to prevent - * authors of malicious applications from selecting the bytes that appear on - * the wire. - * - * Hence this feature is essential when the actual end user of this API - * is untrusted code that wouldn't have access to a lower level net API - * anyway (e.g. web browsers). Hence this feature is low prio for most - * mongoose use cases and thus can be disabled, e.g. when porting to a platform - * that lacks rand(). - */ -#if MG_DISABLE_WS_RANDOM_MASK - mask = 0xefbeadde; /* generated with a random number generator, I swear */ -#else - if (sizeof(long) >= 4) { - mask = (uint32_t) rand(); - } else if (sizeof(long) == 2) { - mask = (uint32_t) rand() << 16 | (uint32_t) rand(); +static mg_event_handler_t mg_http_get_endpoint_handler( + struct mg_connection *nc, struct mg_str *uri_path) { + struct mg_http_proto_data *pd; + mg_event_handler_t ret = NULL; + int matched, matched_max = 0; + struct mg_http_endpoint *ep; + + if (nc == NULL) { + return NULL; } -#endif - return mask; -} -static void mg_send_ws_header(struct mg_connection *nc, int op, size_t len, - struct ws_mask_ctx *ctx) { - int header_len; - unsigned char header[10]; + pd = mg_http_get_proto_data(nc); - header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f); - if (len < 126) { - header[1] = (unsigned char) len; - header_len = 2; - } else if (len < 65535) { - uint16_t tmp = htons((uint16_t) len); - header[1] = 126; - memcpy(&header[2], &tmp, sizeof(tmp)); - header_len = 4; - } else { - uint32_t tmp; - header[1] = 127; - tmp = htonl((uint32_t)((uint64_t) len >> 32)); - memcpy(&header[2], &tmp, sizeof(tmp)); - tmp = htonl((uint32_t)(len & 0xffffffff)); - memcpy(&header[6], &tmp, sizeof(tmp)); - header_len = 10; - } + ep = pd->endpoints; + while (ep != NULL) { + const struct mg_str name_s = {ep->name, ep->name_len}; + if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) { + if (matched > matched_max) { + /* Looking for the longest suitable handler */ + ret = ep->handler; + matched_max = matched; + } + } - /* client connections enable masking */ - if (nc->listener == NULL) { - header[1] |= 1 << 7; /* set masking flag */ - mg_send(nc, header, header_len); - ctx->mask = mg_ws_random_mask(); - mg_send(nc, &ctx->mask, sizeof(ctx->mask)); - ctx->pos = nc->send_mbuf.len; - } else { - mg_send(nc, header, header_len); - ctx->pos = 0; + ep = ep->next; } -} -static void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) { - size_t i; - if (ctx->pos == 0) return; - for (i = 0; i < (mbuf->len - ctx->pos); i++) { - mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4]; - } + return ret; } -void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data, - size_t len) { - struct ws_mask_ctx ctx; - DBG(("%p %d %d", nc, op, (int) len)); - mg_send_ws_header(nc, op, len, &ctx); - mg_send(nc, data, len); - - mg_ws_mask_frame(&nc->send_mbuf, &ctx); +static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev, + struct http_message *hm) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - if (op == WEBSOCKET_OP_CLOSE) { - nc->flags |= MG_F_SEND_AND_CLOSE; + if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) { + pd->endpoint_handler = + ev == MG_EV_HTTP_REQUEST + ? mg_http_get_endpoint_handler(nc->listener, &hm->uri) + : NULL; } + mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev, + hm); } -void mg_send_websocket_framev(struct mg_connection *nc, int op, - const struct mg_str *strv, int strvcnt) { - struct ws_mask_ctx ctx; - int i; - int len = 0; - for (i = 0; i < strvcnt; i++) { - len += strv[i].len; - } +#if MG_ENABLE_HTTP_STREAMING_MULTIPART +static void mg_http_multipart_continue(struct mg_connection *nc); - mg_send_ws_header(nc, op, len, &ctx); +static void mg_http_multipart_begin(struct mg_connection *nc, + struct http_message *hm, int req_len); - for (i = 0; i < strvcnt; i++) { - mg_send(nc, strv[i].p, strv[i].len); - } +#endif - mg_ws_mask_frame(&nc->send_mbuf, &ctx); +/* + * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here) + * If a big structure is declared in a big function, lx106 gcc will make it + * even bigger (round up to 4k, from 700 bytes of actual size). + */ +#ifdef __xtensa__ +static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data, + struct http_message *hm) __attribute__((noinline)); - if (op == WEBSOCKET_OP_CLOSE) { - nc->flags |= MG_F_SEND_AND_CLOSE; - } +void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { + struct http_message hm; + mg_http_handler2(nc, ev, ev_data, &hm); } -void mg_printf_websocket_frame(struct mg_connection *nc, int op, - const char *fmt, ...) { - char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem; - va_list ap; - int len; +static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data, + struct http_message *hm) { +#else /* !__XTENSA__ */ +void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { + struct http_message shm; + struct http_message *hm = &shm; +#endif /* __XTENSA__ */ + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); + struct mbuf *io = &nc->recv_mbuf; + int req_len; + const int is_req = (nc->listener != NULL); +#if MG_ENABLE_HTTP_WEBSOCKET + struct mg_str *vec; +#endif + if (ev == MG_EV_CLOSE) { +#if MG_ENABLE_HTTP_STREAMING_MULTIPART + if (pd->mp_stream.boundary != NULL) { + /* + * Multipart message is in progress, but we get close + * MG_EV_HTTP_PART_END with error flag + */ + struct mg_http_multipart_part mp; + memset(&mp, 0, sizeof(mp)); - va_start(ap, fmt); - if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { - mg_send_websocket_frame(nc, op, buf, len); + mp.status = -1; + mp.var_name = pd->mp_stream.var_name; + mp.file_name = pd->mp_stream.file_name; + mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler), + MG_EV_HTTP_PART_END, &mp); + } else +#endif + if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) { + /* + * For HTTP messages without Content-Length, always send HTTP message + * before MG_EV_CLOSE message. + */ + int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; + hm->message.len = io->len; + hm->body.len = io->buf + io->len - hm->body.p; + mg_http_call_endpoint_handler(nc, ev2, hm); + } } - va_end(ap); - if (buf != mem && buf != NULL) { - MG_FREE(buf); +#if MG_ENABLE_FILESYSTEM + if (pd->file.fp != NULL) { + mg_http_transfer_file_data(nc); } -} +#endif -static void mg_websocket_handler(struct mg_connection *nc, int ev, - void *ev_data) { mg_call(nc, nc->handler, ev, ev_data); - switch (ev) { - case MG_EV_RECV: - do { - } while (mg_deliver_websocket_data(nc)); - break; - case MG_EV_POLL: - /* Ping idle websocket connections */ - { - time_t now = *(time_t *) ev_data; - if (nc->flags & MG_F_IS_WEBSOCKET && - now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) { - mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "", 0); - } - } - break; - default: - break; - } -} + if (ev == MG_EV_RECV) { + struct mg_str *s; -#ifndef MG_EXT_SHA1 -static void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], - const size_t *msg_lens, uint8_t *digest) { - size_t i; - cs_sha1_ctx sha_ctx; - cs_sha1_init(&sha_ctx); - for (i = 0; i < num_msgs; i++) { - cs_sha1_update(&sha_ctx, msgs[i], msg_lens[i]); - } - cs_sha1_final(digest, &sha_ctx); -} -#else -extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], - const size_t *msg_lens, uint8_t *digest); -#endif +#if MG_ENABLE_HTTP_STREAMING_MULTIPART + if (pd->mp_stream.boundary != NULL) { + mg_http_multipart_continue(nc); + return; + } +#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ -static void mg_ws_handshake(struct mg_connection *nc, - const struct mg_str *key) { - static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic}; - const size_t msg_lens[2] = {key->len, 36}; - unsigned char sha[20]; - char b64_sha[30]; + req_len = mg_parse_http(io->buf, io->len, hm, is_req); - mg_hash_sha1_v(2, msgs, msg_lens, sha); - mg_base64_encode(sha, sizeof(sha), b64_sha); - mg_printf(nc, "%s%s%s", - "HTTP/1.1 101 Switching Protocols\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Accept: ", - b64_sha, "\r\n\r\n"); - DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha)); -} + if (req_len > 0 && + (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL && + mg_vcasecmp(s, "chunked") == 0) { + mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len); + } -#endif /* MG_DISABLE_HTTP_WEBSOCKET */ - -#if MG_ENABLE_FILESYSTEM -static void mg_http_transfer_file_data(struct mg_connection *nc) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - char buf[MG_MAX_HTTP_SEND_MBUF]; - size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent); - - if (pd->file.type == DATA_FILE) { - struct mbuf *io = &nc->send_mbuf; - if (io->len < sizeof(buf)) { - to_read = sizeof(buf) - io->len; +#if MG_ENABLE_HTTP_STREAMING_MULTIPART + if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL && + s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) { + mg_http_multipart_begin(nc, hm, req_len); + mg_http_multipart_continue(nc); + return; } +#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ - if (left > 0 && to_read > left) { - to_read = left; + /* TODO(alashkin): refactor this ifelseifelseifelseifelse */ + if ((req_len < 0 || + (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) { + DBG(("invalid request")); + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } else if (req_len == 0) { + /* Do nothing, request is not yet fully buffered */ } +#if MG_ENABLE_HTTP_WEBSOCKET + else if (nc->listener == NULL && + mg_get_http_header(hm, "Sec-WebSocket-Accept")) { + /* We're websocket client, got handshake response from server. */ + /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ + mbuf_remove(io, req_len); + nc->proto_handler = mg_ws_handler; + nc->flags |= MG_F_IS_WEBSOCKET; + mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL); + mg_ws_handler(nc, MG_EV_RECV, ev_data); + } else if (nc->listener != NULL && + (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) { + /* This is a websocket request. Switch protocol handlers. */ + mbuf_remove(io, req_len); + nc->proto_handler = mg_ws_handler; + nc->flags |= MG_F_IS_WEBSOCKET; - if (to_read == 0) { - /* Rate limiting. send_mbuf is too full, wait until it's drained. */ - } else if (pd->file.sent < pd->file.cl && - (n = fread(buf, 1, to_read, pd->file.fp)) > 0) { - mg_send(nc, buf, n); - pd->file.sent += n; - } else { - if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE; - mg_http_free_proto_data_file(&pd->file); - } - } else if (pd->file.type == DATA_PUT) { - struct mbuf *io = &nc->recv_mbuf; - size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len; - size_t n = fwrite(io->buf, 1, to_write, pd->file.fp); - if (n > 0) { - mbuf_remove(io, n); - pd->file.sent += n; - } - if (n == 0 || pd->file.sent >= pd->file.cl) { - if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE; - mg_http_free_proto_data_file(&pd->file); - } - } -#if MG_ENABLE_CGI - else if (pd->cgi.cgi_nc != NULL) { - /* This is POST data that needs to be forwarded to the CGI process */ - if (pd->cgi.cgi_nc != NULL) { - mg_forward(nc, pd->cgi.cgi_nc); - } else { - nc->flags |= MG_F_SEND_AND_CLOSE; + /* Send handshake */ + mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm); + if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) { + if (nc->send_mbuf.len == 0) { + mg_ws_handshake(nc, vec); + } + mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL); + mg_ws_handler(nc, MG_EV_RECV, ev_data); + } } - } -#endif -} -#endif /* MG_ENABLE_FILESYSTEM */ - -/* - * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or - * if it's incomplete. If the chunk is fully buffered, return total number of - * bytes in a chunk, and store data in `data`, `data_len`. - */ -static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data, - size_t *chunk_len) { - unsigned char *s = (unsigned char *) buf; - size_t n = 0; /* scanned chunk length */ - size_t i = 0; /* index in s */ - - /* Scan chunk length. That should be a hexadecimal number. */ - while (i < len && isxdigit(s[i])) { - n *= 16; - n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10; - i++; - } +#endif /* MG_ENABLE_HTTP_WEBSOCKET */ + else if (hm->message.len <= io->len) { + int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; - /* Skip new line */ - if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') { - return 0; - } - i += 2; +/* Whole HTTP message is fully buffered, call event handler */ - /* Record where the data is */ - *chunk_data = (char *) s + i; - *chunk_len = n; +#if MG_ENABLE_JAVASCRIPT + v7_val_t v1, v2, headers, req, args, res; + struct v7 *v7 = nc->mgr->v7; + const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv"; + int i, js_callback_handled_request = 0; - /* Skip data */ - i += n; + if (v7 != NULL) { + /* Lookup JS callback */ + v1 = v7_get(v7, v7_get_global(v7), "Http", ~0); + v2 = v7_get(v7, v1, ev_name, ~0); - /* Skip new line */ - if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') { - return 0; - } - return i + 2; -} + /* Create callback params. TODO(lsm): own/disown those */ + args = v7_mk_array(v7); + req = v7_mk_object(v7); + headers = v7_mk_object(v7); -MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc, - struct http_message *hm, char *buf, - size_t blen) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - char *data; - size_t i, n, data_len, body_len, zero_chunk_received = 0; - /* Find out piece of received data that is not yet reassembled */ - body_len = (size_t) pd->chunk.body_len; - assert(blen >= body_len); + /* Populate request object */ + v7_set(v7, req, "method", ~0, + v7_mk_string(v7, hm->method.p, hm->method.len, 1)); + v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1)); + v7_set(v7, req, "body", ~0, + v7_mk_string(v7, hm->body.p, hm->body.len, 1)); + v7_set(v7, req, "headers", ~0, headers); + for (i = 0; hm->header_names[i].len > 0; i++) { + const struct mg_str *name = &hm->header_names[i]; + const struct mg_str *value = &hm->header_values[i]; + v7_set(v7, headers, name->p, name->len, + v7_mk_string(v7, value->p, value->len, 1)); + } - /* Traverse all fully buffered chunks */ - for (i = body_len; - (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0; - i += n) { - /* Collapse chunk data to the rest of HTTP body */ - memmove(buf + body_len, data, data_len); - body_len += data_len; - hm->body.len = body_len; + /* Invoke callback. TODO(lsm): report errors */ + v7_array_push(v7, args, v7_mk_foreign(v7, nc)); + v7_array_push(v7, args, req); + if (v7_apply(v7, v2, V7_UNDEFINED, args, &res) == V7_OK && + v7_is_truthy(v7, res)) { + js_callback_handled_request++; + } + } - if (data_len == 0) { - zero_chunk_received = 1; - i += n; - break; + /* If JS callback returns true, stop request processing */ + if (js_callback_handled_request) { + nc->flags |= MG_F_SEND_AND_CLOSE; + } else { + mg_http_call_endpoint_handler(nc, trigger_ev, hm); + } +#else + mg_http_call_endpoint_handler(nc, trigger_ev, hm); +#endif + mbuf_remove(io, hm->message.len); } } + (void) pd; +} - if (i > body_len) { - /* Shift unparsed content to the parsed body */ - assert(i <= blen); - memmove(buf + body_len, buf + i, blen - i); - memset(buf + body_len + blen - i, 0, i - body_len); - nc->recv_mbuf.len -= i - body_len; - pd->chunk.body_len = body_len; +static size_t mg_get_line_len(const char *buf, size_t buf_len) { + size_t len = 0; + while (len < buf_len && buf[len] != '\n') len++; + return len == buf_len ? 0 : len + 1; +} - /* Send MG_EV_HTTP_CHUNK event */ - nc->flags &= ~MG_F_DELETE_CHUNK; - mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm); +#if MG_ENABLE_HTTP_STREAMING_MULTIPART +static void mg_http_multipart_begin(struct mg_connection *nc, + struct http_message *hm, int req_len) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); + struct mg_str *ct; + struct mbuf *io = &nc->recv_mbuf; - /* Delete processed data if user set MG_F_DELETE_CHUNK flag */ - if (nc->flags & MG_F_DELETE_CHUNK) { - memset(buf, 0, body_len); - memmove(buf, buf + body_len, blen - i); - nc->recv_mbuf.len -= body_len; - hm->body.len = 0; - pd->chunk.body_len = 0; - } + char boundary[100]; + int boundary_len; - if (zero_chunk_received) { - hm->message.len = (size_t) pd->chunk.body_len + blen - i; - } + if (nc->listener == NULL) { + /* No streaming for replies now */ + goto exit_mp; } - return body_len; -} + ct = mg_get_http_header(hm, "Content-Type"); + if (ct == NULL) { + /* We need more data - or it isn't multipart mesage */ + goto exit_mp; + } -static mg_event_handler_t mg_http_get_endpoint_handler( - struct mg_connection *nc, struct mg_str *uri_path) { - struct mg_http_proto_data *pd; - mg_event_handler_t ret = NULL; - int matched, matched_max = 0; - struct mg_http_endpoint *ep; + /* Content-type should start with "multipart" */ + if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) { + goto exit_mp; + } - if (nc == NULL) { - return NULL; + boundary_len = + mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); + if (boundary_len == 0) { + /* + * Content type is multipart, but there is no boundary, + * probably malformed request + */ + nc->flags = MG_F_CLOSE_IMMEDIATELY; + DBG(("invalid request")); + goto exit_mp; } - pd = mg_http_get_proto_data(nc); + /* If we reach this place - that is multipart request */ - ep = pd->endpoints; - while (ep != NULL) { - const struct mg_str name_s = {ep->name, ep->name_len}; - if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) { - if (matched > matched_max) { - /* Looking for the longest suitable handler */ - ret = ep->handler; - matched_max = matched; - } + if (pd->mp_stream.boundary != NULL) { + /* + * Another streaming request was in progress, + * looks like protocol error + */ + nc->flags |= MG_F_CLOSE_IMMEDIATELY; + } else { + pd->mp_stream.state = MPS_BEGIN; + pd->mp_stream.boundary = strdup(boundary); + pd->mp_stream.boundary_len = strlen(boundary); + pd->mp_stream.var_name = pd->mp_stream.file_name = NULL; + + pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri); + if (pd->endpoint_handler == NULL) { + pd->endpoint_handler = nc->handler; } - ep = ep->next; - } + mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm); - return ret; + mbuf_remove(io, req_len); + } +exit_mp: + ; } -static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev, - struct http_message *hm) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); +#define CONTENT_DISPOSITION "Content-Disposition: " - if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) { - pd->endpoint_handler = - ev == MG_EV_HTTP_REQUEST - ? mg_http_get_endpoint_handler(nc->listener, &hm->uri) - : NULL; - } - mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev, - hm); +static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, + const char *data, size_t data_len) { + struct mg_http_multipart_part mp; + struct mg_http_proto_data *pd = mg_http_get_proto_data(c); + memset(&mp, 0, sizeof(mp)); + + mp.var_name = pd->mp_stream.var_name; + mp.file_name = pd->mp_stream.file_name; + mp.user_data = pd->mp_stream.user_data; + mp.data.p = data; + mp.data.len = data_len; + mg_call(c, pd->endpoint_handler, ev, &mp); + pd->mp_stream.user_data = mp.user_data; } -#if MG_ENABLE_HTTP_STREAMING_MULTIPART -static void mg_http_multipart_continue(struct mg_connection *nc); +static int mg_http_multipart_got_chunk(struct mg_connection *c) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(c); + struct mbuf *io = &c->recv_mbuf; -static void mg_http_multipart_begin(struct mg_connection *nc, - struct http_message *hm, int req_len); + mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, + pd->mp_stream.prev_io_len); + mbuf_remove(io, pd->mp_stream.prev_io_len); + pd->mp_stream.prev_io_len = 0; + pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; -#endif + return 0; +} -/* - * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here) - * If a big structure is declared in a big function, lx106 gcc will make it - * even bigger (round up to 4k, from 700 bytes of actual size). - */ -#ifdef __xtensa__ -static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data, - struct http_message *hm) __attribute__((noinline)); +static int mg_http_multipart_finalize(struct mg_connection *c) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(c); -void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { - struct http_message hm; - mg_http_handler2(nc, ev, ev_data, &hm); + mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); + mg_http_free_proto_data_mp_stream(&pd->mp_stream); + pd->mp_stream.state = MPS_FINISHED; + + return 1; } -static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data, - struct http_message *hm) { -#else /* !__XTENSA__ */ -void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) { - struct http_message shm; - struct http_message *hm = &shm; -#endif /* __XTENSA__ */ - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - struct mbuf *io = &nc->recv_mbuf; - int req_len; - const int is_req = (nc->listener != NULL); -#if !MG_DISABLE_HTTP_WEBSOCKET - struct mg_str *vec; -#endif - if (ev == MG_EV_CLOSE) { -#if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (pd->mp_stream.boundary != NULL) { - /* - * Multipart message is in progress, but we get close - * MG_EV_HTTP_PART_END with error flag - */ - struct mg_http_multipart_part mp; - memset(&mp, 0, sizeof(mp)); +static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { + const char *boundary; + struct mbuf *io = &c->recv_mbuf; + struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - mp.status = -1; - mp.var_name = pd->mp_stream.var_name; - mp.file_name = pd->mp_stream.file_name; - mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler), - MG_EV_HTTP_PART_END, &mp); - } else -#endif - if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) { - /* - * For HTTP messages without Content-Length, always send HTTP message - * before MG_EV_CLOSE message. - */ - int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; - hm->message.len = io->len; - hm->body.len = io->buf + io->len - hm->body.p; - mg_http_call_endpoint_handler(nc, ev2, hm); - } + if ((int) io->len < pd->mp_stream.boundary_len + 2) { + return 0; } -#if MG_ENABLE_FILESYSTEM - if (pd->file.fp != NULL) { - mg_http_transfer_file_data(nc); + boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); + if (boundary != NULL) { + if (io->len - (boundary - io->buf) < 4) { + return 0; + } + if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) { + pd->mp_stream.state = MPS_FINALIZE; + } else { + pd->mp_stream.state = MPS_GOT_BOUNDARY; + } + } else { + return 0; } -#endif - - mg_call(nc, nc->handler, ev, ev_data); - - if (ev == MG_EV_RECV) { - struct mg_str *s; -#if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (pd->mp_stream.boundary != NULL) { - mg_http_multipart_continue(nc); - return; - } -#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ + return 1; +} - req_len = mg_parse_http(io->buf, io->len, hm, is_req); +static int mg_http_multipart_process_boundary(struct mg_connection *c) { + int data_size; + const char *boundary, *block_begin; + struct mbuf *io = &c->recv_mbuf; + struct mg_http_proto_data *pd = mg_http_get_proto_data(c); + char file_name[100], var_name[100]; + int line_len; + boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); + block_begin = boundary + pd->mp_stream.boundary_len + 2; + data_size = io->len - (block_begin - io->buf); - if (req_len > 0 && - (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL && - mg_vcasecmp(s, "chunked") == 0) { - mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len); - } + while (data_size > 0 && + (line_len = mg_get_line_len(block_begin, data_size)) != 0) { + if (line_len > (int) sizeof(CONTENT_DISPOSITION) && + mg_ncasecmp(block_begin, CONTENT_DISPOSITION, + sizeof(CONTENT_DISPOSITION) - 1) == 0) { + struct mg_str header; -#if MG_ENABLE_HTTP_STREAMING_MULTIPART - if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL && - s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) { - mg_http_multipart_begin(nc, hm, req_len); - mg_http_multipart_continue(nc); - return; + header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1; + header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1; + mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2); + mg_http_parse_header(&header, "filename", file_name, + sizeof(file_name) - 2); + block_begin += line_len; + data_size -= line_len; + continue; } -#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ - /* TODO(alashkin): refactor this ifelseifelseifelseifelse */ - if ((req_len < 0 || - (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) { - DBG(("invalid request")); - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } else if (req_len == 0) { - /* Do nothing, request is not yet fully buffered */ - } -#if !MG_DISABLE_HTTP_WEBSOCKET - else if (nc->listener == NULL && - mg_get_http_header(hm, "Sec-WebSocket-Accept")) { - /* We're websocket client, got handshake response from server. */ - /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ - mbuf_remove(io, req_len); - nc->proto_handler = mg_websocket_handler; - nc->flags |= MG_F_IS_WEBSOCKET; - mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL); - mg_websocket_handler(nc, MG_EV_RECV, ev_data); - } else if (nc->listener != NULL && - (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) { - /* This is a websocket request. Switch protocol handlers. */ - mbuf_remove(io, req_len); - nc->proto_handler = mg_websocket_handler; - nc->flags |= MG_F_IS_WEBSOCKET; + if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) { + mbuf_remove(io, block_begin - io->buf + 2); - /* Send handshake */ - mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm); - if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) { - if (nc->send_mbuf.len == 0) { - mg_ws_handshake(nc, vec); - } - mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL); - mg_websocket_handler(nc, MG_EV_RECV, ev_data); + if (pd->mp_stream.processing_part != 0) { + mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); } + + free((void *) pd->mp_stream.file_name); + pd->mp_stream.file_name = strdup(file_name); + free((void *) pd->mp_stream.var_name); + pd->mp_stream.var_name = strdup(var_name); + + mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0); + pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; + pd->mp_stream.processing_part++; + return 1; } -#endif /* MG_DISABLE_HTTP_WEBSOCKET */ - else if (hm->message.len <= io->len) { - int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY; -/* Whole HTTP message is fully buffered, call event handler */ + block_begin += line_len; + } -#if MG_ENABLE_JAVASCRIPT - v7_val_t v1, v2, headers, req, args, res; - struct v7 *v7 = nc->mgr->v7; - const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv"; - int i, js_callback_handled_request = 0; + pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; - if (v7 != NULL) { - /* Lookup JS callback */ - v1 = v7_get(v7, v7_get_global(v7), "Http", ~0); - v2 = v7_get(v7, v1, ev_name, ~0); - - /* Create callback params. TODO(lsm): own/disown those */ - args = v7_mk_array(v7); - req = v7_mk_object(v7); - headers = v7_mk_object(v7); - - /* Populate request object */ - v7_set(v7, req, "method", ~0, - v7_mk_string(v7, hm->method.p, hm->method.len, 1)); - v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1)); - v7_set(v7, req, "body", ~0, - v7_mk_string(v7, hm->body.p, hm->body.len, 1)); - v7_set(v7, req, "headers", ~0, headers); - for (i = 0; hm->header_names[i].len > 0; i++) { - const struct mg_str *name = &hm->header_names[i]; - const struct mg_str *value = &hm->header_values[i]; - v7_set(v7, headers, name->p, name->len, - v7_mk_string(v7, value->p, value->len, 1)); - } - - /* Invoke callback. TODO(lsm): report errors */ - v7_array_push(v7, args, v7_mk_foreign(v7, nc)); - v7_array_push(v7, args, req); - if (v7_apply(v7, v2, V7_UNDEFINED, args, &res) == V7_OK && - v7_is_truthy(v7, res)) { - js_callback_handled_request++; - } - } - - /* If JS callback returns true, stop request processing */ - if (js_callback_handled_request) { - nc->flags |= MG_F_SEND_AND_CLOSE; - } else { - mg_http_call_endpoint_handler(nc, trigger_ev, hm); - } -#else - mg_http_call_endpoint_handler(nc, trigger_ev, hm); -#endif - mbuf_remove(io, hm->message.len); - } - } - (void) pd; -} - -static size_t mg_get_line_len(const char *buf, size_t buf_len) { - size_t len = 0; - while (len < buf_len && buf[len] != '\n') len++; - return len == buf_len ? 0 : len + 1; -} - -#if MG_ENABLE_HTTP_STREAMING_MULTIPART -static void mg_http_multipart_begin(struct mg_connection *nc, - struct http_message *hm, int req_len) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - struct mg_str *ct; - struct mbuf *io = &nc->recv_mbuf; - - char boundary[100]; - int boundary_len; - - if (nc->listener == NULL) { - /* No streaming for replies now */ - goto exit_mp; - } - - ct = mg_get_http_header(hm, "Content-Type"); - if (ct == NULL) { - /* We need more data - or it isn't multipart mesage */ - goto exit_mp; - } - - /* Content-type should start with "multipart" */ - if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) { - goto exit_mp; - } - - boundary_len = - mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary)); - if (boundary_len == 0) { - /* - * Content type is multipart, but there is no boundary, - * probably malformed request - */ - nc->flags = MG_F_CLOSE_IMMEDIATELY; - DBG(("invalid request")); - goto exit_mp; - } - - /* If we reach this place - that is multipart request */ - - if (pd->mp_stream.boundary != NULL) { - /* - * Another streaming request was in progress, - * looks like protocol error - */ - nc->flags |= MG_F_CLOSE_IMMEDIATELY; - } else { - pd->mp_stream.state = MPS_BEGIN; - pd->mp_stream.boundary = strdup(boundary); - pd->mp_stream.boundary_len = strlen(boundary); - pd->mp_stream.var_name = pd->mp_stream.file_name = NULL; - - pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri); - if (pd->endpoint_handler == NULL) { - pd->endpoint_handler = nc->handler; - } - - mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm); - - mbuf_remove(io, req_len); - } -exit_mp: - ; -} - -#define CONTENT_DISPOSITION "Content-Disposition: " - -static void mg_http_multipart_call_handler(struct mg_connection *c, int ev, - const char *data, size_t data_len) { - struct mg_http_multipart_part mp; - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - memset(&mp, 0, sizeof(mp)); - - mp.var_name = pd->mp_stream.var_name; - mp.file_name = pd->mp_stream.file_name; - mp.user_data = pd->mp_stream.user_data; - mp.data.p = data; - mp.data.len = data_len; - mg_call(c, pd->endpoint_handler, ev, &mp); - pd->mp_stream.user_data = mp.user_data; -} - -static int mg_http_multipart_got_chunk(struct mg_connection *c) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - struct mbuf *io = &c->recv_mbuf; - - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, - pd->mp_stream.prev_io_len); - mbuf_remove(io, pd->mp_stream.prev_io_len); - pd->mp_stream.prev_io_len = 0; - pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; - - return 0; -} - -static int mg_http_multipart_finalize(struct mg_connection *c) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); - mg_http_free_proto_data_mp_stream(&pd->mp_stream); - pd->mp_stream.state = MPS_FINISHED; - - return 1; -} - -static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) { - const char *boundary; - struct mbuf *io = &c->recv_mbuf; - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - - if ((int) io->len < pd->mp_stream.boundary_len + 2) { - return 0; - } - - boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); - if (boundary != NULL) { - if (io->len - (boundary - io->buf) < 4) { - return 0; - } - if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) { - pd->mp_stream.state = MPS_FINALIZE; - } else { - pd->mp_stream.state = MPS_GOT_BOUNDARY; - } - } else { - return 0; - } - - return 1; -} - -static int mg_http_multipart_process_boundary(struct mg_connection *c) { - int data_size; - const char *boundary, *block_begin; - struct mbuf *io = &c->recv_mbuf; - struct mg_http_proto_data *pd = mg_http_get_proto_data(c); - char file_name[100], var_name[100]; - int line_len; - boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len); - block_begin = boundary + pd->mp_stream.boundary_len + 2; - data_size = io->len - (block_begin - io->buf); - - while (data_size > 0 && - (line_len = mg_get_line_len(block_begin, data_size)) != 0) { - if (line_len > (int) sizeof(CONTENT_DISPOSITION) && - mg_ncasecmp(block_begin, CONTENT_DISPOSITION, - sizeof(CONTENT_DISPOSITION) - 1) == 0) { - struct mg_str header; - - header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1; - header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1; - mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2); - mg_http_parse_header(&header, "filename", file_name, - sizeof(file_name) - 2); - block_begin += line_len; - data_size -= line_len; - continue; - } - - if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) { - mbuf_remove(io, block_begin - io->buf + 2); - - if (pd->mp_stream.processing_part != 0) { - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0); - } - - free((void *) pd->mp_stream.file_name); - pd->mp_stream.file_name = strdup(file_name); - free((void *) pd->mp_stream.var_name); - pd->mp_stream.var_name = strdup(var_name); - - mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0); - pd->mp_stream.state = MPS_WAITING_FOR_CHUNK; - pd->mp_stream.processing_part++; - return 1; - } - - block_begin += line_len; - } - - pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; - - return 0; -} + return 0; +} static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) { struct mg_http_proto_data *pd = mg_http_get_proto_data(c); @@ -5261,48 +4973,6 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) { nc->proto_handler = mg_http_handler; } -#if !MG_DISABLE_HTTP_WEBSOCKET - -void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path, - const char *host, const char *protocol, - const char *extra_headers) { - char key[25]; - uint32_t nonce[4]; - nonce[0] = mg_ws_random_mask(); - nonce[1] = mg_ws_random_mask(); - nonce[2] = mg_ws_random_mask(); - nonce[3] = mg_ws_random_mask(); - mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key); - - mg_printf(nc, - "GET %s HTTP/1.1\r\n" - "Upgrade: websocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Version: 13\r\n" - "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 */ - void mg_send_response_line_s(struct mg_connection *nc, int status_code, const struct mg_str extra_headers) { const char *status_message = "OK"; @@ -5390,200 +5060,6 @@ static void mg_http_send_error(struct mg_connection *nc, int code, mg_send(nc, reason, strlen(reason)); nc->flags |= MG_F_SEND_AND_CLOSE; } -#if !MG_DISABLE_SSI -static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm, - const char *path, FILE *fp, int include_level, - const struct mg_serve_http_opts *opts); - -static void mg_send_file_data(struct mg_connection *nc, FILE *fp) { - char buf[BUFSIZ]; - size_t n; - while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { - mg_send(nc, buf, n); - } -} - -static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm, - const char *ssi, char *tag, int include_level, - const struct mg_serve_http_opts *opts) { - char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p; - FILE *fp; - - /* - * sscanf() is safe here, since send_ssi_file() also uses buffer - * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. - */ - if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { - /* File name is relative to the webserver root */ - snprintf(path, sizeof(path), "%s/%s", opts->document_root, file_name); - } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) { - /* - * File name is relative to the webserver working directory - * or it is absolute system path - */ - snprintf(path, sizeof(path), "%s", file_name); - } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 || - sscanf(tag, " \"%[^\"]\"", file_name) == 1) { - /* File name is relative to the currect document */ - snprintf(path, sizeof(path), "%s", ssi); - if ((p = strrchr(path, DIRSEP)) != NULL) { - p[1] = '\0'; - } - snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name); - } else { - mg_printf(nc, "Bad SSI #include: [%s]", tag); - return; - } - - if ((fp = fopen(path, "rb")) == NULL) { - mg_printf(nc, "SSI include error: fopen(%s): %s", path, - strerror(mg_get_errno())); - } else { - mg_set_close_on_exec(fileno(fp)); - if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) > - 0) { - mg_send_ssi_file(nc, hm, path, fp, include_level + 1, opts); - } else { - mg_send_file_data(nc, fp); - } - fclose(fp); - } -} - -#if !MG_DISABLE_POPEN -static void do_ssi_exec(struct mg_connection *nc, char *tag) { - char cmd[BUFSIZ]; - FILE *fp; - - if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { - mg_printf(nc, "Bad SSI #exec: [%s]", tag); - } else if ((fp = popen(cmd, "r")) == NULL) { - mg_printf(nc, "Cannot SSI #exec: [%s]: %s", cmd, strerror(mg_get_errno())); - } else { - mg_send_file_data(nc, fp); - pclose(fp); - } -} -#endif /* !MG_DISABLE_POPEN */ - -/* - * SSI directive has the following format: - * <!--#directive parameter=value parameter=value --> - */ -static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm, - const char *path, FILE *fp, int include_level, - const struct mg_serve_http_opts *opts) { - static const struct mg_str btag = MG_MK_STR("<!--#"); - static const struct mg_str d_include = MG_MK_STR("include"); - static const struct mg_str d_call = MG_MK_STR("call"); -#if !MG_DISABLE_POPEN - static const struct mg_str d_exec = MG_MK_STR("exec"); -#endif - char buf[BUFSIZ], *p = buf + btag.len; /* p points to SSI directive */ - int ch, len, in_ssi_tag; - - if (include_level > 10) { - mg_printf(nc, "SSI #include level is too deep (%s)", path); - return; - } - - in_ssi_tag = len = 0; - while ((ch = fgetc(fp)) != EOF) { - if (in_ssi_tag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { - size_t i = len - 2; - in_ssi_tag = 0; - - /* Trim closing --> */ - buf[i--] = '\0'; - while (i > 0 && buf[i] == ' ') { - buf[i--] = '\0'; - } - - /* Handle known SSI directives */ - if (memcmp(p, d_include.p, d_include.len) == 0) { - mg_do_ssi_include(nc, hm, path, p + d_include.len + 1, include_level, - opts); - } else if (memcmp(p, d_call.p, d_call.len) == 0) { - struct mg_ssi_call_ctx cctx; - memset(&cctx, 0, sizeof(cctx)); - cctx.req = hm; - cctx.file = mg_mk_str(path); - cctx.arg = mg_mk_str(p + d_call.len + 1); - mg_call(nc, NULL, MG_EV_SSI_CALL, - (void *) cctx.arg.p); /* NUL added above */ - mg_call(nc, NULL, MG_EV_SSI_CALL_CTX, &cctx); -#if !MG_DISABLE_POPEN - } else if (memcmp(p, d_exec.p, d_exec.len) == 0) { - do_ssi_exec(nc, p + d_exec.len + 1); -#endif - } else { - /* Silently ignore unknown SSI directive. */ - } - len = 0; - } else if (ch == '<') { - in_ssi_tag = 1; - if (len > 0) { - mg_send(nc, buf, (size_t) len); - } - len = 0; - buf[len++] = ch & 0xff; - } else if (in_ssi_tag) { - if (len == (int) btag.len && memcmp(buf, btag.p, btag.len) != 0) { - /* Not an SSI tag */ - in_ssi_tag = 0; - } else if (len == (int) sizeof(buf) - 2) { - mg_printf(nc, "%s: SSI tag is too large", path); - len = 0; - } - buf[len++] = ch & 0xff; - } else { - buf[len++] = ch & 0xff; - if (len == (int) sizeof(buf)) { - mg_send(nc, buf, (size_t) len); - len = 0; - } - } - } - - /* Send the rest of buffered data */ - if (len > 0) { - mg_send(nc, buf, (size_t) len); - } -} - -static void mg_handle_ssi_request(struct mg_connection *nc, - struct http_message *hm, const char *path, - const struct mg_serve_http_opts *opts) { - FILE *fp; - struct mg_str mime_type; - DBG(("%p %s", nc, path)); - - if ((fp = fopen(path, "rb")) == NULL) { - mg_http_send_error(nc, 404, NULL); - } else { - mg_set_close_on_exec(fileno(fp)); - - mime_type = mg_get_mime_type(path, "text/plain", opts); - mg_send_response_line(nc, 200, opts->extra_headers); - mg_printf(nc, - "Content-Type: %.*s\r\n" - "Connection: close\r\n\r\n", - (int) mime_type.len, mime_type.p); - mg_send_ssi_file(nc, hm, path, fp, 0, opts); - fclose(fp); - nc->flags |= MG_F_SEND_AND_CLOSE; - } -} -#else -static void mg_handle_ssi_request(struct mg_connection *nc, - struct http_message *hm, const char *path, - const struct mg_serve_http_opts *opts) { - (void) path; - (void) hm; - (void) opts; - mg_http_send_error(nc, 500, "SSI disabled"); -} -#endif /* MG_DISABLE_SSI */ static void mg_http_construct_etag(char *buf, size_t buf_len, const cs_stat_t *st) { @@ -5710,10 +5186,12 @@ void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm, static void mg_http_serve_file2(struct mg_connection *nc, const char *path, struct http_message *hm, struct mg_serve_http_opts *opts) { +#if MG_ENABLE_HTTP_SSI if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) > 0) { mg_handle_ssi_request(nc, hm, path, opts); return; } +#endif mg_http_serve_file(nc, hm, path, mg_get_mime_type(path, "text/plain", opts), mg_mk_str(opts->extra_headers)); } @@ -6538,11 +6016,11 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, opts->per_directory_auth_file, 0)) { mg_http_send_digest_auth_request(nc, opts->auth_domain); } else if (is_cgi) { -#if MG_ENABLE_CGI +#if MG_ENABLE_HTTP_CGI mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts); #else mg_http_send_error(nc, 501, NULL); -#endif /* MG_DISABLE_CGI */ +#endif /* MG_ENABLE_HTTP_CGI */ } else if ((!exists || mg_is_file_hidden(path, opts, 0 /* specials are ok */)) && !mg_is_creation_request(hm)) { @@ -6781,35 +6259,6 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr, post_data); } -#if !MG_DISABLE_HTTP_WEBSOCKET -struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - struct mg_connect_opts opts, - const char *url, const char *protocol, - const char *extra_headers) { - char *addr = NULL; - const char *path = NULL; - struct mg_connection *nc = mg_connect_http_base( - mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr); - - if (nc != NULL) { - mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers); - } - - MG_FREE(addr); - return nc; -} - -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_connect_opts opts; - memset(&opts, 0, sizeof(opts)); - return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers); -} -#endif /* MG_DISABLE_HTTP_WEBSOCKET */ - size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name, size_t var_name_len, char *file_name, size_t file_name_len, const char **data, @@ -6865,7 +6314,7 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path, pd->endpoints = new_ep; } -#endif /* MG_DISABLE_HTTP */ +#endif /* MG_ENABLE_HTTP */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/http_cgi.c" #endif @@ -6874,7 +6323,15 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path, * All rights reserved */ -#if !MG_DISABLE_HTTP && MG_ENABLE_CGI +#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI + +#ifndef MG_MAX_CGI_ENVIR_VARS +#define MG_MAX_CGI_ENVIR_VARS 64 +#endif + +#ifndef MG_ENV_EXPORT_TO_CGI +#define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI" +#endif /* * This structure helps to create an environment for the spawned CGI program. @@ -7350,37 +6807,233 @@ MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) { } } -#endif /* MG_ENABLE_HTTP && MG_ENABLE_CGI */ +#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI */ #ifdef MG_MODULE_LINES -#line 1 "mongoose/src/http_webdav.c" +#line 1 "mongoose/src/http_ssi.c" #endif /* * Copyright (c) 2014-2016 Cesanta Software Limited * All rights reserved */ -#if !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV +#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_SSI && MG_ENABLE_FILESYSTEM -MG_INTERNAL int mg_is_dav_request(const struct mg_str *s) { - static const char *methods[] = { - "PUT", - "DELETE", - "MKCOL", - "PROPFIND", - "MOVE" -#if MG_ENABLE_FAKE_DAVLOCK - , - "LOCK", - "UNLOCK" -#endif - }; - size_t i; +static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm, + const char *path, FILE *fp, int include_level, + const struct mg_serve_http_opts *opts); - for (i = 0; i < ARRAY_SIZE(methods); i++) { - if (mg_vcmp(s, methods[i]) == 0) { - return 1; - } - } +static void mg_send_file_data(struct mg_connection *nc, FILE *fp) { + char buf[BUFSIZ]; + size_t n; + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) { + mg_send(nc, buf, n); + } +} + +static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm, + const char *ssi, char *tag, int include_level, + const struct mg_serve_http_opts *opts) { + char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p; + FILE *fp; + + /* + * sscanf() is safe here, since send_ssi_file() also uses buffer + * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN. + */ + if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) { + /* File name is relative to the webserver root */ + snprintf(path, sizeof(path), "%s/%s", opts->document_root, file_name); + } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) { + /* + * File name is relative to the webserver working directory + * or it is absolute system path + */ + snprintf(path, sizeof(path), "%s", file_name); + } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 || + sscanf(tag, " \"%[^\"]\"", file_name) == 1) { + /* File name is relative to the currect document */ + snprintf(path, sizeof(path), "%s", ssi); + if ((p = strrchr(path, DIRSEP)) != NULL) { + p[1] = '\0'; + } + snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name); + } else { + mg_printf(nc, "Bad SSI #include: [%s]", tag); + return; + } + + if ((fp = fopen(path, "rb")) == NULL) { + mg_printf(nc, "SSI include error: fopen(%s): %s", path, + strerror(mg_get_errno())); + } else { + mg_set_close_on_exec(fileno(fp)); + if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) > + 0) { + mg_send_ssi_file(nc, hm, path, fp, include_level + 1, opts); + } else { + mg_send_file_data(nc, fp); + } + fclose(fp); + } +} + +#if !MG_DISABLE_POPEN +static void do_ssi_exec(struct mg_connection *nc, char *tag) { + char cmd[BUFSIZ]; + FILE *fp; + + if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) { + mg_printf(nc, "Bad SSI #exec: [%s]", tag); + } else if ((fp = popen(cmd, "r")) == NULL) { + mg_printf(nc, "Cannot SSI #exec: [%s]: %s", cmd, strerror(mg_get_errno())); + } else { + mg_send_file_data(nc, fp); + pclose(fp); + } +} +#endif /* !MG_DISABLE_POPEN */ + +/* + * SSI directive has the following format: + * <!--#directive parameter=value parameter=value --> + */ +static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm, + const char *path, FILE *fp, int include_level, + const struct mg_serve_http_opts *opts) { + static const struct mg_str btag = MG_MK_STR("<!--#"); + static const struct mg_str d_include = MG_MK_STR("include"); + static const struct mg_str d_call = MG_MK_STR("call"); +#if !MG_DISABLE_POPEN + static const struct mg_str d_exec = MG_MK_STR("exec"); +#endif + char buf[BUFSIZ], *p = buf + btag.len; /* p points to SSI directive */ + int ch, len, in_ssi_tag; + + if (include_level > 10) { + mg_printf(nc, "SSI #include level is too deep (%s)", path); + return; + } + + in_ssi_tag = len = 0; + while ((ch = fgetc(fp)) != EOF) { + if (in_ssi_tag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') { + size_t i = len - 2; + in_ssi_tag = 0; + + /* Trim closing --> */ + buf[i--] = '\0'; + while (i > 0 && buf[i] == ' ') { + buf[i--] = '\0'; + } + + /* Handle known SSI directives */ + if (memcmp(p, d_include.p, d_include.len) == 0) { + mg_do_ssi_include(nc, hm, path, p + d_include.len + 1, include_level, + opts); + } else if (memcmp(p, d_call.p, d_call.len) == 0) { + struct mg_ssi_call_ctx cctx; + memset(&cctx, 0, sizeof(cctx)); + cctx.req = hm; + cctx.file = mg_mk_str(path); + cctx.arg = mg_mk_str(p + d_call.len + 1); + mg_call(nc, NULL, MG_EV_SSI_CALL, + (void *) cctx.arg.p); /* NUL added above */ + mg_call(nc, NULL, MG_EV_SSI_CALL_CTX, &cctx); +#if !MG_DISABLE_POPEN + } else if (memcmp(p, d_exec.p, d_exec.len) == 0) { + do_ssi_exec(nc, p + d_exec.len + 1); +#endif + } else { + /* Silently ignore unknown SSI directive. */ + } + len = 0; + } else if (ch == '<') { + in_ssi_tag = 1; + if (len > 0) { + mg_send(nc, buf, (size_t) len); + } + len = 0; + buf[len++] = ch & 0xff; + } else if (in_ssi_tag) { + if (len == (int) btag.len && memcmp(buf, btag.p, btag.len) != 0) { + /* Not an SSI tag */ + in_ssi_tag = 0; + } else if (len == (int) sizeof(buf) - 2) { + mg_printf(nc, "%s: SSI tag is too large", path); + len = 0; + } + buf[len++] = ch & 0xff; + } else { + buf[len++] = ch & 0xff; + if (len == (int) sizeof(buf)) { + mg_send(nc, buf, (size_t) len); + len = 0; + } + } + } + + /* Send the rest of buffered data */ + if (len > 0) { + mg_send(nc, buf, (size_t) len); + } +} + +MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc, + struct http_message *hm, + const char *path, + const struct mg_serve_http_opts *opts) { + FILE *fp; + struct mg_str mime_type; + DBG(("%p %s", nc, path)); + + if ((fp = fopen(path, "rb")) == NULL) { + mg_http_send_error(nc, 404, NULL); + } else { + mg_set_close_on_exec(fileno(fp)); + + mime_type = mg_get_mime_type(path, "text/plain", opts); + mg_send_response_line(nc, 200, opts->extra_headers); + mg_printf(nc, + "Content-Type: %.*s\r\n" + "Connection: close\r\n\r\n", + (int) mime_type.len, mime_type.p); + mg_send_ssi_file(nc, hm, path, fp, 0, opts); + fclose(fp); + nc->flags |= MG_F_SEND_AND_CLOSE; + } +} + +#endif /* MG_ENABLE_HTTP_SSI && MG_ENABLE_HTTP && MG_ENABLE_FILESYSTEM */ +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/http_webdav.c" +#endif +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV + +MG_INTERNAL int mg_is_dav_request(const struct mg_str *s) { + static const char *methods[] = { + "PUT", + "DELETE", + "MKCOL", + "PROPFIND", + "MOVE" +#if MG_ENABLE_FAKE_DAVLOCK + , + "LOCK", + "UNLOCK" +#endif + }; + size_t i; + + for (i = 0; i < ARRAY_SIZE(methods); i++) { + if (mg_vcmp(s, methods[i]) == 0) { + return 1; + } + } return 0; } @@ -7480,148 +7133,522 @@ MG_INTERNAL void mg_handle_lock(struct mg_connection *nc, const char *path) { } #endif -MG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path, - struct http_message *hm) { - int status_code = 500; - if (hm->body.len != (size_t) ~0 && hm->body.len > 0) { - status_code = 415; - } else if (!mg_mkdir(path, 0755)) { - status_code = 201; - } else if (errno == EEXIST) { - status_code = 405; - } else if (errno == EACCES) { - status_code = 403; - } else if (errno == ENOENT) { - status_code = 409; - } else { - status_code = 500; +MG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path, + struct http_message *hm) { + int status_code = 500; + if (hm->body.len != (size_t) ~0 && hm->body.len > 0) { + status_code = 415; + } else if (!mg_mkdir(path, 0755)) { + status_code = 201; + } else if (errno == EEXIST) { + status_code = 405; + } else if (errno == EACCES) { + status_code = 403; + } else if (errno == ENOENT) { + status_code = 409; + } else { + status_code = 500; + } + mg_http_send_error(nc, status_code, NULL); +} + +static int mg_remove_directory(const struct mg_serve_http_opts *opts, + const char *dir) { + char path[MAX_PATH_SIZE]; + struct dirent *dp; + cs_stat_t st; + DIR *dirp; + + if ((dirp = opendir(dir)) == NULL) return 0; + + while ((dp = readdir(dirp)) != NULL) { + if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) { + continue; + } + snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); + mg_stat(path, &st); + if (S_ISDIR(st.st_mode)) { + mg_remove_directory(opts, path); + } else { + remove(path); + } + } + closedir(dirp); + rmdir(dir); + + return 1; +} + +MG_INTERNAL void mg_handle_move(struct mg_connection *c, + const struct mg_serve_http_opts *opts, + const char *path, struct http_message *hm) { + const struct mg_str *dest = mg_get_http_header(hm, "Destination"); + if (dest == NULL) { + mg_http_send_error(c, 411, NULL); + } else { + const char *p = (char *) memchr(dest->p, '/', dest->len); + if (p != NULL && p[1] == '/' && + (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) { + char buf[MAX_PATH_SIZE]; + snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root, + (int) (dest->p + dest->len - p), p); + if (rename(path, buf) == 0) { + mg_http_send_error(c, 200, NULL); + } else { + mg_http_send_error(c, 418, NULL); + } + } else { + mg_http_send_error(c, 500, NULL); + } + } +} + +MG_INTERNAL void mg_handle_delete(struct mg_connection *nc, + const struct mg_serve_http_opts *opts, + const char *path) { + cs_stat_t st; + if (mg_stat(path, &st) != 0) { + mg_http_send_error(nc, 404, NULL); + } else if (S_ISDIR(st.st_mode)) { + mg_remove_directory(opts, path); + mg_http_send_error(nc, 204, NULL); + } else if (remove(path) == 0) { + mg_http_send_error(nc, 204, NULL); + } else { + mg_http_send_error(nc, 423, NULL); + } +} + +/* Return -1 on error, 1 on success. */ +static int mg_create_itermediate_directories(const char *path) { + const char *s; + + /* Create intermediate directories if they do not exist */ + for (s = path + 1; *s != '\0'; s++) { + if (*s == '/') { + char buf[MAX_PATH_SIZE]; + cs_stat_t st; + snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path); + buf[sizeof(buf) - 1] = '\0'; + if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) { + return -1; + } + } + } + + return 1; +} + +MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path, + struct http_message *hm) { + struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); + cs_stat_t st; + const struct mg_str *cl_hdr = mg_get_http_header(hm, "Content-Length"); + int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201; + + mg_http_free_proto_data_file(&pd->file); + if ((rc = mg_create_itermediate_directories(path)) == 0) { + mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code); + } else if (rc == -1) { + mg_http_send_error(nc, 500, NULL); + } else if (cl_hdr == NULL) { + mg_http_send_error(nc, 411, NULL); + } else if ((pd->file.fp = fopen(path, "w+b")) == NULL) { + mg_http_send_error(nc, 500, NULL); + } else { + const struct mg_str *range_hdr = mg_get_http_header(hm, "Content-Range"); + int64_t r1 = 0, r2 = 0; + pd->file.type = DATA_PUT; + mg_set_close_on_exec(fileno(pd->file.fp)); + pd->file.cl = to64(cl_hdr->p); + if (range_hdr != NULL && + mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) { + status_code = 206; + fseeko(pd->file.fp, r1, SEEK_SET); + pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1; + } + mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code); + /* Remove HTTP request from the mbuf, leave only payload */ + mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len); + mg_http_transfer_file_data(nc); + } +} + +#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */ +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/http_websocket.c" +#endif +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET + +#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS +#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5 +#endif + +#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1) + +static int mg_is_ws_fragment(unsigned char flags) { + return (flags & 0x80) == 0 || (flags & 0x0f) == 0; +} + +static int mg_is_ws_first_fragment(unsigned char flags) { + return (flags & 0x80) == 0 && (flags & 0x0f) != 0; +} + +static void mg_handle_incoming_websocket_frame(struct mg_connection *nc, + struct websocket_message *wsm) { + if (wsm->flags & 0x8) { + mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm); + } else { + mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm); + } +} + +static int mg_deliver_websocket_data(struct mg_connection *nc) { + /* Using unsigned char *, cause of integer arithmetic below */ + uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len, + mask_len = 0, header_len = 0; + unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p, + *e = p + buf_len; + unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */ + int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) && + !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG); + + /* If that's a continuation frame that must be reassembled, handle it */ + if (reass && !mg_is_ws_first_fragment(p[0]) && + buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) { + buf += 1 + sizeof(*sizep) + *sizep; + buf_len -= 1 + sizeof(*sizep) + *sizep; + } + + if (buf_len >= 2) { + len = buf[1] & 127; + mask_len = buf[1] & 128 ? 4 : 0; + if (len < 126 && buf_len >= mask_len) { + data_len = len; + header_len = 2 + mask_len; + } else if (len == 126 && buf_len >= 4 + mask_len) { + header_len = 4 + mask_len; + data_len = ntohs(*(uint16_t *) &buf[2]); + } else if (buf_len >= 10 + mask_len) { + header_len = 10 + mask_len; + data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) + + ntohl(*(uint32_t *) &buf[6]); + } + } + + frame_len = header_len + data_len; + ok = frame_len > 0 && frame_len <= buf_len; + + if (ok) { + struct websocket_message wsm; + + wsm.size = (size_t) data_len; + wsm.data = buf + header_len; + wsm.flags = buf[0]; + + /* Apply mask if necessary */ + if (mask_len > 0) { + for (i = 0; i < data_len; i++) { + buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4]; + } + } + + if (reass) { + /* On first fragmented frame, nullify size */ + if (mg_is_ws_first_fragment(wsm.flags)) { + mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep)); + p[0] &= ~0x0f; /* Next frames will be treated as continuation */ + buf = p + 1 + sizeof(*sizep); + *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */ + } + + /* Append this frame to the reassembled buffer */ + memmove(buf, wsm.data, e - wsm.data); + (*sizep) += wsm.size; + nc->recv_mbuf.len -= wsm.data - buf; + + /* On last fragmented frame - call user handler and remove data */ + if (wsm.flags & 0x80) { + wsm.data = p + 1 + sizeof(*sizep); + wsm.size = *sizep; + mg_handle_incoming_websocket_frame(nc, &wsm); + mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep); + } + } else { + /* TODO(lsm): properly handle OOB control frames during defragmentation */ + mg_handle_incoming_websocket_frame(nc, &wsm); + mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */ + } + + /* If client closes, close too */ + if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) { + nc->flags |= MG_F_SEND_AND_CLOSE; + } + } + + return ok; +} + +struct ws_mask_ctx { + size_t pos; /* zero means unmasked */ + uint32_t mask; +}; + +static uint32_t mg_ws_random_mask(void) { + uint32_t mask; +/* + * The spec requires WS client to generate hard to + * guess mask keys. From RFC6455, Section 5.3: + * + * The unpredictability of the masking key is essential to prevent + * authors of malicious applications from selecting the bytes that appear on + * the wire. + * + * Hence this feature is essential when the actual end user of this API + * is untrusted code that wouldn't have access to a lower level net API + * anyway (e.g. web browsers). Hence this feature is low prio for most + * mongoose use cases and thus can be disabled, e.g. when porting to a platform + * that lacks rand(). + */ +#if MG_DISABLE_WS_RANDOM_MASK + mask = 0xefbeadde; /* generated with a random number generator, I swear */ +#else + if (sizeof(long) >= 4) { + mask = (uint32_t) rand(); + } else if (sizeof(long) == 2) { + mask = (uint32_t) rand() << 16 | (uint32_t) rand(); + } +#endif + return mask; +} + +static void mg_send_ws_header(struct mg_connection *nc, int op, size_t len, + struct ws_mask_ctx *ctx) { + int header_len; + unsigned char header[10]; + + header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f); + if (len < 126) { + header[1] = (unsigned char) len; + header_len = 2; + } else if (len < 65535) { + uint16_t tmp = htons((uint16_t) len); + header[1] = 126; + memcpy(&header[2], &tmp, sizeof(tmp)); + header_len = 4; + } else { + uint32_t tmp; + header[1] = 127; + tmp = htonl((uint32_t)((uint64_t) len >> 32)); + memcpy(&header[2], &tmp, sizeof(tmp)); + tmp = htonl((uint32_t)(len & 0xffffffff)); + memcpy(&header[6], &tmp, sizeof(tmp)); + header_len = 10; + } + + /* client connections enable masking */ + if (nc->listener == NULL) { + header[1] |= 1 << 7; /* set masking flag */ + mg_send(nc, header, header_len); + ctx->mask = mg_ws_random_mask(); + mg_send(nc, &ctx->mask, sizeof(ctx->mask)); + ctx->pos = nc->send_mbuf.len; + } else { + mg_send(nc, header, header_len); + ctx->pos = 0; + } +} + +static void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) { + size_t i; + if (ctx->pos == 0) return; + for (i = 0; i < (mbuf->len - ctx->pos); i++) { + mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4]; + } +} + +void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data, + size_t len) { + struct ws_mask_ctx ctx; + DBG(("%p %d %d", nc, op, (int) len)); + mg_send_ws_header(nc, op, len, &ctx); + mg_send(nc, data, len); + + mg_ws_mask_frame(&nc->send_mbuf, &ctx); + + if (op == WEBSOCKET_OP_CLOSE) { + nc->flags |= MG_F_SEND_AND_CLOSE; } - mg_http_send_error(nc, status_code, NULL); } -static int mg_remove_directory(const struct mg_serve_http_opts *opts, - const char *dir) { - char path[MAX_PATH_SIZE]; - struct dirent *dp; - cs_stat_t st; - DIR *dirp; +void mg_send_websocket_framev(struct mg_connection *nc, int op, + const struct mg_str *strv, int strvcnt) { + struct ws_mask_ctx ctx; + int i; + int len = 0; + for (i = 0; i < strvcnt; i++) { + len += strv[i].len; + } - if ((dirp = opendir(dir)) == NULL) return 0; + mg_send_ws_header(nc, op, len, &ctx); - while ((dp = readdir(dirp)) != NULL) { - if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) { - continue; - } - snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name); - mg_stat(path, &st); - if (S_ISDIR(st.st_mode)) { - mg_remove_directory(opts, path); - } else { - remove(path); - } + for (i = 0; i < strvcnt; i++) { + mg_send(nc, strv[i].p, strv[i].len); } - closedir(dirp); - rmdir(dir); - return 1; + mg_ws_mask_frame(&nc->send_mbuf, &ctx); + + if (op == WEBSOCKET_OP_CLOSE) { + nc->flags |= MG_F_SEND_AND_CLOSE; + } } -MG_INTERNAL void mg_handle_move(struct mg_connection *c, - const struct mg_serve_http_opts *opts, - const char *path, struct http_message *hm) { - const struct mg_str *dest = mg_get_http_header(hm, "Destination"); - if (dest == NULL) { - mg_http_send_error(c, 411, NULL); - } else { - const char *p = (char *) memchr(dest->p, '/', dest->len); - if (p != NULL && p[1] == '/' && - (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) { - char buf[MAX_PATH_SIZE]; - snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root, - (int) (dest->p + dest->len - p), p); - if (rename(path, buf) == 0) { - mg_http_send_error(c, 200, NULL); - } else { - mg_http_send_error(c, 418, NULL); +void mg_printf_websocket_frame(struct mg_connection *nc, int op, + const char *fmt, ...) { + char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem; + va_list ap; + int len; + + va_start(ap, fmt); + if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) { + mg_send_websocket_frame(nc, op, buf, len); + } + va_end(ap); + + if (buf != mem && buf != NULL) { + MG_FREE(buf); + } +} + +MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev, + void *ev_data) { + mg_call(nc, nc->handler, ev, ev_data); + + switch (ev) { + case MG_EV_RECV: + do { + } while (mg_deliver_websocket_data(nc)); + break; + case MG_EV_POLL: + /* Ping idle websocket connections */ + { + time_t now = *(time_t *) ev_data; + if (nc->flags & MG_F_IS_WEBSOCKET && + now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) { + mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "", 0); + } } - } else { - mg_http_send_error(c, 500, NULL); - } + break; + default: + break; } } -MG_INTERNAL void mg_handle_delete(struct mg_connection *nc, - const struct mg_serve_http_opts *opts, - const char *path) { - cs_stat_t st; - if (mg_stat(path, &st) != 0) { - mg_http_send_error(nc, 404, NULL); - } else if (S_ISDIR(st.st_mode)) { - mg_remove_directory(opts, path); - mg_http_send_error(nc, 204, NULL); - } else if (remove(path) == 0) { - mg_http_send_error(nc, 204, NULL); - } else { - mg_http_send_error(nc, 423, NULL); +#ifndef MG_EXT_SHA1 +static void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], + const size_t *msg_lens, uint8_t *digest) { + size_t i; + cs_sha1_ctx sha_ctx; + cs_sha1_init(&sha_ctx); + for (i = 0; i < num_msgs; i++) { + cs_sha1_update(&sha_ctx, msgs[i], msg_lens[i]); } + cs_sha1_final(digest, &sha_ctx); } +#else +extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[], + const size_t *msg_lens, uint8_t *digest); +#endif -/* Return -1 on error, 1 on success. */ -static int mg_create_itermediate_directories(const char *path) { - const char *s; +MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc, + const struct mg_str *key) { + static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic}; + const size_t msg_lens[2] = {key->len, 36}; + unsigned char sha[20]; + char b64_sha[30]; - /* Create intermediate directories if they do not exist */ - for (s = path + 1; *s != '\0'; s++) { - if (*s == '/') { - char buf[MAX_PATH_SIZE]; - cs_stat_t st; - snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path); - buf[sizeof(buf) - 1] = '\0'; - if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) { - return -1; - } - } + mg_hash_sha1_v(2, msgs, msg_lens, sha); + mg_base64_encode(sha, sizeof(sha), b64_sha); + mg_printf(nc, "%s%s%s", + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: ", + b64_sha, "\r\n\r\n"); + DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha)); +} + +void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path, + const char *host, const char *protocol, + const char *extra_headers) { + char key[25]; + uint32_t nonce[4]; + nonce[0] = mg_ws_random_mask(); + nonce[1] = mg_ws_random_mask(); + nonce[2] = mg_ws_random_mask(); + nonce[3] = mg_ws_random_mask(); + mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key); + + mg_printf(nc, + "GET %s HTTP/1.1\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Version: 13\r\n" + "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"); +} - return 1; +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); } -MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path, - struct http_message *hm) { - struct mg_http_proto_data *pd = mg_http_get_proto_data(nc); - cs_stat_t st; - const struct mg_str *cl_hdr = mg_get_http_header(hm, "Content-Length"); - int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201; +struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + struct mg_connect_opts opts, + const char *url, const char *protocol, + const char *extra_headers) { + char *addr = NULL; + const char *path = NULL; + struct mg_connection *nc = mg_connect_http_base( + mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr); - mg_http_free_proto_data_file(&pd->file); - if ((rc = mg_create_itermediate_directories(path)) == 0) { - mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code); - } else if (rc == -1) { - mg_http_send_error(nc, 500, NULL); - } else if (cl_hdr == NULL) { - mg_http_send_error(nc, 411, NULL); - } else if ((pd->file.fp = fopen(path, "w+b")) == NULL) { - mg_http_send_error(nc, 500, NULL); - } else { - const struct mg_str *range_hdr = mg_get_http_header(hm, "Content-Range"); - int64_t r1 = 0, r2 = 0; - pd->file.type = DATA_PUT; - mg_set_close_on_exec(fileno(pd->file.fp)); - pd->file.cl = to64(cl_hdr->p); - if (range_hdr != NULL && - mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) { - status_code = 206; - fseeko(pd->file.fp, r1, SEEK_SET); - pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1; - } - mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code); - /* Remove HTTP request from the mbuf, leave only payload */ - mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len); - mg_http_transfer_file_data(nc); + if (nc != NULL) { + mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers); } + + MG_FREE(addr); + return nc; } -#endif /* !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */ +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_connect_opts opts; + memset(&opts, 0, sizeof(opts)); + return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers); +} +#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/util.c" #endif diff --git a/mongoose.h b/mongoose.h index d38e522487906f7564e4a953c81aaee8d4de3218..ffc574a6a95168f4f83381bd15c5484e19913f41 100644 --- a/mongoose.h +++ b/mongoose.h @@ -1024,7 +1024,7 @@ const char *strerror(); * WinCE lacks a lot of used in CGI API functions * TODO(alaskin): look for wce_xxxx alternatives */ -#define MG_ENABLE_CGI 0 +#define MG_ENABLE_HTTP_CGI 0 #endif /* CS_PLATFORM == CS_P_WINCE */ #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ */ @@ -1381,18 +1381,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_DISABLE_HTTP_DIGEST_AUTH 0 #endif -#ifndef MG_DISABLE_HTTP -#define MG_DISABLE_HTTP 0 -#endif - #ifndef MG_DISABLE_HTTP_KEEP_ALIVE #define MG_DISABLE_HTTP_KEEP_ALIVE 0 #endif -#ifndef MG_DISABLE_HTTP_WEBSOCKET -#define MG_DISABLE_HTTP_WEBSOCKET 0 -#endif - #ifndef MG_DISABLE_PFS #define MG_DISABLE_PFS 0 #endif @@ -1413,10 +1405,6 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_DISABLE_SOCKETPAIR 0 #endif -#ifndef MG_DISABLE_SSI -#define MG_DISABLE_SSI 0 -#endif - #ifndef MG_DISABLE_SYNC_RESOLVER #define MG_DISABLE_SYNC_RESOLVER 0 #endif @@ -1425,8 +1413,13 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_DISABLE_WS_RANDOM_MASK 0 #endif -#ifndef MG_ENABLE_CGI -#define MG_ENABLE_CGI (CS_PLATFORM == CS_P_UNIX || CS_PLATFORM == CS_P_WINDOWS) +#ifndef MG_ENABLE_HTTP +#define MG_ENABLE_HTTP 1 +#endif + +#ifndef MG_ENABLE_HTTP_CGI +#define MG_ENABLE_HTTP_CGI \ + (CS_PLATFORM == CS_P_UNIX || CS_PLATFORM == CS_P_WINDOWS) #endif #ifndef MG_ENABLE_COAP @@ -1461,6 +1454,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_ENABLE_HEXDUMP CS_ENABLE_STDIO #endif +#ifndef MG_ENABLE_HTTP_SSI +#define MG_ENABLE_HTTP_SSI MG_ENABLE_FILESYSTEM +#endif + #ifndef MG_ENABLE_HTTP_STREAMING_MULTIPART #define MG_ENABLE_HTTP_STREAMING_MULTIPART 0 #endif @@ -1469,6 +1466,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_ENABLE_HTTP_WEBDAV 0 #endif +#ifndef MG_ENABLE_HTTP_WEBSOCKET +#define MG_ENABLE_HTTP_WEBSOCKET 1 +#endif + #ifndef MG_ENABLE_IPV6 #define MG_ENABLE_IPV6 0 #endif @@ -2452,6 +2453,8 @@ int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); #ifndef CS_MONGOOSE_SRC_HTTP_H_ #define CS_MONGOOSE_SRC_HTTP_H_ +#if MG_ENABLE_HTTP + /* Amalgamated: #include "mongoose/src/net.h" */ #ifdef __cplusplus @@ -2478,22 +2481,10 @@ extern "C" { #define MG_MAX_HTTP_SEND_MBUF 1024 #endif -#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS -#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5 -#endif - #ifndef MG_CGI_ENVIRONMENT_SIZE #define MG_CGI_ENVIRONMENT_SIZE 8192 #endif -#ifndef MG_MAX_CGI_ENVIR_VARS -#define MG_MAX_CGI_ENVIR_VARS 64 -#endif - -#ifndef MG_ENV_EXPORT_TO_CGI -#define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI" -#endif - /* HTTP message */ struct http_message { struct mg_str message; /* Whole message: request line + headers + body */ @@ -2525,12 +2516,14 @@ struct http_message { struct mg_str body; /* Zero-length for requests with no body */ }; +#if MG_ENABLE_HTTP_WEBSOCKET /* WebSocket message */ struct websocket_message { unsigned char *data; size_t size; unsigned char flags; }; +#endif /* HTTP multipart part */ struct mg_http_multipart_part { @@ -2555,7 +2548,7 @@ struct mg_ssi_call_ctx { #define MG_EV_SSI_CALL 105 /* char * */ #define MG_EV_SSI_CALL_CTX 106 /* struct mg_ssi_call_ctx * */ -#if !MG_DISABLE_HTTP_WEBSOCKET +#if MG_ENABLE_HTTP_WEBSOCKET #define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* NULL */ #define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112 /* NULL */ #define MG_EV_WEBSOCKET_FRAME 113 /* struct websocket_message * */ @@ -2615,7 +2608,7 @@ struct mg_ssi_call_ctx { */ void mg_set_protocol_http_websocket(struct mg_connection *nc); -#if !MG_DISABLE_HTTP_WEBSOCKET +#if MG_ENABLE_HTTP_WEBSOCKET /* * Send websocket handshake to the server. * @@ -2721,7 +2714,6 @@ void mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags, */ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags, const char *fmt, ...); -#endif /* MG_DISABLE_HTTP_WEBSOCKET */ /* Websocket opcodes, from http://tools.ietf.org/html/rfc6455 */ #define WEBSOCKET_OP_CONTINUE 0 @@ -2745,6 +2737,8 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags, */ #define WEBSOCKET_DONT_FIN 0x100 +#endif /* MG_ENABLE_HTTP_WEBSOCKET */ + /* * Decodes a URL-encoded string. * @@ -2761,6 +2755,9 @@ int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, #ifdef __cplusplus } #endif /* __cplusplus */ + +#endif /* MG_ENABLE_HTTP */ + #endif /* CS_MONGOOSE_SRC_HTTP_H_ */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/http_server.h" @@ -2772,6 +2769,8 @@ int mg_url_decode(const char *src, int src_len, char *dst, int dst_len, #ifndef CS_MONGOOSE_SRC_HTTP_SERVER_H_ #define CS_MONGOOSE_SRC_HTTP_SERVER_H_ +#if MG_ENABLE_HTTP + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ @@ -3229,6 +3228,9 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...); #ifdef __cplusplus } #endif /* __cplusplus */ + +#endif /* MG_ENABLE_HTTP */ + #endif /* CS_MONGOOSE_SRC_HTTP_SERVER_H_ */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/http_client.h"