mirror of
https://github.com/cesanta/mongoose.git
synced 2025-06-12 04:32:55 +08:00

mongoose: fix formatting mongoose: fix formatting mongoose: remove debugging messages PUBLISHED_FROM=1194e018001cc5f2b598096593d7aac4ec8dc04d
3082 lines
98 KiB
C
3082 lines
98 KiB
C
/*
|
|
* Copyright (c) 2014 Cesanta Software Limited
|
|
* All rights reserved
|
|
*/
|
|
|
|
#if MG_ENABLE_HTTP
|
|
|
|
#include "common/cs_md5.h"
|
|
#include "mg_internal.h"
|
|
#include "mg_util.h"
|
|
|
|
/* altbuf {{{ */
|
|
|
|
/*
|
|
* Alternate buffer: fills the client-provided buffer with data; and if it's
|
|
* not large enough, allocates another buffer (via mbuf), similar to asprintf.
|
|
*/
|
|
struct altbuf {
|
|
struct mbuf m;
|
|
char *user_buf;
|
|
size_t len;
|
|
size_t user_buf_size;
|
|
};
|
|
|
|
/*
|
|
* Initializes altbuf; `buf`, `buf_size` is the client-provided buffer.
|
|
*/
|
|
MG_INTERNAL void altbuf_init(struct altbuf *ab, char *buf, size_t buf_size) {
|
|
mbuf_init(&ab->m, 0);
|
|
ab->user_buf = buf;
|
|
ab->user_buf_size = buf_size;
|
|
ab->len = 0;
|
|
}
|
|
|
|
/*
|
|
* Appends a single char to the altbuf.
|
|
*/
|
|
MG_INTERNAL void altbuf_append(struct altbuf *ab, char c) {
|
|
if (ab->len < ab->user_buf_size) {
|
|
/* The data fits into the original buffer */
|
|
ab->user_buf[ab->len++] = c;
|
|
} else {
|
|
/* The data can't fit into the original buffer, so write it to mbuf. */
|
|
|
|
/*
|
|
* First of all, see if that's the first byte which overflows the original
|
|
* buffer: if so, copy the existing data from there to a newly allocated
|
|
* mbuf.
|
|
*/
|
|
if (ab->len > 0 && ab->m.len == 0) {
|
|
mbuf_append(&ab->m, ab->user_buf, ab->len);
|
|
}
|
|
|
|
mbuf_append(&ab->m, &c, 1);
|
|
ab->len = ab->m.len;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Resets any data previously appended to altbuf.
|
|
*/
|
|
MG_INTERNAL void altbuf_reset(struct altbuf *ab) {
|
|
mbuf_free(&ab->m);
|
|
ab->len = 0;
|
|
}
|
|
|
|
/*
|
|
* Returns whether the additional buffer was allocated (and thus the data
|
|
* is in the mbuf, not the client-provided buffer)
|
|
*/
|
|
MG_INTERNAL int altbuf_reallocated(struct altbuf *ab) {
|
|
return ab->len > ab->user_buf_size;
|
|
}
|
|
|
|
/*
|
|
* Returns the actual buffer with data, either the client-provided or a newly
|
|
* allocated one. If `trim` is non-zero, mbuf-backed buffer is trimmed first.
|
|
*/
|
|
MG_INTERNAL char *altbuf_get_buf(struct altbuf *ab, int trim) {
|
|
if (altbuf_reallocated(ab)) {
|
|
if (trim) {
|
|
mbuf_trim(&ab->m);
|
|
}
|
|
return ab->m.buf;
|
|
} else {
|
|
return ab->user_buf;
|
|
}
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
static const char *mg_version_header = "Mongoose/" MG_VERSION;
|
|
|
|
enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };
|
|
|
|
struct mg_http_proto_data_file {
|
|
FILE *fp; /* Opened file. */
|
|
int64_t cl; /* Content-Length. How many bytes to send. */
|
|
int64_t sent; /* How many bytes have been already sent. */
|
|
int keepalive; /* Keep connection open after sending. */
|
|
enum mg_http_proto_data_type type;
|
|
};
|
|
|
|
#if MG_ENABLE_HTTP_CGI
|
|
struct mg_http_proto_data_cgi {
|
|
struct mg_connection *cgi_nc;
|
|
};
|
|
#endif
|
|
|
|
struct mg_http_proto_data_chuncked {
|
|
int64_t body_len; /* How many bytes of chunked body was reassembled. */
|
|
};
|
|
|
|
struct mg_http_endpoint {
|
|
struct mg_http_endpoint *next;
|
|
struct mg_str uri_pattern; /* owned */
|
|
char *auth_domain; /* owned */
|
|
char *auth_file; /* owned */
|
|
|
|
mg_event_handler_t handler;
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
void *user_data;
|
|
#endif
|
|
};
|
|
|
|
enum mg_http_multipart_stream_state {
|
|
MPS_BEGIN,
|
|
MPS_WAITING_FOR_BOUNDARY,
|
|
MPS_WAITING_FOR_CHUNK,
|
|
MPS_GOT_CHUNK,
|
|
MPS_GOT_BOUNDARY,
|
|
MPS_FINALIZE,
|
|
MPS_FINISHED
|
|
};
|
|
|
|
struct mg_http_multipart_stream {
|
|
const char *boundary;
|
|
int boundary_len;
|
|
const char *var_name;
|
|
const char *file_name;
|
|
void *user_data;
|
|
int prev_io_len;
|
|
enum mg_http_multipart_stream_state state;
|
|
int processing_part;
|
|
};
|
|
|
|
struct mg_reverse_proxy_data {
|
|
struct mg_connection *linked_conn;
|
|
};
|
|
|
|
struct mg_ws_proto_data {
|
|
/*
|
|
* Defragmented size of the frame so far.
|
|
*
|
|
* First byte of nc->recv_mbuf.buf is an op, the rest of the data is
|
|
* defragmented data.
|
|
*/
|
|
size_t reass_len;
|
|
};
|
|
|
|
struct mg_http_proto_data {
|
|
#if MG_ENABLE_FILESYSTEM
|
|
struct mg_http_proto_data_file file;
|
|
#endif
|
|
#if MG_ENABLE_HTTP_CGI
|
|
struct mg_http_proto_data_cgi cgi;
|
|
#endif
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
struct mg_http_multipart_stream mp_stream;
|
|
#endif
|
|
#if MG_ENABLE_HTTP_WEBSOCKET
|
|
struct mg_ws_proto_data ws_data;
|
|
#endif
|
|
struct mg_http_proto_data_chuncked chunk;
|
|
struct mg_http_endpoint *endpoints;
|
|
mg_event_handler_t endpoint_handler;
|
|
struct mg_reverse_proxy_data reverse_proxy_data;
|
|
size_t rcvd; /* How many bytes we have received. */
|
|
};
|
|
|
|
static void mg_http_conn_destructor(void *proto_data);
|
|
struct mg_connection *mg_connect_http_base(
|
|
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
|
|
struct mg_connect_opts opts, const char *scheme1, const char *scheme2,
|
|
const char *scheme_ssl1, const char *scheme_ssl2, const char *url,
|
|
struct mg_str *path, struct mg_str *user_info, struct mg_str *host);
|
|
|
|
static struct mg_http_proto_data *mg_http_get_proto_data(
|
|
struct mg_connection *c) {
|
|
if (c->proto_data == NULL) {
|
|
c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data));
|
|
c->proto_data_destructor = mg_http_conn_destructor;
|
|
}
|
|
|
|
return (struct mg_http_proto_data *) c->proto_data;
|
|
}
|
|
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
static void mg_http_free_proto_data_mp_stream(
|
|
struct mg_http_multipart_stream *mp) {
|
|
MG_FREE((void *) mp->boundary);
|
|
MG_FREE((void *) mp->var_name);
|
|
MG_FREE((void *) mp->file_name);
|
|
memset(mp, 0, sizeof(*mp));
|
|
}
|
|
#endif
|
|
|
|
#if MG_ENABLE_FILESYSTEM
|
|
static void mg_http_free_proto_data_file(struct mg_http_proto_data_file *d) {
|
|
if (d != NULL) {
|
|
if (d->fp != NULL) {
|
|
fclose(d->fp);
|
|
}
|
|
memset(d, 0, sizeof(struct mg_http_proto_data_file));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) {
|
|
struct mg_http_endpoint *current = *ep;
|
|
|
|
while (current != NULL) {
|
|
struct mg_http_endpoint *tmp = current->next;
|
|
MG_FREE((void *) current->uri_pattern.p);
|
|
MG_FREE((void *) current->auth_domain);
|
|
MG_FREE((void *) current->auth_file);
|
|
MG_FREE(current);
|
|
current = tmp;
|
|
}
|
|
|
|
ep = NULL;
|
|
}
|
|
|
|
static void mg_http_free_reverse_proxy_data(struct mg_reverse_proxy_data *rpd) {
|
|
if (rpd->linked_conn != NULL) {
|
|
/*
|
|
* Connection has linked one, we have to unlink & close it
|
|
* since _this_ connection is going to die and
|
|
* it doesn't make sense to keep another one
|
|
*/
|
|
struct mg_http_proto_data *pd = mg_http_get_proto_data(rpd->linked_conn);
|
|
if (pd->reverse_proxy_data.linked_conn != NULL) {
|
|
pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;
|
|
pd->reverse_proxy_data.linked_conn = NULL;
|
|
}
|
|
rpd->linked_conn = NULL;
|
|
}
|
|
}
|
|
|
|
static void mg_http_conn_destructor(void *proto_data) {
|
|
struct mg_http_proto_data *pd = (struct mg_http_proto_data *) proto_data;
|
|
#if MG_ENABLE_FILESYSTEM
|
|
mg_http_free_proto_data_file(&pd->file);
|
|
#endif
|
|
#if MG_ENABLE_HTTP_CGI
|
|
mg_http_free_proto_data_cgi(&pd->cgi);
|
|
#endif
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
mg_http_free_proto_data_mp_stream(&pd->mp_stream);
|
|
#endif
|
|
mg_http_free_proto_data_endpoints(&pd->endpoints);
|
|
mg_http_free_reverse_proxy_data(&pd->reverse_proxy_data);
|
|
MG_FREE(proto_data);
|
|
}
|
|
|
|
#if MG_ENABLE_FILESYSTEM
|
|
|
|
#define MIME_ENTRY(_ext, _type) \
|
|
{ _ext, sizeof(_ext) - 1, _type }
|
|
static const struct {
|
|
const char *extension;
|
|
size_t ext_len;
|
|
const char *mime_type;
|
|
} mg_static_builtin_mime_types[] = {
|
|
MIME_ENTRY("html", "text/html"),
|
|
MIME_ENTRY("html", "text/html"),
|
|
MIME_ENTRY("htm", "text/html"),
|
|
MIME_ENTRY("shtm", "text/html"),
|
|
MIME_ENTRY("shtml", "text/html"),
|
|
MIME_ENTRY("css", "text/css"),
|
|
MIME_ENTRY("js", "application/x-javascript"),
|
|
MIME_ENTRY("ico", "image/x-icon"),
|
|
MIME_ENTRY("gif", "image/gif"),
|
|
MIME_ENTRY("jpg", "image/jpeg"),
|
|
MIME_ENTRY("jpeg", "image/jpeg"),
|
|
MIME_ENTRY("png", "image/png"),
|
|
MIME_ENTRY("svg", "image/svg+xml"),
|
|
MIME_ENTRY("txt", "text/plain"),
|
|
MIME_ENTRY("torrent", "application/x-bittorrent"),
|
|
MIME_ENTRY("wav", "audio/x-wav"),
|
|
MIME_ENTRY("mp3", "audio/x-mp3"),
|
|
MIME_ENTRY("mid", "audio/mid"),
|
|
MIME_ENTRY("m3u", "audio/x-mpegurl"),
|
|
MIME_ENTRY("ogg", "application/ogg"),
|
|
MIME_ENTRY("ram", "audio/x-pn-realaudio"),
|
|
MIME_ENTRY("xml", "text/xml"),
|
|
MIME_ENTRY("ttf", "application/x-font-ttf"),
|
|
MIME_ENTRY("json", "application/json"),
|
|
MIME_ENTRY("xslt", "application/xml"),
|
|
MIME_ENTRY("xsl", "application/xml"),
|
|
MIME_ENTRY("ra", "audio/x-pn-realaudio"),
|
|
MIME_ENTRY("doc", "application/msword"),
|
|
MIME_ENTRY("exe", "application/octet-stream"),
|
|
MIME_ENTRY("zip", "application/x-zip-compressed"),
|
|
MIME_ENTRY("xls", "application/excel"),
|
|
MIME_ENTRY("tgz", "application/x-tar-gz"),
|
|
MIME_ENTRY("tar", "application/x-tar"),
|
|
MIME_ENTRY("gz", "application/x-gunzip"),
|
|
MIME_ENTRY("arj", "application/x-arj-compressed"),
|
|
MIME_ENTRY("rar", "application/x-rar-compressed"),
|
|
MIME_ENTRY("rtf", "application/rtf"),
|
|
MIME_ENTRY("pdf", "application/pdf"),
|
|
MIME_ENTRY("swf", "application/x-shockwave-flash"),
|
|
MIME_ENTRY("mpg", "video/mpeg"),
|
|
MIME_ENTRY("webm", "video/webm"),
|
|
MIME_ENTRY("mpeg", "video/mpeg"),
|
|
MIME_ENTRY("mov", "video/quicktime"),
|
|
MIME_ENTRY("mp4", "video/mp4"),
|
|
MIME_ENTRY("m4v", "video/x-m4v"),
|
|
MIME_ENTRY("asf", "video/x-ms-asf"),
|
|
MIME_ENTRY("avi", "video/x-msvideo"),
|
|
MIME_ENTRY("bmp", "image/bmp"),
|
|
{NULL, 0, NULL}};
|
|
|
|
static struct mg_str mg_get_mime_type(const char *path, const char *dflt,
|
|
const struct mg_serve_http_opts *opts) {
|
|
const char *ext, *overrides;
|
|
size_t i, path_len;
|
|
struct mg_str r, k, v;
|
|
|
|
path_len = strlen(path);
|
|
|
|
overrides = opts->custom_mime_types;
|
|
while ((overrides = mg_next_comma_list_entry(overrides, &k, &v)) != NULL) {
|
|
ext = path + (path_len - k.len);
|
|
if (path_len > k.len && mg_vcasecmp(&k, ext) == 0) {
|
|
return v;
|
|
}
|
|
}
|
|
|
|
for (i = 0; mg_static_builtin_mime_types[i].extension != NULL; i++) {
|
|
ext = path + (path_len - mg_static_builtin_mime_types[i].ext_len);
|
|
if (path_len > mg_static_builtin_mime_types[i].ext_len && ext[-1] == '.' &&
|
|
mg_casecmp(ext, mg_static_builtin_mime_types[i].extension) == 0) {
|
|
r.p = mg_static_builtin_mime_types[i].mime_type;
|
|
r.len = strlen(r.p);
|
|
return r;
|
|
}
|
|
}
|
|
|
|
r.p = dflt;
|
|
r.len = strlen(r.p);
|
|
return r;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Check whether full request is buffered. Return:
|
|
* -1 if request is malformed
|
|
* 0 if request is not yet fully buffered
|
|
* >0 actual request length, including last \r\n\r\n
|
|
*/
|
|
static int mg_http_get_request_len(const char *s, int buf_len) {
|
|
const unsigned char *buf = (unsigned char *) s;
|
|
int i;
|
|
|
|
for (i = 0; i < buf_len; i++) {
|
|
if (!isprint(buf[i]) && buf[i] != '\r' && buf[i] != '\n' && buf[i] < 128) {
|
|
return -1;
|
|
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
|
|
return i + 2;
|
|
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
|
|
buf[i + 2] == '\n') {
|
|
return i + 3;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *mg_http_parse_headers(const char *s, const char *end,
|
|
int len, struct http_message *req) {
|
|
int i = 0;
|
|
while (i < (int) ARRAY_SIZE(req->header_names) - 1) {
|
|
struct mg_str *k = &req->header_names[i], *v = &req->header_values[i];
|
|
|
|
s = mg_skip(s, end, ": ", k);
|
|
s = mg_skip(s, end, "\r\n", v);
|
|
|
|
while (v->len > 0 && v->p[v->len - 1] == ' ') {
|
|
v->len--; /* Trim trailing spaces in header value */
|
|
}
|
|
|
|
/*
|
|
* If header value is empty - skip it and go to next (if any).
|
|
* NOTE: Do not add it to headers_values because such addition changes API
|
|
* behaviour
|
|
*/
|
|
if (k->len != 0 && v->len == 0) {
|
|
continue;
|
|
}
|
|
|
|
if (k->len == 0 || v->len == 0) {
|
|
k->p = v->p = NULL;
|
|
k->len = v->len = 0;
|
|
break;
|
|
}
|
|
|
|
if (!mg_ncasecmp(k->p, "Content-Length", 14)) {
|
|
req->body.len = (size_t) to64(v->p);
|
|
req->message.len = len + req->body.len;
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req) {
|
|
const char *end, *qs;
|
|
int len = mg_http_get_request_len(s, n);
|
|
|
|
if (len <= 0) return len;
|
|
|
|
memset(hm, 0, sizeof(*hm));
|
|
hm->message.p = s;
|
|
hm->body.p = s + len;
|
|
hm->message.len = hm->body.len = (size_t) ~0;
|
|
end = s + len;
|
|
|
|
/* Request is fully buffered. Skip leading whitespaces. */
|
|
while (s < end && isspace(*(unsigned char *) s)) s++;
|
|
|
|
if (is_req) {
|
|
/* Parse request line: method, URI, proto */
|
|
s = mg_skip(s, end, " ", &hm->method);
|
|
s = mg_skip(s, end, " ", &hm->uri);
|
|
s = mg_skip(s, end, "\r\n", &hm->proto);
|
|
if (hm->uri.p <= hm->method.p || hm->proto.p <= hm->uri.p) return -1;
|
|
|
|
/* If URI contains '?' character, initialize query_string */
|
|
if ((qs = (char *) memchr(hm->uri.p, '?', hm->uri.len)) != NULL) {
|
|
hm->query_string.p = qs + 1;
|
|
hm->query_string.len = &hm->uri.p[hm->uri.len] - (qs + 1);
|
|
hm->uri.len = qs - hm->uri.p;
|
|
}
|
|
} else {
|
|
s = mg_skip(s, end, " ", &hm->proto);
|
|
if (end - s < 4 || s[3] != ' ') return -1;
|
|
hm->resp_code = atoi(s);
|
|
if (hm->resp_code < 100 || hm->resp_code >= 600) return -1;
|
|
s += 4;
|
|
s = mg_skip(s, end, "\r\n", &hm->resp_status_msg);
|
|
}
|
|
|
|
s = mg_http_parse_headers(s, end, len, hm);
|
|
|
|
/*
|
|
* mg_parse_http() is used to parse both HTTP requests and HTTP
|
|
* responses. If HTTP response does not have Content-Length set, then
|
|
* body is read until socket is closed, i.e. body.len is infinite (~0).
|
|
*
|
|
* For HTTP requests though, according to
|
|
* http://tools.ietf.org/html/rfc7231#section-8.1.3,
|
|
* only POST and PUT methods have defined body semantics.
|
|
* Therefore, if Content-Length is not specified and methods are
|
|
* not one of PUT or POST, set body length to 0.
|
|
*
|
|
* So,
|
|
* if it is HTTP request, and Content-Length is not set,
|
|
* and method is not (PUT or POST) then reset body length to zero.
|
|
*/
|
|
if (hm->body.len == (size_t) ~0 && is_req &&
|
|
mg_vcasecmp(&hm->method, "PUT") != 0 &&
|
|
mg_vcasecmp(&hm->method, "POST") != 0) {
|
|
hm->body.len = 0;
|
|
hm->message.len = len;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
struct mg_str *mg_get_http_header(struct http_message *hm, const char *name) {
|
|
size_t i, len = strlen(name);
|
|
|
|
for (i = 0; hm->header_names[i].len > 0; i++) {
|
|
struct mg_str *h = &hm->header_names[i], *v = &hm->header_values[i];
|
|
if (h->p != NULL && h->len == len && !mg_ncasecmp(h->p, name, len))
|
|
return v;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#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 >= MG_MAX_HTTP_SEND_MBUF) {
|
|
to_read = 0;
|
|
} else {
|
|
to_read = MG_MAX_HTTP_SEND_MBUF - io->len;
|
|
}
|
|
if (to_read > left) {
|
|
to_read = left;
|
|
}
|
|
if (to_read > 0) {
|
|
n = mg_fread(buf, 1, to_read, pd->file.fp);
|
|
if (n > 0) {
|
|
mg_send(nc, buf, n);
|
|
pd->file.sent += n;
|
|
DBG(("%p sent %d (total %d)", nc, (int) n, (int) pd->file.sent));
|
|
}
|
|
} else {
|
|
/* Rate-limited */
|
|
}
|
|
if (pd->file.sent >= pd->file.cl) {
|
|
LOG(LL_DEBUG, ("%p done, %d bytes", nc, (int) pd->file.sent));
|
|
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 = mg_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 */
|
|
|
|
/*
|
|
* 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++;
|
|
}
|
|
|
|
/* Skip new line */
|
|
if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
|
|
return 0;
|
|
}
|
|
i += 2;
|
|
|
|
/* Record where the data is */
|
|
*chunk_data = (char *) s + i;
|
|
*chunk_len = n;
|
|
|
|
/* Skip data */
|
|
i += n;
|
|
|
|
/* Skip new line */
|
|
if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
|
|
return 0;
|
|
}
|
|
return i + 2;
|
|
}
|
|
|
|
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 (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;
|
|
|
|
/* Send MG_EV_HTTP_CHUNK event */
|
|
nc->flags &= ~MG_F_DELETE_CHUNK;
|
|
mg_call(nc, nc->handler, nc->user_data, MG_EV_HTTP_CHUNK, hm);
|
|
|
|
/* 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 (zero_chunk_received) {
|
|
/* Total message size is len(body) + len(headers) */
|
|
hm->message.len =
|
|
(size_t) pd->chunk.body_len + blen - i + (hm->body.p - hm->message.p);
|
|
}
|
|
}
|
|
|
|
return body_len;
|
|
}
|
|
|
|
struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc,
|
|
struct mg_str *uri_path) {
|
|
struct mg_http_proto_data *pd;
|
|
struct mg_http_endpoint *ret = NULL;
|
|
int matched, matched_max = 0;
|
|
struct mg_http_endpoint *ep;
|
|
|
|
if (nc == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
pd = mg_http_get_proto_data(nc);
|
|
|
|
ep = pd->endpoints;
|
|
while (ep != NULL) {
|
|
if ((matched = mg_match_prefix_n(ep->uri_pattern, *uri_path)) > 0) {
|
|
if (matched > matched_max) {
|
|
/* Looking for the longest suitable handler */
|
|
ret = ep;
|
|
matched_max = matched;
|
|
}
|
|
}
|
|
|
|
ep = ep->next;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
static void mg_http_multipart_continue(struct mg_connection *nc);
|
|
|
|
static void mg_http_multipart_begin(struct mg_connection *nc,
|
|
struct http_message *hm, int req_len);
|
|
|
|
#endif
|
|
|
|
static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
|
|
struct http_message *hm);
|
|
|
|
static void deliver_chunk(struct mg_connection *c, struct http_message *hm,
|
|
int req_len) {
|
|
/* Incomplete message received. Send MG_EV_HTTP_CHUNK event */
|
|
hm->body.len = c->recv_mbuf.len - req_len;
|
|
c->flags &= ~MG_F_DELETE_CHUNK;
|
|
mg_call(c, c->handler, c->user_data, MG_EV_HTTP_CHUNK, hm);
|
|
/* Delete processed data if user set MG_F_DELETE_CHUNK flag */
|
|
if (c->flags & MG_F_DELETE_CHUNK) c->recv_mbuf.len = req_len;
|
|
}
|
|
|
|
/*
|
|
* 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 MG_UD_ARG(void *user_data),
|
|
struct http_message *hm) __attribute__((noinline));
|
|
|
|
void mg_http_handler(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data)) {
|
|
struct http_message hm;
|
|
mg_http_handler2(nc, ev, ev_data MG_UD_ARG(user_data), &hm);
|
|
}
|
|
|
|
static void mg_http_handler2(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data),
|
|
struct http_message *hm) {
|
|
#else /* !__XTENSA__ */
|
|
void mg_http_handler(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data)) {
|
|
struct http_message shm, *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_CGI
|
|
/* Close associated CGI forwarder connection */
|
|
if (pd->cgi.cgi_nc != NULL) {
|
|
pd->cgi.cgi_nc->user_data = NULL;
|
|
pd->cgi.cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY;
|
|
}
|
|
#endif
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
if (pd->mp_stream.boundary != NULL) {
|
|
/*
|
|
* Multipart message is in progress, but connection is closed.
|
|
* Finish part and request with an error flag.
|
|
*/
|
|
struct mg_http_multipart_part mp;
|
|
memset(&mp, 0, sizeof(mp));
|
|
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),
|
|
nc->user_data, MG_EV_HTTP_PART_END, &mp);
|
|
mp.var_name = NULL;
|
|
mp.file_name = NULL;
|
|
mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
|
|
nc->user_data, MG_EV_HTTP_MULTIPART_REQUEST_END, &mp);
|
|
} else
|
|
#endif
|
|
if (io->len > 0 &&
|
|
(req_len = 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;
|
|
deliver_chunk(nc, hm, req_len);
|
|
mg_http_call_endpoint_handler(nc, ev2, hm);
|
|
}
|
|
pd->rcvd = 0;
|
|
}
|
|
|
|
#if MG_ENABLE_FILESYSTEM
|
|
if (pd->file.fp != NULL) {
|
|
mg_http_transfer_file_data(nc);
|
|
}
|
|
#endif
|
|
|
|
mg_call(nc, nc->handler, nc->user_data, ev, ev_data);
|
|
|
|
if (ev == MG_EV_RECV) {
|
|
struct mg_str *s;
|
|
pd->rcvd += *(int *) ev_data;
|
|
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
if (pd->mp_stream.boundary != NULL) {
|
|
mg_http_multipart_continue(nc);
|
|
return;
|
|
}
|
|
#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
|
|
|
|
again:
|
|
req_len = mg_parse_http(io->buf, io->len, hm, is_req);
|
|
|
|
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);
|
|
}
|
|
|
|
#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 */
|
|
|
|
/* 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, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
|
|
NULL);
|
|
mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
|
|
} else if (nc->listener != NULL &&
|
|
(vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
|
|
struct mg_http_endpoint *ep;
|
|
|
|
/* 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 we have a handler set up with mg_register_http_endpoint(),
|
|
* deliver subsequent websocket events to this handler after the
|
|
* protocol switch.
|
|
*/
|
|
ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
|
|
if (ep != NULL) {
|
|
nc->handler = ep->handler;
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
nc->user_data = ep->user_data;
|
|
#endif
|
|
}
|
|
|
|
/* Send handshake */
|
|
mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST,
|
|
hm);
|
|
if (!(nc->flags & (MG_F_CLOSE_IMMEDIATELY | MG_F_SEND_AND_CLOSE))) {
|
|
if (nc->send_mbuf.len == 0) {
|
|
mg_ws_handshake(nc, vec, hm);
|
|
}
|
|
mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE,
|
|
NULL);
|
|
mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data));
|
|
}
|
|
}
|
|
#endif /* MG_ENABLE_HTTP_WEBSOCKET */
|
|
else if (hm->message.len > pd->rcvd) {
|
|
/* Not yet received all HTTP body, deliver MG_EV_HTTP_CHUNK */
|
|
deliver_chunk(nc, hm, req_len);
|
|
if (nc->recv_mbuf_limit > 0 && nc->recv_mbuf.len >= nc->recv_mbuf_limit) {
|
|
LOG(LL_ERROR, ("%p recv buffer (%lu bytes) exceeds the limit "
|
|
"%lu bytes, and not drained, closing",
|
|
nc, (unsigned long) nc->recv_mbuf.len,
|
|
(unsigned long) nc->recv_mbuf_limit));
|
|
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
|
|
}
|
|
} else {
|
|
/* We did receive all HTTP body. */
|
|
int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
|
|
char addr[32];
|
|
mg_sock_addr_to_str(&nc->sa, addr, sizeof(addr),
|
|
MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
|
|
DBG(("%p %s %.*s %.*s", nc, addr, (int) hm->method.len, hm->method.p,
|
|
(int) hm->uri.len, hm->uri.p));
|
|
deliver_chunk(nc, hm, req_len);
|
|
/* Whole HTTP message is fully buffered, call event handler */
|
|
mg_http_call_endpoint_handler(nc, trigger_ev, hm);
|
|
mbuf_remove(io, hm->message.len);
|
|
pd->rcvd -= hm->message.len;
|
|
if (io->len > 0) {
|
|
goto again;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_buf[100];
|
|
char *boundary = boundary_buf;
|
|
int boundary_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;
|
|
}
|
|
|
|
/* Content-type should start with "multipart" */
|
|
if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) {
|
|
goto exit_mp;
|
|
}
|
|
|
|
boundary_len =
|
|
mg_http_parse_header2(ct, "boundary", &boundary, sizeof(boundary_buf));
|
|
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 {
|
|
struct mg_http_endpoint *ep = NULL;
|
|
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 = nc->handler;
|
|
|
|
ep = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
|
|
if (ep != NULL) {
|
|
pd->endpoint_handler = ep->handler;
|
|
}
|
|
|
|
mg_http_call_endpoint_handler(nc, MG_EV_HTTP_MULTIPART_REQUEST, hm);
|
|
|
|
mbuf_remove(io, req_len);
|
|
}
|
|
exit_mp:
|
|
if (boundary != boundary_buf) MG_FREE(boundary);
|
|
}
|
|
|
|
#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, c->user_data, 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_FREE((void *) pd->mp_stream.file_name);
|
|
pd->mp_stream.file_name = NULL;
|
|
MG_FREE((void *) pd->mp_stream.var_name);
|
|
pd->mp_stream.var_name = NULL;
|
|
mg_http_multipart_call_handler(c, MG_EV_HTTP_MULTIPART_REQUEST_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 (pd->mp_stream.boundary == NULL) {
|
|
pd->mp_stream.state = MPS_FINALIZE;
|
|
DBG(("Invalid request: boundary not initialized"));
|
|
return 0;
|
|
}
|
|
|
|
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) {
|
|
const char *boundary_end = (boundary + pd->mp_stream.boundary_len);
|
|
if (io->len - (boundary_end - io->buf) < 4) {
|
|
return 0;
|
|
}
|
|
if (strncmp(boundary_end, "--\r\n", 4) == 0) {
|
|
pd->mp_stream.state = MPS_FINALIZE;
|
|
mbuf_remove(io, (boundary_end - io->buf) + 4);
|
|
} else {
|
|
pd->mp_stream.state = MPS_GOT_BOUNDARY;
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void mg_http_parse_header_internal(struct mg_str *hdr,
|
|
const char *var_name,
|
|
struct altbuf *ab);
|
|
|
|
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);
|
|
struct altbuf ab_file_name, ab_var_name;
|
|
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);
|
|
|
|
altbuf_init(&ab_file_name, NULL, 0);
|
|
altbuf_init(&ab_var_name, NULL, 0);
|
|
|
|
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;
|
|
|
|
altbuf_reset(&ab_var_name);
|
|
mg_http_parse_header_internal(&header, "name", &ab_var_name);
|
|
|
|
altbuf_reset(&ab_file_name);
|
|
mg_http_parse_header_internal(&header, "filename", &ab_file_name);
|
|
|
|
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);
|
|
}
|
|
|
|
/* Reserve 2 bytes for "\r\n" in file_name and var_name */
|
|
altbuf_append(&ab_file_name, '\0');
|
|
altbuf_append(&ab_file_name, '\0');
|
|
altbuf_append(&ab_var_name, '\0');
|
|
altbuf_append(&ab_var_name, '\0');
|
|
|
|
MG_FREE((void *) pd->mp_stream.file_name);
|
|
pd->mp_stream.file_name = altbuf_get_buf(&ab_file_name, 1 /* trim */);
|
|
MG_FREE((void *) pd->mp_stream.var_name);
|
|
pd->mp_stream.var_name = altbuf_get_buf(&ab_var_name, 1 /* trim */);
|
|
|
|
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;
|
|
|
|
altbuf_reset(&ab_var_name);
|
|
altbuf_reset(&ab_file_name);
|
|
|
|
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);
|
|
struct mbuf *io = &c->recv_mbuf;
|
|
|
|
const char *boundary;
|
|
if ((int) io->len < pd->mp_stream.boundary_len + 6 /* \r\n, --, -- */) {
|
|
return 0;
|
|
}
|
|
|
|
boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
|
|
if (boundary == NULL && pd->mp_stream.prev_io_len == 0) {
|
|
pd->mp_stream.prev_io_len = io->len;
|
|
return 0;
|
|
} else if (boundary == NULL &&
|
|
(int) io->len >
|
|
pd->mp_stream.prev_io_len + pd->mp_stream.boundary_len + 4) {
|
|
pd->mp_stream.state = MPS_GOT_CHUNK;
|
|
return 1;
|
|
} else if (boundary != NULL) {
|
|
int data_size = (boundary - io->buf - 4);
|
|
mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_size);
|
|
mbuf_remove(io, (boundary - io->buf));
|
|
pd->mp_stream.prev_io_len = 0;
|
|
pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void mg_http_multipart_continue(struct mg_connection *c) {
|
|
struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
|
|
while (1) {
|
|
switch (pd->mp_stream.state) {
|
|
case MPS_BEGIN: {
|
|
pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
|
|
break;
|
|
}
|
|
case MPS_WAITING_FOR_BOUNDARY: {
|
|
if (mg_http_multipart_wait_for_boundary(c) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case MPS_GOT_BOUNDARY: {
|
|
if (mg_http_multipart_process_boundary(c) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case MPS_WAITING_FOR_CHUNK: {
|
|
if (mg_http_multipart_continue_wait_for_chunk(c) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case MPS_GOT_CHUNK: {
|
|
if (mg_http_multipart_got_chunk(c) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case MPS_FINALIZE: {
|
|
if (mg_http_multipart_finalize(c) == 0) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case MPS_FINISHED: {
|
|
mbuf_remove(&c->recv_mbuf, c->recv_mbuf.len);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct file_upload_state {
|
|
char *lfn;
|
|
size_t num_recd;
|
|
FILE *fp;
|
|
};
|
|
|
|
#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
|
|
|
|
void mg_set_protocol_http_websocket(struct mg_connection *nc) {
|
|
nc->proto_handler = mg_http_handler;
|
|
}
|
|
|
|
const char *mg_status_message(int status_code) {
|
|
switch (status_code) {
|
|
case 206:
|
|
return "Partial Content";
|
|
case 301:
|
|
return "Moved";
|
|
case 302:
|
|
return "Found";
|
|
case 400:
|
|
return "Bad Request";
|
|
case 401:
|
|
return "Unauthorized";
|
|
case 403:
|
|
return "Forbidden";
|
|
case 404:
|
|
return "Not Found";
|
|
case 416:
|
|
return "Requested Range Not Satisfiable";
|
|
case 418:
|
|
return "I'm a teapot";
|
|
case 500:
|
|
return "Internal Server Error";
|
|
case 502:
|
|
return "Bad Gateway";
|
|
case 503:
|
|
return "Service Unavailable";
|
|
|
|
#if MG_ENABLE_EXTRA_ERRORS_DESC
|
|
case 100:
|
|
return "Continue";
|
|
case 101:
|
|
return "Switching Protocols";
|
|
case 102:
|
|
return "Processing";
|
|
case 200:
|
|
return "OK";
|
|
case 201:
|
|
return "Created";
|
|
case 202:
|
|
return "Accepted";
|
|
case 203:
|
|
return "Non-Authoritative Information";
|
|
case 204:
|
|
return "No Content";
|
|
case 205:
|
|
return "Reset Content";
|
|
case 207:
|
|
return "Multi-Status";
|
|
case 208:
|
|
return "Already Reported";
|
|
case 226:
|
|
return "IM Used";
|
|
case 300:
|
|
return "Multiple Choices";
|
|
case 303:
|
|
return "See Other";
|
|
case 304:
|
|
return "Not Modified";
|
|
case 305:
|
|
return "Use Proxy";
|
|
case 306:
|
|
return "Switch Proxy";
|
|
case 307:
|
|
return "Temporary Redirect";
|
|
case 308:
|
|
return "Permanent Redirect";
|
|
case 402:
|
|
return "Payment Required";
|
|
case 405:
|
|
return "Method Not Allowed";
|
|
case 406:
|
|
return "Not Acceptable";
|
|
case 407:
|
|
return "Proxy Authentication Required";
|
|
case 408:
|
|
return "Request Timeout";
|
|
case 409:
|
|
return "Conflict";
|
|
case 410:
|
|
return "Gone";
|
|
case 411:
|
|
return "Length Required";
|
|
case 412:
|
|
return "Precondition Failed";
|
|
case 413:
|
|
return "Payload Too Large";
|
|
case 414:
|
|
return "URI Too Long";
|
|
case 415:
|
|
return "Unsupported Media Type";
|
|
case 417:
|
|
return "Expectation Failed";
|
|
case 422:
|
|
return "Unprocessable Entity";
|
|
case 423:
|
|
return "Locked";
|
|
case 424:
|
|
return "Failed Dependency";
|
|
case 426:
|
|
return "Upgrade Required";
|
|
case 428:
|
|
return "Precondition Required";
|
|
case 429:
|
|
return "Too Many Requests";
|
|
case 431:
|
|
return "Request Header Fields Too Large";
|
|
case 451:
|
|
return "Unavailable For Legal Reasons";
|
|
case 501:
|
|
return "Not Implemented";
|
|
case 504:
|
|
return "Gateway Timeout";
|
|
case 505:
|
|
return "HTTP Version Not Supported";
|
|
case 506:
|
|
return "Variant Also Negotiates";
|
|
case 507:
|
|
return "Insufficient Storage";
|
|
case 508:
|
|
return "Loop Detected";
|
|
case 510:
|
|
return "Not Extended";
|
|
case 511:
|
|
return "Network Authentication Required";
|
|
#endif /* MG_ENABLE_EXTRA_ERRORS_DESC */
|
|
|
|
default:
|
|
return "OK";
|
|
}
|
|
}
|
|
|
|
void mg_send_response_line_s(struct mg_connection *nc, int status_code,
|
|
const struct mg_str extra_headers) {
|
|
mg_printf(nc, "HTTP/1.1 %d %s\r\nServer: %s\r\n", status_code,
|
|
mg_status_message(status_code), mg_version_header);
|
|
if (extra_headers.len > 0) {
|
|
mg_printf(nc, "%.*s\r\n", (int) extra_headers.len, extra_headers.p);
|
|
}
|
|
}
|
|
|
|
void mg_send_response_line(struct mg_connection *nc, int status_code,
|
|
const char *extra_headers) {
|
|
mg_send_response_line_s(nc, status_code, mg_mk_str(extra_headers));
|
|
}
|
|
|
|
void mg_http_send_redirect(struct mg_connection *nc, int status_code,
|
|
const struct mg_str location,
|
|
const struct mg_str extra_headers) {
|
|
char bbody[100], *pbody = bbody;
|
|
int bl = mg_asprintf(&pbody, sizeof(bbody),
|
|
"<p>Moved <a href='%.*s'>here</a>.\r\n",
|
|
(int) location.len, location.p);
|
|
char bhead[150], *phead = bhead;
|
|
mg_asprintf(&phead, sizeof(bhead),
|
|
"Location: %.*s\r\n"
|
|
"Content-Type: text/html\r\n"
|
|
"Content-Length: %d\r\n"
|
|
"Cache-Control: no-cache\r\n"
|
|
"%.*s%s",
|
|
(int) location.len, location.p, bl, (int) extra_headers.len,
|
|
extra_headers.p, (extra_headers.len > 0 ? "\r\n" : ""));
|
|
mg_send_response_line(nc, status_code, phead);
|
|
if (phead != bhead) MG_FREE(phead);
|
|
mg_send(nc, pbody, bl);
|
|
if (pbody != bbody) MG_FREE(pbody);
|
|
}
|
|
|
|
void mg_send_head(struct mg_connection *c, int status_code,
|
|
int64_t content_length, const char *extra_headers) {
|
|
mg_send_response_line(c, status_code, extra_headers);
|
|
if (content_length < 0) {
|
|
mg_printf(c, "%s", "Transfer-Encoding: chunked\r\n");
|
|
} else {
|
|
mg_printf(c, "Content-Length: %" INT64_FMT "\r\n", content_length);
|
|
}
|
|
mg_send(c, "\r\n", 2);
|
|
}
|
|
|
|
void mg_http_send_error(struct mg_connection *nc, int code,
|
|
const char *reason) {
|
|
if (!reason) reason = mg_status_message(code);
|
|
LOG(LL_DEBUG, ("%p %d %s", nc, code, reason));
|
|
mg_send_head(nc, code, strlen(reason),
|
|
"Content-Type: text/plain\r\nConnection: close");
|
|
mg_send(nc, reason, strlen(reason));
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
}
|
|
|
|
#if MG_ENABLE_FILESYSTEM
|
|
static void mg_http_construct_etag(char *buf, size_t buf_len,
|
|
const cs_stat_t *st) {
|
|
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"", (unsigned long) st->st_mtime,
|
|
(int64_t) st->st_size);
|
|
}
|
|
|
|
#ifndef WINCE
|
|
static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t) {
|
|
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
|
|
}
|
|
#else
|
|
/* Look wince_lib.c for WindowsCE implementation */
|
|
static void mg_gmt_time_string(char *buf, size_t buf_len, time_t *t);
|
|
#endif
|
|
|
|
static int mg_http_parse_range_header(const struct mg_str *header, int64_t *a,
|
|
int64_t *b) {
|
|
/*
|
|
* There is no snscanf. Headers are not guaranteed to be NUL-terminated,
|
|
* so we have this. Ugh.
|
|
*/
|
|
int result;
|
|
char *p = (char *) MG_MALLOC(header->len + 1);
|
|
if (p == NULL) return 0;
|
|
memcpy(p, header->p, header->len);
|
|
p[header->len] = '\0';
|
|
result = sscanf(p, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
|
|
MG_FREE(p);
|
|
return result;
|
|
}
|
|
|
|
void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,
|
|
const char *path, const struct mg_str mime_type,
|
|
const struct mg_str extra_headers) {
|
|
struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
|
|
cs_stat_t st;
|
|
LOG(LL_DEBUG, ("%p [%s] %.*s", nc, path, (int) mime_type.len, mime_type.p));
|
|
if (mg_stat(path, &st) != 0 || (pd->file.fp = mg_fopen(path, "rb")) == NULL) {
|
|
int code, err = mg_get_errno();
|
|
switch (err) {
|
|
case EACCES:
|
|
code = 403;
|
|
break;
|
|
case ENOENT:
|
|
code = 404;
|
|
break;
|
|
default:
|
|
code = 500;
|
|
};
|
|
mg_http_send_error(nc, code, "Open failed");
|
|
} else {
|
|
char etag[50], current_time[50], last_modified[50], range[70];
|
|
time_t t = (time_t) mg_time();
|
|
int64_t r1 = 0, r2 = 0, cl = st.st_size;
|
|
struct mg_str *range_hdr = mg_get_http_header(hm, "Range");
|
|
int n, status_code = 200;
|
|
|
|
/* Handle Range header */
|
|
range[0] = '\0';
|
|
if (range_hdr != NULL &&
|
|
(n = mg_http_parse_range_header(range_hdr, &r1, &r2)) > 0 && r1 >= 0 &&
|
|
r2 >= 0) {
|
|
/* If range is specified like "400-", set second limit to content len */
|
|
if (n == 1) {
|
|
r2 = cl - 1;
|
|
}
|
|
if (r1 > r2 || r2 >= cl) {
|
|
status_code = 416;
|
|
cl = 0;
|
|
snprintf(range, sizeof(range),
|
|
"Content-Range: bytes */%" INT64_FMT "\r\n",
|
|
(int64_t) st.st_size);
|
|
} else {
|
|
status_code = 206;
|
|
cl = r2 - r1 + 1;
|
|
snprintf(range, sizeof(range), "Content-Range: bytes %" INT64_FMT
|
|
"-%" INT64_FMT "/%" INT64_FMT "\r\n",
|
|
r1, r1 + cl - 1, (int64_t) st.st_size);
|
|
#if _FILE_OFFSET_BITS == 64 || _POSIX_C_SOURCE >= 200112L || \
|
|
_XOPEN_SOURCE >= 600
|
|
fseeko(pd->file.fp, r1, SEEK_SET);
|
|
#else
|
|
fseek(pd->file.fp, (long) r1, SEEK_SET);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !MG_DISABLE_HTTP_KEEP_ALIVE
|
|
{
|
|
struct mg_str *conn_hdr = mg_get_http_header(hm, "Connection");
|
|
if (conn_hdr != NULL) {
|
|
pd->file.keepalive = (mg_vcasecmp(conn_hdr, "keep-alive") == 0);
|
|
} else {
|
|
pd->file.keepalive = (mg_vcmp(&hm->proto, "HTTP/1.1") == 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
mg_http_construct_etag(etag, sizeof(etag), &st);
|
|
mg_gmt_time_string(current_time, sizeof(current_time), &t);
|
|
mg_gmt_time_string(last_modified, sizeof(last_modified), &st.st_mtime);
|
|
/*
|
|
* Content length casted to size_t because:
|
|
* 1) that's the maximum buffer size anyway
|
|
* 2) ESP8266 RTOS SDK newlib vprintf cannot contain a 64bit arg at non-last
|
|
* position
|
|
* TODO(mkm): fix ESP8266 RTOS SDK
|
|
*/
|
|
mg_send_response_line_s(nc, status_code, extra_headers);
|
|
mg_printf(nc,
|
|
"Date: %s\r\n"
|
|
"Last-Modified: %s\r\n"
|
|
"Accept-Ranges: bytes\r\n"
|
|
"Content-Type: %.*s\r\n"
|
|
"Connection: %s\r\n"
|
|
"Content-Length: %" SIZE_T_FMT
|
|
"\r\n"
|
|
"%sEtag: %s\r\n\r\n",
|
|
current_time, last_modified, (int) mime_type.len, mime_type.p,
|
|
(pd->file.keepalive ? "keep-alive" : "close"), (size_t) cl, range,
|
|
etag);
|
|
|
|
pd->file.cl = cl;
|
|
pd->file.type = DATA_FILE;
|
|
mg_http_transfer_file_data(nc);
|
|
}
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
#endif
|
|
|
|
int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
|
|
int is_form_url_encoded) {
|
|
int i, j, a, b;
|
|
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
|
|
|
|
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
|
|
if (src[i] == '%') {
|
|
if (i < src_len - 2 && isxdigit(*(const unsigned char *) (src + i + 1)) &&
|
|
isxdigit(*(const unsigned char *) (src + i + 2))) {
|
|
a = tolower(*(const unsigned char *) (src + i + 1));
|
|
b = tolower(*(const unsigned char *) (src + i + 2));
|
|
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
|
|
i += 2;
|
|
} else {
|
|
return -1;
|
|
}
|
|
} else if (is_form_url_encoded && src[i] == '+') {
|
|
dst[j] = ' ';
|
|
} else {
|
|
dst[j] = src[i];
|
|
}
|
|
}
|
|
|
|
dst[j] = '\0'; /* Null-terminate the destination */
|
|
|
|
return i >= src_len ? j : -1;
|
|
}
|
|
|
|
int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
|
|
size_t dst_len) {
|
|
const char *p, *e, *s;
|
|
size_t name_len;
|
|
int len;
|
|
|
|
/*
|
|
* According to the documentation function returns negative
|
|
* value in case of error. For debug purposes it returns:
|
|
* -1 - src is wrong (NUUL)
|
|
* -2 - dst is wrong (NULL)
|
|
* -3 - failed to decode url or dst is to small
|
|
* -4 - name does not exist
|
|
*/
|
|
if (dst == NULL || dst_len == 0) {
|
|
len = -2;
|
|
} else if (buf->p == NULL || name == NULL || buf->len == 0) {
|
|
len = -1;
|
|
dst[0] = '\0';
|
|
} else {
|
|
name_len = strlen(name);
|
|
e = buf->p + buf->len;
|
|
len = -4;
|
|
dst[0] = '\0';
|
|
|
|
for (p = buf->p; p + name_len < e; p++) {
|
|
if ((p == buf->p || p[-1] == '&') && p[name_len] == '=' &&
|
|
!mg_ncasecmp(name, p, name_len)) {
|
|
p += name_len + 1;
|
|
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
|
if (s == NULL) {
|
|
s = e;
|
|
}
|
|
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
|
/* -1 means: failed to decode or dst is too small */
|
|
if (len == -1) {
|
|
len = -3;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len) {
|
|
char chunk_size[50];
|
|
int n;
|
|
|
|
n = snprintf(chunk_size, sizeof(chunk_size), "%lX\r\n", (unsigned long) len);
|
|
mg_send(nc, chunk_size, n);
|
|
mg_send(nc, buf, len);
|
|
mg_send(nc, "\r\n", 2);
|
|
}
|
|
|
|
void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...) {
|
|
char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
|
|
int len;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
len = mg_avprintf(&buf, sizeof(mem), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (len >= 0) {
|
|
mg_send_http_chunk(nc, buf, len);
|
|
}
|
|
|
|
/* LCOV_EXCL_START */
|
|
if (buf != mem && buf != NULL) {
|
|
MG_FREE(buf);
|
|
}
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...) {
|
|
char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
|
|
int i, j, len;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
len = mg_avprintf(&buf, sizeof(mem), fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (len >= 0) {
|
|
for (i = j = 0; i < len; i++) {
|
|
if (buf[i] == '<' || buf[i] == '>') {
|
|
mg_send(nc, buf + j, i - j);
|
|
mg_send(nc, buf[i] == '<' ? "<" : ">", 4);
|
|
j = i + 1;
|
|
}
|
|
}
|
|
mg_send(nc, buf + j, i - j);
|
|
}
|
|
|
|
/* LCOV_EXCL_START */
|
|
if (buf != mem && buf != NULL) {
|
|
MG_FREE(buf);
|
|
}
|
|
/* LCOV_EXCL_STOP */
|
|
}
|
|
|
|
static void mg_http_parse_header_internal(struct mg_str *hdr,
|
|
const char *var_name,
|
|
struct altbuf *ab) {
|
|
int ch = ' ', ch1 = ',', n = strlen(var_name);
|
|
const char *p, *end = hdr ? hdr->p + hdr->len : NULL, *s = NULL;
|
|
|
|
/* Find where variable starts */
|
|
for (s = hdr->p; s != NULL && s + n < end; s++) {
|
|
if ((s == hdr->p || s[-1] == ch || s[-1] == ch1 || s[-1] == ';') &&
|
|
s[n] == '=' && !strncmp(s, var_name, n))
|
|
break;
|
|
}
|
|
|
|
if (s != NULL && &s[n + 1] < end) {
|
|
s += n + 1;
|
|
if (*s == '"' || *s == '\'') {
|
|
ch = ch1 = *s++;
|
|
}
|
|
p = s;
|
|
while (p < end && p[0] != ch && p[0] != ch1) {
|
|
if (ch != ' ' && p[0] == '\\' && p[1] == ch) p++;
|
|
altbuf_append(ab, *p++);
|
|
}
|
|
|
|
if (ch != ' ' && *p != ch) {
|
|
altbuf_reset(ab);
|
|
}
|
|
}
|
|
|
|
/* If there is some data, append a NUL. */
|
|
if (ab->len > 0) {
|
|
altbuf_append(ab, '\0');
|
|
}
|
|
}
|
|
|
|
int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,
|
|
size_t buf_size) {
|
|
struct altbuf ab;
|
|
altbuf_init(&ab, *buf, buf_size);
|
|
if (hdr == NULL) return 0;
|
|
if (*buf != NULL && buf_size > 0) *buf[0] = '\0';
|
|
|
|
mg_http_parse_header_internal(hdr, var_name, &ab);
|
|
|
|
/*
|
|
* Get a (trimmed) buffer, and return a len without a NUL byte which might
|
|
* have been added.
|
|
*/
|
|
*buf = altbuf_get_buf(&ab, 1 /* trim */);
|
|
return ab.len > 0 ? ab.len - 1 : 0;
|
|
}
|
|
|
|
int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
|
|
size_t buf_size) {
|
|
char *buf2 = buf;
|
|
|
|
int len = mg_http_parse_header2(hdr, var_name, &buf2, buf_size);
|
|
|
|
if (buf2 != buf) {
|
|
/* Buffer was not enough and was reallocated: free it and just return 0 */
|
|
MG_FREE(buf2);
|
|
return 0;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
int mg_get_http_basic_auth(struct http_message *hm, char *user, size_t user_len,
|
|
char *pass, size_t pass_len) {
|
|
struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
|
|
if (hdr == NULL) return -1;
|
|
return mg_parse_http_basic_auth(hdr, user, user_len, pass, pass_len);
|
|
}
|
|
|
|
int mg_parse_http_basic_auth(struct mg_str *hdr, char *user, size_t user_len,
|
|
char *pass, size_t pass_len) {
|
|
char *buf = NULL;
|
|
char fmt[64];
|
|
int res = 0;
|
|
|
|
if (mg_strncmp(*hdr, mg_mk_str("Basic "), 6) != 0) return -1;
|
|
|
|
buf = (char *) MG_MALLOC(hdr->len);
|
|
cs_base64_decode((unsigned char *) hdr->p + 6, hdr->len, buf, NULL);
|
|
|
|
/* e.g. "%123[^:]:%321[^\n]" */
|
|
snprintf(fmt, sizeof(fmt), "%%%" SIZE_T_FMT "[^:]:%%%" SIZE_T_FMT "[^\n]",
|
|
user_len - 1, pass_len - 1);
|
|
if (sscanf(buf, fmt, user, pass) == 0) {
|
|
res = -1;
|
|
}
|
|
|
|
MG_FREE(buf);
|
|
return res;
|
|
}
|
|
|
|
#if MG_ENABLE_FILESYSTEM
|
|
static int mg_is_file_hidden(const char *path,
|
|
const struct mg_serve_http_opts *opts,
|
|
int exclude_specials) {
|
|
const char *p1 = opts->per_directory_auth_file;
|
|
const char *p2 = opts->hidden_file_pattern;
|
|
|
|
/* Strip directory path from the file name */
|
|
const char *pdir = strrchr(path, DIRSEP);
|
|
if (pdir != NULL) {
|
|
path = pdir + 1;
|
|
}
|
|
|
|
return (exclude_specials && (!strcmp(path, ".") || !strcmp(path, ".."))) ||
|
|
(p1 != NULL && mg_match_prefix(p1, strlen(p1), path) == strlen(p1)) ||
|
|
(p2 != NULL && mg_match_prefix(p2, strlen(p2), path) > 0);
|
|
}
|
|
|
|
#if !MG_DISABLE_HTTP_DIGEST_AUTH
|
|
|
|
#ifndef MG_EXT_MD5
|
|
void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
|
|
const size_t *msg_lens, uint8_t *digest) {
|
|
size_t i;
|
|
cs_md5_ctx md5_ctx;
|
|
cs_md5_init(&md5_ctx);
|
|
for (i = 0; i < num_msgs; i++) {
|
|
cs_md5_update(&md5_ctx, msgs[i], msg_lens[i]);
|
|
}
|
|
cs_md5_final(digest, &md5_ctx);
|
|
}
|
|
#else
|
|
extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
|
|
const size_t *msg_lens, uint8_t *digest);
|
|
#endif
|
|
|
|
void cs_md5(char buf[33], ...) {
|
|
unsigned char hash[16];
|
|
const uint8_t *msgs[20], *p;
|
|
size_t msg_lens[20];
|
|
size_t num_msgs = 0;
|
|
va_list ap;
|
|
|
|
va_start(ap, buf);
|
|
while ((p = va_arg(ap, const unsigned char *) ) != NULL) {
|
|
msgs[num_msgs] = p;
|
|
msg_lens[num_msgs] = va_arg(ap, size_t);
|
|
num_msgs++;
|
|
}
|
|
va_end(ap);
|
|
|
|
mg_hash_md5_v(num_msgs, msgs, msg_lens, hash);
|
|
cs_to_hex(buf, hash, sizeof(hash));
|
|
}
|
|
|
|
static void mg_mkmd5resp(const char *method, size_t method_len, const char *uri,
|
|
size_t uri_len, const char *ha1, size_t ha1_len,
|
|
const char *nonce, size_t nonce_len, const char *nc,
|
|
size_t nc_len, const char *cnonce, size_t cnonce_len,
|
|
const char *qop, size_t qop_len, char *resp) {
|
|
static const char colon[] = ":";
|
|
static const size_t one = 1;
|
|
char ha2[33];
|
|
cs_md5(ha2, method, method_len, colon, one, uri, uri_len, NULL);
|
|
cs_md5(resp, ha1, ha1_len, colon, one, nonce, nonce_len, colon, one, nc,
|
|
nc_len, colon, one, cnonce, cnonce_len, colon, one, qop, qop_len,
|
|
colon, one, ha2, sizeof(ha2) - 1, NULL);
|
|
}
|
|
|
|
int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
|
|
const char *method, const char *uri,
|
|
const char *auth_domain, const char *user,
|
|
const char *passwd, const char *nonce) {
|
|
static const char colon[] = ":", qop[] = "auth";
|
|
static const size_t one = 1;
|
|
char ha1[33], resp[33], cnonce[40];
|
|
|
|
snprintf(cnonce, sizeof(cnonce), "%lx", (unsigned long) mg_time());
|
|
cs_md5(ha1, user, (size_t) strlen(user), colon, one, auth_domain,
|
|
(size_t) strlen(auth_domain), colon, one, passwd,
|
|
(size_t) strlen(passwd), NULL);
|
|
mg_mkmd5resp(method, strlen(method), uri, strlen(uri), ha1, sizeof(ha1) - 1,
|
|
nonce, strlen(nonce), "1", one, cnonce, strlen(cnonce), qop,
|
|
sizeof(qop) - 1, resp);
|
|
return snprintf(buf, buf_len,
|
|
"Authorization: Digest username=\"%s\","
|
|
"realm=\"%s\",uri=\"%s\",qop=%s,nc=1,cnonce=%s,"
|
|
"nonce=%s,response=%s\r\n",
|
|
user, auth_domain, uri, qop, cnonce, nonce, resp);
|
|
}
|
|
|
|
/*
|
|
* Check for authentication timeout.
|
|
* Clients send time stamp encoded in nonce. Make sure it is not too old,
|
|
* to prevent replay attacks.
|
|
* Assumption: nonce is a hexadecimal number of seconds since 1970.
|
|
*/
|
|
static int mg_check_nonce(const char *nonce) {
|
|
unsigned long now = (unsigned long) mg_time();
|
|
unsigned long val = (unsigned long) strtoul(nonce, NULL, 16);
|
|
return (now >= val) && (now - val < 60 * 60);
|
|
}
|
|
|
|
int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
|
|
FILE *fp) {
|
|
int ret = 0;
|
|
struct mg_str *hdr;
|
|
char username_buf[50], cnonce_buf[64], response_buf[40], uri_buf[200],
|
|
qop_buf[20], nc_buf[20], nonce_buf[16];
|
|
|
|
char *username = username_buf, *cnonce = cnonce_buf, *response = response_buf,
|
|
*uri = uri_buf, *qop = qop_buf, *nc = nc_buf, *nonce = nonce_buf;
|
|
|
|
/* Parse "Authorization:" header, fail fast on parse error */
|
|
if (hm == NULL || fp == NULL ||
|
|
(hdr = mg_get_http_header(hm, "Authorization")) == NULL ||
|
|
mg_http_parse_header2(hdr, "username", &username, sizeof(username_buf)) ==
|
|
0 ||
|
|
mg_http_parse_header2(hdr, "cnonce", &cnonce, sizeof(cnonce_buf)) == 0 ||
|
|
mg_http_parse_header2(hdr, "response", &response, sizeof(response_buf)) ==
|
|
0 ||
|
|
mg_http_parse_header2(hdr, "uri", &uri, sizeof(uri_buf)) == 0 ||
|
|
mg_http_parse_header2(hdr, "qop", &qop, sizeof(qop_buf)) == 0 ||
|
|
mg_http_parse_header2(hdr, "nc", &nc, sizeof(nc_buf)) == 0 ||
|
|
mg_http_parse_header2(hdr, "nonce", &nonce, sizeof(nonce_buf)) == 0 ||
|
|
mg_check_nonce(nonce) == 0) {
|
|
ret = 0;
|
|
goto clean;
|
|
}
|
|
|
|
/* NOTE(lsm): due to a bug in MSIE, we do not compare URIs */
|
|
|
|
ret = mg_check_digest_auth(
|
|
hm->method,
|
|
mg_mk_str_n(
|
|
hm->uri.p,
|
|
hm->uri.len + (hm->query_string.len ? hm->query_string.len + 1 : 0)),
|
|
mg_mk_str(username), mg_mk_str(cnonce), mg_mk_str(response),
|
|
mg_mk_str(qop), mg_mk_str(nc), mg_mk_str(nonce), mg_mk_str(auth_domain),
|
|
fp);
|
|
|
|
clean:
|
|
if (username != username_buf) MG_FREE(username);
|
|
if (cnonce != cnonce_buf) MG_FREE(cnonce);
|
|
if (response != response_buf) MG_FREE(response);
|
|
if (uri != uri_buf) MG_FREE(uri);
|
|
if (qop != qop_buf) MG_FREE(qop);
|
|
if (nc != nc_buf) MG_FREE(nc);
|
|
if (nonce != nonce_buf) MG_FREE(nonce);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
|
|
struct mg_str username, struct mg_str cnonce,
|
|
struct mg_str response, struct mg_str qop,
|
|
struct mg_str nc, struct mg_str nonce,
|
|
struct mg_str auth_domain, FILE *fp) {
|
|
char buf[128], f_user[sizeof(buf)], f_ha1[sizeof(buf)], f_domain[sizeof(buf)];
|
|
char expected_response[33];
|
|
|
|
/*
|
|
* Read passwords file line by line. If should have htdigest format,
|
|
* i.e. each line should be a colon-separated sequence:
|
|
* USER_NAME:DOMAIN_NAME:HA1_HASH_OF_USER_DOMAIN_AND_PASSWORD
|
|
*/
|
|
while (fgets(buf, sizeof(buf), fp) != NULL) {
|
|
if (sscanf(buf, "%[^:]:%[^:]:%s", f_user, f_domain, f_ha1) == 3 &&
|
|
mg_vcmp(&username, f_user) == 0 &&
|
|
mg_vcmp(&auth_domain, f_domain) == 0) {
|
|
/* Username and domain matched, check the password */
|
|
mg_mkmd5resp(method.p, method.len, uri.p, uri.len, f_ha1, strlen(f_ha1),
|
|
nonce.p, nonce.len, nc.p, nc.len, cnonce.p, cnonce.len,
|
|
qop.p, qop.len, expected_response);
|
|
LOG(LL_DEBUG,
|
|
("%.*s %s %.*s %s", (int) username.len, username.p, f_domain,
|
|
(int) response.len, response.p, expected_response));
|
|
return mg_ncasecmp(response.p, expected_response, response.len) == 0;
|
|
}
|
|
}
|
|
|
|
/* None of the entries in the passwords file matched - return failure */
|
|
return 0;
|
|
}
|
|
|
|
int mg_http_is_authorized(struct http_message *hm, struct mg_str path,
|
|
const char *domain, const char *passwords_file,
|
|
int flags) {
|
|
char buf[MG_MAX_PATH];
|
|
const char *p;
|
|
FILE *fp;
|
|
int authorized = 1;
|
|
|
|
if (domain != NULL && passwords_file != NULL) {
|
|
if (flags & MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE) {
|
|
fp = mg_fopen(passwords_file, "r");
|
|
} else if (flags & MG_AUTH_FLAG_IS_DIRECTORY) {
|
|
snprintf(buf, sizeof(buf), "%.*s%c%s", (int) path.len, path.p, DIRSEP,
|
|
passwords_file);
|
|
fp = mg_fopen(buf, "r");
|
|
} else {
|
|
p = strrchr(path.p, DIRSEP);
|
|
if (p == NULL) p = path.p;
|
|
snprintf(buf, sizeof(buf), "%.*s%c%s", (int) (p - path.p), path.p, DIRSEP,
|
|
passwords_file);
|
|
fp = mg_fopen(buf, "r");
|
|
}
|
|
|
|
if (fp != NULL) {
|
|
authorized = mg_http_check_digest_auth(hm, domain, fp);
|
|
fclose(fp);
|
|
} else if (!(flags & MG_AUTH_FLAG_ALLOW_MISSING_FILE)) {
|
|
authorized = 0;
|
|
}
|
|
}
|
|
|
|
LOG(LL_DEBUG, ("%.*s %s %x %d", (int) path.len, path.p,
|
|
passwords_file ? passwords_file : "", flags, authorized));
|
|
return authorized;
|
|
}
|
|
#else
|
|
int mg_http_is_authorized(struct http_message *hm, const struct mg_str path,
|
|
const char *domain, const char *passwords_file,
|
|
int flags) {
|
|
(void) hm;
|
|
(void) path;
|
|
(void) domain;
|
|
(void) passwords_file;
|
|
(void) flags;
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#if MG_ENABLE_DIRECTORY_LISTING
|
|
static void mg_escape(const char *src, char *dst, size_t dst_len) {
|
|
size_t n = 0;
|
|
while (*src != '\0' && n + 5 < dst_len) {
|
|
unsigned char ch = *(unsigned char *) src++;
|
|
if (ch == '<') {
|
|
n += snprintf(dst + n, dst_len - n, "%s", "<");
|
|
} else {
|
|
dst[n++] = ch;
|
|
}
|
|
}
|
|
dst[n] = '\0';
|
|
}
|
|
|
|
static void mg_print_dir_entry(struct mg_connection *nc, const char *file_name,
|
|
cs_stat_t *stp) {
|
|
char size[64], mod[64], path[MG_MAX_PATH];
|
|
int64_t fsize = stp->st_size;
|
|
int is_dir = S_ISDIR(stp->st_mode);
|
|
const char *slash = is_dir ? "/" : "";
|
|
struct mg_str href;
|
|
|
|
if (is_dir) {
|
|
snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
|
|
} else {
|
|
/*
|
|
* We use (double) cast below because MSVC 6 compiler cannot
|
|
* convert unsigned __int64 to double.
|
|
*/
|
|
if (fsize < 1024) {
|
|
snprintf(size, sizeof(size), "%d", (int) fsize);
|
|
} else if (fsize < 0x100000) {
|
|
snprintf(size, sizeof(size), "%.1fk", (double) fsize / 1024.0);
|
|
} else if (fsize < 0x40000000) {
|
|
snprintf(size, sizeof(size), "%.1fM", (double) fsize / 1048576);
|
|
} else {
|
|
snprintf(size, sizeof(size), "%.1fG", (double) fsize / 1073741824);
|
|
}
|
|
}
|
|
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M", localtime(&stp->st_mtime));
|
|
mg_escape(file_name, path, sizeof(path));
|
|
href = mg_url_encode(mg_mk_str(file_name));
|
|
mg_printf_http_chunk(nc,
|
|
"<tr><td><a href=\"%s%s\">%s%s</a></td>"
|
|
"<td>%s</td><td name=%" INT64_FMT ">%s</td></tr>\n",
|
|
href.p, slash, path, slash, mod, is_dir ? -1 : fsize,
|
|
size);
|
|
free((void *) href.p);
|
|
}
|
|
|
|
static void mg_scan_directory(struct mg_connection *nc, const char *dir,
|
|
const struct mg_serve_http_opts *opts,
|
|
void (*func)(struct mg_connection *, const char *,
|
|
cs_stat_t *)) {
|
|
char path[MG_MAX_PATH];
|
|
cs_stat_t st;
|
|
struct dirent *dp;
|
|
DIR *dirp;
|
|
|
|
LOG(LL_DEBUG, ("%p [%s]", nc, dir));
|
|
if ((dirp = (opendir(dir))) != NULL) {
|
|
while ((dp = readdir(dirp)) != NULL) {
|
|
/* Do not show current dir and hidden files */
|
|
if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {
|
|
continue;
|
|
}
|
|
snprintf(path, sizeof(path), "%s/%s", dir, dp->d_name);
|
|
if (mg_stat(path, &st) == 0) {
|
|
func(nc, (const char *) dp->d_name, &st);
|
|
}
|
|
}
|
|
closedir(dirp);
|
|
} else {
|
|
LOG(LL_DEBUG, ("%p opendir(%s) -> %d", nc, dir, mg_get_errno()));
|
|
}
|
|
}
|
|
|
|
static void mg_send_directory_listing(struct mg_connection *nc, const char *dir,
|
|
struct http_message *hm,
|
|
struct mg_serve_http_opts *opts) {
|
|
static const char *sort_js_code =
|
|
"<script>function srt(tb, sc, so, d) {"
|
|
"var tr = Array.prototype.slice.call(tb.rows, 0),"
|
|
"tr = tr.sort(function (a, b) { var c1 = a.cells[sc], c2 = b.cells[sc],"
|
|
"n1 = c1.getAttribute('name'), n2 = c2.getAttribute('name'), "
|
|
"t1 = a.cells[2].getAttribute('name'), "
|
|
"t2 = b.cells[2].getAttribute('name'); "
|
|
"return so * (t1 < 0 && t2 >= 0 ? -1 : t2 < 0 && t1 >= 0 ? 1 : "
|
|
"n1 ? parseInt(n2) - parseInt(n1) : "
|
|
"c1.textContent.trim().localeCompare(c2.textContent.trim())); });";
|
|
static const char *sort_js_code2 =
|
|
"for (var i = 0; i < tr.length; i++) tb.appendChild(tr[i]); "
|
|
"if (!d) window.location.hash = ('sc=' + sc + '&so=' + so); "
|
|
"};"
|
|
"window.onload = function() {"
|
|
"var tb = document.getElementById('tb');"
|
|
"var m = /sc=([012]).so=(1|-1)/.exec(window.location.hash) || [0, 2, 1];"
|
|
"var sc = m[1], so = m[2]; document.onclick = function(ev) { "
|
|
"var c = ev.target.rel; if (c) {if (c == sc) so *= -1; srt(tb, c, so); "
|
|
"sc = c; ev.preventDefault();}};"
|
|
"srt(tb, sc, so, true);"
|
|
"}"
|
|
"</script>";
|
|
|
|
mg_send_response_line(nc, 200, opts->extra_headers);
|
|
mg_printf(nc, "%s: %s\r\n%s: %s\r\n\r\n", "Transfer-Encoding", "chunked",
|
|
"Content-Type", "text/html; charset=utf-8");
|
|
|
|
mg_printf_http_chunk(
|
|
nc,
|
|
"<html><head><title>Index of %.*s</title>%s%s"
|
|
"<style>th,td {text-align: left; padding-right: 1em; "
|
|
"font-family: monospace; }</style></head>\n"
|
|
"<body><h1>Index of %.*s</h1>\n<table cellpadding=0><thead>"
|
|
"<tr><th><a href=# rel=0>Name</a></th><th>"
|
|
"<a href=# rel=1>Modified</a</th>"
|
|
"<th><a href=# rel=2>Size</a></th></tr>"
|
|
"<tr><td colspan=3><hr></td></tr>\n"
|
|
"</thead>\n"
|
|
"<tbody id=tb>",
|
|
(int) hm->uri.len, hm->uri.p, sort_js_code, sort_js_code2,
|
|
(int) hm->uri.len, hm->uri.p);
|
|
mg_scan_directory(nc, dir, opts, mg_print_dir_entry);
|
|
mg_printf_http_chunk(nc,
|
|
"</tbody><tr><td colspan=3><hr></td></tr>\n"
|
|
"</table>\n"
|
|
"<address>%s</address>\n"
|
|
"</body></html>",
|
|
mg_version_header);
|
|
mg_send_http_chunk(nc, "", 0);
|
|
/* TODO(rojer): Remove when cesanta/dev/issues/197 is fixed. */
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
}
|
|
#endif /* MG_ENABLE_DIRECTORY_LISTING */
|
|
|
|
/*
|
|
* Given a directory path, find one of the files specified in the
|
|
* comma-separated list of index files `list`.
|
|
* First found index file wins. If an index file is found, then gets
|
|
* appended to the `path`, stat-ed, and result of `stat()` passed to `stp`.
|
|
* If index file is not found, then `path` and `stp` remain unchanged.
|
|
*/
|
|
MG_INTERNAL void mg_find_index_file(const char *path, const char *list,
|
|
char **index_file, cs_stat_t *stp) {
|
|
struct mg_str vec;
|
|
size_t path_len = strlen(path);
|
|
int found = 0;
|
|
*index_file = NULL;
|
|
|
|
/* Traverse index files list. For each entry, append it to the given */
|
|
/* path and see if the file exists. If it exists, break the loop */
|
|
while ((list = mg_next_comma_list_entry(list, &vec, NULL)) != NULL) {
|
|
cs_stat_t st;
|
|
size_t len = path_len + 1 + vec.len + 1;
|
|
*index_file = (char *) MG_REALLOC(*index_file, len);
|
|
if (*index_file == NULL) break;
|
|
snprintf(*index_file, len, "%s%c%.*s", path, DIRSEP, (int) vec.len, vec.p);
|
|
|
|
/* Does it exist? Is it a file? */
|
|
if (mg_stat(*index_file, &st) == 0 && S_ISREG(st.st_mode)) {
|
|
/* Yes it does, break the loop */
|
|
*stp = st;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
MG_FREE(*index_file);
|
|
*index_file = NULL;
|
|
}
|
|
LOG(LL_DEBUG, ("[%s] [%s]", path, (*index_file ? *index_file : "")));
|
|
}
|
|
|
|
#if MG_ENABLE_HTTP_URL_REWRITES
|
|
static int mg_http_send_port_based_redirect(
|
|
struct mg_connection *c, struct http_message *hm,
|
|
const struct mg_serve_http_opts *opts) {
|
|
const char *rewrites = opts->url_rewrites;
|
|
struct mg_str a, b;
|
|
char local_port[20] = {'%'};
|
|
|
|
mg_conn_addr_to_str(c, local_port + 1, sizeof(local_port) - 1,
|
|
MG_SOCK_STRINGIFY_PORT);
|
|
|
|
while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
|
|
if (mg_vcmp(&a, local_port) == 0) {
|
|
mg_send_response_line(c, 301, NULL);
|
|
mg_printf(c, "Content-Length: 0\r\nLocation: %.*s%.*s\r\n\r\n",
|
|
(int) b.len, b.p, (int) (hm->proto.p - hm->uri.p - 1),
|
|
hm->uri.p);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mg_reverse_proxy_handler(struct mg_connection *nc, int ev,
|
|
void *ev_data MG_UD_ARG(void *user_data)) {
|
|
struct http_message *hm = (struct http_message *) ev_data;
|
|
struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
|
|
|
|
if (pd == NULL || pd->reverse_proxy_data.linked_conn == NULL) {
|
|
DBG(("%p: upstream closed", nc));
|
|
return;
|
|
}
|
|
|
|
switch (ev) {
|
|
case MG_EV_CONNECT:
|
|
if (*(int *) ev_data != 0) {
|
|
mg_http_send_error(pd->reverse_proxy_data.linked_conn, 502, NULL);
|
|
}
|
|
break;
|
|
/* TODO(mkm): handle streaming */
|
|
case MG_EV_HTTP_REPLY:
|
|
mg_send(pd->reverse_proxy_data.linked_conn, hm->message.p,
|
|
hm->message.len);
|
|
pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;
|
|
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
|
|
break;
|
|
case MG_EV_CLOSE:
|
|
pd->reverse_proxy_data.linked_conn->flags |= MG_F_SEND_AND_CLOSE;
|
|
break;
|
|
}
|
|
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
(void) user_data;
|
|
#endif
|
|
}
|
|
|
|
void mg_http_reverse_proxy(struct mg_connection *nc,
|
|
const struct http_message *hm, struct mg_str mount,
|
|
struct mg_str upstream) {
|
|
struct mg_connection *be;
|
|
char burl[256], *purl = burl;
|
|
int i;
|
|
const char *error;
|
|
struct mg_connect_opts opts;
|
|
struct mg_str path = MG_NULL_STR, user_info = MG_NULL_STR, host = MG_NULL_STR;
|
|
memset(&opts, 0, sizeof(opts));
|
|
opts.error_string = &error;
|
|
|
|
mg_asprintf(&purl, sizeof(burl), "%.*s%.*s", (int) upstream.len, upstream.p,
|
|
(int) (hm->uri.len - mount.len), hm->uri.p + mount.len);
|
|
|
|
be = mg_connect_http_base(nc->mgr, MG_CB(mg_reverse_proxy_handler, NULL),
|
|
opts, "http", NULL, "https", NULL, purl, &path,
|
|
&user_info, &host);
|
|
LOG(LL_DEBUG, ("Proxying %.*s to %s (rule: %.*s)", (int) hm->uri.len,
|
|
hm->uri.p, purl, (int) mount.len, mount.p));
|
|
|
|
if (be == NULL) {
|
|
LOG(LL_ERROR, ("Error connecting to %s: %s", purl, error));
|
|
mg_http_send_error(nc, 502, NULL);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* link connections to each other, they must live and die together */
|
|
mg_http_get_proto_data(be)->reverse_proxy_data.linked_conn = nc;
|
|
mg_http_get_proto_data(nc)->reverse_proxy_data.linked_conn = be;
|
|
|
|
/* send request upstream */
|
|
mg_printf(be, "%.*s %.*s HTTP/1.1\r\n", (int) hm->method.len, hm->method.p,
|
|
(int) path.len, path.p);
|
|
|
|
mg_printf(be, "Host: %.*s\r\n", (int) host.len, host.p);
|
|
for (i = 0; i < MG_MAX_HTTP_HEADERS && hm->header_names[i].len > 0; i++) {
|
|
struct mg_str hn = hm->header_names[i];
|
|
struct mg_str hv = hm->header_values[i];
|
|
|
|
/* we rewrite the host header */
|
|
if (mg_vcasecmp(&hn, "Host") == 0) continue;
|
|
/*
|
|
* Don't pass chunked transfer encoding to the client because hm->body is
|
|
* already dechunked when we arrive here.
|
|
*/
|
|
if (mg_vcasecmp(&hn, "Transfer-encoding") == 0 &&
|
|
mg_vcasecmp(&hv, "chunked") == 0) {
|
|
mg_printf(be, "Content-Length: %" SIZE_T_FMT "\r\n", hm->body.len);
|
|
continue;
|
|
}
|
|
/* We don't support proxying Expect: 100-continue. */
|
|
if (mg_vcasecmp(&hn, "Expect") == 0 &&
|
|
mg_vcasecmp(&hv, "100-continue") == 0) {
|
|
continue;
|
|
}
|
|
|
|
mg_printf(be, "%.*s: %.*s\r\n", (int) hn.len, hn.p, (int) hv.len, hv.p);
|
|
}
|
|
|
|
mg_send(be, "\r\n", 2);
|
|
mg_send(be, hm->body.p, hm->body.len);
|
|
|
|
cleanup:
|
|
if (purl != burl) MG_FREE(purl);
|
|
}
|
|
|
|
static int mg_http_handle_forwarding(struct mg_connection *nc,
|
|
struct http_message *hm,
|
|
const struct mg_serve_http_opts *opts) {
|
|
const char *rewrites = opts->url_rewrites;
|
|
struct mg_str a, b;
|
|
struct mg_str p1 = MG_MK_STR("http://"), p2 = MG_MK_STR("https://");
|
|
|
|
while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
|
|
if (mg_strncmp(a, hm->uri, a.len) == 0) {
|
|
if (mg_strncmp(b, p1, p1.len) == 0 || mg_strncmp(b, p2, p2.len) == 0) {
|
|
mg_http_reverse_proxy(nc, hm, a, b);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* 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) {
|
|
int ok = 1;
|
|
const char *cp = hm->uri.p, *cp_end = hm->uri.p + hm->uri.len;
|
|
struct mg_str root = {NULL, 0};
|
|
const char *file_uri_start = cp;
|
|
*local_path = NULL;
|
|
remainder->p = NULL;
|
|
remainder->len = 0;
|
|
|
|
{ /* 1. Determine which root to use. */
|
|
|
|
#if MG_ENABLE_HTTP_URL_REWRITES
|
|
const char *rewrites = opts->url_rewrites;
|
|
#else
|
|
const char *rewrites = "";
|
|
#endif
|
|
struct mg_str *hh = mg_get_http_header(hm, "Host");
|
|
struct mg_str a, b;
|
|
/* Check rewrites first. */
|
|
while ((rewrites = mg_next_comma_list_entry(rewrites, &a, &b)) != NULL) {
|
|
if (a.len > 1 && a.p[0] == '@') {
|
|
/* Host rewrite. */
|
|
if (hh != NULL && hh->len == a.len - 1 &&
|
|
mg_ncasecmp(a.p + 1, hh->p, a.len - 1) == 0) {
|
|
root = b;
|
|
break;
|
|
}
|
|
} else {
|
|
/* Regular rewrite, URI=directory */
|
|
size_t match_len = mg_match_prefix_n(a, hm->uri);
|
|
if (match_len > 0) {
|
|
file_uri_start = hm->uri.p + match_len;
|
|
if (*file_uri_start == '/' || file_uri_start == cp_end) {
|
|
/* Match ended at component boundary, ok. */
|
|
} else if (*(file_uri_start - 1) == '/') {
|
|
/* Pattern ends with '/', backtrack. */
|
|
file_uri_start--;
|
|
} else {
|
|
/* No match: must fall on the component boundary. */
|
|
continue;
|
|
}
|
|
root = b;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* If no rewrite rules matched, use DAV or regular document root. */
|
|
if (root.p == NULL) {
|
|
#if MG_ENABLE_HTTP_WEBDAV
|
|
if (opts->dav_document_root != NULL && mg_is_dav_request(&hm->method)) {
|
|
root.p = opts->dav_document_root;
|
|
root.len = strlen(opts->dav_document_root);
|
|
} else
|
|
#endif
|
|
{
|
|
root.p = opts->document_root;
|
|
root.len = strlen(opts->document_root);
|
|
}
|
|
}
|
|
assert(root.p != NULL && root.len > 0);
|
|
}
|
|
|
|
{ /* 2. Find where in the canonical URI path the local path ends. */
|
|
const char *u = file_uri_start + 1;
|
|
char *lp = (char *) MG_MALLOC(root.len + hm->uri.len + 1);
|
|
char *lp_end = lp + root.len + hm->uri.len + 1;
|
|
char *p = lp, *ps;
|
|
int exists = 1;
|
|
if (lp == NULL) {
|
|
ok = 0;
|
|
goto out;
|
|
}
|
|
memcpy(p, root.p, root.len);
|
|
p += root.len;
|
|
if (*(p - 1) == DIRSEP) p--;
|
|
*p = '\0';
|
|
ps = p;
|
|
|
|
/* Chop off URI path components one by one and build local path. */
|
|
while (u <= cp_end) {
|
|
const char *next = u;
|
|
struct mg_str component;
|
|
if (exists) {
|
|
cs_stat_t st;
|
|
exists = (mg_stat(lp, &st) == 0);
|
|
if (exists && S_ISREG(st.st_mode)) {
|
|
/* We found the terminal, the rest of the URI (if any) is path_info.
|
|
*/
|
|
if (*(u - 1) == '/') u--;
|
|
break;
|
|
}
|
|
}
|
|
if (u >= cp_end) break;
|
|
parse_uri_component((const char **) &next, cp_end, "/", &component);
|
|
if (component.len > 0) {
|
|
int len;
|
|
memmove(p + 1, component.p, component.len);
|
|
len = mg_url_decode(p + 1, component.len, p + 1, lp_end - p - 1, 0);
|
|
if (len <= 0) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
component.p = p + 1;
|
|
component.len = len;
|
|
if (mg_vcmp(&component, ".") == 0) {
|
|
/* Yum. */
|
|
} else if (mg_vcmp(&component, "..") == 0) {
|
|
while (p > ps && *p != DIRSEP) p--;
|
|
*p = '\0';
|
|
} else {
|
|
size_t i;
|
|
#ifdef _WIN32
|
|
/* On Windows, make sure it's valid Unicode (no funny stuff). */
|
|
wchar_t buf[MG_MAX_PATH * 2];
|
|
if (to_wchar(component.p, buf, MG_MAX_PATH) == 0) {
|
|
DBG(("[%.*s] smells funny", (int) component.len, component.p));
|
|
ok = 0;
|
|
break;
|
|
}
|
|
#endif
|
|
*p++ = DIRSEP;
|
|
/* No NULs and DIRSEPs in the component (percent-encoded). */
|
|
for (i = 0; i < component.len; i++, p++) {
|
|
if (*p == '\0' || *p == DIRSEP
|
|
#ifdef _WIN32
|
|
/* On Windows, "/" is also accepted, so check for that too. */
|
|
||
|
|
*p == '/'
|
|
#endif
|
|
) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
u = next;
|
|
}
|
|
if (ok) {
|
|
*local_path = lp;
|
|
if (u > cp_end) u = cp_end;
|
|
remainder->p = u;
|
|
remainder->len = cp_end - u;
|
|
} else {
|
|
MG_FREE(lp);
|
|
}
|
|
}
|
|
|
|
out:
|
|
LOG(LL_DEBUG,
|
|
("'%.*s' -> '%s' + '%.*s'", (int) hm->uri.len, hm->uri.p,
|
|
*local_path ? *local_path : "", (int) remainder->len, remainder->p));
|
|
return ok;
|
|
}
|
|
|
|
static int mg_get_month_index(const char *s) {
|
|
static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
|
size_t i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(month_names); i++)
|
|
if (!strcmp(s, month_names[i])) return (int) i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int mg_num_leap_years(int year) {
|
|
return year / 4 - year / 100 + year / 400;
|
|
}
|
|
|
|
/* Parse UTC date-time string, and return the corresponding time_t value. */
|
|
MG_INTERNAL time_t mg_parse_date_string(const char *datetime) {
|
|
static const unsigned short days_before_month[] = {
|
|
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
|
|
char month_str[32];
|
|
int second, minute, hour, day, month, year, leap_days, days;
|
|
time_t result = (time_t) 0;
|
|
|
|
if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", &day, month_str, &year, &hour,
|
|
&minute, &second) == 6) ||
|
|
(sscanf(datetime, "%d %3s %d %d:%d:%d", &day, month_str, &year, &hour,
|
|
&minute, &second) == 6) ||
|
|
(sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", &day, month_str, &year,
|
|
&hour, &minute, &second) == 6) ||
|
|
(sscanf(datetime, "%d-%3s-%d %d:%d:%d", &day, month_str, &year, &hour,
|
|
&minute, &second) == 6)) &&
|
|
year > 1970 && (month = mg_get_month_index(month_str)) != -1) {
|
|
leap_days = mg_num_leap_years(year) - mg_num_leap_years(1970);
|
|
year -= 1970;
|
|
days = year * 365 + days_before_month[month] + (day - 1) + leap_days;
|
|
result = days * 24 * 3600 + hour * 3600 + minute * 60 + second;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st) {
|
|
struct mg_str *hdr;
|
|
if ((hdr = mg_get_http_header(hm, "If-None-Match")) != NULL) {
|
|
char etag[64];
|
|
mg_http_construct_etag(etag, sizeof(etag), st);
|
|
return mg_vcasecmp(hdr, etag) == 0;
|
|
} else if ((hdr = mg_get_http_header(hm, "If-Modified-Since")) != NULL) {
|
|
return st->st_mtime <= mg_parse_date_string(hdr->p);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void mg_http_send_digest_auth_request(struct mg_connection *c,
|
|
const char *domain) {
|
|
mg_printf(c,
|
|
"HTTP/1.1 401 Unauthorized\r\n"
|
|
"WWW-Authenticate: Digest qop=\"auth\", "
|
|
"realm=\"%s\", nonce=\"%lx\"\r\n"
|
|
"Content-Length: 0\r\n\r\n",
|
|
domain, (unsigned long) mg_time());
|
|
}
|
|
|
|
static void mg_http_send_options(struct mg_connection *nc) {
|
|
mg_printf(nc, "%s",
|
|
"HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS"
|
|
#if MG_ENABLE_HTTP_WEBDAV
|
|
", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2"
|
|
#endif
|
|
"\r\n\r\n");
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
}
|
|
|
|
static int mg_is_creation_request(const struct http_message *hm) {
|
|
return mg_vcmp(&hm->method, "MKCOL") == 0 || mg_vcmp(&hm->method, "PUT") == 0;
|
|
}
|
|
|
|
MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
|
|
const struct mg_str *path_info,
|
|
struct http_message *hm,
|
|
struct mg_serve_http_opts *opts) {
|
|
int exists, is_directory, is_cgi;
|
|
#if MG_ENABLE_HTTP_WEBDAV
|
|
int is_dav = mg_is_dav_request(&hm->method);
|
|
#else
|
|
int is_dav = 0;
|
|
#endif
|
|
char *index_file = NULL;
|
|
cs_stat_t st;
|
|
|
|
exists = (mg_stat(path, &st) == 0);
|
|
is_directory = exists && S_ISDIR(st.st_mode);
|
|
|
|
if (is_directory)
|
|
mg_find_index_file(path, opts->index_files, &index_file, &st);
|
|
|
|
is_cgi =
|
|
(mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern),
|
|
index_file ? index_file : path) > 0);
|
|
|
|
LOG(LL_DEBUG,
|
|
("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc,
|
|
(int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav,
|
|
is_cgi, index_file ? index_file : ""));
|
|
|
|
if (is_directory && hm->uri.p[hm->uri.len - 1] != '/' && !is_dav) {
|
|
mg_printf(nc,
|
|
"HTTP/1.1 301 Moved\r\nLocation: %.*s/\r\n"
|
|
"Content-Length: 0\r\n\r\n",
|
|
(int) hm->uri.len, hm->uri.p);
|
|
MG_FREE(index_file);
|
|
return;
|
|
}
|
|
|
|
/* If we have path_info, the only way to handle it is CGI. */
|
|
if (path_info->len > 0 && !is_cgi) {
|
|
mg_http_send_error(nc, 501, NULL);
|
|
MG_FREE(index_file);
|
|
return;
|
|
}
|
|
|
|
if (is_dav && opts->dav_document_root == NULL) {
|
|
mg_http_send_error(nc, 501, NULL);
|
|
} else if (!mg_http_is_authorized(
|
|
hm, mg_mk_str(path), opts->auth_domain, opts->global_auth_file,
|
|
((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
|
|
MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |
|
|
MG_AUTH_FLAG_ALLOW_MISSING_FILE)) ||
|
|
!mg_http_is_authorized(
|
|
hm, mg_mk_str(path), opts->auth_domain,
|
|
opts->per_directory_auth_file,
|
|
((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
|
|
MG_AUTH_FLAG_ALLOW_MISSING_FILE))) {
|
|
mg_http_send_digest_auth_request(nc, opts->auth_domain);
|
|
} else if (is_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_ENABLE_HTTP_CGI */
|
|
} else if ((!exists ||
|
|
mg_is_file_hidden(path, opts, 0 /* specials are ok */)) &&
|
|
!mg_is_creation_request(hm)) {
|
|
mg_http_send_error(nc, 404, NULL);
|
|
#if MG_ENABLE_HTTP_WEBDAV
|
|
} else if (!mg_vcmp(&hm->method, "PROPFIND")) {
|
|
mg_handle_propfind(nc, path, &st, hm, opts);
|
|
#if !MG_DISABLE_DAV_AUTH
|
|
} else if (is_dav &&
|
|
(opts->dav_auth_file == NULL ||
|
|
(strcmp(opts->dav_auth_file, "-") != 0 &&
|
|
!mg_http_is_authorized(
|
|
hm, mg_mk_str(path), opts->auth_domain, opts->dav_auth_file,
|
|
((is_directory ? MG_AUTH_FLAG_IS_DIRECTORY : 0) |
|
|
MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE |
|
|
MG_AUTH_FLAG_ALLOW_MISSING_FILE))))) {
|
|
mg_http_send_digest_auth_request(nc, opts->auth_domain);
|
|
#endif
|
|
} else if (!mg_vcmp(&hm->method, "MKCOL")) {
|
|
mg_handle_mkcol(nc, path, hm);
|
|
} else if (!mg_vcmp(&hm->method, "DELETE")) {
|
|
mg_handle_delete(nc, opts, path);
|
|
} else if (!mg_vcmp(&hm->method, "PUT")) {
|
|
mg_handle_put(nc, path, hm);
|
|
} else if (!mg_vcmp(&hm->method, "MOVE")) {
|
|
mg_handle_move(nc, opts, path, hm);
|
|
#if MG_ENABLE_FAKE_DAVLOCK
|
|
} else if (!mg_vcmp(&hm->method, "LOCK")) {
|
|
mg_handle_lock(nc, path);
|
|
#endif
|
|
#endif /* MG_ENABLE_HTTP_WEBDAV */
|
|
} else if (!mg_vcmp(&hm->method, "OPTIONS")) {
|
|
mg_http_send_options(nc);
|
|
} else if (is_directory && index_file == NULL) {
|
|
#if MG_ENABLE_DIRECTORY_LISTING
|
|
if (strcmp(opts->enable_directory_listing, "yes") == 0) {
|
|
mg_send_directory_listing(nc, path, hm, opts);
|
|
} else {
|
|
mg_http_send_error(nc, 403, NULL);
|
|
}
|
|
#else
|
|
mg_http_send_error(nc, 501, NULL);
|
|
#endif
|
|
} else if (mg_is_not_modified(hm, &st)) {
|
|
mg_http_send_error(nc, 304, "Not Modified");
|
|
} else {
|
|
mg_http_serve_file2(nc, index_file ? index_file : path, hm, opts);
|
|
}
|
|
MG_FREE(index_file);
|
|
}
|
|
|
|
void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
|
|
struct mg_serve_http_opts opts) {
|
|
char *path = NULL;
|
|
struct mg_str *hdr, path_info;
|
|
uint32_t remote_ip = ntohl(*(uint32_t *) &nc->sa.sin.sin_addr);
|
|
|
|
if (mg_check_ip_acl(opts.ip_acl, remote_ip) != 1) {
|
|
/* Not allowed to connect */
|
|
mg_http_send_error(nc, 403, NULL);
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
return;
|
|
}
|
|
|
|
#if MG_ENABLE_HTTP_URL_REWRITES
|
|
if (mg_http_handle_forwarding(nc, hm, &opts)) {
|
|
return;
|
|
}
|
|
|
|
if (mg_http_send_port_based_redirect(nc, hm, &opts)) {
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
if (opts.document_root == NULL) {
|
|
opts.document_root = ".";
|
|
}
|
|
if (opts.per_directory_auth_file == NULL) {
|
|
opts.per_directory_auth_file = ".htpasswd";
|
|
}
|
|
if (opts.enable_directory_listing == NULL) {
|
|
opts.enable_directory_listing = "yes";
|
|
}
|
|
if (opts.cgi_file_pattern == NULL) {
|
|
opts.cgi_file_pattern = "**.cgi$|**.php$";
|
|
}
|
|
if (opts.ssi_pattern == NULL) {
|
|
opts.ssi_pattern = "**.shtml$|**.shtm$";
|
|
}
|
|
if (opts.index_files == NULL) {
|
|
opts.index_files = "index.html,index.htm,index.shtml,index.cgi,index.php";
|
|
}
|
|
/* Normalize path - resolve "." and ".." (in-place). */
|
|
if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) {
|
|
mg_http_send_error(nc, 400, NULL);
|
|
return;
|
|
}
|
|
if (mg_uri_to_local_path(hm, &opts, &path, &path_info) == 0) {
|
|
mg_http_send_error(nc, 404, NULL);
|
|
return;
|
|
}
|
|
mg_send_http_file(nc, path, &path_info, hm, &opts);
|
|
|
|
MG_FREE(path);
|
|
path = NULL;
|
|
|
|
/* Close connection for non-keep-alive requests */
|
|
if (mg_vcmp(&hm->proto, "HTTP/1.1") != 0 ||
|
|
((hdr = mg_get_http_header(hm, "Connection")) != NULL &&
|
|
mg_vcmp(hdr, "keep-alive") != 0)) {
|
|
#if 0
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
|
|
mg_fu_fname_fn local_name_fn
|
|
MG_UD_ARG(void *user_data)) {
|
|
switch (ev) {
|
|
case MG_EV_HTTP_PART_BEGIN: {
|
|
struct mg_http_multipart_part *mp =
|
|
(struct mg_http_multipart_part *) ev_data;
|
|
struct file_upload_state *fus =
|
|
(struct file_upload_state *) MG_CALLOC(1, sizeof(*fus));
|
|
struct mg_str lfn = local_name_fn(nc, mg_mk_str(mp->file_name));
|
|
mp->user_data = NULL;
|
|
if (lfn.p == NULL || lfn.len == 0) {
|
|
LOG(LL_ERROR, ("%p Not allowed to upload %s", nc, mp->file_name));
|
|
mg_printf(nc,
|
|
"HTTP/1.1 403 Not Allowed\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Connection: close\r\n\r\n"
|
|
"Not allowed to upload %s\r\n",
|
|
mp->file_name);
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
return;
|
|
}
|
|
fus->lfn = (char *) MG_MALLOC(lfn.len + 1);
|
|
memcpy(fus->lfn, lfn.p, lfn.len);
|
|
fus->lfn[lfn.len] = '\0';
|
|
if (lfn.p != mp->file_name) MG_FREE((char *) lfn.p);
|
|
LOG(LL_DEBUG,
|
|
("%p Receiving file %s -> %s", nc, mp->file_name, fus->lfn));
|
|
fus->fp = mg_fopen(fus->lfn, "w");
|
|
if (fus->fp == NULL) {
|
|
mg_printf(nc,
|
|
"HTTP/1.1 500 Internal Server Error\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Connection: close\r\n\r\n");
|
|
LOG(LL_ERROR, ("Failed to open %s: %d\n", fus->lfn, mg_get_errno()));
|
|
mg_printf(nc, "Failed to open %s: %d\n", fus->lfn, mg_get_errno());
|
|
/* Do not close the connection just yet, discard remainder of the data.
|
|
* This is because at the time of writing some browsers (Chrome) fail to
|
|
* render response before all the data is sent. */
|
|
}
|
|
mp->user_data = (void *) fus;
|
|
break;
|
|
}
|
|
case MG_EV_HTTP_PART_DATA: {
|
|
struct mg_http_multipart_part *mp =
|
|
(struct mg_http_multipart_part *) ev_data;
|
|
struct file_upload_state *fus =
|
|
(struct file_upload_state *) mp->user_data;
|
|
if (fus == NULL || fus->fp == NULL) break;
|
|
if (mg_fwrite(mp->data.p, 1, mp->data.len, fus->fp) != mp->data.len) {
|
|
LOG(LL_ERROR, ("Failed to write to %s: %d, wrote %d", fus->lfn,
|
|
mg_get_errno(), (int) fus->num_recd));
|
|
if (mg_get_errno() == ENOSPC
|
|
#ifdef SPIFFS_ERR_FULL
|
|
|| mg_get_errno() == SPIFFS_ERR_FULL
|
|
#endif
|
|
) {
|
|
mg_printf(nc,
|
|
"HTTP/1.1 413 Payload Too Large\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Connection: close\r\n\r\n");
|
|
mg_printf(nc, "Failed to write to %s: no space left; wrote %d\r\n",
|
|
fus->lfn, (int) fus->num_recd);
|
|
} else {
|
|
mg_printf(nc,
|
|
"HTTP/1.1 500 Internal Server Error\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Connection: close\r\n\r\n");
|
|
mg_printf(nc, "Failed to write to %s: %d, wrote %d", mp->file_name,
|
|
mg_get_errno(), (int) fus->num_recd);
|
|
}
|
|
fclose(fus->fp);
|
|
remove(fus->lfn);
|
|
fus->fp = NULL;
|
|
/* Do not close the connection just yet, discard remainder of the data.
|
|
* This is because at the time of writing some browsers (Chrome) fail to
|
|
* render response before all the data is sent. */
|
|
return;
|
|
}
|
|
fus->num_recd += mp->data.len;
|
|
LOG(LL_DEBUG, ("%p rec'd %d bytes, %d total", nc, (int) mp->data.len,
|
|
(int) fus->num_recd));
|
|
break;
|
|
}
|
|
case MG_EV_HTTP_PART_END: {
|
|
struct mg_http_multipart_part *mp =
|
|
(struct mg_http_multipart_part *) ev_data;
|
|
struct file_upload_state *fus =
|
|
(struct file_upload_state *) mp->user_data;
|
|
if (fus == NULL) break;
|
|
if (mp->status >= 0 && fus->fp != NULL) {
|
|
LOG(LL_DEBUG, ("%p Uploaded %s (%s), %d bytes", nc, mp->file_name,
|
|
fus->lfn, (int) fus->num_recd));
|
|
mg_printf(nc,
|
|
"HTTP/1.1 200 OK\r\n"
|
|
"Content-Type: text/plain\r\n"
|
|
"Connection: close\r\n\r\n"
|
|
"Ok, %s - %d bytes.\r\n",
|
|
mp->file_name, (int) fus->num_recd);
|
|
} else {
|
|
LOG(LL_ERROR, ("Failed to store %s (%s)", mp->file_name, fus->lfn));
|
|
/*
|
|
* mp->status < 0 means connection was terminated, so no reason to send
|
|
* HTTP reply
|
|
*/
|
|
}
|
|
if (fus->fp != NULL) fclose(fus->fp);
|
|
MG_FREE(fus->lfn);
|
|
MG_FREE(fus);
|
|
mp->user_data = NULL;
|
|
nc->flags |= MG_F_SEND_AND_CLOSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
(void) user_data;
|
|
#endif
|
|
}
|
|
|
|
#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
|
|
#endif /* MG_ENABLE_FILESYSTEM */
|
|
|
|
struct mg_connection *mg_connect_http_base(
|
|
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
|
|
struct mg_connect_opts opts, const char *scheme1, const char *scheme2,
|
|
const char *scheme_ssl1, const char *scheme_ssl2, const char *url,
|
|
struct mg_str *path, struct mg_str *user_info, struct mg_str *host) {
|
|
struct mg_connection *nc = NULL;
|
|
unsigned int port_i = 0;
|
|
int use_ssl = 0;
|
|
struct mg_str scheme, query, fragment;
|
|
char conn_addr_buf[2];
|
|
char *conn_addr = conn_addr_buf;
|
|
|
|
if (mg_parse_uri(mg_mk_str(url), &scheme, user_info, host, &port_i, path,
|
|
&query, &fragment) != 0) {
|
|
MG_SET_PTRPTR(opts.error_string, "cannot parse url");
|
|
goto out;
|
|
}
|
|
|
|
/* If query is present, do not strip it. Pass to the caller. */
|
|
if (query.len > 0) path->len += query.len + 1;
|
|
|
|
if (scheme.len == 0 || mg_vcmp(&scheme, scheme1) == 0 ||
|
|
(scheme2 != NULL && mg_vcmp(&scheme, scheme2) == 0)) {
|
|
use_ssl = 0;
|
|
if (port_i == 0) port_i = 80;
|
|
} else if (mg_vcmp(&scheme, scheme_ssl1) == 0 ||
|
|
(scheme2 != NULL && mg_vcmp(&scheme, scheme_ssl2) == 0)) {
|
|
use_ssl = 1;
|
|
if (port_i == 0) port_i = 443;
|
|
} else {
|
|
goto out;
|
|
}
|
|
|
|
mg_asprintf(&conn_addr, sizeof(conn_addr_buf), "tcp://%.*s:%u",
|
|
(int) host->len, host->p, port_i);
|
|
if (conn_addr == NULL) goto out;
|
|
|
|
LOG(LL_DEBUG, ("%s use_ssl? %d %s", url, use_ssl, conn_addr));
|
|
if (use_ssl) {
|
|
#if MG_ENABLE_SSL
|
|
/*
|
|
* Schema requires SSL, but no SSL parameters were provided in opts.
|
|
* In order to maintain backward compatibility, use a faux-SSL with no
|
|
* verification.
|
|
*/
|
|
if (opts.ssl_ca_cert == NULL) {
|
|
opts.ssl_ca_cert = "*";
|
|
}
|
|
#else
|
|
MG_SET_PTRPTR(opts.error_string, "ssl is disabled");
|
|
goto out;
|
|
#endif
|
|
}
|
|
|
|
if ((nc = mg_connect_opt(mgr, conn_addr, MG_CB(ev_handler, user_data),
|
|
opts)) != NULL) {
|
|
mg_set_protocol_http_websocket(nc);
|
|
}
|
|
|
|
out:
|
|
if (conn_addr != NULL && conn_addr != conn_addr_buf) MG_FREE(conn_addr);
|
|
return nc;
|
|
}
|
|
|
|
struct mg_connection *mg_connect_http_opt(
|
|
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
|
|
struct mg_connect_opts opts, const char *url, const char *extra_headers,
|
|
const char *post_data) {
|
|
struct mg_str user = MG_NULL_STR, null_str = MG_NULL_STR;
|
|
struct mg_str host = MG_NULL_STR, path = MG_NULL_STR;
|
|
struct mbuf auth;
|
|
struct mg_connection *nc =
|
|
mg_connect_http_base(mgr, MG_CB(ev_handler, user_data), opts, "http",
|
|
NULL, "https", NULL, url, &path, &user, &host);
|
|
|
|
if (nc == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
mbuf_init(&auth, 0);
|
|
if (user.len > 0) {
|
|
mg_basic_auth_header(user, null_str, &auth);
|
|
}
|
|
|
|
if (post_data == NULL) post_data = "";
|
|
if (extra_headers == NULL) extra_headers = "";
|
|
if (path.len == 0) path = mg_mk_str("/");
|
|
if (host.len == 0) host = mg_mk_str("");
|
|
|
|
mg_printf(nc, "%s %.*s HTTP/1.1\r\nHost: %.*s\r\nContent-Length: %" SIZE_T_FMT
|
|
"\r\n%.*s%s\r\n%s",
|
|
(post_data[0] == '\0' ? "GET" : "POST"), (int) path.len, path.p,
|
|
(int) (path.p - host.p), host.p, strlen(post_data), (int) auth.len,
|
|
(auth.buf == NULL ? "" : auth.buf), extra_headers, post_data);
|
|
|
|
mbuf_free(&auth);
|
|
return nc;
|
|
}
|
|
|
|
struct mg_connection *mg_connect_http(
|
|
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
|
|
const char *url, const char *extra_headers, const char *post_data) {
|
|
struct mg_connect_opts opts;
|
|
memset(&opts, 0, sizeof(opts));
|
|
return mg_connect_http_opt(mgr, MG_CB(ev_handler, user_data), opts, url,
|
|
extra_headers, post_data);
|
|
}
|
|
|
|
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,
|
|
size_t *data_len) {
|
|
static const char cd[] = "Content-Disposition: ";
|
|
size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;
|
|
int shl;
|
|
|
|
if (buf == NULL || buf_len <= 0) return 0;
|
|
if ((shl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0;
|
|
hl = shl;
|
|
if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0;
|
|
|
|
/* Get boundary length */
|
|
bl = mg_get_line_len(buf, buf_len);
|
|
|
|
/* Loop through headers, fetch variable name and file name */
|
|
var_name[0] = file_name[0] = '\0';
|
|
for (n = bl; (ll = mg_get_line_len(buf + n, hl - n)) > 0; n += ll) {
|
|
if (mg_ncasecmp(cd, buf + n, cdl) == 0) {
|
|
struct mg_str header;
|
|
header.p = buf + n + cdl;
|
|
header.len = ll - (cdl + 2);
|
|
{
|
|
char *var_name2 = var_name;
|
|
mg_http_parse_header2(&header, "name", &var_name2, var_name_len);
|
|
/* TODO: handle reallocated buffer correctly */
|
|
if (var_name2 != var_name) {
|
|
MG_FREE(var_name2);
|
|
var_name[0] = '\0';
|
|
}
|
|
}
|
|
{
|
|
char *file_name2 = file_name;
|
|
mg_http_parse_header2(&header, "filename", &file_name2, file_name_len);
|
|
/* TODO: handle reallocated buffer correctly */
|
|
if (file_name2 != file_name) {
|
|
MG_FREE(file_name2);
|
|
file_name[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Scan through the body, search for terminating boundary */
|
|
for (pos = hl; pos + (bl - 2) < buf_len; pos++) {
|
|
if (buf[pos] == '-' && !strncmp(buf, &buf[pos], bl - 2)) {
|
|
if (data_len != NULL) *data_len = (pos - 2) - hl;
|
|
if (data != NULL) *data = buf + hl;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void mg_register_http_endpoint_opt(struct mg_connection *nc,
|
|
const char *uri_path,
|
|
mg_event_handler_t handler,
|
|
struct mg_http_endpoint_opts opts) {
|
|
struct mg_http_proto_data *pd = NULL;
|
|
struct mg_http_endpoint *new_ep = NULL;
|
|
|
|
if (nc == NULL) return;
|
|
new_ep = (struct mg_http_endpoint *) MG_CALLOC(1, sizeof(*new_ep));
|
|
if (new_ep == NULL) return;
|
|
|
|
pd = mg_http_get_proto_data(nc);
|
|
new_ep->uri_pattern = mg_strdup(mg_mk_str(uri_path));
|
|
if (opts.auth_domain != NULL && opts.auth_file != NULL) {
|
|
new_ep->auth_domain = strdup(opts.auth_domain);
|
|
new_ep->auth_file = strdup(opts.auth_file);
|
|
}
|
|
new_ep->handler = handler;
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
new_ep->user_data = opts.user_data;
|
|
#endif
|
|
new_ep->next = pd->endpoints;
|
|
pd->endpoints = new_ep;
|
|
}
|
|
|
|
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);
|
|
void *user_data = nc->user_data;
|
|
|
|
if (ev == MG_EV_HTTP_REQUEST
|
|
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
|
|
|| ev == MG_EV_HTTP_MULTIPART_REQUEST
|
|
#endif
|
|
) {
|
|
struct mg_http_endpoint *ep =
|
|
mg_http_get_endpoint_handler(nc->listener, &hm->uri);
|
|
if (ep != NULL) {
|
|
#if MG_ENABLE_FILESYSTEM && !MG_DISABLE_HTTP_DIGEST_AUTH
|
|
if (!mg_http_is_authorized(hm, hm->uri, ep->auth_domain, ep->auth_file,
|
|
MG_AUTH_FLAG_IS_GLOBAL_PASS_FILE)) {
|
|
mg_http_send_digest_auth_request(nc, ep->auth_domain);
|
|
return;
|
|
}
|
|
#endif
|
|
pd->endpoint_handler = ep->handler;
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
user_data = ep->user_data;
|
|
#endif
|
|
}
|
|
}
|
|
mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler,
|
|
user_data, ev, hm);
|
|
}
|
|
|
|
void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
|
|
MG_CB(mg_event_handler_t handler,
|
|
void *user_data)) {
|
|
struct mg_http_endpoint_opts opts;
|
|
memset(&opts, 0, sizeof(opts));
|
|
#if MG_ENABLE_CALLBACK_USERDATA
|
|
opts.user_data = user_data;
|
|
#endif
|
|
mg_register_http_endpoint_opt(nc, uri_path, handler, opts);
|
|
}
|
|
|
|
#endif /* MG_ENABLE_HTTP */
|