/* * 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_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; enum mg_http_multipart_stream_state state; int processing_part; int data_avail; }; 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_proto_data_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); MG_INTERNAL struct mg_http_proto_data *mg_http_create_proto_data( struct mg_connection *c) { /* If we have proto data from previous connection, flush it. */ if (c->proto_data != NULL) { void *pd = c->proto_data; c->proto_data = NULL; mg_http_proto_data_destructor(pd); } c->proto_data = MG_CALLOC(1, sizeof(struct mg_http_proto_data)); c->proto_data_destructor = mg_http_proto_data_destructor; return (struct mg_http_proto_data *) c->proto_data; } static struct mg_http_proto_data *mg_http_get_proto_data( struct mg_connection *c) { 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_proto_data_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[0] < '0' || s[0] > '9' || 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, ka %d", nc, (int) pd->file.sent, pd->file.keepalive)); 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++; if (i > 6) { /* Chunk size is unreasonable. */ return 0; } } /* 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); if (pd == NULL) return NULL; 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 != NULL && 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 != NULL && 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.user_data = pd->mp_stream.user_data; 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); } if (pd != NULL && pd->endpoint_handler != NULL && pd->endpoint_handler != nc->handler) { mg_call(nc, pd->endpoint_handler, nc->user_data, ev, NULL); } } #if MG_ENABLE_FILESYSTEM if (pd != NULL && pd->file.fp != NULL) { mg_http_transfer_file_data(nc); } #endif mg_call(nc, nc->handler, nc->user_data, ev, ev_data); #if MG_ENABLE_HTTP_STREAMING_MULTIPART if (pd != NULL && pd->mp_stream.boundary != NULL && (ev == MG_EV_RECV || ev == MG_EV_POLL)) { if (ev == MG_EV_RECV) { pd->rcvd += *(int *) ev_data; mg_http_multipart_continue(nc); } else if (pd->mp_stream.data_avail) { /* Try re-delivering the data. */ mg_http_multipart_continue(nc); } return; } #endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */ if (ev == MG_EV_RECV) { struct mg_str *s; again: req_len = mg_parse_http(io->buf, io->len, hm, is_req); if (req_len > 0) { /* New request - new proto data */ pd = mg_http_create_proto_data(nc); pd->rcvd = io->len; } 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 && (nc->flags & MG_F_IS_WEBSOCKET)) { /* We're websocket client, got handshake response from server. */ DBG(("%p WebSocket upgrade code %d", nc, hm->resp_code)); if (hm->resp_code == 101 && mg_get_http_header(hm, "Sec-WebSocket-Accept")) { /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */ mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, hm); mbuf_remove(io, req_len); nc->proto_handler = mg_ws_handler; mg_ws_handler(nc, MG_EV_RECV, ev_data MG_UD_ARG(user_data)); } else { mg_call(nc, nc->handler, nc->user_data, MG_EV_WEBSOCKET_HANDSHAKE_DONE, hm); nc->flags |= MG_F_CLOSE_IMMEDIATELY; mbuf_remove(io, req_len); } } 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, hm); 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 request_done = 1; 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 MG_ENABLE_FILESYSTEM /* We don't have a generic mechanism of communicating that we are done * responding to a request (should probably add one). But if we are * serving * a file, we are definitely not done. */ if (pd->file.fp != NULL) request_done = 0; #endif #if MG_ENABLE_HTTP_CGI /* If this is a CGI request, we are not done either. */ if (pd->cgi.cgi_nc != NULL) request_done = 0; #endif if (request_done && 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 size_t 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; mp.num_data_consumed = data_len; mg_call(c, pd->endpoint_handler, c->user_data, ev, &mp); pd->mp_stream.user_data = mp.user_data; pd->mp_stream.data_avail = (mp.num_data_consumed != data_len); return mp.num_data_consumed; } 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) { int data_len = (io->len - (pd->mp_stream.boundary_len + 6)); if (data_len > 0) { size_t consumed = mg_http_multipart_call_handler( c, MG_EV_HTTP_PART_DATA, io->buf, (size_t) data_len); mbuf_remove(io, consumed); } return 0; } else if (boundary != NULL) { size_t data_len = ((size_t)(boundary - io->buf) - 4); size_t consumed = mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf, data_len); mbuf_remove(io, consumed); if (consumed == data_len) { mbuf_remove(io, 4); pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY; return 1; } else { return 0; } } 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_FINALIZE: { if (mg_http_multipart_finalize(c) == 0) { return; } break; } case MPS_FINISHED: { 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\n", status_code, mg_status_message(status_code)); #ifndef MG_HIDE_SERVER_INFO mg_printf(nc, "Server: %s\r\n", mg_version_header); #endif 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 = ',', ch2 = ';', 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 = ch2 = *s++; } p = s; while (p < end && p[0] != ch && p[0] != ch1 && p[0] != ch2) { 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 exp_resp[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, exp_resp); LOG(LL_DEBUG, ("%.*s %s %.*s %s", (int) username.len, username.p, f_domain, (int) response.len, response.p, exp_resp)); return mg_ncasecmp(response.p, exp_resp, strlen(exp_resp)) == 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 + 1]; 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, struct mg_serve_http_opts *opts) { mg_send_response_line(nc, 200, opts->extra_headers); mg_printf(nc, "%s", "Allow: 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, opts); } 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 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 = (struct file_upload_state *) MG_CALLOC(1, sizeof(*fus)); if (fus == NULL) { nc->flags |= MG_F_CLOSE_IMMEDIATELY; 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, "wb"); 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)); } 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; /* Don't close the connection yet, there may be more files to come. */ break; } case MG_EV_HTTP_MULTIPART_REQUEST_END: { mg_printf(nc, "HTTP/1.1 200 OK\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Ok.\r\n"); 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); if (pd == NULL) pd = mg_http_create_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 */