Compare commits

...

11 commits

Author SHA1 Message Date
48db5eea1d Fix path for root case
All checks were successful
continuous-integration/drone/push Build is passing
In case of root, res->path returns '.' which does not mix well with
concatenating the entry name. For example this may result in `.bin` for a folder
`bin` in root.
2020-08-31 18:58:01 +02:00
66558aa615 Stat full path, allocate enough space
All checks were successful
continuous-integration/drone/push Build is passing
Not stat'ing the full path (just entry->d_name) strangely works on glibc/fedora
but fails on musl/alpine. Stat'ing the full path is the right thing to do,
anyways.

There was a heap corruption in `dirl_find_templ_dir` due not allocating enough
space for the terminating NULL in `path_buf`. This again only showed up in
musl/alpine.
2020-08-31 06:42:23 +02:00
d06c37fbd5 Add README for dirl, publish CI builds
All checks were successful
continuous-integration/drone/push Build is passing
2020-08-30 19:07:57 +02:00
2a3e25dc6b Fix merge leftovers, add attribution footer by default 2020-08-30 18:07:23 +02:00
e262166522 Re-add replace and read_file to util
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-30 14:32:48 +02:00
30ca105c66 Merge branch 'master' into dirlist 2020-08-30 14:16:41 +02:00
eba491b4b5 Find template in root, don't search tail dir twice
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-30 09:38:02 +02:00
bd51ff593d Refactoring
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-30 08:43:10 +02:00
a22ac176ca Template overrides in subdirs, prefetch templates
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-29 20:36:10 +02:00
5b6f8e5083 Read from template files with placeholder replacement
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-28 00:46:08 +02:00
329dedc33b Read dirlist from templates
Some checks failed
continuous-integration/drone/push Build is failing
Reads header, footer and entry templates from files defined in config.h.
Defaults to original output if not found.
2020-08-25 21:08:23 +02:00
10 changed files with 776 additions and 199 deletions

137
.clang-format Normal file
View file

@ -0,0 +1,137 @@
---
Language: Cpp
# BasedOnStyle: Mozilla
AccessModifierOffset: -2
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Right
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortLambdasOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: TopLevel
AlwaysBreakAfterReturnType: TopLevel
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: true
AfterControlStatement: false
AfterEnum: true
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: true
AfterExternBlock: true
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: false
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Mozilla
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeComma
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 2
ContinuationIndentWidth: 2
Cpp11BracedListStyle: false
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2
SortPriority: 0
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3
SortPriority: 0
- Regex: '.*'
Priority: 1
SortPriority: 0
IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: ''
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: true
ObjCSpaceBeforeProtocolList: false
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceBeforeSquareBrackets: false
Standard: Latest
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 8
UseCRLF: false
UseTab: Never
...

View file

@ -7,18 +7,16 @@ steps:
image: gcc image: gcc
commands: commands:
- make - make
- mv quark quark-dirl
- name: run - name: publish
image: debian image: appleboy/drone-scp
commands: settings:
- cp quark /usr/local/bin host: friedl.net
- useradd web && su web && cd username:
- mkdir -p web && cd web && echo "hello from quark" > index.html from_secret: deploy_user
- quark -p 9130 -h run -l -u web -g web password:
detach: true from_secret: deploy_password
port: 22
- name: test target: /var/services/dirlist/repo/bin/suckless/quark
image: curlimages/curl source: quark-dirl
commands:
- sleep 15
- curl http://run:9130

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
*.o
.gdb_history
config.h
quark

View file

@ -4,11 +4,12 @@
include config.mk include config.mk
COMPONENTS = data http sock util COMPONENTS = data http sock util dirl
all: quark all: quark
data.o: data.c data.h util.h http.h config.mk data.o: data.c data.h util.h http.h dirl.h config.mk
dirl.o: dirl.c dirl.h util.h http.h config.mk
http.o: http.c http.h util.h http.h data.h config.h config.mk http.o: http.c http.h util.h http.h data.h config.h config.mk
main.o: main.c util.h sock.h http.h arg.h config.h config.mk main.o: main.c util.h sock.h http.h arg.h config.h config.mk
sock.o: sock.c sock.h util.h config.mk sock.o: sock.c sock.h util.h config.mk

View file

@ -5,20 +5,94 @@ found at https://git.suckless.org/quark.
Quark is a small http server. Quark is a small http server.
# Issues # DIRL
## fork: Resource temporarily unavailable dirl is a quark extension for customized directory listings.
When running [quark](http://tools.suckless.org/quark/) (#6606994) on my system
with `sudo ./quark -p 9763 -u <user> -g <group>` it dies with `./quark: fork:
Resource temporarily unavailable` at `fork()`.
Reason being that by default quark sets the RLIMIT_NPROC to 512 processes. When running as a non-exclusive user this limit is easily reached before even starting quark. Per default dirl generates html for a directory listing like this:
`resource-depletion-fix` contains a small forkbomb (`minibomb.c`) to simulate a user with > 512 processes. Compile it with `make minibomb`. When running the minibomb and quark with the same user quark fails. ```html
<!DOCTYPE HTML PUBLIC " - // W3C//DTD HTML 3.2 Final//EN">
<!-- Header Section -->
<html>
<head>
<link rel="stylesheet" href="style.css">
<title>Index of {uri}</title>
</head>
<body>
<h1>Index of {uri}</h1>
<p><a href="..">&crarr; Parent Directory</a></p>
<hr />
<table>
<tr><th>Name</th><th>Modified</th><th>Size</th></tr>
<!-- /Header Section -->
The `resource-depletion-fix` branch contains a fix by setting the RLIMIT_NPROC only if the current system limit is lower than what would be set by quark. You can [download the patch](https://dirlist.friedl.net/suckless/quark/), or compile from the `resource-depletion-fix` branch. <!-- Entry Section -->
<!-- (repeated for each entry in the directory) -->
<tr>
<td><a href="{entry}">{entry}{suffix}</a>
<td>{modified}</td>
<td>{size}</td>
</tr>
<!-- /Entry Section -->
Note that quark also has a `-n` parameter with which the max number of processes can be set as an alternative to this patch. <!-- Footer Section -->
</table>
<hr />
<p>
Served by <a href="http://tools.suckless.org/quark/">quark</a> and <a href="https://git.friedl.net/playground/suckless-quark/src/branch/dirlist">dirl</a>
</p>
</body>
</html>
<!-- /Footer Section -->
```
## Customize
The default listing can be styled by a `style.css` in the root directory.
You can also use your fully customized template by creating one or all the
template files for each section. Per default the section templates are named:
- .header.tpl
- .entry.tpl (repeated for each directory entry)
- .footer.tpl
Note that if you only provide some of the template files, they have to be
compatible with the generated default for the other sections.
For each of these templates you can use placeholders that are replaced by their respective values:
- header
* `{uri}`: Replaced by the current path
- entry
* `{entry}`: Name of the entry
* `{suffix}`: A suffix for the entry, mostly useful to distinguish directories (suffix '/') from files
* `{modified}`: Date the entry was last modified
* `{size}`: Size of the entry (if available)
### Subdirectory styling
dirl tries to the closest template for the currently visited path. This gives
you the opportunity to override templates in subdirectories. dirl walks the
directory hierarchy upwards from the currently visited path. As soon as it finds
one of the template files in a directory, it stops searching and uses the
templates in that directory.
In case no templates are found up until and including root, the default
templates are used.
### Customize names
The files defined as templates and style are ignored in the directory listing
itself. In case you need to list one of these directories, or have any other
reason to choose different names, the filenames can be configured in `dirl.h`.
Note that you need to compile your own quark version then.
# Download
You can also download CI builds for [quark-dirl](https://dirlist.friedl.net/bin/suckless/quark/).
There are no official releases. Quark has no dependencies and you can easily
build it from source. Don't forget to read up on the [suckless
philosophy](http://suckless.org/philosophy/).
# Github Users # Github Users
If you are visiting this repository on GitHub, you are on a mirror of If you are visiting this repository on GitHub, you are on a mirror of

111
data.c
View file

@ -10,6 +10,7 @@
#include "http.h" #include "http.h"
#include "data.h" #include "data.h"
#include "util.h" #include "util.h"
#include "dirl.h"
static int static int
compareent(const struct dirent **d1, const struct dirent **d2) compareent(const struct dirent **d1, const struct dirent **d2)
@ -25,64 +26,6 @@ compareent(const struct dirent **d1, const struct dirent **d2)
return strcmp((*d1)->d_name, (*d2)->d_name); return strcmp((*d1)->d_name, (*d2)->d_name);
} }
static char *
suffix(int t)
{
switch (t) {
case DT_FIFO: return "|";
case DT_DIR: return "/";
case DT_LNK: return "@";
case DT_SOCK: return "=";
}
return "";
}
static void
html_escape(const char *src, char *dst, size_t dst_siz)
{
const struct {
char c;
char *s;
} escape[] = {
{ '&', "&amp;" },
{ '<', "&lt;" },
{ '>', "&gt;" },
{ '"', "&quot;" },
{ '\'', "&#x27;" },
};
size_t i, j, k, esclen;
for (i = 0, j = 0; src[i] != '\0'; i++) {
for (k = 0; k < LEN(escape); k++) {
if (src[i] == escape[k].c) {
break;
}
}
if (k == LEN(escape)) {
/* no escape char at src[i] */
if (j == dst_siz - 1) {
/* silent truncation */
break;
} else {
dst[j++] = src[i];
}
} else {
/* escape char at src[i] */
esclen = strlen(escape[k].s);
if (j >= dst_siz - esclen) {
/* silent truncation */
break;
} else {
memcpy(&dst[j], escape[k].s, esclen);
j += esclen;
}
}
}
dst[j] = '\0';
}
enum status enum status
data_send_dirlisting(int fd, const struct response *res) data_send_dirlisting(int fd, const struct response *res)
{ {
@ -90,48 +33,38 @@ data_send_dirlisting(int fd, const struct response *res)
struct dirent **e; struct dirent **e;
size_t i; size_t i;
int dirlen; int dirlen;
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
/* read directory */ /* read directory */
if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) { if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) {
return S_FORBIDDEN; return S_FORBIDDEN;
} }
/* listing header (we use esc because sizeof(esc) >= PATH_MAX) */ /* read templates */
html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc))); struct dirl_templ templates = dirl_read_templ(res->uri);
if (dprintf(fd,
"<!DOCTYPE html>\n<html>\n\t<head>"
"<title>Index of %s</title></head>\n"
"\t<body>\n\t\t<a href=\"..\">..</a>",
esc) < 0) {
ret = S_REQUEST_TIMEOUT;
goto cleanup;
}
/* listing */ /* listing header */
if ((ret = dirl_header(fd, res, &templates))) {
return ret;
}
/* entries */
for (i = 0; i < (size_t)dirlen; i++) { for (i = 0; i < (size_t)dirlen; i++) {
/* skip hidden files, "." and ".." */ /* skip dirl special files */
if (e[i]->d_name[0] == '.') { if(dirl_skip(e[i]->d_name)) {
continue; continue;
} }
/* entry line */
if ((ret = dirl_entry(fd, e[i], res, &templates))) {
goto cleanup;
}
/* entry line */
html_escape(e[i]->d_name, esc, sizeof(esc));
if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
esc,
(e[i]->d_type == DT_DIR) ? "/" : "",
esc,
suffix(e[i]->d_type)) < 0) {
ret = S_REQUEST_TIMEOUT;
goto cleanup;
}
} }
/* listing footer */ /* listing footer */
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) { if ((ret = dirl_footer(fd, &templates))) {
ret = S_REQUEST_TIMEOUT; goto cleanup;
goto cleanup; }
}
cleanup: cleanup:
while (dirlen--) { while (dirlen--) {

250
dirl.c Normal file
View file

@ -0,0 +1,250 @@
/* See LICENSE file for copyright and license details. */
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "config.h"
#include "dirl.h"
#include "http.h"
#include "util.h"
static char*
suffix(int t)
{
switch (t) {
case DT_FIFO:
return "|";
case DT_DIR:
return "/";
case DT_LNK:
return "@";
case DT_SOCK:
return "=";
}
return "";
}
static void
html_escape(const char* src, char* dst, size_t dst_siz)
{
const struct
{
char c;
char* s;
} escape[] = {
{ '&', "&amp;" }, { '<', "&lt;" }, { '>', "&gt;" },
{ '"', "&quot;" }, { '\'', "&#x27;" },
};
size_t i, j, k, esclen;
for (i = 0, j = 0; src[i] != '\0'; i++) {
for (k = 0; k < LEN(escape); k++) {
if (src[i] == escape[k].c) {
break;
}
}
if (k == LEN(escape)) {
/* no escape char at src[i] */
if (j == dst_siz - 1) {
/* silent truncation */
break;
} else {
dst[j++] = src[i];
}
} else {
/* escape char at src[i] */
esclen = strlen(escape[k].s);
if (j >= dst_siz - esclen) {
/* silent truncation */
break;
} else {
memcpy(&dst[j], escape[k].s, esclen);
j += esclen;
}
}
}
dst[j] = '\0';
}
/* Try to find templates up until root
*
* Iterates the directory hierarchy upwards. Returns the closest path containing
* one of the template files or NULL if none could be found.
*
* Note that we are chrooted.
*/
static char*
dirl_find_templ_dir(const char* start_path)
{
char* path_buf = calloc(sizeof(char), strlen(start_path)+1);
strcat(path_buf, start_path);
if (strlen(path_buf) > 1 && path_buf[strlen(path_buf) - 1] == '/') {
// don't read last dir twice, except at root
path_buf[strlen(path_buf) - 1] = '\0';
}
while (strlen(path_buf) != 0) {
DIR* cur = opendir(path_buf);
struct dirent* de;
errno = 0;
while ((de = readdir(cur))) {
if (de->d_type == DT_REG) {
if (!strcmp(DIRL_HEADER, de->d_name) || !strcmp(DIRL_ENTRY, de->d_name) ||
!strcmp(DIRL_FOOTER, de->d_name)) {
closedir(cur);
return path_buf;
}
}
}
if (strlen(path_buf) > 1) {
char* parent = strrchr(path_buf, '/');
(*parent) = '\0'; // strip tail from path_buf
if (strlen(path_buf) == 0) { // we stripped root, let it loop once more
path_buf[0] = '/';
path_buf[1] = '\0';
}
} else {
path_buf[0] = '\0'; // we checked root, now terminate loop
}
}
free(path_buf);
return NULL;
}
/* Helper function to fill template from base+name if file exists */
static void
dirl_fill_templ(char** templ, char* base, char* name, char* def)
{
if (!base || !name) {
*templ = def;
return;
}
char* path = calloc(sizeof(char), strlen(base) + strlen(name));
strcpy(path, base);
strcat(path, name);
char* file_buf = read_file(path);
free(path);
if (file_buf) {
*templ = file_buf;
} else {
*templ = def;
}
}
struct dirl_templ
dirl_read_templ(const char* path)
{
struct dirl_templ templ;
char* templ_dir = dirl_find_templ_dir(path);
dirl_fill_templ(&templ.header, templ_dir, DIRL_HEADER, DIRL_HEADER_DEFAULT);
dirl_fill_templ(&templ.entry, templ_dir, DIRL_ENTRY, DIRL_ENTRY_DEFAULT);
dirl_fill_templ(&templ.footer, templ_dir, DIRL_FOOTER, DIRL_FOOTER_DEFAULT);
free(templ_dir);
return templ;
}
enum status
dirl_header(int fd, const struct response* res, const struct dirl_templ* templ)
{
/* Replace placeholder */
char* nhead = calloc(sizeof(char), strlen(templ->header) + 1);
memcpy(nhead, templ->header, strlen(templ->header) + 1);
replace(&nhead, "{uri}", res->uri);
/* Write header */
write(fd, nhead, strlen(nhead));
free(nhead);
/* listing header */
return 0;
}
enum status
dirl_entry(int fd,
const struct dirent* entry,
const struct response* res,
const struct dirl_templ* templ)
{
struct stat stat_buf;
char* path_buf = calloc(sizeof(char), strlen(res->path)+strlen(entry->d_name));
strcat(path_buf, res->uri);
strcat(path_buf, entry->d_name);
lstat(path_buf, &stat_buf);
char* nentry = calloc(sizeof(char), strlen(templ->entry) + 1);
memcpy(nentry, templ->entry, strlen(templ->entry) + 1);
/* Replace placeholder */
char esc[PATH_MAX * 6];
html_escape(entry->d_name, esc, PATH_MAX * 6);
replace(&nentry, "{entry}", entry->d_name);
replace(&nentry, "{suffix}", suffix(entry->d_type));
char size_buf[1024];
if (entry->d_type == DT_REG) {
snprintf(size_buf, 1024, "%ld", stat_buf.st_size);
} else {
sprintf(size_buf, "-");
}
replace(&nentry, "{size}", size_buf);
char time_buf[1024];
struct tm tm;
gmtime_r(&stat_buf.st_mtim.tv_sec, &tm);
strftime(time_buf, 1024, "%F %H:%m", &tm);
replace(&nentry, "{modified}", time_buf);
/* Write entry */
write(fd, nentry, strlen(nentry));
free(nentry);
return 0;
}
enum status
dirl_footer(int fd, const struct dirl_templ* templ)
{
/* Replace placeholder */
char* nfoot = calloc(sizeof(char), strlen(templ->footer) + 1);
memcpy(nfoot, templ->footer, strlen(templ->footer) + 1);
/* Write footer */
write(fd, nfoot, strlen(nfoot));
free(nfoot);
return 0;
}
int
dirl_skip(const char* name)
{
return name[0] == '.' //
|| !strcmp(name, DIRL_HEADER) //
|| !strcmp(name, DIRL_ENTRY) //
|| !strcmp(name, DIRL_FOOTER) //
|| !strcmp(name, DIRL_STYLE); //
}

91
dirl.h Normal file
View file

@ -0,0 +1,91 @@
/* See LICENSE file for copyright and license details. */
#ifndef DIRL_H
#define DIRL_H
#include <dirent.h>
#include <sys/stat.h>
#include <sys/types.h>
#include "http.h"
#define DIRL_HEADER ".header.tpl"
#define DIRL_ENTRY ".entry.tpl"
#define DIRL_FOOTER ".footer.tpl"
#define DIRL_STYLE "style.css"
/* Default template definitions
*
* Used if no template files can be found
*/
#define DIRL_HEADER_DEFAULT \
"<!DOCTYPE HTML PUBLIC \" - // W3C//DTD HTML 3.2 Final//EN\">\n" \
"<html>\n" \
" <head>\n" \
" <link rel=\"stylesheet\" href=\"/" DIRL_STYLE "\">\n" \
" <title>Index of {uri}</title>\n" \
" </head>\n" \
" <body>\n" \
" <h1>Index of {uri}</h1>\n" \
" <p><a href=\"..\">&crarr; Parent Directory</a></p>\n" \
" <hr />\n" \
" <table>\n" \
" <tr><th>Name</th><th>Modified</th><th>Size</th></tr>"
#define DIRL_ENTRY_DEFAULT \
" <tr>\n" \
" <td><a href=\"{entry}\">{entry}{suffix}</a>\n" \
" <td>{modified}</td>\n" \
" <td>{size}</td>\n" \
" </tr>\n"
#define DIRL_FOOTER_DEFAULT \
" </table>\n" \
" <hr />\n" \
" <p>" \
"Served by" \
" <a href=\"http://tools.suckless.org/quark/\">quark</a>" \
" and" \
" <a href=" \
"\"https://git.friedl.net/playground/suckless-quark/src/branch/dirlist\">" \
"dirl" \
"</a>" \
"</p>\n" \
"</body>\n" \
"</html>"
struct dirl_templ
{
char* header;
char* entry;
char* footer;
};
struct dirl_templ
dirl_read_templ(const char* path);
/* Determine if an dirlist entry should be skipped
*
* Skips:
* - hidden files and directories
* - special directory entries (., ..)
* - header template: DIRL_HEADER
* - entry template: DIRL_ENTRY
* - footer template: DIRL_FOOTER
* - dirlist style: DRIL_STYLE
*/
int
dirl_skip(const char*);
/* Print header into the response */
enum status
dirl_header(int, const struct response*, const struct dirl_templ*);
/* Print entry into the response */
enum status
dirl_entry(int, const struct dirent*, const struct response*, const struct dirl_templ*);
/* Print footer into the response */
enum status
dirl_footer(int, const struct dirl_templ*);
#endif /* DIRL_H */

257
util.c
View file

@ -20,107 +20,194 @@ char *argv0;
static void static void
verr(const char *fmt, va_list ap) verr(const char *fmt, va_list ap)
{ {
if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) { if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) {
fprintf(stderr, "%s: ", argv0); fprintf(stderr, "%s: ", argv0);
} }
vfprintf(stderr, fmt, ap); vfprintf(stderr, fmt, ap);
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
fputc(' ', stderr); fputc(' ', stderr);
perror(NULL); perror(NULL);
} else { } else {
fputc('\n', stderr); fputc('\n', stderr);
} }
} }
void void
warn(const char *fmt, ...) warn(const char *fmt, ...)
{ {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
verr(fmt, ap); verr(fmt, ap);
va_end(ap); va_end(ap);
} }
void void
die(const char *fmt, ...) die(const char *fmt, ...)
{ {
va_list ap; va_list ap;
va_start(ap, fmt); va_start(ap, fmt);
verr(fmt, ap); verr(fmt, ap);
va_end(ap); va_end(ap);
exit(1); exit(1);
} }
void void
epledge(const char *promises, const char *execpromises) epledge(const char *promises, const char *execpromises)
{ {
(void)promises; (void)promises;
(void)execpromises; (void)execpromises;
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (pledge(promises, execpromises) == -1) { if (pledge(promises, execpromises) == -1) {
die("pledge:"); die("pledge:");
} }
#endif /* __OpenBSD__ */ #endif /* __OpenBSD__ */
} }
void void
eunveil(const char *path, const char *permissions) eunveil(const char *path, const char *permissions)
{ {
(void)path; (void)path;
(void)permissions; (void)permissions;
#ifdef __OpenBSD__ #ifdef __OpenBSD__
if (unveil(path, permissions) == -1) { if (unveil(path, permissions) == -1) {
die("unveil:"); die("unveil:");
} }
#endif /* __OpenBSD__ */ #endif /* __OpenBSD__ */
} }
int int
timestamp(char *buf, size_t len, time_t t) timestamp(char *buf, size_t len, time_t t)
{ {
struct tm tm; struct tm tm;
if (gmtime_r(&t, &tm) == NULL || if (gmtime_r(&t, &tm) == NULL ||
strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) { strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) {
return 1; return 1;
} }
return 0; return 0;
} }
int int
esnprintf(char *str, size_t size, const char *fmt, ...) esnprintf(char *str, size_t size, const char *fmt, ...)
{ {
va_list ap; va_list ap;
int ret; int ret;
va_start(ap, fmt); va_start(ap, fmt);
ret = vsnprintf(str, size, fmt, ap); ret = vsnprintf(str, size, fmt, ap);
va_end(ap); va_end(ap);
return (ret < 0 || (size_t)ret >= size); return (ret < 0 || (size_t)ret >= size);
} }
int int
prepend(char *str, size_t size, const char *prefix) prepend(char *str, size_t size, const char *prefix)
{ {
size_t len = strlen(str), prefixlen = strlen(prefix); size_t len = strlen(str), prefixlen = strlen(prefix);
if (len + prefixlen + 1 > size) { if (len + prefixlen + 1 > size) {
return 1; return 1;
} }
memmove(str + prefixlen, str, len + 1); memmove(str + prefixlen, str, len + 1);
memcpy(str, prefix, prefixlen); memcpy(str, prefix, prefixlen);
return 0; return 0;
}
void
replace(char **src, const char *old, const char* new) {
int old_len = strlen(old);
int new_len = strlen(new);
int src_len = strlen(*src);
/* Count needed replacements */
const char* tmp = *src;
int replc=0;
while ((tmp=strstr(tmp, old))) {
replc++;
tmp += old_len;
}
/* Allocate enough space for the new string */
size_t buf_size = src_len + replc * (new_len - old_len) + 1;
char* buf = calloc(sizeof(char), buf_size);
/* Now start replacing */
const char *srcidx = *src;
const char *srcidx_old = *src;
char *bufidx = buf;
while (replc--) {
srcidx_old = strstr(srcidx, old);
long repl_len = labs(srcidx_old - srcidx);
bufidx = strncpy(bufidx, srcidx, repl_len) + repl_len;
bufidx = strcpy(bufidx, new) + new_len;
srcidx = srcidx_old+old_len;
}
strncpy(bufidx, srcidx, strlen(srcidx)); // copy tail
free(*src);
*src = buf;
}
char*
read_file(const char* path){
FILE* tpl_fp;
if (!(tpl_fp = fopen(path, "r"))) {
return NULL;
}
/* Get size of template */
if (fseek(tpl_fp, 0L, SEEK_END) < 0) {
fclose(tpl_fp);
return NULL;
}
long tpl_size;
if ((tpl_size = ftell(tpl_fp)) < 0) {
fclose(tpl_fp);
return NULL;
}
rewind(tpl_fp);
/* Read template into tpl_buf */
char* tpl_buf = (char*)calloc(sizeof(char), tpl_size);
if (tpl_buf == NULL) {
fclose(tpl_fp);
free(tpl_buf);
return NULL;
}
if (fread(tpl_buf, 1, tpl_size, tpl_fp) < (size_t) tpl_size) {
if (feof(tpl_fp)) {
warn("Reached end of file %s prematurely", path);
} else if (ferror(tpl_fp)) {
warn("Error while reading file %s", path);
}
fclose(tpl_fp);
free(tpl_buf);
clearerr(tpl_fp);
return NULL;
}
return tpl_buf;
} }
#define INVALID 1 #define INVALID 1
@ -131,39 +218,39 @@ long long
strtonum(const char *numstr, long long minval, long long maxval, strtonum(const char *numstr, long long minval, long long maxval,
const char **errstrp) const char **errstrp)
{ {
long long ll = 0; long long ll = 0;
int error = 0; int error = 0;
char *ep; char *ep;
struct errval { struct errval {
const char *errstr; const char *errstr;
int err; int err;
} ev[4] = { } ev[4] = {
{ NULL, 0 }, { NULL, 0 },
{ "invalid", EINVAL }, { "invalid", EINVAL },
{ "too small", ERANGE }, { "too small", ERANGE },
{ "too large", ERANGE }, { "too large", ERANGE },
}; };
ev[0].err = errno; ev[0].err = errno;
errno = 0; errno = 0;
if (minval > maxval) { if (minval > maxval) {
error = INVALID; error = INVALID;
} else { } else {
ll = strtoll(numstr, &ep, 10); ll = strtoll(numstr, &ep, 10);
if (numstr == ep || *ep != '\0') if (numstr == ep || *ep != '\0')
error = INVALID; error = INVALID;
else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
error = TOOSMALL; error = TOOSMALL;
else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
error = TOOLARGE; error = TOOLARGE;
} }
if (errstrp != NULL) if (errstrp != NULL)
*errstrp = ev[error].errstr; *errstrp = ev[error].errstr;
errno = ev[error].err; errno = ev[error].err;
if (error) if (error)
ll = 0; ll = 0;
return ll; return ll;
} }
/* /*
@ -175,10 +262,10 @@ strtonum(const char *numstr, long long minval, long long maxval,
void * void *
reallocarray(void *optr, size_t nmemb, size_t size) reallocarray(void *optr, size_t nmemb, size_t size)
{ {
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
nmemb > 0 && SIZE_MAX / nmemb < size) { nmemb > 0 && SIZE_MAX / nmemb < size) {
errno = ENOMEM; errno = ENOMEM;
return NULL; return NULL;
} }
return realloc(optr, size * nmemb); return realloc(optr, size * nmemb);
} }

2
util.h
View file

@ -52,6 +52,8 @@ void eunveil(const char *, const char *);
int timestamp(char *, size_t, time_t); int timestamp(char *, size_t, time_t);
int esnprintf(char *, size_t, const char *, ...); int esnprintf(char *, size_t, const char *, ...);
int prepend(char *, size_t, const char *); int prepend(char *, size_t, const char *);
void replace(char **, const char *, const char *);
char *read_file(const char* path);
void *reallocarray(void *, size_t, size_t); void *reallocarray(void *, size_t, size_t);
long long strtonum(const char *, long long, long long, const char **); long long strtonum(const char *, long long, long long, const char **);