Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Xavier Del Campo Romero | 2648076370 |
|
@ -2,4 +2,3 @@ build/
|
|||
slcl
|
||||
*.o
|
||||
*.d
|
||||
./Makefile
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[submodule "libweb"]
|
||||
path = libweb
|
||||
url = https://gitea.privatedns.org/xavi/libweb
|
||||
[submodule "dynstr"]
|
||||
path = dynstr
|
||||
url = https://gitea.privatedns.org/xavi92/dynstr
|
||||
|
|
|
@ -1,36 +1,22 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(slcl LANGUAGES C VERSION 0.2.0)
|
||||
project(slcl)
|
||||
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
|
||||
wildcard_cmp.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.
|
||||
endif()
|
||||
|
||||
add_subdirectory(dynstr)
|
||||
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 cjson OpenSSL::SSL)
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
.POSIX:
|
||||
|
||||
PROJECT = slcl
|
||||
O = -Og
|
||||
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
|
||||
CFLAGS = $(O) $(CDEFS) -g -Wall -Idynstr/include -MD -MF $(@:.o=.d)
|
||||
LIBS = -lcjson -lssl -lm -lcrypto
|
||||
LDFLAGS = $(LIBS)
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
DYNSTR = dynstr/libdynstr.a
|
||||
DYNSTR_FLAGS = -Ldynstr -ldynstr
|
||||
OBJECTS = \
|
||||
auth.o \
|
||||
base64.o \
|
||||
cftw.o \
|
||||
handler.o \
|
||||
hex.o \
|
||||
html.o \
|
||||
http.o \
|
||||
jwt.o \
|
||||
main.o \
|
||||
page.o \
|
||||
server.o \
|
||||
wildcard_cmp.o \
|
||||
|
||||
all: $(PROJECT)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJECTS) $(DEPS)
|
||||
|
||||
$(PROJECT): $(OBJECTS) $(DYNSTR)
|
||||
$(CC) $(OBJECTS) $(LDFLAGS) $(DYNSTR_FLAGS) -o $@
|
||||
|
||||
$(DYNSTR):
|
||||
+cd dynstr && $(MAKE)
|
||||
|
||||
-include $(DEPS)
|
62
README.md
62
README.md
|
@ -1,26 +1,19 @@
|
|||
# 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.1-compatible server.
|
||||
- A simple JSON file as the credentials database.
|
||||
- No JavaScript.
|
||||
|
||||
|
@ -45,12 +38,11 @@ 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).
|
||||
- `xxd` (for [`usergen`](usergen) only).
|
||||
- `jq` (for [`usergen`](usergen) only).
|
||||
- CMake (optional).
|
||||
|
||||
|
@ -59,13 +51,13 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
|
|||
#### 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
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
@ -74,8 +66,7 @@ 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 mostly POSIX-compliant [`Makefile`](Makefile).
|
||||
- A [`CMakeLists.txt`](CMakeLists.txt).
|
||||
|
||||
`slcl` can be built using the standard build process:
|
||||
|
@ -83,15 +74,15 @@ them:
|
|||
#### Make
|
||||
|
||||
```sh
|
||||
$ ./configure
|
||||
$ make
|
||||
```
|
||||
|
||||
#### CMake
|
||||
|
||||
```sh
|
||||
$ cmake -B build
|
||||
$ cmake --build build/
|
||||
$ mkdir build/
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
### Setting up
|
||||
|
@ -179,25 +170,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 +200,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
|
||||
|
|
7
auth.c
7
auth.c
|
@ -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>
|
||||
|
@ -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
2
auth.h
|
@ -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);
|
||||
|
|
37
cftw.c
37
cftw.c
|
@ -9,11 +9,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,19 +22,8 @@ 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, ".."))
|
||||
|
@ -57,21 +47,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 +72,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
3
cftw.h
|
@ -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 */
|
||||
|
|
|
@ -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()
|
|
@ -1,168 +0,0 @@
|
|||
#! /bin/sh
|
||||
|
||||
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"
|
||||
|
||||
CC=${CC:-$default_CC}
|
||||
CFLAGS=${CFLAGS:-"$default_CFLAGS $default_NPCFLAGS"}
|
||||
LDFLAGS=${LDFLAGS:-$default_LDFLAGS}
|
||||
|
||||
help()
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
while true; do
|
||||
split_arg=0
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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 = \
|
||||
auth.o \
|
||||
base64.o \
|
||||
cftw.o \
|
||||
hex.o \
|
||||
jwt.o \
|
||||
main.o \
|
||||
page.o \
|
||||
style.o
|
||||
|
||||
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
|
||||
|
||||
if [ $in_tree_libweb -ne 0 ]
|
||||
then
|
||||
cat <<"EOF" >> $F
|
||||
+cd libweb && $(MAKE) clean
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat <<"EOF" >> $F
|
||||
distclean: clean
|
||||
rm Makefile
|
||||
EOF
|
||||
|
||||
cat <<"EOF" >> $F
|
||||
-include $(DEPS)
|
||||
EOF
|
||||
|
||||
mv $F Makefile
|
|
@ -1,4 +0,0 @@
|
|||
install(DIRECTORY man1
|
||||
TYPE MAN
|
||||
FILES_MATCHING PATTERN "*.1"
|
||||
)
|
|
@ -1,8 +0,0 @@
|
|||
.POSIX:
|
||||
|
||||
PREFIX = /usr/local
|
||||
|
||||
all:
|
||||
|
||||
install: all
|
||||
+cd man1 && $(MAKE) PREFIX=$(PREFIX) install
|
BIN
doc/login.png
BIN
doc/login.png
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
|
@ -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 $@
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
BIN
doc/user.png
BIN
doc/user.png
Binary file not shown.
Before Width: | Height: | Size: 54 KiB |
|
@ -0,0 +1 @@
|
|||
Subproject commit 5c13c9b8385cb2742f342d4d2f64d4e2d21108ba
|
|
@ -0,0 +1,315 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "handler.h"
|
||||
#include "http.h"
|
||||
#include "server.h"
|
||||
#include "wildcard_cmp.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 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, true))
|
||||
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_poll(h->server, &io, &exit);
|
||||
|
||||
if (exit)
|
||||
{
|
||||
printf("Exiting...\n");
|
||||
break;
|
||||
}
|
||||
else if (!c)
|
||||
{
|
||||
fprintf(stderr, "%s: server_poll 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;
|
||||
}
|
|
@ -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 */
|
|
@ -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 = ">"},
|
||||
{.c = '>', .str = "<"},
|
||||
{.c = '&', .str = "&"},
|
||||
{.c = '\"', .str = """},
|
||||
{.c = '\'', .str = "'"}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 */
|
|
@ -0,0 +1,106 @@
|
|||
#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;
|
||||
|
||||
const struct http_arg
|
||||
{
|
||||
char *key, *value;
|
||||
} *args;
|
||||
|
||||
size_t n_args;
|
||||
};
|
||||
|
||||
#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, bool spaces);
|
||||
|
||||
#endif /* HTTP_H */
|
1
libweb
1
libweb
|
@ -1 +0,0 @@
|
|||
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f
|
14
page.h
14
page.h
|
@ -1,8 +1,7 @@
|
|||
#ifndef PAGE_H
|
||||
#define PAGE_H
|
||||
|
||||
#include <libweb/http.h>
|
||||
#include <stdbool.h>
|
||||
#include "http.h"
|
||||
#include <stddef.h>
|
||||
|
||||
struct page_quota
|
||||
|
@ -28,27 +27,18 @@ struct page_search
|
|||
|
||||
const char *root;
|
||||
size_t n;
|
||||
bool limit_exceeded;
|
||||
};
|
||||
|
||||
struct page_rm
|
||||
{
|
||||
const char *dir, **items;
|
||||
size_t n;
|
||||
};
|
||||
|
||||
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_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);
|
||||
|
||||
#endif /* PAGE_H */
|
||||
|
|
|
@ -0,0 +1,373 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "server.h"
|
||||
#include <fcntl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <poll.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)
|
||||
{
|
||||
switch (signum)
|
||||
{
|
||||
case SIGINT:
|
||||
/* Fall through. */
|
||||
case SIGTERM:
|
||||
do_exit = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct server_client *server_poll(struct server *const s, bool *const io,
|
||||
bool *const exit)
|
||||
{
|
||||
struct server_client *ret = NULL;
|
||||
const nfds_t n = s->n + 1;
|
||||
struct pollfd *const fds = malloc(n * sizeof *fds);
|
||||
|
||||
if (!fds)
|
||||
{
|
||||
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
struct pollfd *const sfd = &fds[0];
|
||||
|
||||
*io = *exit = false;
|
||||
*sfd = (const struct pollfd)
|
||||
{
|
||||
.fd = s->fd,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
for (size_t i = 0, j = 1; i < s->n; i++, j++)
|
||||
{
|
||||
struct pollfd *const p = &fds[j];
|
||||
const struct server_client *const c = &s->c[i];
|
||||
const int fd = c->fd;
|
||||
|
||||
*p = (const struct pollfd)
|
||||
{
|
||||
.fd = fd,
|
||||
.events = POLLIN
|
||||
};
|
||||
|
||||
if (c->write)
|
||||
p->events |= POLLOUT;
|
||||
}
|
||||
|
||||
int res;
|
||||
|
||||
again:
|
||||
|
||||
res = poll(fds, n, -1);
|
||||
|
||||
if (res < 0)
|
||||
{
|
||||
if (do_exit)
|
||||
{
|
||||
*exit = true;
|
||||
goto end;
|
||||
}
|
||||
|
||||
switch (errno)
|
||||
{
|
||||
case EAGAIN:
|
||||
/* Fall through. */
|
||||
case EINTR:
|
||||
goto again;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "%s: poll(2): %s\n", __func__, strerror(errno));
|
||||
break;
|
||||
}
|
||||
|
||||
goto end;
|
||||
}
|
||||
else if (!res)
|
||||
{
|
||||
fprintf(stderr, "%s: poll(2) returned zero\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (sfd->revents)
|
||||
{
|
||||
ret = alloc_client(s);
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (size_t i = 0, j = 1; i < s->n; i++, j++)
|
||||
{
|
||||
const struct pollfd *const p = &fds[j];
|
||||
struct server_client *const c = &s->c[i];
|
||||
|
||||
if (p->revents)
|
||||
{
|
||||
*io = true;
|
||||
ret = c;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: unlisted fd\n", __func__);
|
||||
|
||||
end:
|
||||
free(fds);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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) SIGTERM: %s\n",
|
||||
__func__, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
else if (sigaction(SIGPIPE, &sa, NULL))
|
||||
{
|
||||
fprintf(stderr, "%s: sigaction(2) SIGPIPE: %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;
|
||||
}
|
|
@ -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_poll(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
55
style.c
|
@ -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;
|
9
style.h
9
style.h
|
@ -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
|
32
usergen
32
usergen
|
@ -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
|
||||
|
@ -59,23 +42,22 @@ echo
|
|||
|
||||
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()
|
||||
{
|
||||
|
@ -93,5 +75,5 @@ jq ".users += [
|
|||
\"quota\": \"$QUOTA\"
|
||||
}]" "$DB" > $TMP
|
||||
|
||||
mkdir -p "$DIR/user/$USER"
|
||||
mv $TMP "$DB"
|
||||
mkdir "$DIR/user/$USER"
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
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) =
|
||||
|
@ -17,36 +16,18 @@ int wildcard_cmp(const char *s, const char *p, const bool casecmp)
|
|||
const char *const wc = strchr(p, '*');
|
||||
|
||||
if (!wc)
|
||||
{
|
||||
const int r = cmp(s, p);
|
||||
return 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;
|
||||
const size_t n = wc - p;
|
||||
|
||||
if (n)
|
||||
{
|
||||
const int r = ncmp(s, p, n);
|
||||
|
||||
if (r)
|
||||
{
|
||||
if (last)
|
||||
p = last;
|
||||
else
|
||||
return r;
|
||||
}
|
||||
else
|
||||
p += n;
|
||||
return r;
|
||||
|
||||
p += n;
|
||||
s += n;
|
||||
}
|
||||
else
|
||||
|
@ -55,13 +36,16 @@ int wildcard_cmp(const char *s, const char *p, const bool casecmp)
|
|||
|
||||
if (!cmp(wca, sa))
|
||||
{
|
||||
last = p;
|
||||
p = wc + 1;
|
||||
s += n;
|
||||
}
|
||||
else if (next == '*')
|
||||
p++;
|
||||
else
|
||||
{
|
||||
s++;
|
||||
p += n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue