fileshare/src/dirlisting.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;
}