Initial commit
- Working version with the following features: - Directory listing and navigation - Download files - HTTP error pages (403, 404, 500) - Verbose logging
This commit is contained in:
commit
6cd65091ff
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
*.o
|
||||||
|
*.swp
|
||||||
|
fileshare
|
||||||
|
core
|
||||||
|
tags
|
||||||
|
testdir
|
24
Makefile
Normal file
24
Makefile
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
CC=gcc
|
||||||
|
#CFLAGS+=-O2 -Wall -march=native -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L -D_XOPEN_SOURCE $(LUA_CFLAGS)
|
||||||
|
#CFLAGS+=-O2 -Wall -march=native -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L
|
||||||
|
CFLAGS+=-g -Wall -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L
|
||||||
|
LIBS=-lmicrohttpd
|
||||||
|
|
||||||
|
TARGET=fileshare
|
||||||
|
SOURCE=main.c logger.c dirlisting.c
|
||||||
|
DEPS=logger.h templates.h dirlisting.h
|
||||||
|
|
||||||
|
OBJ=$(patsubst %.c, %.o, $(SOURCE))
|
||||||
|
|
||||||
|
$(TARGET): $(OBJ) $(DEPS)
|
||||||
|
$(CC) -o $(TARGET) $(OBJ) $(LIBS)
|
||||||
|
|
||||||
|
%.o: %.c $(DEPS)
|
||||||
|
$(CC) -c $(CFLAGS) -o $@ $< $(INCLUDES)
|
||||||
|
|
||||||
|
doc:
|
||||||
|
doxygen doxygen.conf
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TARGET)
|
||||||
|
rm -f $(OBJ)
|
269
dirlisting.c
Normal file
269
dirlisting.c
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <strings.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#include <linux/limits.h>
|
||||||
|
#include <linux/unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "templates.h"
|
||||||
|
|
||||||
|
#define FLG_USED 1
|
||||||
|
#define FLG_ISDIR 2
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
char *name;
|
||||||
|
size_t size;
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
const char* format_size(size_t s) {
|
||||||
|
static char buf[32];
|
||||||
|
const char *prefixes = " kMGTPEZY";
|
||||||
|
float fs = s;
|
||||||
|
|
||||||
|
int divisions = 0;
|
||||||
|
|
||||||
|
while(fs > 1024) {
|
||||||
|
fs /= 1024.0f;
|
||||||
|
divisions++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(divisions == 0) {
|
||||||
|
sprintf(buf, "%lu B", s);
|
||||||
|
} else {
|
||||||
|
sprintf(buf, "%.2f %ciB", fs, prefixes[divisions]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int compare_entries(const void *sp1,const void *sp2 ) {
|
||||||
|
const struct Entry *e1 = sp1;
|
||||||
|
const struct Entry *e2 = sp2;
|
||||||
|
|
||||||
|
return( strcasecmp(e1->name, e2->name) );
|
||||||
|
}
|
||||||
|
|
||||||
|
char* gen_html(const char *url, struct Entry *entries, size_t numentries) {
|
||||||
|
char *result;
|
||||||
|
size_t allocated;
|
||||||
|
size_t i;
|
||||||
|
char buf[256];
|
||||||
|
char fullpath[PATH_MAX];
|
||||||
|
|
||||||
|
// allocate a buffer for the result string
|
||||||
|
allocated = 64 * numentries + strlen(HEADER1 HEADER2 FOOTER) + 4096;
|
||||||
|
LOG(LVL_DEBUG, "initially allocating result string - size: %lu bytes", allocated * sizeof(char));
|
||||||
|
result = malloc(allocated * sizeof(char));
|
||||||
|
result[0] = '\0';
|
||||||
|
|
||||||
|
// this is still safe
|
||||||
|
strcat(result, HEADER1);
|
||||||
|
|
||||||
|
sprintf(buf, "<title>Directory listing for %s</title>", url);
|
||||||
|
strcat(result, buf);
|
||||||
|
|
||||||
|
strcat(result, HEADER2);
|
||||||
|
|
||||||
|
sprintf(buf, "<h1>Directory listing for %s</h1>", url);
|
||||||
|
strcat(result, buf);
|
||||||
|
|
||||||
|
int isrootdir = strcmp(url, "/") == 0;
|
||||||
|
|
||||||
|
// Begin Navigation
|
||||||
|
strcat(result, "<p>Navigation: ");
|
||||||
|
|
||||||
|
// tokenize the URL: init
|
||||||
|
char *token, *urlclone;
|
||||||
|
|
||||||
|
urlclone = strdup(url);
|
||||||
|
token = strtok(urlclone, "/");
|
||||||
|
strcpy(fullpath, "/");
|
||||||
|
|
||||||
|
if(isrootdir) {
|
||||||
|
strcat(result, "/");
|
||||||
|
} else {
|
||||||
|
strcat(result, "<a href=\"/\">/</a>");
|
||||||
|
|
||||||
|
// create a link for each token in URL
|
||||||
|
do {
|
||||||
|
LOG(LVL_DUMP, "Found token of %s: %s", url, token);
|
||||||
|
|
||||||
|
strcat(fullpath, token);
|
||||||
|
strcat(fullpath, "/");
|
||||||
|
|
||||||
|
sprintf(buf, " <a href=\"%s\">%s</a> /", fullpath, token);
|
||||||
|
strcat(result, buf);
|
||||||
|
}
|
||||||
|
while((token = strtok(NULL, "/")) != NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(urlclone);
|
||||||
|
|
||||||
|
strcat(result, "</p>");
|
||||||
|
|
||||||
|
// begin listing subdirs
|
||||||
|
strcat(result, "<h2>Sub-directories</h2>");
|
||||||
|
strcat(result, "<ul>");
|
||||||
|
|
||||||
|
for(i = 0; i < numentries; i++) {
|
||||||
|
// skip all non-directories
|
||||||
|
if(!(entries[i].flags & FLG_ISDIR))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(isrootdir) {
|
||||||
|
sprintf(buf, "<li><a href=\"/%s/\">%s/</a></li>\n",
|
||||||
|
entries[i].name, entries[i].name);
|
||||||
|
} else {
|
||||||
|
sprintf(buf, "<li><a href=\"%s/%s/\">%s/</a></li>\n",
|
||||||
|
url, entries[i].name, entries[i].name);
|
||||||
|
}
|
||||||
|
strcat(result, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(result, "</ul>");
|
||||||
|
|
||||||
|
// begin listing files
|
||||||
|
strcat(result, "<h2>Files</h2>");
|
||||||
|
strcat(result, "<ul>");
|
||||||
|
|
||||||
|
// here the listing entries are generated
|
||||||
|
for(i = 0; i < numentries; i++) {
|
||||||
|
size_t entrylen = 32 + 2 * strlen(entries[i].name);
|
||||||
|
size_t resultlen = strlen(result);
|
||||||
|
|
||||||
|
// skip all directories
|
||||||
|
if(entries[i].flags & FLG_ISDIR)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(allocated < resultlen + entrylen) {
|
||||||
|
allocated *= 2;
|
||||||
|
LOG(LVL_DEBUG, "reallocating result string - new size: %lu bytes", allocated * sizeof(char));
|
||||||
|
char *tmpresult = realloc(result, allocated * sizeof(char));
|
||||||
|
if(!tmpresult) {
|
||||||
|
LOG(LVL_ERR, "realloc failed for result string: %s", strerror(errno));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = tmpresult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isrootdir) {
|
||||||
|
sprintf(buf, "<li><a href=\"/%s\">%s</a> [%s]</li>\n",
|
||||||
|
entries[i].name, entries[i].name, format_size(entries[i].size));
|
||||||
|
} else {
|
||||||
|
sprintf(buf, "<li><a href=\"%s/%s\">%s</a> [%s]</li>\n",
|
||||||
|
url, entries[i].name, entries[i].name, format_size(entries[i].size));
|
||||||
|
}
|
||||||
|
strcat(result, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
strcat(result, "</ul>");
|
||||||
|
strcat(result, FOOTER);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* create_dirlisting(const char *url, const char *localpath) {
|
||||||
|
char *result;
|
||||||
|
struct Entry *entries;
|
||||||
|
|
||||||
|
size_t numentries, allocated;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
|
||||||
|
struct stat s;
|
||||||
|
|
||||||
|
char fullpath[PATH_MAX];
|
||||||
|
|
||||||
|
if((dir = opendir(localpath)) == NULL) {
|
||||||
|
LOG(LVL_ERR, "Cannot opendir %s: %s", localpath, strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// allocate some entry pointers
|
||||||
|
allocated = 100;
|
||||||
|
numentries = 0;
|
||||||
|
|
||||||
|
entries = malloc(allocated * sizeof(struct Entry));
|
||||||
|
if(!entries) {
|
||||||
|
LOG(LVL_ERR, "malloc failed for directory entries: %s", strerror(errno));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize all entry strings with NULL
|
||||||
|
for(i = 0; i < allocated; i++) {
|
||||||
|
entries[i].flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
while((de = readdir(dir)) != NULL) {
|
||||||
|
if(de->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if entries array is full
|
||||||
|
if(numentries == allocated) {
|
||||||
|
// double the size of the entries array
|
||||||
|
LOG(LVL_DEBUG, "reallocating entry list - new size: %lu bytes", 2 * allocated * sizeof(struct Entry));
|
||||||
|
struct Entry *tmpentries = realloc(entries, 2 * allocated * sizeof(struct Entry));
|
||||||
|
if(!tmpentries) {
|
||||||
|
LOG(LVL_ERR, "realloc failed for directory entries: %s", strerror(errno));
|
||||||
|
free(entries);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = tmpentries;
|
||||||
|
|
||||||
|
// initialize the new entries
|
||||||
|
for(i = allocated; i < 2*allocated; i++) {
|
||||||
|
entries[i].flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocated *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(fullpath, PATH_MAX, "%s/%s", localpath, de->d_name);
|
||||||
|
|
||||||
|
if(stat(fullpath, &s) == -1) {
|
||||||
|
LOG(LVL_WARN, "Cannot stat %s while listing directories: %s",
|
||||||
|
fullpath, strerror(errno));
|
||||||
|
|
||||||
|
// stat failed -> ignore this entry
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[numentries].name = strdup(de->d_name);
|
||||||
|
entries[numentries].flags |= FLG_USED;
|
||||||
|
entries[numentries].size = s.st_size;
|
||||||
|
|
||||||
|
if(S_ISDIR(s.st_mode)) {
|
||||||
|
entries[numentries].flags |= FLG_ISDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
numentries++;
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
|
||||||
|
qsort(entries, numentries, sizeof(struct Entry), compare_entries);
|
||||||
|
|
||||||
|
result = gen_html(url, entries, numentries);
|
||||||
|
|
||||||
|
for(i = 0; i < numentries; i++) {
|
||||||
|
if(entries[i].flags & FLG_USED) free(entries[i].name);
|
||||||
|
}
|
||||||
|
free(entries);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
6
dirlisting.h
Normal file
6
dirlisting.h
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef DIRLISTING_H
|
||||||
|
#define DIRLISTING_H
|
||||||
|
|
||||||
|
char* create_dirlisting(const char *url, const char *localpath);
|
||||||
|
|
||||||
|
#endif // DIRLISTING_H
|
151
logger.c
Normal file
151
logger.c
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
// define constants
|
||||||
|
const char *LOGGER_STR_FATAL = "FATAL";
|
||||||
|
const char *LOGGER_STR_ERR = "ERROR";
|
||||||
|
const char *LOGGER_STR_WARN = "WARN ";
|
||||||
|
const char *LOGGER_STR_INFO = "INFO ";
|
||||||
|
const char *LOGGER_STR_DEBUG = "DEBUG";
|
||||||
|
const char *LOGGER_STR_DUMP = "DUMP ";
|
||||||
|
|
||||||
|
const char *LOGGER_COLOR_FATAL = "\033[1;31m";
|
||||||
|
const char *LOGGER_COLOR_ERR = "\033[1;31m";
|
||||||
|
const char *LOGGER_COLOR_WARN = "\033[1;33m";
|
||||||
|
const char *LOGGER_COLOR_INFO = "\033[1;32m";
|
||||||
|
const char *LOGGER_COLOR_DEBUG = "\033[1m";
|
||||||
|
const char *LOGGER_COLOR_DUMP = "\033[1;30m";
|
||||||
|
const char *LOGGER_COLOR_NONE = "\033[0m";
|
||||||
|
|
||||||
|
// global variables
|
||||||
|
sem_t logger_semaphore;
|
||||||
|
int logger_verbosity;
|
||||||
|
int logger_use_colors;
|
||||||
|
|
||||||
|
void logger_init(void) {
|
||||||
|
// Initialize the semaphore
|
||||||
|
sem_init(&logger_semaphore, 0, 1);
|
||||||
|
|
||||||
|
logger_verbosity = 2147483647;
|
||||||
|
logger_use_colors = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_shutdown(void) {
|
||||||
|
sem_destroy(&logger_semaphore);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_enable_colors(int enable) {
|
||||||
|
logger_use_colors = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_set_verbosity(int verbosity) {
|
||||||
|
logger_verbosity = verbosity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_debug_message(const char *prefix, const char *fmt, va_list ap) {
|
||||||
|
/* Guess we need no more than 100 bytes. */
|
||||||
|
int n, size = 100;
|
||||||
|
char *p, *np;
|
||||||
|
va_list internal_ap;
|
||||||
|
|
||||||
|
if ((p = (char*)malloc(size)) == NULL) {
|
||||||
|
fprintf(stderr, "[%s] FATAL: Cannot allocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
/* Try to print in the allocated space. */
|
||||||
|
va_copy(internal_ap, ap);
|
||||||
|
n = vsnprintf(p, size, fmt, internal_ap);
|
||||||
|
va_end(internal_ap);
|
||||||
|
|
||||||
|
/* If that worked, return the string. */
|
||||||
|
if (n > -1 && n < size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Else try again with more space. */
|
||||||
|
if (n > -1) /* glibc 2.1 */
|
||||||
|
size = n+1; /* precisely what is needed */
|
||||||
|
else /* glibc 2.0 */
|
||||||
|
size *= 2; /* twice the old size */
|
||||||
|
|
||||||
|
if ((np = (char*)realloc (p, size)) == NULL) {
|
||||||
|
free(p);
|
||||||
|
fprintf(stderr, "[%s] FATAL: Cannot reallocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
p = np;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sem_wait(&logger_semaphore);
|
||||||
|
fprintf(stderr, "%s %s\n", prefix, p);
|
||||||
|
sem_post(&logger_semaphore);
|
||||||
|
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
void logger_log(int level, const char *format, ...) {
|
||||||
|
va_list argptr;
|
||||||
|
|
||||||
|
char timebuf[32];
|
||||||
|
char timebuf2[32];
|
||||||
|
|
||||||
|
char prefixbuf[64];
|
||||||
|
const char *prefixcolor = "", *prefixtext = "";
|
||||||
|
|
||||||
|
if(level > logger_verbosity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
struct timeval tv;
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
strftime(timebuf, 32, "%Y-%M-%d %H:%M:%S.%%03d", localtime(&(tv.tv_sec)));
|
||||||
|
snprintf(timebuf2, 32, timebuf, tv.tv_usec/1000);
|
||||||
|
|
||||||
|
if(level >= LVL_DUMP) {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_DUMP;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_DUMP;
|
||||||
|
} else if(level >= LVL_DEBUG) {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_DEBUG;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_DEBUG;
|
||||||
|
} else if(level >= LVL_INFO) {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_INFO;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_INFO;
|
||||||
|
} else if(level >= LVL_WARN) {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_WARN;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_WARN;
|
||||||
|
} else if(level >= LVL_ERR) {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_ERR;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_ERR;
|
||||||
|
} else {
|
||||||
|
if(logger_use_colors)
|
||||||
|
prefixcolor = LOGGER_COLOR_FATAL;
|
||||||
|
|
||||||
|
prefixtext = LOGGER_STR_FATAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(logger_use_colors) {
|
||||||
|
sprintf(prefixbuf, "%s [%s%s%s]", timebuf2, prefixcolor, prefixtext, LOGGER_COLOR_NONE);
|
||||||
|
} else {
|
||||||
|
sprintf(prefixbuf, "%s [%s]", timebuf2, prefixtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(argptr, format);
|
||||||
|
logger_debug_message(prefixbuf, format, argptr);
|
||||||
|
va_end(argptr);
|
||||||
|
}
|
27
logger.h
Normal file
27
logger.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef LOGGER_H
|
||||||
|
#define LOGGER_H
|
||||||
|
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
static const int LVL_FATAL = 0; /*!< Fatal message level */
|
||||||
|
static const int LVL_ERR = 5; /*!< Error message level */
|
||||||
|
static const int LVL_WARN = 50; /*!< Warning message level */
|
||||||
|
static const int LVL_INFO = 100; /*!< Information message level */
|
||||||
|
static const int LVL_DEBUG = 200; /*!< Debug message level */
|
||||||
|
static const int LVL_DUMP = 500; /*!< Dump message level */
|
||||||
|
|
||||||
|
extern sem_t logger_semaphore;
|
||||||
|
extern int logger_verbosity;
|
||||||
|
extern int logger_use_colors;
|
||||||
|
|
||||||
|
void logger_init(void);
|
||||||
|
void logger_shutdown(void);
|
||||||
|
void logger_enable_colors(int enable);
|
||||||
|
void logger_set_verbosity(int verbosity);
|
||||||
|
|
||||||
|
void logger_log(int level, const char *format, ...);
|
||||||
|
|
||||||
|
#define LOG(level, ...) logger_log(level, __VA_ARGS__)
|
||||||
|
|
||||||
|
#endif // LOGGER_H
|
269
main.c
Normal file
269
main.c
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <linux/limits.h>
|
||||||
|
|
||||||
|
#include <microhttpd.h>
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include "templates.h"
|
||||||
|
#include "dirlisting.h"
|
||||||
|
|
||||||
|
#define DEFAULT_PORT 8888
|
||||||
|
|
||||||
|
struct ConnectionState {
|
||||||
|
char cleanedURL[PATH_MAX];
|
||||||
|
char localFileName[PATH_MAX];
|
||||||
|
struct stat targetStat;
|
||||||
|
};
|
||||||
|
|
||||||
|
char *shareRoot;
|
||||||
|
struct MHD_Response *error403Response;
|
||||||
|
struct MHD_Response *error404Response;
|
||||||
|
struct MHD_Response *error500Response;
|
||||||
|
|
||||||
|
int running = 1;
|
||||||
|
|
||||||
|
void remove_trailing_slash(char *str) {
|
||||||
|
size_t offset = strlen(str)-1;
|
||||||
|
if((offset != 0) && (str[offset] == '/')) {
|
||||||
|
str[offset] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void request_completed(void *cls,
|
||||||
|
struct MHD_Connection * connection,
|
||||||
|
void **ptr,
|
||||||
|
enum MHD_RequestTerminationCode toe) {
|
||||||
|
struct ConnectionState *connstate = *ptr;
|
||||||
|
|
||||||
|
// TODO: show IP+Port in log
|
||||||
|
LOG(LVL_DEBUG, "Freeing state for request of %s", connstate->localFileName);
|
||||||
|
|
||||||
|
free(connstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connection_handler(void * cls,
|
||||||
|
struct MHD_Connection *connection,
|
||||||
|
const char *url,
|
||||||
|
const char *method,
|
||||||
|
const char *version,
|
||||||
|
const char *upload_data,
|
||||||
|
size_t *upload_data_size,
|
||||||
|
void **ptr) {
|
||||||
|
struct ConnectionState *connstate;
|
||||||
|
|
||||||
|
struct MHD_Response *response;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (0 != strcmp(method, "GET")) {
|
||||||
|
LOG(LVL_WARN, "Unexpected method: %s.", method);
|
||||||
|
return MHD_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*ptr == NULL) {
|
||||||
|
// This is the first time this connection is seen.
|
||||||
|
// Initialize the connection state
|
||||||
|
connstate = malloc(sizeof(struct ConnectionState));
|
||||||
|
*ptr = connstate;
|
||||||
|
|
||||||
|
// set the local file name
|
||||||
|
strncpy(connstate->localFileName, shareRoot, PATH_MAX);
|
||||||
|
strncat(connstate->localFileName, url,
|
||||||
|
PATH_MAX - strlen(connstate->localFileName));
|
||||||
|
|
||||||
|
remove_trailing_slash(connstate->localFileName);
|
||||||
|
|
||||||
|
strncpy(connstate->cleanedURL, url, PATH_MAX);
|
||||||
|
remove_trailing_slash(connstate->cleanedURL);
|
||||||
|
|
||||||
|
LOG(LVL_INFO, "New %s request for %s (local: %s)",
|
||||||
|
method, url, connstate->localFileName);
|
||||||
|
|
||||||
|
return MHD_YES;
|
||||||
|
} else {
|
||||||
|
connstate = *ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 != *upload_data_size) {
|
||||||
|
return MHD_NO; // upload data in a GET!?
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if url contains a "/.." sequence. If so, block the request as it is
|
||||||
|
// trying to access the parent directory (and may be an attempt to leave the
|
||||||
|
// shared space.
|
||||||
|
if(NULL != strstr(url, "/..")) {
|
||||||
|
LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.",
|
||||||
|
url);
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// check properties of the target file/dir
|
||||||
|
if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) {
|
||||||
|
LOG(LVL_ERR, "Cannot stat %s: %s",
|
||||||
|
connstate->localFileName, strerror(errno));
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(S_ISREG(connstate->targetStat.st_mode)) {
|
||||||
|
LOG(LVL_DEBUG, "Serving %s as a regular file.", connstate->localFileName);
|
||||||
|
|
||||||
|
// open a file descriptor to the file
|
||||||
|
int fd = open(connstate->localFileName, O_RDONLY);
|
||||||
|
|
||||||
|
if(fd == -1) {
|
||||||
|
LOG(LVL_ERR, "Cannot open %s: %s",
|
||||||
|
connstate->localFileName, strerror(errno));
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no error so far -> serve the file
|
||||||
|
response = MHD_create_response_from_fd(
|
||||||
|
connstate->targetStat.st_size,
|
||||||
|
fd);
|
||||||
|
|
||||||
|
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
|
||||||
|
MHD_destroy_response(response);
|
||||||
|
return ret;
|
||||||
|
} else if(S_ISDIR(connstate->targetStat.st_mode)) {
|
||||||
|
LOG(LVL_DEBUG, "Generating directory listing for %s.", connstate->localFileName);
|
||||||
|
|
||||||
|
char *result = create_dirlisting(connstate->cleanedURL, connstate->localFileName);
|
||||||
|
if(!result) {
|
||||||
|
LOG(LVL_ERR, "Failed to generate dirlisting for %s.", connstate->localFileName);
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, error500Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
response = MHD_create_response_from_buffer(
|
||||||
|
strlen(result), result, MHD_RESPMEM_MUST_FREE);
|
||||||
|
|
||||||
|
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
|
||||||
|
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
|
||||||
|
MHD_destroy_response(response);
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
LOG(LVL_WARN,
|
||||||
|
"%s is neither a directory nor a regular file. Don't allow the access.",
|
||||||
|
connstate->localFileName);
|
||||||
|
|
||||||
|
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a 500 if request was not handled properly
|
||||||
|
return MHD_queue_response(
|
||||||
|
connection, MHD_HTTP_INTERNAL_SERVER_ERROR, error500Response);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char ** argv) {
|
||||||
|
struct MHD_Daemon *d;
|
||||||
|
struct stat sBuf;
|
||||||
|
|
||||||
|
int port;
|
||||||
|
|
||||||
|
logger_init();
|
||||||
|
|
||||||
|
if (argc < 2) {
|
||||||
|
printf("%s <dir> [<port>]\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if shareRoot is an existing directory
|
||||||
|
shareRoot = argv[1];
|
||||||
|
if(stat(shareRoot, &sBuf) == -1) {
|
||||||
|
LOG(LVL_FATAL, "Cannot stat %s: %s", shareRoot, strerror(errno));
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!S_ISDIR(sBuf.st_mode)) {
|
||||||
|
LOG(LVL_FATAL, "%s is not a directory!", shareRoot);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove trailing / from shareRoot
|
||||||
|
remove_trailing_slash(shareRoot);
|
||||||
|
|
||||||
|
LOG(LVL_INFO, "Shared directory is: %s", shareRoot);
|
||||||
|
|
||||||
|
if(argc >= 3) {
|
||||||
|
// a port was given on the command line, try to parse it
|
||||||
|
if(sscanf(argv[2], "%i", &port) != 1) {
|
||||||
|
LOG(LVL_FATAL, "%s is not a valid port number!", argv[2]);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(port < 1 || port > 65535) {
|
||||||
|
LOG(LVL_FATAL, "Port %i is out of range [1-65535]!", port);
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(LVL_INFO, "Using port: %i", port);
|
||||||
|
} else {
|
||||||
|
LOG(LVL_INFO, "Using default port: %i", DEFAULT_PORT);
|
||||||
|
port = DEFAULT_PORT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the static response for error pages
|
||||||
|
error403Response = MHD_create_response_from_data(
|
||||||
|
strlen(ERROR_403),
|
||||||
|
(void*) ERROR_403,
|
||||||
|
MHD_NO,
|
||||||
|
MHD_NO);
|
||||||
|
|
||||||
|
error404Response = MHD_create_response_from_data(
|
||||||
|
strlen(ERROR_404),
|
||||||
|
(void*) ERROR_404,
|
||||||
|
MHD_NO,
|
||||||
|
MHD_NO);
|
||||||
|
|
||||||
|
error500Response = MHD_create_response_from_data(
|
||||||
|
strlen(ERROR_500),
|
||||||
|
(void*) ERROR_500,
|
||||||
|
MHD_NO,
|
||||||
|
MHD_NO);
|
||||||
|
|
||||||
|
MHD_add_response_header(error403Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
MHD_add_response_header(error404Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
MHD_add_response_header(error500Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
|
||||||
|
|
||||||
|
d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
|
||||||
|
port,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&connection_handler,
|
||||||
|
NULL,
|
||||||
|
MHD_OPTION_NOTIFY_COMPLETED,
|
||||||
|
&request_completed, NULL,
|
||||||
|
MHD_OPTION_END);
|
||||||
|
if (d == NULL) {
|
||||||
|
LOG(LVL_FATAL, "Cannot start up libmicrohttpd.");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(running) {
|
||||||
|
getc(stdin);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG(LVL_INFO, "Shutting down...");
|
||||||
|
|
||||||
|
MHD_stop_daemon(d);
|
||||||
|
|
||||||
|
MHD_destroy_response(error403Response);
|
||||||
|
MHD_destroy_response(error404Response);
|
||||||
|
MHD_destroy_response(error500Response);
|
||||||
|
|
||||||
|
logger_shutdown();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
79
templates.h
Normal file
79
templates.h
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
#ifndef TEMPLATES_H
|
||||||
|
#define TEMPLATES_H
|
||||||
|
|
||||||
|
// generic definitions
|
||||||
|
#define HEADER1 \
|
||||||
|
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" \
|
||||||
|
" \"http://www.w3.org/TR/html4/strict.dtd\">" \
|
||||||
|
"<html>" \
|
||||||
|
" <head>"
|
||||||
|
|
||||||
|
#define HEADER2 \
|
||||||
|
" <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" >" \
|
||||||
|
" </head>" \
|
||||||
|
" <body>"
|
||||||
|
|
||||||
|
#define FOOTER \
|
||||||
|
" </body>" \
|
||||||
|
"</html>"
|
||||||
|
|
||||||
|
|
||||||
|
// error page definitions
|
||||||
|
#define ERROR_404 \
|
||||||
|
HEADER1 \
|
||||||
|
" <title>Error 404*i - File is imaginary</title>" \
|
||||||
|
HEADER2 \
|
||||||
|
" <h1>Error 404*i - File is imaginary</h1>" \
|
||||||
|
" <p>This page cannot be displayed because it is imaginary.<p>" \
|
||||||
|
" <p>To view this page in the real world, turn your screen by <sup>π</sup>/<sub>2</sub> .</p>" \
|
||||||
|
" <pre>" \
|
||||||
|
" ^ Im\n" \
|
||||||
|
" |\n" \
|
||||||
|
" | +j\n" \
|
||||||
|
" __--+--__\n" \
|
||||||
|
" _- | -_\n" \
|
||||||
|
" / X \\\n" \
|
||||||
|
" | | |\n" \
|
||||||
|
"----+-------+-------+-----> Re\n" \
|
||||||
|
" -1 | | +1 |\n" \
|
||||||
|
" \\ | /\n" \
|
||||||
|
" --__ | __--\n" \
|
||||||
|
" --+-- \n" \
|
||||||
|
" | -j\n" \
|
||||||
|
" |\n" \
|
||||||
|
" </pre>" \
|
||||||
|
FOOTER
|
||||||
|
|
||||||
|
#define ERROR_500 \
|
||||||
|
HEADER1 \
|
||||||
|
" <title>Error 500 - Internal Server Error</title>" \
|
||||||
|
HEADER2 \
|
||||||
|
" <h1>Error 500 - Internal Server Error</h1>" \
|
||||||
|
" <pre>" \
|
||||||
|
" _______________________________________ \n" \
|
||||||
|
"/ Oops. Something went wrong. \\\n" \
|
||||||
|
"| Please contact the administrator and |\n" \
|
||||||
|
"\\ tell him how to reproduce this error. /\n" \
|
||||||
|
" --------------------------------------- \n" \
|
||||||
|
" \\ \\_______\n" \
|
||||||
|
" v__v \\ \\ O )\n" \
|
||||||
|
" (oo) ||----w |\n" \
|
||||||
|
" (__) || || \\/\\\n" \
|
||||||
|
" </pre>" \
|
||||||
|
FOOTER
|
||||||
|
|
||||||
|
#define ERROR_403 \
|
||||||
|
HEADER1 \
|
||||||
|
" <title>Error 403 - Forbidden</title>" \
|
||||||
|
HEADER2 \
|
||||||
|
" <h1>Error 403 - Forbidden</h1>" \
|
||||||
|
" <p>You are not allowed to access this file or directory.<p>" \
|
||||||
|
" <p>This error is shown under the following conditions:<p>" \
|
||||||
|
" <ul>" \
|
||||||
|
" <li>You are trying to access a file that the server isn't allowed to read</li>" \
|
||||||
|
" <li>The target filesystem entry is not a regular file or directory</li>" \
|
||||||
|
" <li>The URL contains \"/..\", which is blocked for security reasons</li>" \
|
||||||
|
" </ul>" \
|
||||||
|
FOOTER
|
||||||
|
|
||||||
|
#endif // TEMPLATES_H
|
Loading…
Reference in a new issue