mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-26 12:14:56 +00:00
directory ops moved to directory.c
This commit is contained in:
1 parent
45347bd44c
commit
37751a2875
7 files changed
+520
-673
No files matched your search
+1
-1
@@ -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
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
#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> %s</td><td> %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> %s</td><td> %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
|
||||
|
||||
+2
-325
@@ -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> %s</td><td> %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> %s</td><td> %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';
|
||||
}
|
||||
+259
-329
@@ -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> %s</td><td> %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> %s</td><td> %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> %s</td><td> %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> %s</td><td> %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();
|
||||
|
||||
Reference in new issue
Block a user