mirror of
https://github.com/cesanta/mongoose.git
synced 2024-11-25 11:39:01 +08:00
Serving files
This commit is contained in:
parent
ebc909b61b
commit
d00b7f4300
331
build/src/core.c
331
build/src/core.c
@ -37,6 +37,8 @@
|
||||
#undef WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@ -46,6 +48,7 @@
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <ctype.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
@ -58,7 +61,11 @@ typedef unsigned __int64 uint64_t;
|
||||
typedef __int64 int64_t;
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#define snprintf _snprintf
|
||||
#define vsnprintf _vsnprintf
|
||||
#define INT64_FMT "I64d"
|
||||
#ifndef va_copy
|
||||
#define va_copy(x,y) x = y
|
||||
#endif // MINGW #defines va_copy
|
||||
#else
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
@ -90,6 +97,10 @@ struct linked_list_link { struct linked_list_link *prev, *next; };
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
|
||||
#define MAX_REQUEST_SIZE 16384
|
||||
#define IOBUF_SIZE 8192
|
||||
#define MAX_PATH_SIZE 8192
|
||||
#define DBG(x) do { printf("%s:%d: ", __FILE__, __LINE__); \
|
||||
printf x; putchar('\n'); fflush(stdout); } while(0)
|
||||
//#define DBG(x)
|
||||
|
||||
union socket_address {
|
||||
struct sockaddr sa;
|
||||
@ -101,7 +112,7 @@ union socket_address {
|
||||
|
||||
struct vec {
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
int len;
|
||||
};
|
||||
|
||||
// NOTE(lsm): this enum shoulds be in sync with the config_options.
|
||||
@ -124,8 +135,8 @@ struct mg_server {
|
||||
|
||||
struct iobuf {
|
||||
char *buf; // Buffer that holds the data
|
||||
int size; // Buffer size
|
||||
int len; // Number of bytes currently in a buffer
|
||||
int size; // Buffer size
|
||||
int len; // Number of bytes currently in a buffer
|
||||
};
|
||||
|
||||
union endpoint {
|
||||
@ -142,10 +153,11 @@ struct mg_connection {
|
||||
struct iobuf local_iobuf;
|
||||
struct iobuf remote_iobuf;
|
||||
union endpoint endpoint;
|
||||
enum { EP_NONE, EP_FILE, EP_SOCKET, EP_SSL } endpoint_type;
|
||||
enum { CONN_CLOSE = 1 } flags;
|
||||
enum { EP_NONE, EP_FILE, EP_DIR, EP_CGI, EP_SSL } endpoint_type;
|
||||
enum { CONN_CLOSE = 1, CONN_SPOOL_DONE = 2 } flags;
|
||||
time_t expire_time;
|
||||
struct mg_request_info request_info;
|
||||
char *path_info;
|
||||
int request_len;
|
||||
};
|
||||
|
||||
@ -298,7 +310,7 @@ static pid_t start_process(char *cmd, char *env[], char *dir, sock_t sock) {
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
closesocket(sock);
|
||||
|
||||
printf("Starting commad: [%s]\n", cmd);
|
||||
DBG(("Starting commad: [%s]", cmd));
|
||||
CreateProcess(NULL, cmd, NULL, NULL, TRUE,
|
||||
CREATE_NEW_PROCESS_GROUP, (void *) env, dir, &si, &pi);
|
||||
|
||||
@ -431,6 +443,7 @@ static struct mg_connection *accept_new_connection(struct mg_server *server) {
|
||||
// Put so socket structure into the queue
|
||||
set_close_on_exec(sock);
|
||||
set_non_blocking_mode(sock);
|
||||
conn->server = server;
|
||||
conn->client_sock = sock;
|
||||
conn->csa = sa;
|
||||
conn->local_iobuf.buf = (char *) conn + sizeof(*conn);
|
||||
@ -438,14 +451,14 @@ static struct mg_connection *accept_new_connection(struct mg_server *server) {
|
||||
conn->remote_iobuf.buf = calloc(1, IOBUF_SIZE);
|
||||
conn->remote_iobuf.size = IOBUF_SIZE;
|
||||
LINKED_LIST_ADD_TO_FRONT(&server->active_connections, &conn->link);
|
||||
printf("added conn %p\n", conn);
|
||||
DBG(("added conn %p", conn));
|
||||
}
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void close_conn(struct mg_connection *conn) {
|
||||
printf("closing %p\n", conn);
|
||||
DBG(("closing %p", conn));
|
||||
LINKED_LIST_REMOVE(&conn->link);
|
||||
closesocket(conn->client_sock);
|
||||
free(conn->remote_iobuf.buf);
|
||||
@ -498,9 +511,9 @@ static char *skip(char **buf, const char *delimiters) {
|
||||
// Parse HTTP headers from the given buffer, advance buffer to the point
|
||||
// where parsing stopped.
|
||||
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
|
||||
int i;
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
|
||||
for (i = 0; i < ARRAY_SIZE(ri->http_headers); i++) {
|
||||
ri->http_headers[i].name = skip(buf, ": ");
|
||||
ri->http_headers[i].value = skip(buf, "\r\n");
|
||||
if (ri->http_headers[i].name[0] == '\0')
|
||||
@ -557,15 +570,236 @@ static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
|
||||
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
|
||||
}
|
||||
|
||||
static int lowercase(const char *s) {
|
||||
return tolower(* (const unsigned char *) s);
|
||||
}
|
||||
|
||||
// Perform case-insensitive match of string against pattern
|
||||
static int match_prefix(const char *pattern, int pattern_len, const char *str) {
|
||||
const char *or_str;
|
||||
int i, j, len, res;
|
||||
|
||||
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
|
||||
res = match_prefix(pattern, or_str - pattern, str);
|
||||
return res > 0 ? res :
|
||||
match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
|
||||
}
|
||||
|
||||
i = j = 0;
|
||||
res = -1;
|
||||
for (; i < pattern_len; i++, j++) {
|
||||
if (pattern[i] == '?' && str[j] != '\0') {
|
||||
continue;
|
||||
} else if (pattern[i] == '$') {
|
||||
return str[j] == '\0' ? j : -1;
|
||||
} else if (pattern[i] == '*') {
|
||||
i++;
|
||||
if (pattern[i] == '*') {
|
||||
i++;
|
||||
len = (int) strlen(str + j);
|
||||
} else {
|
||||
len = (int) strcspn(str + j, "/");
|
||||
}
|
||||
if (i == pattern_len) {
|
||||
return j + len;
|
||||
}
|
||||
do {
|
||||
res = match_prefix(pattern + i, pattern_len - i, str + j + len);
|
||||
} while (res == -1 && len-- > 0);
|
||||
return res == -1 ? -1 : j + res + len;
|
||||
} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
// Protect against directory disclosure attack by removing '..',
|
||||
// excessive '/' and '\' characters
|
||||
static void remove_double_dots_and_double_slashes(char *s) {
|
||||
char *p = s;
|
||||
|
||||
while (*s != '\0') {
|
||||
*p++ = *s++;
|
||||
if (s[-1] == '/' || s[-1] == '\\') {
|
||||
// Skip all following slashes, backslashes and double-dots
|
||||
while (s[0] != '\0') {
|
||||
if (s[0] == '/' || s[0] == '\\') { s++; }
|
||||
else if (s[0] == '.' && s[1] == '.') { s += 2; }
|
||||
else { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
*p = '\0';
|
||||
}
|
||||
|
||||
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] == '%' && 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 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;
|
||||
}
|
||||
|
||||
// Return 1 if real file has been found, 0 otherwise
|
||||
static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
|
||||
size_t buf_len, struct stat *st) {
|
||||
struct vec a, b;
|
||||
const char *rewrites = conn->server->config_options[URL_REWRITES],
|
||||
*root = conn->server->config_options[DOCUMENT_ROOT],
|
||||
*cgi_pat = conn->server->config_options[CGI_PATTERN],
|
||||
*uri = conn->request_info.uri;
|
||||
char *p;
|
||||
int match_len;
|
||||
|
||||
// No filesystem access
|
||||
if (root == NULL) return 0;
|
||||
|
||||
// Handle URL rewrites
|
||||
snprintf(buf, buf_len, "%s%s", root, uri);
|
||||
while ((rewrites = next_option(rewrites, &a, &b)) != NULL) {
|
||||
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
|
||||
snprintf(buf, buf_len, "%.*s%s", (int) b.len, b.ptr, uri + match_len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stat(buf, st) == 0) return 1;
|
||||
|
||||
// Support PATH_INFO for CGI scripts.
|
||||
for (p = buf + strlen(root); *p != '\0'; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
if (match_prefix(cgi_pat, strlen(cgi_pat), buf) > 0 && !stat(buf, st)) {
|
||||
*p = '/';
|
||||
conn->path_info = mg_strdup(p);
|
||||
break;
|
||||
}
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int spool(struct iobuf *io, const void *buf, int len) {
|
||||
char *ptr = io->buf;
|
||||
|
||||
if (len <= 0) {
|
||||
} else if (len < io->size - io->len ||
|
||||
(ptr = realloc(io->buf, io->len + len - io->size)) != NULL) {
|
||||
io->buf = ptr;
|
||||
memcpy(io->buf + io->len, buf, len);
|
||||
io->len += len;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int vspool(struct iobuf *io, const char *fmt, va_list ap) {
|
||||
char *ptr = io->buf;
|
||||
va_list ap_copy;
|
||||
int len;
|
||||
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(NULL, 0, fmt, ap_copy);
|
||||
DBG(("len = %d", len));
|
||||
|
||||
if (len <= 0) {
|
||||
} else if (len < io->size - io->len ||
|
||||
(ptr = realloc(io->buf, io->len + len + 1 - io->size)) != NULL) {
|
||||
io->buf = ptr;
|
||||
vsnprintf(io->buf + io->len, len + 1, fmt, ap);
|
||||
io->len += len;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int fmtspool(struct iobuf *io, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
int ret;
|
||||
va_start(ap, fmt);
|
||||
ret = vspool(io, fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int is_error(int n) {
|
||||
return n == 0 || (n < 0 && errno != EINTR && errno != EAGAIN);
|
||||
}
|
||||
|
||||
static void write_to_client(struct mg_connection *conn) {
|
||||
struct iobuf *io = &conn->remote_iobuf;
|
||||
int n = send(conn->client_sock, io->buf, io->len, 0);
|
||||
|
||||
DBG(("Written %d of %d(%d): [%.*s]", n, io->len, io->size, 0, io->buf));
|
||||
|
||||
if (is_error(n)) {
|
||||
conn->flags |= CONN_CLOSE;
|
||||
} else if (n > 0) {
|
||||
memmove(io->buf, io->buf + n, io->len - n);
|
||||
io->len -= n;
|
||||
}
|
||||
if (io->len == 0 && conn->flags & CONN_SPOOL_DONE) {
|
||||
conn->flags |= CONN_CLOSE;
|
||||
}
|
||||
}
|
||||
|
||||
static void open_local_endpoint(struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = &conn->request_info;
|
||||
char path[MAX_PATH_SIZE] = {'\0'};
|
||||
struct stat st;
|
||||
int uri_len, exists = 0;
|
||||
|
||||
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
|
||||
* ((char *) conn->request_info.query_string++) = '\0';
|
||||
}
|
||||
uri_len = (int) strlen(ri->uri);
|
||||
mg_url_decode(ri->uri, uri_len, (char *) ri->uri, uri_len + 1, 0);
|
||||
remove_double_dots_and_double_slashes((char *) ri->uri);
|
||||
exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
|
||||
|
||||
if (exists && (conn->endpoint.fd = open(path, O_RDONLY)) != -1) {
|
||||
fmtspool(&conn->remote_iobuf, "HTTP/1.0 200 OK\r\n"
|
||||
"Content-Length: %" INT64_FMT "\r\n" "\r\n",
|
||||
(int64_t) st.st_size);
|
||||
conn->endpoint_type = EP_FILE;
|
||||
} else {
|
||||
fmtspool(&conn->remote_iobuf, "%s", "HTTP/1.0 404 Not Found\r\n\r\n");
|
||||
conn->flags |= CONN_SPOOL_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
static void read_from_client(struct mg_connection *conn) {
|
||||
struct iobuf *io = &conn->local_iobuf;
|
||||
int n = recv(conn->client_sock, io->buf + io->len, io->size - io->len, 0);
|
||||
|
||||
printf("read %d bytes\n", n);
|
||||
DBG(("Read: %d [%.*s]", n, n, io->buf + io->len));
|
||||
assert(io->len >= 0);
|
||||
assert(io->len <= io->size);
|
||||
|
||||
if (n == 0 || (n < 0 && (errno == EINTR || errno == EAGAIN))) {
|
||||
if (is_error(n)) {
|
||||
conn->flags |= CONN_CLOSE;
|
||||
} else if (n > 0) {
|
||||
io->len += n;
|
||||
@ -576,8 +810,8 @@ static void read_from_client(struct mg_connection *conn) {
|
||||
if (conn->request_len < 0 ||
|
||||
(conn->request_len == 0 || io->len >= io->size)) {
|
||||
conn->flags |= CONN_CLOSE;
|
||||
} else if (conn->request_len > 0) {
|
||||
printf("%.*s", (int) io->len, io->buf);
|
||||
} else if (conn->endpoint_type == EP_NONE) {
|
||||
open_local_endpoint(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -589,6 +823,34 @@ void add_to_set(sock_t sock, fd_set *set, sock_t *max_fd) {
|
||||
}
|
||||
}
|
||||
|
||||
static int io_space(const struct iobuf *io) {
|
||||
return io->size - io->len;
|
||||
}
|
||||
|
||||
static void transfer_file_data(struct mg_connection *conn) {
|
||||
struct iobuf *io = &conn->remote_iobuf, *iol = &conn->local_iobuf;
|
||||
int n = read(conn->endpoint.fd, io->buf + io->len, io->size - io->len);
|
||||
//DBG(("%s: %d", __func__, n));
|
||||
if (is_error(n)) {
|
||||
close(conn->endpoint.fd);
|
||||
conn->endpoint.fd = 0;
|
||||
conn->endpoint_type = EP_NONE;
|
||||
memmove(iol->buf, iol->buf + conn->request_len,
|
||||
iol->len - conn->request_len);
|
||||
iol->len -= conn->request_len;
|
||||
conn->request_len = 0;
|
||||
if (iol->len <= 0) {
|
||||
conn->flags |= io->len > 0 ? CONN_SPOOL_DONE : CONN_CLOSE;
|
||||
} else {
|
||||
DBG(("%s", "more req!"));
|
||||
}
|
||||
//DBG(("%s: %s", __func__, "YEEEEEE"));
|
||||
//conn->flags |= io->len > 0 ? CONN_SPOOL_DONE : CONN_CLOSE;
|
||||
} else if (n > 0) {
|
||||
io->len += n;
|
||||
}
|
||||
}
|
||||
|
||||
void mg_poll_server(struct mg_server *server, unsigned int milliseconds) {
|
||||
struct linked_list_link *lp, *tmp;
|
||||
struct mg_connection *conn;
|
||||
@ -604,24 +866,39 @@ void mg_poll_server(struct mg_server *server, unsigned int milliseconds) {
|
||||
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
|
||||
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
|
||||
add_to_set(conn->client_sock, &read_set, &max_fd);
|
||||
if (conn->endpoint_type == EP_FILE && io_space(&conn->remote_iobuf) > 0) {
|
||||
transfer_file_data(conn);
|
||||
// read can return 0, meaning EOF. Need to signal the writer to close.
|
||||
add_to_set(conn->client_sock, &write_set, &max_fd);
|
||||
}
|
||||
if (conn->remote_iobuf.len > 0) {
|
||||
add_to_set(conn->client_sock, &write_set, &max_fd);
|
||||
}
|
||||
}
|
||||
|
||||
tv.tv_sec = milliseconds / 1000;
|
||||
tv.tv_usec = (milliseconds % 1000) * 1000;
|
||||
|
||||
if (select(max_fd + 1, &read_set, &write_set, NULL, &tv) > 0) {
|
||||
// Accept new connections
|
||||
if (FD_ISSET(server->listening_sock, &read_set)) {
|
||||
while ((conn = accept_new_connection(server)) != NULL) {
|
||||
conn->expire_time = expire_time;
|
||||
}
|
||||
}
|
||||
|
||||
// Read/write from clients
|
||||
LINKED_LIST_FOREACH(&server->active_connections, lp, tmp) {
|
||||
conn = LINKED_LIST_ENTRY(lp, struct mg_connection, link);
|
||||
if (FD_ISSET(conn->client_sock, &read_set)) {
|
||||
conn->expire_time = expire_time;
|
||||
read_from_client(conn);
|
||||
}
|
||||
if (FD_ISSET(conn->client_sock, &write_set)) {
|
||||
assert(conn->remote_iobuf.len >= 0);
|
||||
conn->expire_time = expire_time;
|
||||
write_to_client(conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,7 +953,7 @@ struct mg_server *mg_create_server(const char *opts[], mg_event_handler_t func,
|
||||
free(server->config_options[i]);
|
||||
}
|
||||
server->config_options[i] = mg_strdup(value);
|
||||
printf("[%s] -> [%s]\n", name, value);
|
||||
DBG(("[%s] -> [%s]", name, value));
|
||||
}
|
||||
}
|
||||
|
||||
@ -699,33 +976,11 @@ struct mg_server *mg_create_server(const char *opts[], mg_event_handler_t func,
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *options[] = {"listening_port", "8080", NULL};
|
||||
const char *options[] = {"document_root", ".", "listening_port", "8080", 0};
|
||||
struct mg_server *server = mg_create_server(options, NULL, NULL);
|
||||
for (;;) {
|
||||
mg_poll_server(server, 1000);
|
||||
}
|
||||
mg_destroy_server(&server);
|
||||
|
||||
#if 0
|
||||
char buf[1000];
|
||||
int fds[2];
|
||||
struct iobuf loc, rem;
|
||||
|
||||
mg_socketpair(fds);
|
||||
if (!start_process(argv[1], NULL, NULL, fds[1])) {
|
||||
printf("start_process() failed\n");
|
||||
}
|
||||
|
||||
init_iobuf(&rem, buf, sizeof(buf), IO_FILE, (void *) fileno(stdout));
|
||||
init_iobuf(&loc, NULL, 0, IO_SOCKET, (void *) fds[0]);
|
||||
|
||||
while ((rem.len = read_from_iobuf(&loc, rem.buf, rem.size)) > 0) {
|
||||
write_to_iobuf(&rem);
|
||||
}
|
||||
|
||||
close_iobuf(&rem);
|
||||
close_iobuf(&loc);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user