Commit 37751a28 authored by Sergey Lyubka's avatar Sergey Lyubka

directory ops moved to directory.c

parent 45347bd4
......@@ -29,7 +29,7 @@ VERSION = $(shell perl -lne \
SOURCES = src/internal.h src/util.c src/string.c src/parse_date.c \
src/options.c src/crypto.c src/auth.c src/win32.c src/unix.c \
src/mg_printf.c src/ssl.c src/http_client.c src/mime.c \
src/mongoose.c src/lua.c
src/directory.c src/mongoose.c src/lua.c
TINY_SOURCES = ../mongoose.c main.c
LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c
......
#include "internal.h"
static void print_dir_entry(const struct de *de) {
char size[64], mod[64], href[PATH_MAX * 3];
const char *slash = de->file.is_directory ? "/" : "";
if (de->file.is_directory) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh.
if (de->file.size < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
} else if (de->file.size < 0x100000) {
mg_snprintf(size, sizeof(size),
"%.1fk", (double) de->file.size / 1024.0);
} else if (de->file.size < 0x40000000) {
mg_snprintf(size, sizeof(size),
"%.1fM", (double) de->file.size / 1048576);
} else {
mg_snprintf(size, sizeof(size),
"%.1fG", (double) de->file.size / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
mg_url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
}
// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
const char *query_string = a->conn->request_info.query_string;
int cmp_result = 0;
if (query_string == NULL) {
query_string = "na";
}
if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top
} else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top
} else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') {
cmp_result = a->file.size == b->file.size ? 0 :
a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') {
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->file.modification_time > b->file.modification_time ? 1 : -1;
}
return query_string[1] == 'd' ? -cmp_result : cmp_result;
}
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
de.file_name = dp->d_name;
cb(&de, data);
}
(void) closedir(dirp);
}
return 1;
}
static int remove_directory(struct mg_connection *conn, const char *dir) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir, but show hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..")) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if(de.file.modification_time) {
if(de.file.is_directory) {
remove_directory(conn, path);
} else {
mg_remove(path);
}
}
}
(void) closedir(dirp);
rmdir(dir);
}
return 1;
}
struct dir_scan_data {
struct de *entries;
int num_entries;
int arr_size;
};
// Behaves like realloc(), but frees original pointer on failure
static void *realloc2(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
free(ptr);
}
return new_ptr;
}
static void dir_scan_callback(struct de *de, void *data) {
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
dsd->arr_size *= 2;
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
sizeof(dsd->entries[0]));
}
if (dsd->entries == NULL) {
// TODO(lsm): propagate an error to the caller
dsd->num_entries = 0;
} else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++;
}
}
static void handle_directory_request(struct mg_connection *conn,
const char *dir) {
int i, sort_direction;
struct dir_scan_data data = { NULL, 0, 128 };
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
send_http_error(conn, 500, "Cannot open directory",
"Error: opendir(%s): %s", dir, strerror(ERRNO));
return;
}
sort_direction = conn->request_info.query_string != NULL &&
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
conn->must_close = 1;
mg_printf(conn, "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_chunked_printf(conn,
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->request_info.uri, conn->request_info.uri,
sort_direction, sort_direction, sort_direction);
// Print first entry - link to a parent directory
conn->num_bytes_sent += mg_chunked_printf(conn,
"<tr><td><a href=\"%s%s\">%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
conn->request_info.uri, "..", "Parent directory", "-", "-");
// Sort and print directory entries
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
compare_dir_entries);
for (i = 0; i < data.num_entries; i++) {
print_dir_entry(&data.entries[i]);
free(data.entries[i].file_name);
}
free(data.entries);
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
"</table></body></html>");
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
conn->status_code = 200;
}
......@@ -399,7 +399,7 @@ struct socket {
// NOTE(lsm): this enum shoulds be in sync with the config_options.
enum {
CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, THROTTLE,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS,
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
......@@ -446,9 +446,6 @@ struct mg_connection {
int request_len; // Size of the request + headers in a buffer
int data_len; // Total size of data in a buffer
int status_code; // HTTP reply status code, e.g. 200
int throttle; // Throttling, bytes/sec. <= 0 means no throttle
time_t last_throttle_time; // Last time throttled data was sent
int64_t last_throttle_bytes;// Bytes sent this second
};
// Directory entry
......
......@@ -255,41 +255,8 @@ int mg_read(struct mg_connection *conn, void *buf, int len) {
}
int mg_write(struct mg_connection *conn, const void *buf, int len) {
time_t now;
int64_t n, total, allowed;
if (conn->throttle > 0) {
if ((now = time(NULL)) != conn->last_throttle_time) {
conn->last_throttle_time = now;
conn->last_throttle_bytes = 0;
}
allowed = conn->throttle - conn->last_throttle_bytes;
if (allowed > (int64_t) len) {
allowed = len;
}
if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) == allowed) {
buf = (char *) buf + total;
conn->last_throttle_bytes += total;
while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
allowed = conn->throttle > (int64_t) len - total ?
(int64_t) len - total : conn->throttle;
if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) != allowed) {
break;
}
sleep(1);
conn->last_throttle_bytes = allowed;
conn->last_throttle_time = time(NULL);
buf = (char *) buf + n;
total += n;
}
}
} else {
total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
return push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) len);
}
return (int) total;
}
int mg_url_decode(const char *src, int src_len, char *dst,
......@@ -503,262 +470,6 @@ static int get_request_len(const char *buf, int buf_len) {
return 0;
}
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
static void print_dir_entry(const struct de *de) {
char size[64], mod[64], href[PATH_MAX * 3];
const char *slash = de->file.is_directory ? "/" : "";
if (de->file.is_directory) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh.
if (de->file.size < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
} else if (de->file.size < 0x100000) {
mg_snprintf(size, sizeof(size),
"%.1fk", (double) de->file.size / 1024.0);
} else if (de->file.size < 0x40000000) {
mg_snprintf(size, sizeof(size),
"%.1fM", (double) de->file.size / 1048576);
} else {
mg_snprintf(size, sizeof(size),
"%.1fG", (double) de->file.size / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
mg_url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
}
// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
const char *query_string = a->conn->request_info.query_string;
int cmp_result = 0;
if (query_string == NULL) {
query_string = "na";
}
if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top
} else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top
} else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') {
cmp_result = a->file.size == b->file.size ? 0 :
a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') {
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->file.modification_time > b->file.modification_time ? 1 : -1;
}
return query_string[1] == 'd' ? -cmp_result : cmp_result;
}
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
de.file_name = dp->d_name;
cb(&de, data);
}
(void) closedir(dirp);
}
return 1;
}
static int remove_directory(struct mg_connection *conn, const char *dir) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir, but show hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..")) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if(de.file.modification_time) {
if(de.file.is_directory) {
remove_directory(conn, path);
} else {
mg_remove(path);
}
}
}
(void) closedir(dirp);
rmdir(dir);
}
return 1;
}
struct dir_scan_data {
struct de *entries;
int num_entries;
int arr_size;
};
// Behaves like realloc(), but frees original pointer on failure
static void *realloc2(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
free(ptr);
}
return new_ptr;
}
static void dir_scan_callback(struct de *de, void *data) {
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
dsd->arr_size *= 2;
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
sizeof(dsd->entries[0]));
}
if (dsd->entries == NULL) {
// TODO(lsm): propagate an error to the caller
dsd->num_entries = 0;
} else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++;
}
}
static void handle_directory_request(struct mg_connection *conn,
const char *dir) {
int i, sort_direction;
struct dir_scan_data data = { NULL, 0, 128 };
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
send_http_error(conn, 500, "Cannot open directory",
"Error: opendir(%s): %s", dir, strerror(ERRNO));
return;
}
sort_direction = conn->request_info.query_string != NULL &&
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
conn->must_close = 1;
mg_printf(conn, "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_chunked_printf(conn,
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->request_info.uri, conn->request_info.uri,
sort_direction, sort_direction, sort_direction);
// Print first entry - link to a parent directory
conn->num_bytes_sent += mg_chunked_printf(conn,
"<tr><td><a href=\"%s%s\">%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
conn->request_info.uri, "..", "Parent directory", "-", "-");
// Sort and print directory entries
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
compare_dir_entries);
for (i = 0; i < data.num_entries; i++) {
print_dir_entry(&data.entries[i]);
free(data.entries[i].file_name);
}
free(data.entries);
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
"</table></body></html>");
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
conn->status_code = 200;
}
// Send len bytes from the opened file to the client.
static void send_file_data(struct mg_connection *conn, FILE *fp,
int64_t offset, int64_t len) {
......@@ -2076,38 +1787,6 @@ static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
return len;
}
static int set_throttle(const char *spec, uint32_t remote_ip, const char *uri) {
int throttle = 0;
struct vec vec, val;
uint32_t net, mask;
char mult;
double v;
while ((spec = next_option(spec, &vec, &val)) != NULL) {
mult = ',';
if (sscanf(val.ptr, "%lf%c", &v, &mult) < 1 || v < 0 ||
(lowercase(&mult) != 'k' && lowercase(&mult) != 'm' && mult != ',')) {
continue;
}
v *= lowercase(&mult) == 'k' ? 1024 : lowercase(&mult) == 'm' ? 1048576 : 1;
if (vec.len == 1 && vec.ptr[0] == '*') {
throttle = (int) v;
} else if (parse_net(vec.ptr, &net, &mask) > 0) {
if ((remote_ip & mask) == net) {
throttle = (int) v;
}
} else if (match_prefix(vec.ptr, vec.len, uri) > 0) {
throttle = (int) v;
}
}
return throttle;
}
static uint32_t get_remote_ip(const struct mg_connection *conn) {
return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr);
}
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
char *path, int path_len) {
const char *content_type_header, *boundary_start;
......@@ -2312,8 +1991,6 @@ static void handle_request(struct mg_connection *conn) {
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);
conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
get_remote_ip(conn), ri->uri);
path[0] = '\0';
convert_uri_to_file_name(conn, path, sizeof(path), &file);
......@@ -2607,7 +2284,7 @@ static void reset_per_request_attributes(struct mg_connection *conn) {
conn->path_info = NULL;
conn->num_bytes_sent = conn->num_bytes_read = 0;
conn->status_code = -1;
conn->must_close = conn->request_len = conn->throttle = 0;
conn->must_close = conn->request_len = 0;
}
static void close_socket_gracefully(struct mg_connection *conn) {
......
......@@ -266,3 +266,22 @@ static void remove_double_dots_and_double_slashes(char *s) {
*p = '\0';
}
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
......@@ -399,7 +399,7 @@ struct socket {
// NOTE(lsm): this enum shoulds be in sync with the config_options.
enum {
CGI_EXTENSIONS, CGI_ENVIRONMENT, PUT_DELETE_PASSWORDS_FILE, CGI_INTERPRETER,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS, THROTTLE,
PROTECT_URI, AUTHENTICATION_DOMAIN, SSI_EXTENSIONS,
ACCESS_LOG_FILE, ENABLE_DIRECTORY_LISTING, ERROR_LOG_FILE,
GLOBAL_PASSWORDS_FILE, INDEX_FILES, ENABLE_KEEP_ALIVE, ACCESS_CONTROL_LIST,
EXTRA_MIME_TYPES, LISTENING_PORTS, DOCUMENT_ROOT, SSL_CERTIFICATE,
......@@ -446,9 +446,6 @@ struct mg_connection {
int request_len; // Size of the request + headers in a buffer
int data_len; // Total size of data in a buffer
int status_code; // HTTP reply status code, e.g. 200
int throttle; // Throttling, bytes/sec. <= 0 means no throttle
time_t last_throttle_time; // Last time throttled data was sent
int64_t last_throttle_bytes;// Bytes sent this second
};
// Directory entry
......@@ -749,6 +746,25 @@ static void remove_double_dots_and_double_slashes(char *s) {
*p = '\0';
}
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
static const char *month_names[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
......@@ -2291,6 +2307,243 @@ static void get_mime_type(struct mg_context *ctx, const char *path,
vec->len = strlen(vec->ptr);
}
static void print_dir_entry(const struct de *de) {
char size[64], mod[64], href[PATH_MAX * 3];
const char *slash = de->file.is_directory ? "/" : "";
if (de->file.is_directory) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh.
if (de->file.size < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
} else if (de->file.size < 0x100000) {
mg_snprintf(size, sizeof(size),
"%.1fk", (double) de->file.size / 1024.0);
} else if (de->file.size < 0x40000000) {
mg_snprintf(size, sizeof(size),
"%.1fM", (double) de->file.size / 1048576);
} else {
mg_snprintf(size, sizeof(size),
"%.1fG", (double) de->file.size / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
mg_url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
}
// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
const char *query_string = a->conn->request_info.query_string;
int cmp_result = 0;
if (query_string == NULL) {
query_string = "na";
}
if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top
} else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top
} else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') {
cmp_result = a->file.size == b->file.size ? 0 :
a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') {
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->file.modification_time > b->file.modification_time ? 1 : -1;
}
return query_string[1] == 'd' ? -cmp_result : cmp_result;
}
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
de.file_name = dp->d_name;
cb(&de, data);
}
(void) closedir(dirp);
}
return 1;
}
static int remove_directory(struct mg_connection *conn, const char *dir) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir, but show hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..")) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if(de.file.modification_time) {
if(de.file.is_directory) {
remove_directory(conn, path);
} else {
mg_remove(path);
}
}
}
(void) closedir(dirp);
rmdir(dir);
}
return 1;
}
struct dir_scan_data {
struct de *entries;
int num_entries;
int arr_size;
};
// Behaves like realloc(), but frees original pointer on failure
static void *realloc2(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
free(ptr);
}
return new_ptr;
}
static void dir_scan_callback(struct de *de, void *data) {
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
dsd->arr_size *= 2;
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
sizeof(dsd->entries[0]));
}
if (dsd->entries == NULL) {
// TODO(lsm): propagate an error to the caller
dsd->num_entries = 0;
} else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++;
}
}
static void handle_directory_request(struct mg_connection *conn,
const char *dir) {
int i, sort_direction;
struct dir_scan_data data = { NULL, 0, 128 };
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
send_http_error(conn, 500, "Cannot open directory",
"Error: opendir(%s): %s", dir, strerror(ERRNO));
return;
}
sort_direction = conn->request_info.query_string != NULL &&
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
conn->must_close = 1;
mg_printf(conn, "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_chunked_printf(conn,
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->request_info.uri, conn->request_info.uri,
sort_direction, sort_direction, sort_direction);
// Print first entry - link to a parent directory
conn->num_bytes_sent += mg_chunked_printf(conn,
"<tr><td><a href=\"%s%s\">%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
conn->request_info.uri, "..", "Parent directory", "-", "-");
// Sort and print directory entries
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
compare_dir_entries);
for (i = 0; i < data.num_entries; i++) {
print_dir_entry(&data.entries[i]);
free(data.entries[i].file_name);
}
free(data.entries);
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
"</table></body></html>");
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
conn->status_code = 200;
}
// Return number of bytes left to read for this connection
static int64_t left_to_read(const struct mg_connection *conn) {
return conn->content_len + conn->request_len - conn->num_bytes_read;
......@@ -2546,41 +2799,8 @@ int mg_read(struct mg_connection *conn, void *buf, int len) {
}
int mg_write(struct mg_connection *conn, const void *buf, int len) {
time_t now;
int64_t n, total, allowed;
if (conn->throttle > 0) {
if ((now = time(NULL)) != conn->last_throttle_time) {
conn->last_throttle_time = now;
conn->last_throttle_bytes = 0;
}
allowed = conn->throttle - conn->last_throttle_bytes;
if (allowed > (int64_t) len) {
allowed = len;
}
if ((total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) == allowed) {
buf = (char *) buf + total;
conn->last_throttle_bytes += total;
while (total < (int64_t) len && conn->ctx->stop_flag == 0) {
allowed = conn->throttle > (int64_t) len - total ?
(int64_t) len - total : conn->throttle;
if ((n = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) allowed)) != allowed) {
break;
}
sleep(1);
conn->last_throttle_bytes = allowed;
conn->last_throttle_time = time(NULL);
buf = (char *) buf + n;
total += n;
}
}
} else {
total = push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
return push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
(int64_t) len);
}
return (int) total;
}
int mg_url_decode(const char *src, int src_len, char *dst,
......@@ -2794,262 +3014,6 @@ static int get_request_len(const char *buf, int buf_len) {
return 0;
}
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
static const char *dont_escape = "._-$,;~()";
static const char *hex = "0123456789abcdef";
const char *end = dst + dst_len - 1;
for (; *src != '\0' && dst < end; src++, dst++) {
if (isalnum(*(const unsigned char *) src) ||
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
*dst = *src;
} else if (dst + 2 < end) {
dst[0] = '%';
dst[1] = hex[(* (const unsigned char *) src) >> 4];
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
dst += 2;
}
}
*dst = '\0';
}
static void print_dir_entry(const struct de *de) {
char size[64], mod[64], href[PATH_MAX * 3];
const char *slash = de->file.is_directory ? "/" : "";
if (de->file.is_directory) {
mg_snprintf(size, sizeof(size), "%s", "[DIRECTORY]");
} else {
// We use (signed) cast below because MSVC 6 compiler cannot
// convert unsigned __int64 to double. Sigh.
if (de->file.size < 1024) {
mg_snprintf(size, sizeof(size), "%d", (int) de->file.size);
} else if (de->file.size < 0x100000) {
mg_snprintf(size, sizeof(size),
"%.1fk", (double) de->file.size / 1024.0);
} else if (de->file.size < 0x40000000) {
mg_snprintf(size, sizeof(size),
"%.1fM", (double) de->file.size / 1048576);
} else {
mg_snprintf(size, sizeof(size),
"%.1fG", (double) de->file.size / 1073741824);
}
}
strftime(mod, sizeof(mod), "%d-%b-%Y %H:%M",
localtime(&de->file.modification_time));
mg_url_encode(de->file_name, href, sizeof(href));
de->conn->num_bytes_sent += mg_chunked_printf(de->conn,
"<tr><td><a href=\"%s%s%s\">%s%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
de->conn->request_info.uri, href, slash, de->file_name, slash, mod, size);
}
// This function is called from send_directory() and used for
// sorting directory entries by size, or name, or modification time.
// On windows, __cdecl specification is needed in case if project is built
// with __stdcall convention. qsort always requires __cdels callback.
static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
const char *query_string = a->conn->request_info.query_string;
int cmp_result = 0;
if (query_string == NULL) {
query_string = "na";
}
if (a->file.is_directory && !b->file.is_directory) {
return -1; // Always put directories on top
} else if (!a->file.is_directory && b->file.is_directory) {
return 1; // Always put directories on top
} else if (*query_string == 'n') {
cmp_result = strcmp(a->file_name, b->file_name);
} else if (*query_string == 's') {
cmp_result = a->file.size == b->file.size ? 0 :
a->file.size > b->file.size ? 1 : -1;
} else if (*query_string == 'd') {
cmp_result = a->file.modification_time == b->file.modification_time ? 0 :
a->file.modification_time > b->file.modification_time ? 1 : -1;
}
return query_string[1] == 'd' ? -cmp_result : cmp_result;
}
static int must_hide_file(struct mg_connection *conn, const char *path) {
const char *pw_pattern = "**" PASSWORDS_FILE_NAME "$";
const char *pattern = conn->ctx->config[HIDE_FILES];
return match_prefix(pw_pattern, strlen(pw_pattern), path) > 0 ||
(pattern != NULL && match_prefix(pattern, strlen(pattern), path) > 0);
}
static int scan_directory(struct mg_connection *conn, const char *dir,
void *data, void (*cb)(struct de *, void *)) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir and hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..") ||
must_hide_file(conn, dp->d_name)) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
de.file_name = dp->d_name;
cb(&de, data);
}
(void) closedir(dirp);
}
return 1;
}
static int remove_directory(struct mg_connection *conn, const char *dir) {
char path[PATH_MAX];
struct dirent *dp;
DIR *dirp;
struct de de;
if ((dirp = opendir(dir)) == NULL) {
return 0;
} else {
de.conn = conn;
while ((dp = readdir(dirp)) != NULL) {
// Do not show current dir, but show hidden files
if (!strcmp(dp->d_name, ".") ||
!strcmp(dp->d_name, "..")) {
continue;
}
mg_snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
// If we don't memset stat structure to zero, mtime will have
// garbage and strftime() will segfault later on in
// print_dir_entry(). memset is required only if mg_stat()
// fails. For more details, see
// http://code.google.com/p/mongoose/issues/detail?id=79
memset(&de.file, 0, sizeof(de.file));
mg_stat(path, &de.file);
if(de.file.modification_time) {
if(de.file.is_directory) {
remove_directory(conn, path);
} else {
mg_remove(path);
}
}
}
(void) closedir(dirp);
rmdir(dir);
}
return 1;
}
struct dir_scan_data {
struct de *entries;
int num_entries;
int arr_size;
};
// Behaves like realloc(), but frees original pointer on failure
static void *realloc2(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
free(ptr);
}
return new_ptr;
}
static void dir_scan_callback(struct de *de, void *data) {
struct dir_scan_data *dsd = (struct dir_scan_data *) data;
if (dsd->entries == NULL || dsd->num_entries >= dsd->arr_size) {
dsd->arr_size *= 2;
dsd->entries = (struct de *) realloc2(dsd->entries, dsd->arr_size *
sizeof(dsd->entries[0]));
}
if (dsd->entries == NULL) {
// TODO(lsm): propagate an error to the caller
dsd->num_entries = 0;
} else {
dsd->entries[dsd->num_entries].file_name = mg_strdup(de->file_name);
dsd->entries[dsd->num_entries].file = de->file;
dsd->entries[dsd->num_entries].conn = de->conn;
dsd->num_entries++;
}
}
static void handle_directory_request(struct mg_connection *conn,
const char *dir) {
int i, sort_direction;
struct dir_scan_data data = { NULL, 0, 128 };
if (!scan_directory(conn, dir, &data, dir_scan_callback)) {
send_http_error(conn, 500, "Cannot open directory",
"Error: opendir(%s): %s", dir, strerror(ERRNO));
return;
}
sort_direction = conn->request_info.query_string != NULL &&
conn->request_info.query_string[1] == 'd' ? 'a' : 'd';
conn->must_close = 1;
mg_printf(conn, "%s",
"HTTP/1.1 200 OK\r\n"
"Transfer-Encoding: Chunked\r\n"
"Content-Type: text/html; charset=utf-8\r\n\r\n");
conn->num_bytes_sent += mg_chunked_printf(conn,
"<html><head><title>Index of %s</title>"
"<style>th {text-align: left;}</style></head>"
"<body><h1>Index of %s</h1><pre><table cellpadding=\"0\">"
"<tr><th><a href=\"?n%c\">Name</a></th>"
"<th><a href=\"?d%c\">Modified</a></th>"
"<th><a href=\"?s%c\">Size</a></th></tr>"
"<tr><td colspan=\"3\"><hr></td></tr>",
conn->request_info.uri, conn->request_info.uri,
sort_direction, sort_direction, sort_direction);
// Print first entry - link to a parent directory
conn->num_bytes_sent += mg_chunked_printf(conn,
"<tr><td><a href=\"%s%s\">%s</a></td>"
"<td>&nbsp;%s</td><td>&nbsp;&nbsp;%s</td></tr>\n",
conn->request_info.uri, "..", "Parent directory", "-", "-");
// Sort and print directory entries
qsort(data.entries, (size_t) data.num_entries, sizeof(data.entries[0]),
compare_dir_entries);
for (i = 0; i < data.num_entries; i++) {
print_dir_entry(&data.entries[i]);
free(data.entries[i].file_name);
}
free(data.entries);
conn->num_bytes_sent += mg_chunked_printf(conn, "%s",
"</table></body></html>");
conn->num_bytes_sent += mg_write(conn, "0\r\n\r\n", 5);
conn->status_code = 200;
}
// Send len bytes from the opened file to the client.
static void send_file_data(struct mg_connection *conn, FILE *fp,
int64_t offset, int64_t len) {
......@@ -4367,38 +4331,6 @@ static int parse_net(const char *spec, uint32_t *net, uint32_t *mask) {
return len;
}
static int set_throttle(const char *spec, uint32_t remote_ip, const char *uri) {
int throttle = 0;
struct vec vec, val;
uint32_t net, mask;
char mult;
double v;
while ((spec = next_option(spec, &vec, &val)) != NULL) {
mult = ',';
if (sscanf(val.ptr, "%lf%c", &v, &mult) < 1 || v < 0 ||
(lowercase(&mult) != 'k' && lowercase(&mult) != 'm' && mult != ',')) {
continue;
}
v *= lowercase(&mult) == 'k' ? 1024 : lowercase(&mult) == 'm' ? 1048576 : 1;
if (vec.len == 1 && vec.ptr[0] == '*') {
throttle = (int) v;
} else if (parse_net(vec.ptr, &net, &mask) > 0) {
if ((remote_ip & mask) == net) {
throttle = (int) v;
}
} else if (match_prefix(vec.ptr, vec.len, uri) > 0) {
throttle = (int) v;
}
}
return throttle;
}
static uint32_t get_remote_ip(const struct mg_connection *conn) {
return ntohl(* (uint32_t *) &conn->client.rsa.sin.sin_addr);
}
FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
char *path, int path_len) {
const char *content_type_header, *boundary_start;
......@@ -4603,8 +4535,6 @@ static void handle_request(struct mg_connection *conn) {
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);
conn->throttle = set_throttle(conn->ctx->config[THROTTLE],
get_remote_ip(conn), ri->uri);
path[0] = '\0';
convert_uri_to_file_name(conn, path, sizeof(path), &file);
......@@ -4898,7 +4828,7 @@ static void reset_per_request_attributes(struct mg_connection *conn) {
conn->path_info = NULL;
conn->num_bytes_sent = conn->num_bytes_read = 0;
conn->status_code = -1;
conn->must_close = conn->request_len = conn->throttle = 0;
conn->must_close = conn->request_len = 0;
}
static void close_socket_gracefully(struct mg_connection *conn) {
......
......@@ -416,19 +416,6 @@ static void test_mg_get_var(void) {
ASSERT(mg_get_var(post[1], strlen(post[1]), "st", buf, 17) == 16);
}
static void test_set_throttle(void) {
ASSERT(set_throttle(NULL, 0x0a000001, "/") == 0);
ASSERT(set_throttle("10.0.0.0/8=20", 0x0a000001, "/") == 20);
ASSERT(set_throttle("10.0.0.0/8=0.5k", 0x0a000001, "/") == 512);
ASSERT(set_throttle("10.0.0.0/8=17m", 0x0a000001, "/") == 1048576 * 17);
ASSERT(set_throttle("10.0.0.0/8=1x", 0x0a000001, "/") == 0);
ASSERT(set_throttle("10.0.0.0/8=5,0.0.0.0/0=10", 0x0a000001, "/") == 10);
ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0a000001, "/index") == 5);
ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0a000001, "/foo/x") == 7);
ASSERT(set_throttle("10.0.0.0/8=5,/foo/**=7", 0x0b000001, "/foxo/x") == 0);
ASSERT(set_throttle("10.0.0.0/8=5,*=1", 0x0b000001, "/foxo/x") == 1);
}
static void test_next_option(void) {
const char *p, *list = "x/8,/y**=1;2k,z";
struct vec a, b;
......@@ -684,7 +671,6 @@ int __cdecl main(void) {
test_should_keep_alive();
test_mg_download();
test_mg_get_var();
test_set_throttle();
test_next_option();
test_mg_stat();
test_skip_quoted();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment