Commit e928f364 authored by Deomid Ryabkov's avatar Deomid Ryabkov Committed by Cesanta Bot

MG_DISABLE_HTTP(|_SSI|_WEBSOCKET) -> MG_ENABLE$1

Factor out SSI and WS code from http.c

HTTP + WS are enabled by default, but MQTT is built without HTTP
support as a compilation test.

PUBLISHED_FROM=925ed9a55abb193ed7deac1c9675a0e5b35dca50
parent fcb94a17
......@@ -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)
......
......@@ -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
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
PROG = mqtt_client
MODULE_CFLAGS = -DMG_ENABLE_MQTT
MODULE_CFLAGS = -DMG_ENABLE_MQTT -DMG_ENABLE_HTTP=0
SSL_LIB=openssl
include ../examples.mk
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
......@@ -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
......
......@@ -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
......
......@@ -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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment