Compare commits

..

2 Commits

Author SHA1 Message Date
Xavier Del Campo Romero fa97b1904c
WIP thumbnail stuff 2023-03-28 03:15:27 +02:00
Xavier Del Campo Romero 30d31e8c9d
Import mkdir_r 2023-03-28 02:35:59 +02:00
39 changed files with 3693 additions and 2523 deletions

2
.gitignore vendored
View File

@ -2,4 +2,4 @@ build/
slcl
*.o
*.d
./Makefile
/Makefile

9
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "libweb"]
path = libweb
url = https://gitea.privatedns.org/xavi/libweb
[submodule "dynstr"]
path = dynstr
url = https://gitea.privatedns.org/xavi92/dynstr
[submodule "mkdir_r"]
path = mkdir_r
url = https://gitea.privatedns.org/xavi92/mkdir_r

View File

@ -1,36 +1,39 @@
cmake_minimum_required(VERSION 3.13)
project(slcl LANGUAGES C VERSION 0.2.0)
project(slcl)
option(THUMBNAILS "Enables thumbnail generation for images." OFF)
add_executable(${PROJECT_NAME}
auth.c
base64.c
cftw.c
handler.c
hex.c
html.c
http.c
jwt.c
main.c
page.c
style.c
server.c
)
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/cmake)
find_package(web 0.3.0)
if(WEB_FOUND)
find_package(dynstr 0.1.0)
else()
message(STATUS "Using in-tree libweb")
add_subdirectory(libweb)
#dynstr is already provided by libweb.
if(NOT DEFINED THUMBNAIL_HEIGHT)
set(THUMBNAIL_HEIGHT 96)
endif()
if(THUMBNAILS)
find_package(ImageMagick REQUIRED)
target_sources(${PROJECT_NAME} PRIVATE thumbnail.c)
target_compile_definitions(${PROJECT_NAME}
PRIVATE THUMBNAIL_HEIGHT=${THUMBNAIL_HEIGHT})
elseif(THUMBNAIL_HEIGHT)
target_sources(${PROJECT_NAME} PRIVATE thumbnail_stub.c)
endif()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
add_subdirectory(dynstr)
add_subdirectory(mkdir_r)
find_package(cJSON 1.0 REQUIRED)
find_package(OpenSSL 2.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr cjson OpenSSL::SSL)
install(TARGETS ${PROJECT_NAME})
install(FILES usergen
TYPE BIN
PERMISSIONS
OWNER_READ OWNER_WRITE OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE)
add_subdirectory(doc)
find_package(OpenSSL 3.0 REQUIRED)
target_link_libraries(${PROJECT_NAME}
PRIVATE dynstr mkdir_r cjson OpenSSL::SSL ImageMagick)
#message(FATAL_ERROR "TODO")

View File

@ -1,28 +1,23 @@
# slcl, a simple and lightweight cloud
# slcl, a suckless cloud
`slcl` is a simple and lightweight implementation of a web file server,
commonly known as "cloud storage" or simply "cloud", written in C99 plus
POSIX.1-2008 extensions.
## Screenshots
![Screenshot of slcl's login page](doc/login.png)
![Screenshot of an example user directory](doc/user.png)
`slcl` is a simple and fast implementation of a web file server, commonly
known as "cloud storage" or simply "cloud", written in C99.
## Disclaimer
Intentionally, `slcl` does not share some of the philosophical views from the
[suckless project](https://suckless.org). However, it still strives towards
portability, minimalism, simplicity and efficiency.
While `slcl` might not share some of the philosophical views from the
[suckless project](https://suckless.org), it still strives towards minimalism,
simplicity and efficiency.
## Features
- Private access directory with file uploading, with configurable quota.
- Read-only public file sharing.
- Uses [`libweb`](https://gitea.privatedns.org/xavi/libweb), a tiny web framework.
- Its own, tiny HTTP/1.0 and 1.1-compatible server.
- A simple JSON file as the credentials database.
- No JavaScript.
- Optional, resizable thumbnails (made available by
[GraphicsMagick](http://www.graphicsmagick.org/)).
### TLS
@ -45,27 +40,29 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
## Requirements
- A POSIX environment.
- OpenSSL >= 2.0.
- OpenSSL >= 3.0.
- cJSON >= 1.7.15.
- [`dynstr`](https://gitea.privatedns.org/xavi/dynstr)
(provided as a `git` submodule by `libweb`).
- [`libweb`](https://gitea.privatedns.org/xavi/libweb)
- [`dynstr`](https://gitea.privatedns.org/xavi92/dynstr)
(provided as a `git` submodule).
- [`mkdir_r`](https://gitea.privatedns.org/xavi92/mkdir_r)
(provided as a `git` submodule).
- `xxd` (for [`usergen`](usergen) only).
- `jq` (for [`usergen`](usergen) only).
- CMake (optional).
- [GraphicsMagick](http://www.graphicsmagick.org/) >= 1.4 (optional).
### Ubuntu / Debian
#### Mandatory packages
```sh
sudo apt install build-essential libcjson-dev libssl-dev m4 jq
sudo apt install build-essential libcjson-dev libssl-dev
```
#### Optional packages
```sh
sudo apt install cmake
sudo apt install cmake xxd jq libgraphicsmagick1-dev
```
## How to use
@ -74,8 +71,8 @@ sudo apt install cmake
Two build environments are provided for `slcl` - feel free to choose any of
them:
- A [`configure`](configure) POSIX shell and mostly POSIX-compliant
[`Makefile`](Makefile).
- A [`configure`](configure) POSIX shell script that generates a mostly
POSIX-compliant `Makefile`.
- A [`CMakeLists.txt`](CMakeLists.txt).
`slcl` can be built using the standard build process:
@ -90,8 +87,9 @@ $ make
#### CMake
```sh
$ cmake -B build
$ cmake --build build/
$ mkdir build/
$ cmake ..
$ cmake --build .
```
### Setting up
@ -179,25 +177,6 @@ command line option. For example:
slcl -p 7822 ~/my-db/
```
`slcl` requires a temporary directory where files uploaded by users are
temporarily stored until moved to the user directory. By default, `slcl`
attempts to retrieve the path to the temporary directory by inspecting the
`TMPDIR` environment variable, and falls back to `/tmp` if undefined.
If a custom temporary directory is required, it can be defined via command
line option `-t`. For example:
```sh
slcl -t ~/my-tmp -p 7822 ~/my-db
```
**Note:** system-level temporary directories such as `/tmp` might reside
on a filesytem different than the one where the database resides. This
would force `slcl` to copy the contents from uploaded files from the
temporary directory to the database, which might be an expensive operation.
Therefore, in order to avoid expensive copies, define a custom temporary
directory that resides on the same filesystem.
## Why this project?
Previously, I had been recommended Nextcloud as an alternative to proprietary
@ -228,7 +207,7 @@ be even available e.g.: phones.
## License
```
slcl, a simple and lightweight cloud.
slcl, a suckless cloud.
Copyright (C) 2023 Xavier Del Campo Romero
This program is free software: you can redistribute it and/or modify

9
auth.c
View File

@ -1,7 +1,7 @@
#include "auth.h"
#include "hex.h"
#include "http.h"
#include "jwt.h"
#include <libweb/http.h>
#include <cjson/cJSON.h>
#include <dynstr.h>
#include <openssl/sha.h>
@ -9,7 +9,6 @@
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
@ -35,7 +34,7 @@ static char *dump_db(const char *const path)
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
goto end;
}
else if (sb.st_size > SIZE_MAX - 1)
else if (sb.st_size > SIZE_MAX)
{
fprintf(stderr, "%s: %s too big (%llu bytes, %zu max)\n",
__func__, path, (unsigned long long)sb.st_size, (size_t)SIZE_MAX);
@ -422,7 +421,7 @@ int auth_quota(const struct auth *const a, const char *const user,
*available = true;
*quota = strtoull(qs, &end, 10);
const unsigned long long mul = 1024ul * 1024ul;
const unsigned long long mul = 1024 * 1024;
if (errno || *end != '\0')
{
@ -436,7 +435,7 @@ int auth_quota(const struct auth *const a, const char *const user,
goto end;
}
*quota *= mul;
*quota *= 1024 * 1024;
break;
}
}

2
auth.h
View File

@ -1,7 +1,7 @@
#ifndef AUTH_H
#define AUTH_H
#include <libweb/http.h>
#include "http.h"
#include <stdbool.h>
struct auth *auth_alloc(const char *dir);

42
cftw.c
View File

@ -1,5 +1,3 @@
#define _POSIX_C_SOURCE 200809L
#include "cftw.h"
#include <dynstr.h>
#include <dirent.h>
@ -9,11 +7,12 @@
#include <stdint.h>
#include <string.h>
static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), bool *const done, void *const user)
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, void *), void *const user)
{
int ret = -1;
DIR *const d = opendir(dirpath);
struct dirent *de;
if (!d)
{
@ -21,31 +20,19 @@ static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
goto end;
}
for (;;)
while ((de = readdir(d)))
{
errno = 0;
struct dirent *const de = readdir(d);
if (errno)
{
fprintf(stderr, "%s: readdir(3): %s\n", __func__, strerror(errno));
goto end;
}
else if (!de)
break;
const char *const path = de->d_name;
if (!strcmp(path, ".") || !strcmp(path, ".."))
continue;
const char *const sep = dirpath[strlen(dirpath) - 1] == '/' ? "" : "/";
struct stat sb;
struct dynstr d;
dynstr_init(&d);
if (dynstr_append(&d, "%s%s%s", dirpath, sep, path))
if (dynstr_append(&d, "%s/%s", dirpath, path))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
return -1;
@ -57,21 +44,16 @@ static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, path, strerror(errno));
else if (S_ISDIR(sb.st_mode))
{
if ((ret = do_cftw(d.str, fn, done, user)))
;
else if ((ret = fn(d.str, &sb, done, user)))
;
}
ret = cftw(d.str, fn, user);
else if (S_ISREG(sb.st_mode))
ret = fn(d.str, &sb, done, user);
ret = fn(d.str, &sb, user);
else
fprintf(stderr, "%s: unexpected st_mode %ju\n",
__func__, (uintmax_t)sb.st_mode);
dynstr_free(&d);
if (ret || *done)
if (ret)
goto end;
}
@ -87,11 +69,3 @@ end:
return ret;
}
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), void *const user)
{
bool done = false;
return do_cftw(dirpath, fn, &done, user);
}

3
cftw.h
View File

@ -1,12 +1,11 @@
#ifndef CFTW_H
#define CFTW_H
#include <stdbool.h>
#include <sys/stat.h>
/* Thread-safe variant of ftw(3) and nftw(3) that allows passing an
* opaque pointer and removes some unneeded parameters. */
int cftw(const char *dirpath, int (*fn)(const char *fpath,
const struct stat *sb, bool *done, void *user), void *user);
const struct stat *sb, void *user), void *user);
#endif /* CFTW_H */

View File

@ -1,24 +0,0 @@
mark_as_advanced(WEB_LIBRARY WEB_INCLUDE_DIR)
find_library(WEB_LIBRARY NAMES libweb web)
find_path(WEB_INCLUDE_DIR
NAMES
handler.h
html.h
http.h
server.h
wildcard_cmp.h
PATH_SUFFIXES libweb include/libweb)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(web
DEFAULT_MSG WEB_LIBRARY WEB_INCLUDE_DIR)
if(WEB_FOUND)
if(NOT TARGET web)
add_library(web UNKNOWN IMPORTED)
set_target_properties(web PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${WEB_INCLUDE_DIR}"
IMPORTED_LOCATION "${WEB_LIBRARY}")
endif()
endif()

239
configure vendored
View File

@ -2,167 +2,130 @@
set -e
default_prefix=/usr/local
prefix=$default_prefix
default_CC='c99'
# FILE_OFFSET_BITS=64 is required for large file support on 32-bit platforms.
default_CFLAGS='-O1 -g -D_FILE_OFFSET_BITS=64 -Wall -MD'
default_LDFLAGS="-lcjson -lssl -lm -lcrypto"
DEFAULT_PREFIX=/usr/local
DEFAULT_THUMBNAIL_HEIGHT=96
CC=${CC:-$default_CC}
CFLAGS=${CFLAGS:-"$default_CFLAGS $default_NPCFLAGS"}
LDFLAGS=${LDFLAGS:-$default_LDFLAGS}
help()
usage()
{
cat <<-EOF
$0 [OPTION ...]
--prefix Set installation directory [$default_prefix]
Some influential environment variables:
CC C compiler [$default_CC]
CFLAGS C compiler flags [$default_CFLAGS]
LDFLAGS Link-time flags [$default_LDFLAGS]
EOF
printf "$0 [OPTION]...\n\n"
printf "Configuration:\n"
printf "%s\t\t\t" "--prefix=PREFIX"
printf "Sets installation prefix (default: $DEFAULT_PREFIX).\n"
printf "%s\t\t\t" "--thumbnails"
printf "Enables thumbnail generation for images.\n"
printf "%s\t" "--thumbnail-height=HEIGHT"
printf "Sets thumbnail height, in pixels "
printf "(default: $DEFAULT_THUMBNAIL_HEIGHT). "
printf "Requires --thumbnails.\n"
printf "%s\t\t\t" "-h|--help"
printf "Prints this help page.\n"
}
while true; do
split_arg=0
CC=${CC:-cc}
CFLAGS=${CFLAGS:-}
if printf "%s" "$1" | grep -e '=' > /dev/null
then
key="$(printf "%s" "$1" | cut -d '=' -f1)"
value="$(printf "%s" "$1" | cut -d '=' -f2)"
split_arg=1
else
key="$1"
value="$2"
fi
case "$key" in
--prefix ) prefix="$value"; shift; test $split_arg -eq 0 && shift ;;
-h | --help ) help; exit 0 ;;
* ) test "$1" != "" && help && exit 1 || break ;;
esac
for arg
do
case "$arg" in
--thumbnails)
THUMBNAILS=1
;;
--thumbnail-height=*)
THUMBNAIL_HEIGHT=${arg#*=}
;;
--prefix=*)
PREFIX=${arg#*=}
;;
-h | --help)
usage
exit 0
;;
*)
echo "Invalid argument $arg" >&2
usage >&2
exit 1
;;
esac
done
if pkg-config dynstr
then
in_tree_dynstr=0
CFLAGS="$CFLAGS $(pkg-config --cflags dynstr)"
LDFLAGS="$LDFLAGS $(pkg-config --libs dynstr)"
else
echo "Info: dynstr not found. Using in-tree copy" >&2
in_tree_dynstr=1
CFLAGS="$CFLAGS -Ilibweb/dynstr/include"
LDFLAGS="$LDFLAGS -Llibweb/dynstr -ldynstr"
fi
THUMBNAILS=${THUMBNAILS:-0}
THUMBNAIL_HEIGHT=${THUMBNAIL_HEIGHT:-$DEFAULT_THUMBNAIL_HEIGHT}
PREFIX=${PREFIX:-$DEFAULT_PREFIX}
if pkg-config libweb
then
in_tree_libweb=0
CFLAGS="$CFLAGS $(pkg-config --cflags libweb)"
LDFLAGS="$LDFLAGS $(pkg-config --libs libweb)"
else
echo "Info: libweb not found. Using in-tree copy" >&2
in_tree_libweb=1
CFLAGS="$CFLAGS -Ilibweb/include"
LDFLAGS="$LDFLAGS -Llibweb -lweb"
fi
cleanup()
{
rm -f $F
}
F=/tmp/Makefile.slcl
trap cleanup EXIT
cat <<EOF > $F
.POSIX:
CC = $CC
PREFIX = $prefix
DST = $prefix/bin
CFLAGS = $CFLAGS
LDFLAGS = $LDFLAGS
EOF
cat <<"EOF" >> $F
PROJECT = slcl
DEPS = $(OBJECTS:.o=.d)
OBJECTS = \
OBJECTS="\
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
style.o
server.o \
dynstr/dynstr.o"
LIBS="\
-lcjson \
-lssl \
-lm \
-lcrypto"
CFLAGS=${CFLAGS:-'$(O) $(CDEFS) -g -Wall -Idynstr/include -Imkdir_r -MD -MF -'}
if [ $THUMBNAILS -ne 0 ]
then
LIBS="$LIBS $(GraphicsMagick-config --libs)"
CFLAGS="$CFLAGS $(GraphicsMagick-config --cppflags |
tr '\n' ' ')"
CFLAGS="$CFLAGS -DTHUMBNAIL_HEIGHT=$THUMBNAIL_HEIGHT"
CFLAGS="$CFLAGS -Imkdir_r/private_include"
LDFLAGS="$LDFLAGS $(GraphicsMagick-config --ldflags)"
OBJECTS="$OBJECTS \
thumbnail.o \
mkdir_r/mkdir_r.o \
mkdir_r/posix.o"
else
OBJECTS="$OBJECTS thumbnail_stub.o mkdir_r_stub.o"
fi
cat > Makefile <<"EOF"
.POSIX:
.SUFFIXES: .c .o
PROJECT = slcl
O = -Og
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
DEPS = $(OBJECTS:.o=.d)
EOF
cat >> Makefile <<EOF
CFLAGS = $CFLAGS
LIBS = $LIBS
LDFLAGS = $LDFLAGS
OBJECTS = $OBJECTS
PREFIX = $PREFIX
EOF
cat >> Makefile <<"EOF"
all: $(PROJECT)
install: all usergen
mkdir -p $(DST)
cp slcl usergen $(DST)
chmod 0755 $(DST)/slcl
chmod 0755 $(DST)/usergen
+cd doc && $(MAKE) PREFIX=$(PREFIX) install
FORCE:
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
DYNSTR = libweb/dynstr/libdynstr.a
$(PROJECT): $(DYNSTR)
$(DYNSTR): FORCE
+cd libweb/dynstr && $(MAKE) CC=$(CC)
EOF
fi
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
LIBWEB = libweb/libweb.a
$(PROJECT): $(LIBWEB)
$(LIBWEB): FORCE
+cd libweb && $(MAKE) CC=$(CC)
EOF
fi
cat <<"EOF" >> $F
clean:
rm -f $(OBJECTS) $(DEPS)
EOF
if [ $in_tree_dynstr -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb/dynstr && $(MAKE) clean
EOF
fi
install: $(PROJECT)
mkdir -p $(PREFIX) $(PREFIX)/bin
cp $(PROJECT) $(PREFIX)/bin
chmod 0755 $(PREFIX)/bin/$(PROJECT)
+cd doc && $(MAKE) install PREFIX=$(PREFIX)
if [ $in_tree_libweb -ne 0 ]
then
cat <<"EOF" >> $F
+cd libweb && $(MAKE) clean
EOF
fi
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) -o $@
cat <<"EOF" >> $F
distclean: clean
rm Makefile
EOF
.c.o:
$(CC) $(CFLAGS) -c $< -o $@ > $(@:.o=.d)
cat <<"EOF" >> $F
-include $(DEPS)
EOF
mv $F Makefile

View File

@ -1,4 +0,0 @@
install(DIRECTORY man1
TYPE MAN
FILES_MATCHING PATTERN "*.1"
)

View File

@ -1,8 +1,14 @@
.POSIX:
PREFIX = /usr/local
DOCS1 = \
man1/slcl.1 \
man1/usergen.1
all:
DOCSPREFIX=$(PREFIX)/share/man
DOCS1PREFIX=$(DOCSPREFIX)/man1
install: all
+cd man1 && $(MAKE) PREFIX=$(PREFIX) install
install: $(DOCS1)
test $(PREFIX) || (echo Please define PREFIX. >&2 ; exit 1)
mkdir -p $(DOCS1PREFIX)
cp $(DOCS1) $(DOCS1PREFIX)
for d in $(DOCS1); do chmod 0644 $(DOCSPREFIX)/$$d; done

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,16 +0,0 @@
.POSIX:
PREFIX = /usr/local
DST = $(PREFIX)/share/man/man1
OBJECTS = \
$(DST)/slcl.1 \
$(DST)/usergen.1
all:
install: $(OBJECTS)
$(DST)/%.1: %.1
mkdir -p $(DST)
cp $< $@
chmod 0644 $@

View File

@ -1,7 +1,7 @@
.TH SLCL 1 slcl
.SH NAME
slcl \- a simple and lightweight cloud
slcl \- a suckless cloud
.SH SYNOPSIS
.B slcl
@ -114,13 +114,14 @@ storing one file each, as well as a publicly-shared file by
└── file2.txt
.EE
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH TODO
Allow deleting files and directories from the web interface.
.SH SEE ALSO
.B usergen(1)

View File

@ -81,13 +81,11 @@ should be updated to something similar to:
}
.EE
.SH COPYRIGHT
Copyright (C) 2023 Xavier Del Campo Romero.
.P
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
.SH LICENSE
See the LICENSE file for further reference.
.SH AUTHORS
Written by Xavier Del Campo Romero.
.SH SEE ALSO

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

1
dynstr Submodule

@ -0,0 +1 @@
Subproject commit 909d716a1e6d0c03b61b2a73ad3c2bd20fa55cf2

356
handler.c Normal file
View File

@ -0,0 +1,356 @@
#define _POSIX_C_SOURCE 200809L
#include "handler.h"
#include "http.h"
#include "server.h"
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct handler
{
struct handler_cfg cfg;
struct elem
{
char *url;
enum http_op op;
handler_fn f;
void *user;
} *elem;
struct server *server;
struct client
{
struct handler *h;
struct server_client *c;
struct http_ctx *http;
struct client *next;
} *clients;
size_t n_cfg;
};
static int on_read(void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_read(buf, n, c->c);
}
static int on_write(const void *const buf, const size_t n, void *const user)
{
struct client *const c = user;
return server_write(buf, n, c->c);
}
static int wildcard_cmp(const char *s, const char *p)
{
while (*p && *s)
{
const char *const wc = strchr(p, '*');
if (!wc)
return strcmp(s, p);
const size_t n = wc - p;
if (n)
{
const int r = strncmp(s, p, n);
if (r)
return r;
p += n;
s += n;
}
else if (*(wc + 1) == *s)
{
p = wc + 1;
s += n;
}
else if (*(wc + 1) == '*')
p++;
else
{
s++;
p += n;
}
}
while (*p)
if (*p++ != '*')
return -1;
return 0;
}
static int on_payload(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct client *const c = user;
struct handler *const h = c->h;
for (size_t i = 0; i < h->n_cfg; i++)
{
const struct elem *const e = &h->elem[i];
if (e->op == p->op && !wildcard_cmp(p->resource, e->url))
return e->f(p, r, e->user);
}
fprintf(stderr, "Not found: %s\n", p->resource);
*r = (const struct http_response)
{
.status = HTTP_STATUS_NOT_FOUND
};
return 0;
}
static int on_length(const unsigned long long len,
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
struct client *const cl = user;
struct handler *const h = cl->h;
if (h->cfg.length)
return h->cfg.length(len, c, r, h->cfg.user);
return 0;
}
static struct client *find_or_alloc_client(struct handler *const h,
struct server_client *const c)
{
for (struct client *cl = h->clients; cl; cl = cl->next)
{
if (cl->c == c)
return cl;
}
struct client *const ret = malloc(sizeof *ret);
if (!ret)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
const struct http_cfg cfg =
{
.read = on_read,
.write = on_write,
.payload = on_payload,
.length = on_length,
.user = ret,
.tmpdir = h->cfg.tmpdir
};
*ret = (const struct client)
{
.c = c,
.h = h,
.http = http_alloc(&cfg)
};
if (!ret->http)
{
fprintf(stderr, "%s: http_alloc failed\n", __func__);
return NULL;
}
if (!h->clients)
h->clients = ret;
else
{
for (struct client *c = h->clients; c; c = c->next)
if (!c->next)
{
c->next = ret;
break;
}
}
return ret;
}
static void client_free(struct client *const c)
{
if (c)
http_free(c->http);
free(c);
}
static int remove_client_from_list(struct handler *const h,
struct client *const c)
{
int ret = -1;
if (server_client_close(h->server, c->c))
{
fprintf(stderr, "%s: server_client_close failed\n",
__func__);
goto end;
}
for (struct client *cl = h->clients, *prev = NULL; cl;
prev = cl, cl = cl->next)
{
if (cl == c)
{
if (!prev)
h->clients = c->next;
else
prev->next = cl->next;
break;
}
}
ret = 0;
end:
client_free(c);
return ret;
}
int handler_listen(struct handler *const h, const short port)
{
if (!(h->server = server_init(port)))
{
fprintf(stderr, "%s: server_init failed\n", __func__);
return -1;
}
for (;;)
{
bool exit, io;
struct server_client *const c = server_select(h->server, &io, &exit);
if (exit)
{
printf("Exiting...\n");
break;
}
else if (!c)
{
fprintf(stderr, "%s: server_select failed\n", __func__);
return -1;
}
struct client *const cl = find_or_alloc_client(h, c);
if (!cl)
{
fprintf(stderr, "%s: find_or_alloc_client failed\n", __func__);
return -1;
}
else if (io)
{
bool write, close;
const int res = http_update(cl->http, &write, &close);
if (res || close)
{
if (res < 0)
{
fprintf(stderr, "%s: http_update failed\n", __func__);
return -1;
}
else if (remove_client_from_list(h, cl))
{
fprintf(stderr, "%s: remove_client_from_list failed\n",
__func__);
return -1;
}
}
else
server_client_write_pending(cl->c, write);
}
}
return 0;
}
static void free_clients(struct handler *const h)
{
for (struct client *c = h->clients; c;)
{
struct client *const next = c->next;
server_client_close(h->server, c->c);
client_free(c);
c = next;
}
}
void handler_free(struct handler *const h)
{
if (h)
{
for (size_t i = 0; i < h->n_cfg; i++)
free(h->elem[i].url);
free(h->elem);
free_clients(h);
server_close(h->server);
}
free(h);
}
struct handler *handler_alloc(const struct handler_cfg *const cfg)
{
struct handler *const h = malloc(sizeof *h);
if (!h)
{
fprintf(stderr, "%s: malloc(3) handler: %s\n",
__func__, strerror(errno));
return NULL;
}
*h = (const struct handler){.cfg = *cfg};
return h;
}
int handler_add(struct handler *const h, const char *url,
const enum http_op op, const handler_fn f, void *const user)
{
const size_t n = h->n_cfg + 1;
struct elem *const elem = realloc(h->elem, n * sizeof *h->elem);
if (!elem)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
struct elem *const e = &elem[h->n_cfg];
*e = (const struct elem)
{
.url = strdup(url),
.op = op,
.f = f,
.user = user
};
if (!e->url)
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
h->elem = elem;
h->n_cfg = n;
return 0;
}

24
handler.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef HANDLER_H
#define HANDLER_H
#include "http.h"
#include <stddef.h>
typedef int (*handler_fn)(const struct http_payload *p,
struct http_response *r, void *user);
struct handler_cfg
{
const char *tmpdir;
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
void *user;
};
struct handler *handler_alloc(const struct handler_cfg *cfg);
void handler_free(struct handler *h);
int handler_add(struct handler *h, const char *url, enum http_op op,
handler_fn f, void *user);
int handler_listen(struct handler *h, short port);
#endif /* HANDLER_H */

301
html.c Normal file
View File

@ -0,0 +1,301 @@
#define _POSIX_C_SOURCE 200809L
#include "html.h"
#include <dynstr.h>
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct html_node
{
struct html_attribute
{
char *attr, *value;
} *attrs;
char *element, *value;
size_t n;
struct html_node *child, *sibling;
};
static char *html_encode(const char *s)
{
struct dynstr d;
dynstr_init(&d);
if (!*s && dynstr_append(&d, ""))
{
fprintf(stderr, "%s: dynstr_append empty failed\n", __func__);
goto failure;
}
while (*s)
{
static const struct esc
{
char c;
const char *str;
} esc[] =
{
{.c = '<', .str = "&gt;"},
{.c = '>', .str = "&lt;"},
{.c = '&', .str = "&amp;"},
{.c = '\"', .str = "&quot;"},
{.c = '\'', .str = "&apos;"}
};
char buf[sizeof "a"] = {0};
const char *str = NULL;
for (size_t i = 0; i < sizeof esc / sizeof *esc; i++)
{
const struct esc *const e = &esc[i];
if (*s == e->c)
{
str = e->str;
break;
}
}
if (!str)
{
*buf = *s;
str = buf;
}
if (dynstr_append(&d, "%s", str))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto failure;
}
s++;
}
return d.str;
failure:
dynstr_free(&d);
return NULL;
}
int html_node_set_value(struct html_node *const n, const char *const val)
{
if (!(n->value = html_encode(val)))
{
fprintf(stderr, "%s: html_encode failed\n", __func__);
return -1;
}
return 0;
}
int html_node_set_value_unescaped(struct html_node *const n,
const char *const val)
{
if (!(n->value = strdup(val)))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
return -1;
}
return 0;
}
int html_node_add_attr(struct html_node *const n, const char *const attr,
const char *const val)
{
const size_t el = n->n + 1;
struct html_attribute *const attrs = realloc(n->attrs,
el * sizeof *n->attrs), *a = NULL;
if (!attrs)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return -1;
}
a = &attrs[n->n];
*a = (const struct html_attribute){0};
if (!(a->attr = strdup(attr))
|| (val && !(a->value = strdup(val))))
{
fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno));
free(a->attr);
free(a->value);
return -1;
}
n->attrs = attrs;
n->n = el;
return 0;
}
void html_node_add_sibling(struct html_node *const n,
struct html_node *const sibling)
{
for (struct html_node *c = n; c; c = c->sibling)
if (!c->sibling)
{
c->sibling = sibling;
break;
}
}
struct html_node *html_node_add_child(struct html_node *const n,
const char *const element)
{
struct html_node *const child = html_node_alloc(element);
if (!child)
return NULL;
else if (n->child)
html_node_add_sibling(n->child, child);
else
n->child = child;
return child;
}
int serialize_node(struct dynstr *const d, const struct html_node *const n,
const unsigned level)
{
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
dynstr_append_or_ret_nonzero(d, "<%s", n->element);
if (n->n)
dynstr_append_or_ret_nonzero(d, " ");
for (size_t i = 0; i < n->n; i++)
{
const struct html_attribute *const a = &n->attrs[i];
if (a->value)
dynstr_append_or_ret_nonzero(d, "%s=\"%s\"", a->attr, a->value);
else
dynstr_append_or_ret_nonzero(d, "%s", a->attr);
if (i + 1 < n->n)
dynstr_append_or_ret_nonzero(d, " ");
}
if (!n->value && !n->child)
dynstr_append_or_ret_nonzero(d, "/>");
else
{
dynstr_append_or_ret_nonzero(d, ">");
if (n->value)
dynstr_append_or_ret_nonzero(d, "%s", n->value);
if (n->child)
{
dynstr_append_or_ret_nonzero(d, "\n");
if (serialize_node(d, n->child, level + 1))
{
fprintf(stderr, "%s: serialize_node failed\n", __func__);
return -1;
}
for (unsigned i = 0; i < level; i++)
dynstr_append(d, "\t");
}
dynstr_append_or_ret_nonzero(d, "</%s>", n->element);
}
/* TODO: print siblings */
dynstr_append_or_ret_nonzero(d, "\n");
if (n->sibling)
return serialize_node(d, n->sibling, level);
return 0;
}
int html_serialize(const struct html_node *const n, struct dynstr *const d)
{
return serialize_node(d, n, 0);
}
static void html_attribute_free(struct html_attribute *const a)
{
if (a)
{
free(a->attr);
free(a->value);
}
}
void html_node_free(struct html_node *const n)
{
if (n)
{
struct html_node *s = n->sibling;
html_node_free(n->child);
while (s)
{
struct html_node *const next = s->sibling;
html_node_free(s->child);
free(s->element);
free(s->value);
for (size_t i = 0 ; i < s->n; i++)
html_attribute_free(&s->attrs[i]);
free(s->attrs);
free(s);
s = next;
}
free(n->element);
free(n->value);
for (size_t i = 0 ; i < n->n; i++)
html_attribute_free(&n->attrs[i]);
free(n->attrs);
}
free(n);
}
struct html_node *html_node_alloc(const char *const element)
{
struct html_node *const n = malloc(sizeof *n);
if (!n)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*n = (const struct html_node)
{
.element = strdup(element)
};
if (!n->element)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
return n;
failure:
html_node_free(n);
return NULL;
}

15
html.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef HTML_H
#define HTML_H
#include <dynstr.h>
struct html_node *html_node_alloc(const char *element);
void html_node_free(struct html_node *n);
int html_node_set_value(struct html_node *n, const char *val);
int html_node_set_value_unescaped(struct html_node *n, const char *val);
int html_node_add_attr(struct html_node *n, const char *attr, const char *val);
struct html_node *html_node_add_child(struct html_node *n, const char *elem);
void html_node_add_sibling(struct html_node *n, struct html_node *sibling);
int html_serialize(const struct html_node *n, struct dynstr *d);
#endif /* HTML_H */

1733
http.c Normal file

File diff suppressed because it is too large Load Diff

99
http.h Normal file
View File

@ -0,0 +1,99 @@
#ifndef HTTP_H
#define HTTP_H
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
struct http_payload
{
enum http_op
{
HTTP_OP_GET,
HTTP_OP_POST
} op;
const char *resource;
struct http_cookie
{
const char *field, *value;
} cookie;
union
{
struct http_post
{
bool expect_continue;
const void *data;
size_t n;
const char *dir;
const struct http_post_file
{
const char *tmpname, *filename;
} *files;
} post;
} u;
};
#define HTTP_STATUSES \
X(CONTINUE, "Continue", 100) \
X(OK, "OK", 200) \
X(SEE_OTHER, "See other", 303) \
X(BAD_REQUEST, "Bad Request", 400) \
X(UNAUTHORIZED, "Unauthorized", 401) \
X(FORBIDDEN, "Forbidden", 403) \
X(NOT_FOUND, "Not found", 404) \
X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \
X(INTERNAL_ERROR, "Internal Server Error", 500)
struct http_response
{
enum http_status
{
#define X(x, y, z) HTTP_STATUS_##x,
HTTP_STATUSES
#undef X
} status;
struct http_header
{
char *header, *value;
} *headers;
union
{
const void *ro;
void *rw;
} buf;
FILE *f;
unsigned long long n;
size_t n_headers;
void (*free)(void *);
};
struct http_cfg
{
int (*read)(void *buf , size_t n, void *user);
int (*write)(const void *buf, size_t n, void *user);
int (*payload)(const struct http_payload *p, struct http_response *r,
void *user);
int (*length)(unsigned long long len, const struct http_cookie *c,
struct http_response *r, void *user);
const char *tmpdir;
void *user;
};
struct http_ctx *http_alloc(const struct http_cfg *cfg);
void http_free(struct http_ctx *h);
/* Positive return value: user input error, negative: fatal error. */
int http_update(struct http_ctx *h, bool *write, bool *close);
int http_response_add_header(struct http_response *r, const char *header,
const char *value);
char *http_cookie_create(const char *key, const char *value);
char *http_encode_url(const char *url);
char *http_decode_url(const char *url);
#endif /* HTTP_H */

1
libweb

@ -1 +0,0 @@
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f

1309
main.c

File diff suppressed because it is too large Load Diff

1
mkdir_r Submodule

@ -0,0 +1 @@
Subproject commit 822ff99e6f6a5c8ad7309a535d384d354b2d326e

1127
page.c

File diff suppressed because it is too large Load Diff

37
page.h
View File

@ -1,9 +1,7 @@
#ifndef PAGE_H
#define PAGE_H
#include <libweb/http.h>
#include <stdbool.h>
#include <stddef.h>
#include "http.h"
struct page_quota
{
@ -12,43 +10,20 @@ struct page_quota
struct page_resource
{
struct http_response *r;
const char *dir, *root, *res;
const struct page_quota *q;
const struct http_arg *args;
size_t n_args;
};
struct page_search
{
struct page_search_result
{
char *name;
} *results;
const char *root;
size_t n;
bool limit_exceeded;
};
struct page_rm
{
const char *dir, **items;
size_t n;
const char *root, *user_root, *username, *res;
};
int page_login(struct http_response *r);
int page_style(struct http_response *r, const char *path);
int page_style(struct http_response *r);
int page_failed_login(struct http_response *r);
int page_forbidden(struct http_response *r);
int page_bad_request(struct http_response *r);
int page_resource(const struct page_resource *r);
int page_head_resource(struct http_response *r, const char *res);
int page_resource(struct http_response *r, const struct page_resource *res,
const struct page_quota *q);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
int page_search(struct http_response *r, const struct page_search *s);
int page_rm(struct http_response *r, const struct page_rm *rm);
int page_thumbnail(struct http_response *r, const char *res);
#endif /* PAGE_H */

318
server.c Normal file
View File

@ -0,0 +1,318 @@
#define _POSIX_C_SOURCE 200809L
#include "server.h"
#include <fcntl.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct server
{
int fd;
struct server_client
{
int fd;
bool write;
} *c;
size_t n;
};
int server_close(struct server *const s)
{
int ret = 0;
if (!s)
return 0;
else if (s->fd >= 0)
ret = close(s->fd);
free(s);
return ret;
}
int server_client_close(struct server *const s, struct server_client *const c)
{
int ret = 0;
for (size_t i = 0; i < s->n; i++)
{
struct server_client *const ref = &s->c[i];
if (c == ref)
{
const size_t n = s->n - 1;
if ((ret = close(c->fd)))
{
fprintf(stderr, "%s: close(2): %s\n",
__func__, strerror(errno));
return -1;
}
else if (n)
{
memcpy(ref, ref + 1, (s->n - i - 1) * sizeof *ref);
struct server_client *const c = realloc(s->c, n * sizeof *s->c);
if (!c)
{
fprintf(stderr, "%s: realloc(3): %s\n",
__func__, strerror(errno));
return -1;
}
s->c = c;
}
else
{
free(s->c);
s->c = NULL;
}
s->n = n;
break;
}
}
return ret;
}
int server_read(void *const buf, const size_t n, struct server_client *const c)
{
const ssize_t r = read(c->fd, buf, n);
if (r < 0)
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
return r;
}
int server_write(const void *const buf, const size_t n,
struct server_client *const c)
{
const ssize_t w = write(c->fd, buf, n);
if (w < 0)
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
return w;
}
static struct server_client *alloc_client(struct server *const s)
{
struct sockaddr_in addr;
socklen_t sz = sizeof addr;
const int fd = accept(s->fd, (struct sockaddr *)&addr, &sz);
if (fd < 0)
{
fprintf(stderr, "%s: accept(2): %s\n",
__func__, strerror(errno));
return NULL;
}
const int flags = fcntl(fd, F_GETFL);
if (flags < 0)
{
fprintf(stderr, "%s: fcntl(2) F_GETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
else if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
{
fprintf(stderr, "%s: fcntl(2) F_SETFL: %s\n",
__func__, strerror(errno));
return NULL;
}
const size_t n = s->n + 1;
struct server_client *const clients = realloc(s->c, n * sizeof *s->c);
if (!clients)
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
return NULL;
}
struct server_client *const c = &clients[s->n];
*c = (const struct server_client)
{
.fd = fd
};
s->c = clients;
s->n = n;
return c;
}
void server_client_write_pending(struct server_client *const c,
const bool write)
{
c->write = write;
}
static volatile sig_atomic_t do_exit;
static void handle_signal(const int signum)
{
do_exit = 1;
}
struct server_client *server_select(struct server *const s, bool *const io,
bool *const exit)
{
int nfds = -1;
fd_set rfds, wfds;
*io = *exit = false;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
for (size_t i = 0; i < s->n; i++)
{
const struct server_client *const c = &s->c[i];
const int fd = c->fd;
FD_SET(fd, &rfds);
if (c->write)
FD_SET(fd, &wfds);
if (fd > nfds)
nfds = fd;
}
FD_SET(s->fd, &rfds);
if (s->fd > nfds)
nfds = s->fd;
const int res = select(nfds + 1, &rfds, &wfds, NULL, NULL);
if (res < 0)
{
if (do_exit)
*exit = true;
else
fprintf(stderr, "%s: select(2): %s\n", __func__, strerror(errno));
return NULL;
}
else if (FD_ISSET(s->fd, &rfds))
return alloc_client(s);
for (size_t i = 0; i < s->n; i++)
{
struct server_client *const c = &s->c[i];
if (FD_ISSET(c->fd, &rfds) || FD_ISSET(c->fd, &wfds))
{
*io = true;
return c;
}
}
fprintf(stderr, "%s: unlisted fd\n", __func__);
return NULL;
}
static int init_signals(void)
{
struct sigaction sa =
{
.sa_handler = handle_signal,
.sa_flags = SA_RESTART
};
sigemptyset(&sa.sa_mask);
if (sigaction(SIGINT, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n",
__func__, strerror(errno));
return -1;
}
else if (sigaction(SIGTERM, &sa, NULL))
{
fprintf(stderr, "%s: sigaction(2) SIGINT: %s\n",
__func__, strerror(errno));
return -1;
}
return 0;
}
struct server *server_init(const unsigned short port)
{
struct server *const s = malloc(sizeof *s);
if (!s)
{
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
goto failure;
}
*s = (const struct server)
{
.fd = socket(AF_INET, SOCK_STREAM, 0)
};
if (s->fd < 0)
{
fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (init_signals())
{
fprintf(stderr, "%s: init_signals failed\n", __func__);
goto failure;
}
const struct sockaddr_in addr =
{
.sin_family = AF_INET,
.sin_port = htons(port)
};
enum {QUEUE_LEN = 10};
if (bind(s->fd, (const struct sockaddr *)&addr, sizeof addr))
{
fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (listen(s->fd, QUEUE_LEN))
{
fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno));
goto failure;
}
struct sockaddr_in in;
socklen_t sz = sizeof in;
if (getsockname(s->fd, (struct sockaddr *)&in, &sz))
{
fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno));
goto failure;
}
printf("Listening on port %hu\n", ntohs(in.sin_port));
return s;
failure:
server_close(s);
return NULL;
}

15
server.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef SERVER_H
#define SERVER_H
#include <stdbool.h>
#include <stddef.h>
struct server *server_init(unsigned short port);
struct server_client *server_select(struct server *s, bool *io, bool *exit);
int server_read(void *buf, size_t n, struct server_client *c);
int server_write(const void *buf, size_t n, struct server_client *c);
int server_close(struct server *s);
int server_client_close(struct server *s, struct server_client *c);
void server_client_write_pending(struct server_client *c, bool write);
#endif /* SERVER_H */

55
style.c
View File

@ -1,55 +0,0 @@
#include "style.h"
#include <stddef.h>
const char style_default[] =
"body\n"
"{\n"
" font-family: 'Courier New', Courier, monospace;\n"
"}\n"
"td\n"
"{\n"
" font-size: 14px;\n"
"}\n"
"a\n"
"{\n"
" text-decoration: none;\n"
"}\n"
".userform\n"
"{\n"
" padding: 4px;\n"
"}\n"
".loginform\n"
"{\n"
" display: grid;\n"
"}\n"
"form, label, table\n"
"{\n"
" margin: auto;\n"
"}\n"
"div\n"
"{\n"
" align-items: center;\n"
" display: grid;\n"
"}\n"
"input, .abutton\n"
"{\n"
" margin: auto;\n"
" border: 1px solid;\n"
" border-radius: 8px;\n"
"}\n"
"header, footer\n"
"{\n"
" display: flex;\n"
" justify-content: center;\n"
" text-decoration: auto;\n"
"}\n"
"table\n"
"{\n"
" max-width: 50%;\n"
"}\n"
"tr:nth-child(even)\n"
"{\n"
" background-color: lightgray;\n"
"}\n";
const size_t style_default_len = sizeof style_default - 1;

View File

@ -1,9 +0,0 @@
#ifndef STYLE_H
#define STYLE_H
#include <stddef.h>
extern const char style_default[];
extern const size_t style_default_len;
#endif

143
thumbnail.c Normal file
View File

@ -0,0 +1,143 @@
#include "thumbnail.h"
#include <magick/api.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
/* http://www.graphicsmagick.org/api/api.html */
int thumbnail_create(const char *const src, const char *const dst)
{
int ret = -1;
const size_t slen = strlen(src), dlen = strlen(dst);
ImageInfo *info = NULL;
Image *i = NULL, *t = NULL;
ExceptionInfo exc;
InitializeMagick(NULL);
GetExceptionInfo(&exc);
if (slen >= sizeof i->filename)
{
fprintf(stderr, "%s: src maximum length exceeded (%zu, max %zu)\n",
__func__, slen, sizeof i->filename - 1);
goto end;
}
else if (dlen >= sizeof t->filename)
{
fprintf(stderr, "%s: dst maximum length exceeded (%zu, max %zu)\n",
__func__, slen, sizeof t->filename - 1);
goto end;
}
else if (!(info = CloneImageInfo(NULL)))
{
fprintf(stderr, "%s: CloneImageInfo failed\n", __func__);
goto end;
}
strcpy(info->filename, src);
info->adjoin = MagickTrue;
if (!(i = ReadImage(info, &exc)))
{
if (exc.severity == MissingDelegateError)
/* Non-image file format. */
ret = 1;
else
fprintf(stderr, "%s: ReadImage failed: "
"reason: %s, description: %s\n",
__func__, exc.reason, exc.description);
goto end;
}
const unsigned long y = i->rows > THUMBNAIL_HEIGHT ?
THUMBNAIL_HEIGHT : i->rows,
x = y * i->columns / i->rows;
if (!(t = ResizeImage(i, x, y, PointFilter, 1, &exc)))
{
fprintf(stderr, "%s: ResizeImage failed, "
"reason: %s, description: %s\n",
__func__, exc.reason, exc.description);
goto end;
}
else if (WriteImages(info, t, dst, &exc) != MagickPass)
{
fprintf(stderr, "%s: WriteImages failed, "
"reason: %s, description: %s\n",
__func__, exc.reason, exc.description);
goto end;
}
ret = 0;
end:
DestroyImageInfo(info);
DestroyImage(i);
DestroyImage(t);
DestroyExceptionInfo(&exc);
DestroyMagick();
return ret;
}
bool thumbnail_configured(void)
{
return true;
}
int thumbnail_dim(const char *const path, struct thumbnail_dim *const d)
{
int ret = -1;
const size_t slen = strlen(path);
ImageInfo *info = NULL;
Image *i = NULL;
ExceptionInfo exc;
InitializeMagick(NULL);
GetExceptionInfo(&exc);
if (slen >= sizeof i->filename)
{
fprintf(stderr, "%s: src maximum length exceeded (%zu, max %zu)\n",
__func__, slen, sizeof i->filename - 1);
goto end;
}
else if (!(info = CloneImageInfo(NULL)))
{
fprintf(stderr, "%s: CloneImageInfo failed\n", __func__);
goto end;
}
strcpy(info->filename, path);
info->adjoin = MagickTrue;
if (!(i = ReadImage(info, &exc)))
{
if (exc.severity == MissingDelegateError)
/* Non-image file format. */
ret = 1;
else
fprintf(stderr, "%s: ReadImage failed: "
"reason: %s, description: %s\n",
__func__, exc.reason, exc.description);
goto end;
}
*d = (const struct thumbnail_dim)
{
.w = i->columns,
.h = i->rows
};
ret = 0;
end:
DestroyImageInfo(info);
DestroyImage(i);
DestroyExceptionInfo(&exc);
DestroyMagick();
return ret;
}

15
thumbnail.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef THUMBNAIL_H
#define THUMBNAIL_H
#include <stdbool.h>
struct thumbnail_dim
{
unsigned long w, h;
};
bool thumbnail_configured(void);
int thumbnail_create(const char *src, const char *dst);
int thumbnail_dim(const char *path, struct thumbnail_dim *d);
#endif /* THUMBNAIL_H */

17
thumbnail_stub.c Normal file
View File

@ -0,0 +1,17 @@
#include "thumbnail.h"
#include <stdbool.h>
int thumbnail_create(const char *const src, const char *const dst)
{
return -1;
}
bool thumbnail_configured(void)
{
return false;
}
int thumbnail_dim(const char *const path, struct thumbnail_dim *const d)
{
return -1;
}

52
usergen
View File

@ -7,23 +7,6 @@ usage()
echo "$0 <dir>"
}
to_hex()
{
od -An -t x1 | tr -d ' ' | tr -d '\n'
}
to_bin()
{
sed -e 's,\([0-9a-f]\{2\}\),\\\\\\x\1,g' | xargs printf
}
mktemp_posix()
{
m4 <<EOF
mkstemp(${TMPDIR:-/tmp}/tmp.XXXXXX)
EOF
}
if [ $# != 1 ]; then
usage >&2
exit 1
@ -32,13 +15,7 @@ fi
DIR=$1
echo Username: >&2
IFS= read -r USER
if printf '%s' "$USER" | grep -qe '[[:space:]]'
then
echo Username cannot contain whitespaces >&2
exit 1
fi
read -r USER
DB="$DIR/db.json"
@ -48,34 +25,26 @@ then
exit 1
fi
TTYCFG=$(stty -g)
trap "stty $TTYCFG" INT QUIT TERM EXIT
stty -echo
echo Password: >&2
IFS= read -r PWD
stty echo
# Force newline
echo
read -r PWD
echo "Quota, in MiB (leave empty for unlimited quota):" >&2
read -r QUOTA
QUOTA="$(printf '%d' "$QUOTA")"
PWD=$(printf '%s' "$PWD" | to_hex)
SALT=$(openssl rand -hex 32)
KEY=$(openssl rand -hex 32)
PWD=$(printf '%s%s' "$SALT" "$PWD")
PWD=$(printf '%s' $PWD | xxd -p | tr -d '\n')
SALT=$(openssl rand 32 | xxd -p | tr -d '\n')
KEY=$(openssl rand 32 | xxd -p | tr -d '\n')
PWD=$(printf '%s%s' $SALT $PWD)
ROUNDS=1000
for i in $(seq $ROUNDS)
do
printf "\r%d/$ROUNDS" $i >&2
PWD=$(printf '%s' "$PWD" | to_bin | openssl sha256 -r | cut -d' ' -f1)
PWD=$(printf '%s' $PWD | xxd -p -r | sha256sum | cut -d' ' -f1)
done
echo >&2
TMP=$(mktemp_posix)
TMP=$(mktemp)
cleanup()
{
@ -87,11 +56,12 @@ trap cleanup EXIT
jq ".users += [
{
\"name\": \"$USER\",
\"password\": \""$PWD"\",
\"password\": \"$PWD\",
\"salt\": \"$SALT\",
\"key\": \"$KEY\",
\"quota\": \"$QUOTA\"
}]" "$DB" > $TMP
mkdir -p "$DIR/user/$USER"
mv $TMP "$DB"
mkdir "$DIR/user/$USER"
test -d "$DIR/thumbnails" && mkdir -p "$DIR/thumbnails/$USER"

View File

@ -1,73 +0,0 @@
#include "wildcard_cmp.h"
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>
int wildcard_cmp(const char *s, const char *p, const bool casecmp)
{
const char *last = NULL;
int (*const cmp)(const char *, const char *) =
casecmp ? strcmp : strcasecmp;
int (*const ncmp)(const char *, const char *, size_t) =
casecmp ? strncmp : strncasecmp;
while (*p && *s)
{
const char *const wc = strchr(p, '*');
if (!wc)
{
const int r = cmp(s, p);
if (r && last)
{
p = last;
s += strlen(p);
continue;
}
else
return r;
}
const size_t auxn = wc - p, rem = strlen(s),
n = auxn > rem ? rem : auxn;
if (n)
{
const int r = ncmp(s, p, n);
if (r)
{
if (last)
p = last;
else
return r;
}
else
p += n;
s += n;
}
else
{
const char next = *(wc + 1), wca[2] = {next}, sa[sizeof wca] = {*s};
if (!cmp(wca, sa))
{
last = p;
p = wc + 1;
}
else if (next == '*')
p++;
else
s++;
}
}
while (*p)
if (*p++ != '*')
return -1;
return 0;
}

View File

@ -1,8 +0,0 @@
#ifndef WILDCARD_CMP_H
#define WILDCARD_CMP_H
#include <stdbool.h>
int wildcard_cmp(const char *s, const char *p, bool casecmp);
#endif /* WILDCARD_CMP_H */