Compare commits
11 commits
Author | SHA1 | Date | |
---|---|---|---|
48db5eea1d | |||
66558aa615 | |||
d06c37fbd5 | |||
2a3e25dc6b | |||
e262166522 | |||
30ca105c66 | |||
eba491b4b5 | |||
bd51ff593d | |||
a22ac176ca | |||
5b6f8e5083 | |||
329dedc33b |
10 changed files with 776 additions and 199 deletions
137
.clang-format
Normal file
137
.clang-format
Normal 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
|
||||
...
|
||||
|
26
.drone.yml
26
.drone.yml
|
@ -7,18 +7,16 @@ steps:
|
|||
image: gcc
|
||||
commands:
|
||||
- make
|
||||
- mv quark quark-dirl
|
||||
|
||||
- name: run
|
||||
image: debian
|
||||
commands:
|
||||
- cp quark /usr/local/bin
|
||||
- useradd web && su web && cd
|
||||
- mkdir -p web && cd web && echo "hello from quark" > index.html
|
||||
- quark -p 9130 -h run -l -u web -g web
|
||||
detach: true
|
||||
|
||||
- name: test
|
||||
image: curlimages/curl
|
||||
commands:
|
||||
- sleep 15
|
||||
- curl http://run:9130
|
||||
- name: publish
|
||||
image: appleboy/drone-scp
|
||||
settings:
|
||||
host: friedl.net
|
||||
username:
|
||||
from_secret: deploy_user
|
||||
password:
|
||||
from_secret: deploy_password
|
||||
port: 22
|
||||
target: /var/services/dirlist/repo/bin/suckless/quark
|
||||
source: quark-dirl
|
||||
|
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.o
|
||||
.gdb_history
|
||||
config.h
|
||||
quark
|
5
Makefile
5
Makefile
|
@ -4,11 +4,12 @@
|
|||
|
||||
include config.mk
|
||||
|
||||
COMPONENTS = data http sock util
|
||||
COMPONENTS = data http sock util dirl
|
||||
|
||||
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
|
||||
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
|
||||
|
|
92
README.md
92
README.md
|
@ -5,20 +5,94 @@ found at https://git.suckless.org/quark.
|
|||
|
||||
Quark is a small http server.
|
||||
|
||||
# Issues
|
||||
# DIRL
|
||||
|
||||
## fork: Resource temporarily unavailable
|
||||
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()`.
|
||||
dirl is a quark extension for customized directory listings.
|
||||
|
||||
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="..">↵ 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
|
||||
If you are visiting this repository on GitHub, you are on a mirror of
|
||||
|
|
111
data.c
111
data.c
|
@ -10,6 +10,7 @@
|
|||
#include "http.h"
|
||||
#include "data.h"
|
||||
#include "util.h"
|
||||
#include "dirl.h"
|
||||
|
||||
static int
|
||||
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);
|
||||
}
|
||||
|
||||
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[] = {
|
||||
{ '&', "&" },
|
||||
{ '<', "<" },
|
||||
{ '>', ">" },
|
||||
{ '"', """ },
|
||||
{ '\'', "'" },
|
||||
};
|
||||
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
|
||||
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;
|
||||
size_t i;
|
||||
int dirlen;
|
||||
char esc[PATH_MAX /* > NAME_MAX */ * 6]; /* strlen("&...;") <= 6 */
|
||||
|
||||
/* read directory */
|
||||
if ((dirlen = scandir(res->path, &e, NULL, compareent)) < 0) {
|
||||
return S_FORBIDDEN;
|
||||
}
|
||||
|
||||
/* listing header (we use esc because sizeof(esc) >= PATH_MAX) */
|
||||
html_escape(res->uri, esc, MIN(PATH_MAX, sizeof(esc)));
|
||||
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;
|
||||
}
|
||||
/* read templates */
|
||||
struct dirl_templ templates = dirl_read_templ(res->uri);
|
||||
|
||||
/* listing */
|
||||
/* listing header */
|
||||
if ((ret = dirl_header(fd, res, &templates))) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* entries */
|
||||
for (i = 0; i < (size_t)dirlen; i++) {
|
||||
/* skip hidden files, "." and ".." */
|
||||
if (e[i]->d_name[0] == '.') {
|
||||
continue;
|
||||
}
|
||||
/* skip dirl special files */
|
||||
if(dirl_skip(e[i]->d_name)) {
|
||||
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 */
|
||||
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
|
||||
ret = S_REQUEST_TIMEOUT;
|
||||
goto cleanup;
|
||||
}
|
||||
/* listing footer */
|
||||
if ((ret = dirl_footer(fd, &templates))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
while (dirlen--) {
|
||||
|
|
250
dirl.c
Normal file
250
dirl.c
Normal 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[] = {
|
||||
{ '&', "&" }, { '<', "<" }, { '>', ">" },
|
||||
{ '"', """ }, { '\'', "'" },
|
||||
};
|
||||
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
91
dirl.h
Normal 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=\"..\">↵ 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
257
util.c
|
@ -20,107 +20,194 @@ char *argv0;
|
|||
static void
|
||||
verr(const char *fmt, va_list ap)
|
||||
{
|
||||
if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) {
|
||||
fprintf(stderr, "%s: ", argv0);
|
||||
}
|
||||
if (argv0 && strncmp(fmt, "usage", sizeof("usage") - 1)) {
|
||||
fprintf(stderr, "%s: ", argv0);
|
||||
}
|
||||
|
||||
vfprintf(stderr, fmt, ap);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
|
||||
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
|
||||
fputc(' ', stderr);
|
||||
perror(NULL);
|
||||
} else {
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
|
||||
fputc(' ', stderr);
|
||||
perror(NULL);
|
||||
} else {
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
warn(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
va_end(ap);
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
void
|
||||
die(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
va_end(ap);
|
||||
va_start(ap, fmt);
|
||||
verr(fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
exit(1);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
void
|
||||
epledge(const char *promises, const char *execpromises)
|
||||
{
|
||||
(void)promises;
|
||||
(void)execpromises;
|
||||
(void)promises;
|
||||
(void)execpromises;
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
if (pledge(promises, execpromises) == -1) {
|
||||
die("pledge:");
|
||||
}
|
||||
if (pledge(promises, execpromises) == -1) {
|
||||
die("pledge:");
|
||||
}
|
||||
#endif /* __OpenBSD__ */
|
||||
}
|
||||
|
||||
void
|
||||
eunveil(const char *path, const char *permissions)
|
||||
{
|
||||
(void)path;
|
||||
(void)permissions;
|
||||
(void)path;
|
||||
(void)permissions;
|
||||
|
||||
#ifdef __OpenBSD__
|
||||
if (unveil(path, permissions) == -1) {
|
||||
die("unveil:");
|
||||
}
|
||||
if (unveil(path, permissions) == -1) {
|
||||
die("unveil:");
|
||||
}
|
||||
#endif /* __OpenBSD__ */
|
||||
}
|
||||
|
||||
int
|
||||
timestamp(char *buf, size_t len, time_t t)
|
||||
{
|
||||
struct tm tm;
|
||||
struct tm tm;
|
||||
|
||||
if (gmtime_r(&t, &tm) == NULL ||
|
||||
strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) {
|
||||
return 1;
|
||||
}
|
||||
if (gmtime_r(&t, &tm) == NULL ||
|
||||
strftime(buf, len, "%a, %d %b %Y %T GMT", &tm) == 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
esnprintf(char *str, size_t size, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int ret;
|
||||
va_list ap;
|
||||
int ret;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(str, size, fmt, ap);
|
||||
va_end(ap);
|
||||
va_start(ap, fmt);
|
||||
ret = vsnprintf(str, size, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return (ret < 0 || (size_t)ret >= size);
|
||||
return (ret < 0 || (size_t)ret >= size);
|
||||
}
|
||||
|
||||
int
|
||||
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) {
|
||||
return 1;
|
||||
}
|
||||
if (len + prefixlen + 1 > size) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
memmove(str + prefixlen, str, len + 1);
|
||||
memcpy(str, prefix, prefixlen);
|
||||
memmove(str + prefixlen, str, len + 1);
|
||||
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
|
||||
|
@ -131,39 +218,39 @@ long long
|
|||
strtonum(const char *numstr, long long minval, long long maxval,
|
||||
const char **errstrp)
|
||||
{
|
||||
long long ll = 0;
|
||||
int error = 0;
|
||||
char *ep;
|
||||
struct errval {
|
||||
const char *errstr;
|
||||
int err;
|
||||
} ev[4] = {
|
||||
{ NULL, 0 },
|
||||
{ "invalid", EINVAL },
|
||||
{ "too small", ERANGE },
|
||||
{ "too large", ERANGE },
|
||||
};
|
||||
long long ll = 0;
|
||||
int error = 0;
|
||||
char *ep;
|
||||
struct errval {
|
||||
const char *errstr;
|
||||
int err;
|
||||
} ev[4] = {
|
||||
{ NULL, 0 },
|
||||
{ "invalid", EINVAL },
|
||||
{ "too small", ERANGE },
|
||||
{ "too large", ERANGE },
|
||||
};
|
||||
|
||||
ev[0].err = errno;
|
||||
errno = 0;
|
||||
if (minval > maxval) {
|
||||
error = INVALID;
|
||||
} else {
|
||||
ll = strtoll(numstr, &ep, 10);
|
||||
if (numstr == ep || *ep != '\0')
|
||||
error = INVALID;
|
||||
else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
|
||||
error = TOOSMALL;
|
||||
else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
|
||||
error = TOOLARGE;
|
||||
}
|
||||
if (errstrp != NULL)
|
||||
*errstrp = ev[error].errstr;
|
||||
errno = ev[error].err;
|
||||
if (error)
|
||||
ll = 0;
|
||||
ev[0].err = errno;
|
||||
errno = 0;
|
||||
if (minval > maxval) {
|
||||
error = INVALID;
|
||||
} else {
|
||||
ll = strtoll(numstr, &ep, 10);
|
||||
if (numstr == ep || *ep != '\0')
|
||||
error = INVALID;
|
||||
else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
|
||||
error = TOOSMALL;
|
||||
else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
|
||||
error = TOOLARGE;
|
||||
}
|
||||
if (errstrp != NULL)
|
||||
*errstrp = ev[error].errstr;
|
||||
errno = ev[error].err;
|
||||
if (error)
|
||||
ll = 0;
|
||||
|
||||
return ll;
|
||||
return ll;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -175,10 +262,10 @@ strtonum(const char *numstr, long long minval, long long maxval,
|
|||
void *
|
||||
reallocarray(void *optr, size_t nmemb, size_t size)
|
||||
{
|
||||
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
|
||||
nmemb > 0 && SIZE_MAX / nmemb < size) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
return realloc(optr, size * nmemb);
|
||||
if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) &&
|
||||
nmemb > 0 && SIZE_MAX / nmemb < size) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
return realloc(optr, size * nmemb);
|
||||
}
|
||||
|
|
2
util.h
2
util.h
|
@ -52,6 +52,8 @@ void eunveil(const char *, const char *);
|
|||
int timestamp(char *, size_t, time_t);
|
||||
int esnprintf(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);
|
||||
long long strtonum(const char *, long long, long long, const char **);
|
||||
|
|
Loading…
Reference in a new issue