303 lines
8.1 KiB
C
303 lines
8.1 KiB
C
/*
|
|
* vim: sw=2 ts=2 expandtab
|
|
*
|
|
* Copyright (c) 2013-2019 Thomas Kolb
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
#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"
|
|
#include "util.h"
|
|
|
|
#define FLG_USED 1
|
|
#define FLG_ISDIR 2
|
|
|
|
struct Entry {
|
|
char *name;
|
|
off_t size;
|
|
uint8_t flags;
|
|
};
|
|
|
|
const char* format_size(off_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, "%lli B", (long long)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, int uploadEnabled) {
|
|
char *result;
|
|
size_t allocated;
|
|
size_t i;
|
|
|
|
const size_t BUFSIZE = 5*PATH_MAX;
|
|
char buf[BUFSIZE];
|
|
char fullpath[PATH_MAX];
|
|
char encName[3*PATH_MAX]; // urlencode grows string by factor 3 at most
|
|
|
|
// allocate a buffer for the result string
|
|
allocated = strlen(HEADER1 HEADER2 FOOTER) + 64 * numentries + 4096;
|
|
LOG(LVL_DEBUG, "initially allocating result string - size: %lu bytes", allocated * sizeof(char));
|
|
result = malloc(allocated * sizeof(char));
|
|
result[0] = '\0';
|
|
|
|
result = safe_append(result, &allocated, HEADER1);
|
|
|
|
snprintf(buf, BUFSIZE, "<title>Directory listing for %s</title>", url);
|
|
result = safe_append(result, &allocated, buf);
|
|
|
|
result = safe_append(result, &allocated, HEADER2);
|
|
|
|
snprintf(buf, BUFSIZE, "<h1>Directory listing for %s</h1>", url);
|
|
result = safe_append(result, &allocated, buf);
|
|
|
|
int isrootdir = strcmp(url, "/") == 0;
|
|
|
|
// Begin Navigation
|
|
result = safe_append(result, &allocated, "<p>Navigation: ");
|
|
|
|
// tokenize the URL: init
|
|
char *token, *urlclone;
|
|
|
|
urlclone = strdup(url);
|
|
token = strtok(urlclone, "/");
|
|
strcpy(fullpath, "/");
|
|
|
|
if(isrootdir) {
|
|
result = safe_append(result, &allocated, "/");
|
|
} else {
|
|
result = safe_append(result, &allocated, "<a href=\"/\">/</a>");
|
|
|
|
// create a link for each token in URL
|
|
do {
|
|
LOG(LVL_DUMP, "Found token of %s: %s", url, token);
|
|
|
|
// TODO: check if this is safe
|
|
strcat(fullpath, token);
|
|
strcat(fullpath, "/");
|
|
|
|
snprintf(buf, BUFSIZE, " <a href=\"%s\">%s</a> /", fullpath, token);
|
|
result = safe_append(result, &allocated, buf);
|
|
}
|
|
while((token = strtok(NULL, "/")) != NULL);
|
|
}
|
|
|
|
free(urlclone);
|
|
|
|
result = safe_append(result, &allocated, "</p>");
|
|
|
|
// begin listing subdirs
|
|
result = safe_append(result, &allocated, "<h2>Sub-directories</h2>");
|
|
result = safe_append(result, &allocated, "<ul>");
|
|
|
|
for(i = 0; i < numentries; i++) {
|
|
// skip all non-directories
|
|
if(!(entries[i].flags & FLG_ISDIR))
|
|
continue;
|
|
|
|
urlencode(entries[i].name, encName);
|
|
|
|
if(isrootdir) {
|
|
snprintf(buf, BUFSIZE, "<li><a href=\"/%s/\">%s/</a></li>\n",
|
|
encName, entries[i].name);
|
|
} else {
|
|
snprintf(buf, BUFSIZE, "<li><a href=\"%s/%s/\">%s/</a></li>\n",
|
|
url, encName, entries[i].name);
|
|
}
|
|
result = safe_append(result, &allocated, buf);
|
|
}
|
|
|
|
result = safe_append(result, &allocated, "</ul>");
|
|
|
|
// begin listing files
|
|
result = safe_append(result, &allocated, "<h2>Files</h2>");
|
|
result = safe_append(result, &allocated, "<ul>");
|
|
|
|
// here the listing entries are generated
|
|
for(i = 0; i < numentries; i++) {
|
|
// skip all directories
|
|
if(entries[i].flags & FLG_ISDIR)
|
|
continue;
|
|
|
|
urlencode(entries[i].name, encName);
|
|
|
|
if(isrootdir) {
|
|
snprintf(buf, BUFSIZE, "<li><a href=\"/%s\">%s</a> [%s]</li>\n",
|
|
encName, entries[i].name, format_size(entries[i].size));
|
|
} else {
|
|
snprintf(buf, BUFSIZE, "<li><a href=\"%s/%s\">%s</a> [%s]</li>\n",
|
|
url, encName, entries[i].name, format_size(entries[i].size));
|
|
}
|
|
result = safe_append(result, &allocated, buf);
|
|
}
|
|
|
|
result = safe_append(result, &allocated, "</ul>");
|
|
|
|
if(uploadEnabled) {
|
|
result = safe_append(result, &allocated, "<h2>Upload</h2>");
|
|
result = safe_append(result, &allocated, "<p>Upload a file to this directory:</p>");
|
|
result = safe_append(result, &allocated,
|
|
"<p>"
|
|
" <form method=\"POST\" action=\".\" enctype=\"multipart/form-data\">"
|
|
" <input name=\"data\" type=\"file\" size=\"30\">"
|
|
" <input type=\"submit\" value=\"Go!\">"
|
|
" </form>"
|
|
"</p>"
|
|
);
|
|
}
|
|
|
|
result = safe_append(result, &allocated, FOOTER);
|
|
|
|
LOG(LVL_DEBUG, "result string (files) usage: %lu/%lu bytes", strlen(result), allocated);
|
|
|
|
return result;
|
|
}
|
|
|
|
char* create_dirlisting(const char *url, const char *localpath, int uploadEnabled) {
|
|
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, uploadEnabled);
|
|
|
|
for(i = 0; i < numentries; i++) {
|
|
if(entries[i].flags & FLG_USED) free(entries[i].name);
|
|
}
|
|
free(entries);
|
|
|
|
return result;
|
|
}
|
|
|