Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
Xavier Del Campo Romero | 05a76f2899 | |
Xavier Del Campo Romero | aae26c510d |
|
@ -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,23 @@
|
|||
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
|
||||
server.c
|
||||
style.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,38 @@
|
|||
.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 \
|
||||
style.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)
|
64
README.md
64
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,27 +38,28 @@ 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).
|
||||
- `entr` (for [`watchdir`](watchdir) only).
|
||||
- ImageMagick (for [`tngen`](tngen) only).
|
||||
|
||||
### 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 inotify-tools imagemagick
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
@ -74,8 +68,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 +76,15 @@ them:
|
|||
#### Make
|
||||
|
||||
```sh
|
||||
$ ./configure
|
||||
$ make
|
||||
```
|
||||
|
||||
#### CMake
|
||||
|
||||
```sh
|
||||
$ cmake -B build
|
||||
$ cmake --build build/
|
||||
$ mkdir build/
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
```
|
||||
|
||||
### Setting up
|
||||
|
@ -179,25 +172,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 +202,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);
|
||||
|
|
|
@ -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
|
436
main.c
436
main.c
|
@ -2,12 +2,12 @@
|
|||
|
||||
#include "auth.h"
|
||||
#include "cftw.h"
|
||||
#include "handler.h"
|
||||
#include "hex.h"
|
||||
#include "http.h"
|
||||
#include "page.h"
|
||||
#include "style.h"
|
||||
#include <libweb/handler.h>
|
||||
#include <libweb/http.h>
|
||||
#include <libweb/wildcard_cmp.h>
|
||||
#include "wildcard_cmp.h"
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <dynstr.h>
|
||||
|
@ -135,8 +135,7 @@ static int append_form(struct form **const forms, const char **const s,
|
|||
{
|
||||
int ret = -1;
|
||||
const char *end;
|
||||
char *const data = alloc_form_data(*s, &end), *enckey = NULL,
|
||||
*encvalue = NULL, *key = NULL, *value = NULL;
|
||||
char *const data = alloc_form_data(*s, &end), *key = NULL, *value = NULL;
|
||||
struct form *f = NULL, *fs = NULL;
|
||||
|
||||
if (!data)
|
||||
|
@ -162,16 +161,14 @@ static int append_form(struct form **const forms, const char **const s,
|
|||
|
||||
const size_t keylen = sep - data;
|
||||
|
||||
if (!(enckey = strndup(data, keylen)))
|
||||
if (!(key = strndup(data, keylen)))
|
||||
{
|
||||
fprintf(stderr, "%s: strndup(3) enckey: %s\n",
|
||||
__func__, strerror(errno));
|
||||
fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (!(encvalue = strdup(sep + 1)))
|
||||
else if (!(value = strdup(sep + 1)))
|
||||
{
|
||||
fprintf(stderr, "%s: strdup(3) encvalue: %s\n",
|
||||
__func__, strerror(errno));
|
||||
fprintf(stderr, "%s: strdup(3) value: %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (!(fs = realloc(*forms, (*n + 1) * sizeof **forms)))
|
||||
|
@ -181,39 +178,27 @@ static int append_form(struct form **const forms, const char **const s,
|
|||
}
|
||||
|
||||
*forms = fs;
|
||||
|
||||
/* HTML input forms use '+' for whitespace, rather than %20. */
|
||||
if ((ret = http_decode_url(enckey, true, &key)))
|
||||
{
|
||||
fprintf(stderr, "%s: http_decode_url enckey failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if ((ret = http_decode_url(encvalue, true, &value)))
|
||||
{
|
||||
fprintf(stderr, "%s: http_decode_url encvalue failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
f = &(*forms)[(*n)++];
|
||||
|
||||
/* HTML input forms use '+' for whitespace, rather than %20. */
|
||||
*f = (const struct form)
|
||||
{
|
||||
.key = key,
|
||||
.value = value
|
||||
.key = http_decode_url(key, true),
|
||||
.value = http_decode_url(value, true)
|
||||
};
|
||||
|
||||
if (!f->key || !f->value)
|
||||
{
|
||||
fprintf(stderr, "%s: http_decode_url key/value failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
*s = end;
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
if (ret)
|
||||
{
|
||||
free(key);
|
||||
free(value);
|
||||
}
|
||||
|
||||
free(enckey);
|
||||
free(encvalue);
|
||||
free(key);
|
||||
free(value);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
@ -223,16 +208,22 @@ static int get_forms(const struct http_payload *const pl,
|
|||
{
|
||||
int ret = -1;
|
||||
const struct http_post *const p = &pl->u.post;
|
||||
const char *const ref = p->data;
|
||||
char *dup = NULL;
|
||||
struct form *f = NULL;
|
||||
|
||||
if (!p->data)
|
||||
if (!ref)
|
||||
{
|
||||
fprintf(stderr, "%s: expected non-NULL buffer\n", __func__);
|
||||
ret = 1;
|
||||
goto end;
|
||||
}
|
||||
else if (!(dup = strndup(ref, p->n)))
|
||||
{
|
||||
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
const char *s = p->data;
|
||||
const char *s = dup;
|
||||
|
||||
*outn = 0;
|
||||
|
||||
|
@ -249,6 +240,8 @@ static int get_forms(const struct http_payload *const pl,
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
free(dup);
|
||||
|
||||
if (ret)
|
||||
forms_free(f, *outn);
|
||||
|
||||
|
@ -393,24 +386,14 @@ end:
|
|||
|
||||
static bool path_isrel(const char *const path)
|
||||
{
|
||||
if (!strcmp(path, "..")
|
||||
|| !strcmp(path, ".")
|
||||
|| !strncmp(path, "./", strlen("./"))
|
||||
|| !strncmp(path, "../", strlen("../"))
|
||||
|| strstr(path, "/./")
|
||||
|| strstr(path, "/../"))
|
||||
if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
|
||||
return true;
|
||||
|
||||
static const char *const suffixes[] = {"/.", "/.."};
|
||||
static const char suffix[] = "/..";
|
||||
const size_t n = strlen(path), sn = strlen(suffix);
|
||||
|
||||
for (size_t i = 0; i < sizeof suffixes / sizeof *suffixes; i++)
|
||||
{
|
||||
const char *const suffix = suffixes[i];
|
||||
const size_t n = strlen(path), sn = strlen(suffix);
|
||||
|
||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||
return true;
|
||||
}
|
||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -435,8 +418,7 @@ static int getpublic(const struct http_payload *const p,
|
|||
{
|
||||
int ret = -1;
|
||||
struct auth *const a = user;
|
||||
const char *const adir = auth_dir(a),
|
||||
*const file = p->resource + strlen("/public/");
|
||||
const char *const adir = auth_dir(a);
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&d);
|
||||
|
@ -446,13 +428,6 @@ static int getpublic(const struct http_payload *const p,
|
|||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!*file || filename_invalid(file))
|
||||
{
|
||||
fprintf(stderr, "%s: invalid filename %s\n",
|
||||
__func__, p->resource);
|
||||
ret = page_forbidden(r);
|
||||
goto end;
|
||||
}
|
||||
else if (path_invalid(p->resource))
|
||||
{
|
||||
fprintf(stderr, "%s: illegal relative path %s\n",
|
||||
|
@ -718,10 +693,14 @@ static int search(const struct http_payload *const p,
|
|||
int ret = -1;
|
||||
const struct auth *const a = user;
|
||||
const char *const username = p->cookie.field, *const root = auth_dir(a);
|
||||
struct page_search s = {0};
|
||||
int (*f)(struct http_response *);
|
||||
char *dir = NULL;
|
||||
struct dynstr userd, d, res;
|
||||
struct page_search s =
|
||||
{
|
||||
.username = username,
|
||||
.adir = root
|
||||
};
|
||||
|
||||
dynstr_init(&userd);
|
||||
dynstr_init(&d);
|
||||
|
@ -911,16 +890,7 @@ static int check_length(const unsigned long long len,
|
|||
bool has_quota;
|
||||
unsigned long long quota;
|
||||
|
||||
if (auth_cookie(a, c))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||
|
||||
if (page_forbidden(r))
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
else if (auth_quota(a, username, &has_quota, "a))
|
||||
if (auth_quota(a, username, &has_quota, "a))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_quota failed\n", __func__);
|
||||
return -1;
|
||||
|
@ -1005,6 +975,8 @@ static int getnode(const struct http_payload *const p,
|
|||
.r = r,
|
||||
.args = p->args,
|
||||
.n_args = p->n_args,
|
||||
.username = username,
|
||||
.adir = adir,
|
||||
.dir = dir.str,
|
||||
.root = root.str,
|
||||
.res = d.str,
|
||||
|
@ -1022,113 +994,52 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int getnode_head(const struct http_payload *const p,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
struct auth *const a = user;
|
||||
|
||||
if (auth_cookie(a, &p->cookie))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||
return page_forbidden(r);
|
||||
}
|
||||
|
||||
const char *const username = p->cookie.field,
|
||||
*const resource = p->resource + strlen("/user/");
|
||||
|
||||
if (path_invalid(resource))
|
||||
{
|
||||
fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
|
||||
return page_forbidden(r);
|
||||
}
|
||||
|
||||
int ret = -1;
|
||||
struct dynstr dir, root, d;
|
||||
const char *const adir = auth_dir(a),
|
||||
*const sep = p->resource[strlen(p->resource) - 1] != '/' ? "/" : "";
|
||||
|
||||
dynstr_init(&dir);
|
||||
dynstr_init(&d);
|
||||
dynstr_init(&root);
|
||||
|
||||
if (!adir)
|
||||
{
|
||||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&dir, "%s%s", p->resource, sep))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&root, "%s/user/%s/", adir, username)
|
||||
|| dynstr_append(&d, "%s%s", root.str, resource))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = page_head_resource(r, d.str);
|
||||
|
||||
end:
|
||||
dynstr_free(&dir);
|
||||
dynstr_free(&d);
|
||||
dynstr_free(&root);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int move_file(const char *const old, const char *const new)
|
||||
{
|
||||
int ret = -1;
|
||||
const int fd_old = open(old, O_RDONLY),
|
||||
fd_new = open(new, O_WRONLY | O_CREAT, 0600);
|
||||
FILE *const f = fopen(old, "rb");
|
||||
const int fd = open(new, O_WRONLY | O_CREAT, 0600);
|
||||
struct stat sb;
|
||||
|
||||
if (fd_old < 0)
|
||||
if (!f)
|
||||
{
|
||||
fprintf(stderr, "%s: open(2) fd_old: %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (fd_new < 0)
|
||||
else if (fd < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (fstat(fd_old, &sb))
|
||||
else if (stat(old, &sb))
|
||||
{
|
||||
fprintf(stderr, "%s: fstat(2): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (off_t i = 0; i < sb.st_size;)
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
char buf[1024];
|
||||
const off_t left = sb.st_size - i;
|
||||
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
||||
const ssize_t r = read(fd_old, buf, rem);
|
||||
ssize_t w;
|
||||
|
||||
if (r < 0)
|
||||
if (!fread(buf, rem, 1, f))
|
||||
{
|
||||
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
|
||||
__func__, feof(f), ferror(f));
|
||||
goto end;
|
||||
}
|
||||
|
||||
size_t wrem = r;
|
||||
const void *p = buf;
|
||||
|
||||
while (wrem)
|
||||
else if ((w = write(fd, buf, rem)) < 0)
|
||||
{
|
||||
const ssize_t w = write(fd_new, p, wrem);
|
||||
|
||||
if (w < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: write(2): %s\n",
|
||||
__func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
p = (const char *)p + w;
|
||||
wrem -= w;
|
||||
fprintf(stderr, "%s: write(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (w != rem)
|
||||
{
|
||||
fprintf(stderr, "%s: write(2): expected to write %zu bytes, "
|
||||
"only %ju written\n", __func__, rem, (intmax_t)w);
|
||||
goto end;
|
||||
}
|
||||
|
||||
i += rem;
|
||||
|
@ -1137,15 +1048,15 @@ static int move_file(const char *const old, const char *const new)
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
if (fd_old >= 0 && close(fd_old))
|
||||
if (fd >= 0 && close(fd))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2) fd_old: %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (fd_new >= 0 && close(fd_new))
|
||||
if (f && fclose(f))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2) fd_new: %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
|
@ -1164,43 +1075,12 @@ static int rename_or_move(const char *const old, const char *const new)
|
|||
return res;
|
||||
}
|
||||
|
||||
static int check_upload_dir(const char *const dir)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(dir, &sb))
|
||||
{
|
||||
switch (errno)
|
||||
{
|
||||
case ENOENT:
|
||||
/* Fall through. */
|
||||
case ENOTDIR:
|
||||
fprintf(stderr, "%s: cannot upload to non-existing dir %s\n",
|
||||
__func__, dir);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (!S_ISDIR(sb.st_mode))
|
||||
{
|
||||
fprintf(stderr, "%s: %s not a dir\n", __func__, dir);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int upload_file(const struct http_post_file *const f,
|
||||
const char *const user, const char *const root, const char *const dir)
|
||||
{
|
||||
int ret = -1;
|
||||
struct dynstr dird, d;
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&dird);
|
||||
dynstr_init(&d);
|
||||
|
||||
if (!root)
|
||||
|
@ -1208,21 +1088,9 @@ static int upload_file(const struct http_post_file *const f,
|
|||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&dird, "%s/user/%s/%s", root, user, dir))
|
||||
else if (dynstr_append(&d, "%s/user/%s/%s%s", root, user, dir, f->filename))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if ((ret = check_upload_dir(dird.str)))
|
||||
{
|
||||
if (ret < 0)
|
||||
fprintf(stderr, "%s: check_upload_dir failed\n", __func__);
|
||||
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%s%s", dird.str, f->filename))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (rename_or_move(f->tmpname, d.str))
|
||||
|
@ -1234,7 +1102,6 @@ static int upload_file(const struct http_post_file *const f,
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&dird);
|
||||
dynstr_free(&d);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1277,25 +1144,12 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static const char *get_upload_dir(const struct http_post *const po)
|
||||
{
|
||||
for (size_t i = 0; i < po->npairs; i++)
|
||||
{
|
||||
const struct http_post_pair *p = &po->pairs[i];
|
||||
|
||||
if (!strcmp(p->name, "dir"))
|
||||
return p->value;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int upload_files(const struct http_payload *const p,
|
||||
struct http_response *const r, const struct auth *const a)
|
||||
{
|
||||
const struct http_post *const po = &p->u.post;
|
||||
const char *const root = auth_dir(a), *const user = p->cookie.field,
|
||||
*const dir = get_upload_dir(po);
|
||||
*const dir = po->dir;
|
||||
|
||||
if (!po->files)
|
||||
{
|
||||
|
@ -1332,17 +1186,13 @@ static int upload_files(const struct http_payload *const p,
|
|||
return page_bad_request(r);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < po->nfiles; i++)
|
||||
for (size_t i = 0; i < po->n; i++)
|
||||
{
|
||||
const int ret = upload_file(&po->files[i], user, root, dir);
|
||||
|
||||
if (ret < 0)
|
||||
if (upload_file(&po->files[i], user, root, po->dir))
|
||||
{
|
||||
fprintf(stderr, "%s: upload_file failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
else if (ret)
|
||||
return page_bad_request(r);
|
||||
}
|
||||
|
||||
return redirect_to_dir(dir, r);
|
||||
|
@ -1358,7 +1208,7 @@ static int upload(const struct http_payload *const p,
|
|||
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||
return page_forbidden(r);
|
||||
}
|
||||
else if (p->expect_continue)
|
||||
else if (p->u.post.expect_continue)
|
||||
{
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
|
@ -1379,7 +1229,6 @@ static int createdir(const struct http_payload *const p,
|
|||
struct dynstr d, userd;
|
||||
struct form *forms = NULL;
|
||||
size_t n = 0;
|
||||
char *encurl = NULL;
|
||||
|
||||
dynstr_init(&d);
|
||||
dynstr_init(&userd);
|
||||
|
@ -1406,7 +1255,7 @@ static int createdir(const struct http_payload *const p,
|
|||
goto end;
|
||||
}
|
||||
|
||||
const char *name = NULL, *dir = NULL;
|
||||
char *name = NULL, *dir = NULL;
|
||||
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
|
@ -1493,12 +1342,7 @@ static int createdir(const struct http_payload *const p,
|
|||
.status = HTTP_STATUS_SEE_OTHER
|
||||
};
|
||||
|
||||
if (!(encurl = http_encode_url(userd.str)))
|
||||
{
|
||||
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (http_response_add_header(r, "Location", encurl))
|
||||
if (http_response_add_header(r, "Location", userd.str))
|
||||
{
|
||||
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1511,7 +1355,6 @@ end:
|
|||
forms_free(forms, n);
|
||||
dynstr_free(&userd);
|
||||
dynstr_free(&d);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1863,6 +1706,54 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int getthumbnail(const struct http_payload *const p,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
int ret = -1;
|
||||
struct auth *const a = user;
|
||||
struct dynstr d, dir;
|
||||
|
||||
dynstr_init(&d);
|
||||
dynstr_init(&dir);
|
||||
|
||||
if (auth_cookie(a, &p->cookie))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||
ret = page_forbidden(r);
|
||||
goto end;
|
||||
}
|
||||
|
||||
const char *const adir = auth_dir(a);
|
||||
|
||||
if (!adir)
|
||||
{
|
||||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%s%s", adir, p->resource))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_dup(&dir, &d))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_dup failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (page_thumbnail(r, d.str))
|
||||
{
|
||||
fprintf(stderr, "%s: page_public failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
dynstr_free(&dir);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void usage(char *const argv[])
|
||||
{
|
||||
fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv);
|
||||
|
@ -2069,45 +1960,6 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int add_urls(struct handler *const h, void *const user)
|
||||
{
|
||||
static const struct url
|
||||
{
|
||||
const char *url;
|
||||
enum http_op op;
|
||||
handler_fn f;
|
||||
} urls[] =
|
||||
{
|
||||
{.url = "/", .op = HTTP_OP_GET, .f = serve_index},
|
||||
{.url = "/index.html", .op = HTTP_OP_GET, .f = serve_index},
|
||||
{.url = "/style.css", .op = HTTP_OP_GET, .f = serve_style},
|
||||
{.url = "/user/*", .op = HTTP_OP_GET, .f = getnode},
|
||||
{.url = "/user/*", .op = HTTP_OP_HEAD, .f = getnode_head},
|
||||
{.url = "/login", .op = HTTP_OP_POST, .f = login},
|
||||
{.url = "/logout", .op = HTTP_OP_POST, .f = logout},
|
||||
{.url = "/public/*", .op = HTTP_OP_GET, .f = getpublic},
|
||||
{.url = "/search", .op = HTTP_OP_POST, .f = search},
|
||||
{.url = "/share", .op = HTTP_OP_POST, .f = share},
|
||||
{.url = "/upload", .op = HTTP_OP_POST, .f = upload},
|
||||
{.url = "/mkdir", .op = HTTP_OP_POST, .f = createdir},
|
||||
{.url = "/confirm/rm", .op = HTTP_OP_POST, .f = confirm_rm},
|
||||
{.url = "/rm", .op = HTTP_OP_POST, .f = rm}
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
|
||||
{
|
||||
const struct url *const u = &urls[i];
|
||||
|
||||
if (handler_add(h, u->url, u->op, u->f, user))
|
||||
{
|
||||
fprintf(stderr, "%s: handler_add %s failed\n", __func__, u->url);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret = EXIT_FAILURE;
|
||||
|
@ -2126,31 +1978,27 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
.length = check_length,
|
||||
.tmpdir = tmpdir,
|
||||
.user = a,
|
||||
.post =
|
||||
{
|
||||
/* Arbitrary limit. */
|
||||
.max_files = 10000,
|
||||
/* File upload only requires one pair. */
|
||||
.max_pairs = 1
|
||||
}
|
||||
.user = a
|
||||
};
|
||||
|
||||
unsigned short outport;
|
||||
|
||||
if (!(h = handler_alloc(&cfg))
|
||||
|| add_urls(h, a)
|
||||
|| handler_listen(h, port, &outport))
|
||||
|| handler_add(h, "/", HTTP_OP_GET, serve_index, a)
|
||||
|| handler_add(h, "/index.html", HTTP_OP_GET, serve_index, a)
|
||||
|| handler_add(h, "/style.css", HTTP_OP_GET, serve_style, a)
|
||||
|| handler_add(h, "/user/*", HTTP_OP_GET, getnode, a)
|
||||
|| handler_add(h, "/login", HTTP_OP_POST, login, a)
|
||||
|| handler_add(h, "/logout", HTTP_OP_POST, logout, a)
|
||||
|| handler_add(h, "/public/*", HTTP_OP_GET, getpublic, a)
|
||||
|| handler_add(h, "/search", HTTP_OP_POST, search, a)
|
||||
|| handler_add(h, "/share", HTTP_OP_POST, share, a)
|
||||
|| handler_add(h, "/upload", HTTP_OP_POST, upload, a)
|
||||
|| handler_add(h, "/mkdir", HTTP_OP_POST, createdir, a)
|
||||
|| handler_add(h, "/confirm/rm", HTTP_OP_POST, confirm_rm, a)
|
||||
|| handler_add(h, "/rm", HTTP_OP_POST, rm, a)
|
||||
|| handler_add(h, "/thumbnails/*", HTTP_OP_GET, getthumbnail, a)
|
||||
|| handler_listen(h, port))
|
||||
goto end;
|
||||
|
||||
printf("Listening on port %hu\n", outport);
|
||||
|
||||
if (handler_loop(h))
|
||||
{
|
||||
fprintf(stderr, "%s: handler_loop failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
|
||||
end:
|
||||
|
|
376
page.c
376
page.c
|
@ -1,8 +1,8 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
#include "page.h"
|
||||
#include <libweb/html.h>
|
||||
#include <libweb/http.h>
|
||||
#include "http.h"
|
||||
#include "html.h"
|
||||
#include <dynstr.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -20,10 +20,10 @@
|
|||
#include <time.h>
|
||||
|
||||
#define PROJECT_NAME "slcl"
|
||||
#define PROJECT_TITLE PROJECT_NAME ", a simple and lightweight cloud"
|
||||
#define PROJECT_TITLE PROJECT_NAME ", a suckless cloud"
|
||||
#define PROJECT_TAG "<title>" PROJECT_TITLE "</title>"
|
||||
#define DOCTYPE_TAG "<!DOCTYPE html>\n"
|
||||
#define PROJECT_URL "https://gitea.privatedns.org/xavi/" PROJECT_NAME
|
||||
#define PROJECT_URL "https://gitea.privatedns.org/Xavi92/" PROJECT_NAME
|
||||
#define COMMON_HEAD \
|
||||
" <meta charset=\"UTF-8\">\n" \
|
||||
" <meta name=\"viewport\"\n" \
|
||||
|
@ -33,8 +33,7 @@
|
|||
#define STYLE_A "<link href=\"/style.css\" rel=\"stylesheet\">"
|
||||
#define LOGIN_BODY \
|
||||
"<header>\n" \
|
||||
" <a href=\"" PROJECT_URL "\">" PROJECT_NAME "</a>," \
|
||||
" a simple and lightweight cloud\n" \
|
||||
" <a href=\"" PROJECT_URL "\">" PROJECT_NAME "</a>, a suckless cloud\n" \
|
||||
"</header>\n" \
|
||||
" <form class=\"loginform\" action=\"/login\" method=\"post\">\n" \
|
||||
" <label for=\"username\">Username:</label>\n" \
|
||||
|
@ -48,11 +47,6 @@
|
|||
#define MAXSIZEFMT "18446744073709551615.0 XiB"
|
||||
#define RM_FORM_ID "rm"
|
||||
|
||||
struct element_stats
|
||||
{
|
||||
unsigned long long n_dirs, n_files;
|
||||
};
|
||||
|
||||
static int prepare_rm_checkbox(struct html_node *const n,
|
||||
const struct stat *const sb, const char *const name)
|
||||
{
|
||||
|
@ -101,6 +95,67 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int prepare_thumbnail(struct html_node *const n,
|
||||
const struct stat *const sb, const struct page_resource *const pr,
|
||||
const char *const name, const char *const sep)
|
||||
{
|
||||
int ret = -1;
|
||||
struct html_node *img;
|
||||
struct dynstr abs, rel;
|
||||
struct stat tsb;
|
||||
|
||||
dynstr_init(&abs);
|
||||
dynstr_init(&rel);
|
||||
|
||||
if (!S_ISREG(sb->st_mode))
|
||||
{
|
||||
ret = 0;
|
||||
goto end;
|
||||
}
|
||||
else if (!pr->adir || !pr->username)
|
||||
{
|
||||
ret = 0;
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&rel, "/thumbnails/%s%s%s%s",
|
||||
pr->username, pr->dir + strlen("/user"), sep, name))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&abs, "%s%s", pr->adir, rel.str))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (stat(abs.str, &tsb))
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
ret = 0;
|
||||
else
|
||||
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
||||
|
||||
goto end;
|
||||
}
|
||||
else if (!(img = html_node_add_child(n, "img")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(img, "src", rel.str))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr src failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&abs);
|
||||
dynstr_free(&rel);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int prepare_name(struct html_node *const n, struct stat *const sb,
|
||||
const char *const dir, const char *const name)
|
||||
{
|
||||
|
@ -108,7 +163,6 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
|
|||
struct html_node *a;
|
||||
struct dynstr d, dname;
|
||||
const char *const sep = S_ISDIR(sb->st_mode) ? "/" : "";
|
||||
char *encurl = NULL;
|
||||
|
||||
dynstr_init(&d);
|
||||
dynstr_init(&dname);
|
||||
|
@ -123,12 +177,7 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
|
|||
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(encurl = http_encode_url(d.str)))
|
||||
{
|
||||
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(a, "href", encurl))
|
||||
else if (html_node_add_attr(a, "href", d.str))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr href failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -149,7 +198,6 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
|
|||
end:
|
||||
dynstr_free(&d);
|
||||
dynstr_free(&dname);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -310,7 +358,6 @@ static int prepare_preview(struct html_node *const n,
|
|||
const struct stat *const sb, const char *const dir, const char *const name)
|
||||
{
|
||||
int ret = -1;
|
||||
char *encurl = NULL;
|
||||
struct html_node *a;
|
||||
struct dynstr d;
|
||||
|
||||
|
@ -323,22 +370,9 @@ static int prepare_preview(struct html_node *const n,
|
|||
fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%s%s", dir, name))
|
||||
else if (dynstr_append(&d, "%s%s?preview=1", dir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(encurl = http_encode_url(d.str)))
|
||||
{
|
||||
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
dynstr_free(&d);
|
||||
|
||||
if (dynstr_append(&d, "%s?preview=1", encurl))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append encd failed\n", __func__);
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(a, "href", d.str))
|
||||
|
@ -356,23 +390,22 @@ static int prepare_preview(struct html_node *const n,
|
|||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int add_element(struct html_node *const n, const char *const dir,
|
||||
const char *const res, const char *const name, const bool chbx,
|
||||
struct element_stats *const st)
|
||||
static int add_element(struct html_node *const n,
|
||||
const struct page_resource *const pr, const char *const name,
|
||||
const bool chbx)
|
||||
{
|
||||
int ret = -1;
|
||||
enum {RM_CHECKBOX, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
|
||||
enum {RM_CHECKBOX, THUMBNAIL, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
|
||||
struct html_node *tr, *td[COLUMNS];
|
||||
struct dynstr path;
|
||||
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
|
||||
const char *const sep = pr->res[strlen(pr->res) - 1] != '/' ? "/" : "";
|
||||
|
||||
dynstr_init(&path);
|
||||
|
||||
if (dynstr_append(&path, "%s%s%s", res, sep, name))
|
||||
if (dynstr_append(&path, "%s%s%s", pr->res, sep, name))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -392,7 +425,6 @@ static int add_element(struct html_node *const n, const char *const dir,
|
|||
}
|
||||
|
||||
struct stat sb;
|
||||
const bool parentdir = !strcmp(name, "..");
|
||||
|
||||
if (stat(path.str, &sb))
|
||||
{
|
||||
|
@ -400,21 +432,18 @@ static int add_element(struct html_node *const n, const char *const dir,
|
|||
__func__, path.str, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (st && !parentdir)
|
||||
{
|
||||
if (S_ISDIR(sb.st_mode))
|
||||
st->n_dirs++;
|
||||
else if (S_ISREG(sb.st_mode))
|
||||
st->n_files++;
|
||||
}
|
||||
|
||||
if (chbx && !parentdir
|
||||
else if (chbx && strcmp(name, "..")
|
||||
&& prepare_rm_checkbox(td[RM_CHECKBOX], &sb, name))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_name(td[NAME], &sb, dir, name))
|
||||
else if (prepare_thumbnail(td[THUMBNAIL], &sb, pr, name, sep))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_thumbnail failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_name(td[NAME], &sb, pr->dir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_name failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -429,12 +458,12 @@ static int add_element(struct html_node *const n, const char *const dir,
|
|||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_share(td[SHARE], &sb, dir, name))
|
||||
else if (prepare_share(td[SHARE], &sb, pr->dir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_preview(td[PREVIEW], &sb, dir, name))
|
||||
else if (prepare_preview(td[PREVIEW], &sb, pr->dir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -971,7 +1000,7 @@ static int prepare_footer(struct html_node *const n)
|
|||
fprintf(stderr, "%s: html_node_set_value a failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "Powered by "))
|
||||
else if (dynstr_append(&d, "Powered by "))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1073,12 +1102,11 @@ end:
|
|||
}
|
||||
|
||||
static struct html_node *resource_layout(const char *const dir,
|
||||
const struct page_quota *const q, struct html_node **const body,
|
||||
struct html_node **const table)
|
||||
const struct page_quota *const q, struct html_node **const table)
|
||||
{
|
||||
const char *const fdir = dir + strlen("/user");
|
||||
struct html_node *const html = html_node_alloc("html"),
|
||||
*ret = NULL, *head, *div;
|
||||
*ret = NULL, *head, *body, *div;
|
||||
|
||||
if (!html)
|
||||
{
|
||||
|
@ -1090,12 +1118,12 @@ static struct html_node *resource_layout(const char *const dir,
|
|||
fprintf(stderr, "%s: html_node_add_child head failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(*body = html_node_add_child(html, "body")))
|
||||
else if (!(body = html_node_add_child(html, "body")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child body failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(div = html_node_add_child(*body, "div")))
|
||||
else if (!(div = html_node_add_child(body, "div")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1110,36 +1138,41 @@ static struct html_node *resource_layout(const char *const dir,
|
|||
fprintf(stderr, "%s: common_head failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_search_form(*body, fdir))
|
||||
else if (prepare_search_form(body, fdir))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_search_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_upload_form(*body, fdir))
|
||||
else if (prepare_upload_form(body, fdir))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_mkdir_form(*body, fdir))
|
||||
else if (prepare_mkdir_form(body, fdir))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_rm_form(*body, fdir))
|
||||
else if (prepare_rm_form(body, fdir))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (q && prepare_quota_form(*body, q))
|
||||
else if (q && prepare_quota_form(body, q))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_quota_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_logout_form(*body))
|
||||
else if (prepare_logout_form(body))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_logout_form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_footer(body))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_footer failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = html;
|
||||
|
||||
|
@ -1151,13 +1184,12 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int add_elements(const char *const root, const char *const res,
|
||||
const char *const dir, struct html_node *const table,
|
||||
struct element_stats *const st)
|
||||
static int add_elements(const struct page_resource *const pr,
|
||||
struct html_node *const table)
|
||||
{
|
||||
int ret = -1;
|
||||
struct dirent **pde = NULL;
|
||||
const int n = scandir(res, &pde, NULL, alphasort);
|
||||
const int n = scandir(pr->res, &pde, NULL, alphasort);
|
||||
|
||||
if (n < 0)
|
||||
{
|
||||
|
@ -1165,17 +1197,15 @@ static int add_elements(const char *const root, const char *const res,
|
|||
goto end;
|
||||
}
|
||||
|
||||
*st = (const struct element_stats){0};
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
const struct dirent *const de = pde[i];
|
||||
const char *const name = de->d_name;
|
||||
|
||||
if (!strcmp(name, ".")
|
||||
|| (!strcmp(name, "..") && !strcmp(root, res)))
|
||||
|| (!strcmp(name, "..") && !strcmp(pr->root, pr->res)))
|
||||
continue;
|
||||
else if (add_element(table, dir, res, name, true, st))
|
||||
else if (add_element(table, pr, name, true))
|
||||
{
|
||||
fprintf(stderr, "%s: add_element failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1193,47 +1223,12 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int prepare_element_stats(struct html_node *const n,
|
||||
const struct element_stats *const st)
|
||||
{
|
||||
int ret = -1;
|
||||
struct html_node *const p = html_node_add_child(n, "div");
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&d);
|
||||
|
||||
if (!p)
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%llu directories, %llu files",
|
||||
st->n_dirs, st->n_files))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_set_value(p, d.str))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int list_dir(const struct page_resource *const pr)
|
||||
{
|
||||
int ret = -1;
|
||||
struct dynstr out;
|
||||
struct html_node *table, *body,
|
||||
*const html = resource_layout(pr->dir, pr->q, &body, &table);
|
||||
struct element_stats st;
|
||||
struct html_node *table,
|
||||
*const html = resource_layout(pr->dir, pr->q, &table);
|
||||
|
||||
dynstr_init(&out);
|
||||
|
||||
|
@ -1242,21 +1237,11 @@ static int list_dir(const struct page_resource *const pr)
|
|||
fprintf(stderr, "%s: resource_layout failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (add_elements(pr->root, pr->res, pr->dir, table, &st))
|
||||
else if (add_elements(pr, table))
|
||||
{
|
||||
fprintf(stderr, "%s: read_elements failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_element_stats(body, &st))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_element_stats failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (prepare_footer(body))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_footer failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&out, DOCTYPE_TAG))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_prepend failed\n", __func__);
|
||||
|
@ -1294,24 +1279,20 @@ end:
|
|||
}
|
||||
|
||||
static int serve_file(struct http_response *const r,
|
||||
const struct stat *const sb, const char *const res, const bool preview,
|
||||
const int fd, bool *const fdopened)
|
||||
const struct stat *const sb, const char *const res, const bool preview)
|
||||
{
|
||||
int ret = -1;
|
||||
FILE *f = NULL;
|
||||
FILE *const f = fopen(res, "rb");
|
||||
struct dynstr b, d;
|
||||
char *bn;
|
||||
|
||||
dynstr_init(&b);
|
||||
dynstr_init(&d);
|
||||
|
||||
if (preview)
|
||||
if (!f)
|
||||
{
|
||||
if (dynstr_append(&d, "inline"))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append inline failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&b, "%s", res))
|
||||
{
|
||||
|
@ -1323,20 +1304,20 @@ static int serve_file(struct http_response *const r,
|
|||
fprintf(stderr, "%s: basename(3) failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (preview)
|
||||
{
|
||||
if (dynstr_append(&d, "inline"))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append inline failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
else if (dynstr_append(&d, "attachment; filename=\"%s\"", bn))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append attachment failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
if (!(f = fdopen(fd, "rb")))
|
||||
{
|
||||
fprintf(stderr, "%s: fdopen(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
*fdopened = true;
|
||||
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK,
|
||||
|
@ -1406,63 +1387,28 @@ static bool preview(const struct page_resource *const pr)
|
|||
|
||||
int page_resource(const struct page_resource *const pr)
|
||||
{
|
||||
int ret = -1;
|
||||
struct stat sb;
|
||||
const int fd = open(pr->res, O_RDONLY);
|
||||
bool fdopened = false;
|
||||
|
||||
if (fd < 0)
|
||||
if (stat(pr->res, &sb))
|
||||
{
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
ret = page_not_found(pr->r);
|
||||
else
|
||||
fprintf(stderr, "%s: open(2) %s: %s\n",
|
||||
__func__, pr->res, strerror(errno));
|
||||
|
||||
goto end;
|
||||
}
|
||||
else if (fstat(fd, &sb))
|
||||
{
|
||||
fprintf(stderr, "%s: fstat(2) %s: %s\n",
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, pr->res, strerror(errno));
|
||||
goto end;
|
||||
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
return page_not_found(pr->r);
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
||||
if (S_ISDIR(m))
|
||||
{
|
||||
if (list_dir(pr))
|
||||
{
|
||||
fprintf(stderr, "%s: list_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
return list_dir(pr);
|
||||
else if (S_ISREG(m))
|
||||
{
|
||||
if (serve_file(pr->r, &sb, pr->res, preview(pr), fd, &fdopened))
|
||||
{
|
||||
fprintf(stderr, "%s: serve_file failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: unexpected st_mode %jo\n", __func__, (intmax_t)m);
|
||||
goto end;
|
||||
}
|
||||
return serve_file(pr->r, &sb, pr->res, preview(pr));
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
if (!fdopened && fd >= 0 && close(fd))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2) %s: %s\n",
|
||||
__func__, pr->res, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static char *resolve_link(const char *const res)
|
||||
|
@ -1505,26 +1451,18 @@ static char *resolve_link(const char *const res)
|
|||
int page_public(struct http_response *const r, const char *const res)
|
||||
{
|
||||
int ret = -1;
|
||||
const int fd = open(res, O_RDONLY);
|
||||
struct stat sb;
|
||||
char *path = NULL;
|
||||
bool fdopened = false;
|
||||
|
||||
if (fd < 0)
|
||||
if (stat(res, &sb))
|
||||
{
|
||||
if (errno == ENOENT)
|
||||
ret = page_not_found(r);
|
||||
else
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
|
||||
goto end;
|
||||
}
|
||||
else if (fstat(fd, &sb))
|
||||
{
|
||||
fprintf(stderr, "%s: fstat(2) %s: %s\n",
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
goto end;
|
||||
|
||||
if (errno == ENOENT)
|
||||
return page_not_found(r);
|
||||
else
|
||||
goto end;
|
||||
}
|
||||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
@ -1539,7 +1477,7 @@ int page_public(struct http_response *const r, const char *const res)
|
|||
fprintf(stderr, "%s: resolve_link failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (serve_file(r, &sb, path, false, fd, &fdopened))
|
||||
else if (serve_file(r, &sb, path, false))
|
||||
{
|
||||
fprintf(stderr, "%s: serve_file failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1548,13 +1486,6 @@ int page_public(struct http_response *const r, const char *const res)
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
if (!fdopened && fd >= 0 && close(fd))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
free(path);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1891,8 +1822,15 @@ static int add_search_results(struct html_node *const n,
|
|||
for (size_t i = 0; i < s->n; i++)
|
||||
{
|
||||
const struct page_search_result *const r = &s->results[i];
|
||||
const struct page_resource pr =
|
||||
{
|
||||
.dir = "/user/",
|
||||
.adir = s->adir,
|
||||
.username = s->username,
|
||||
.res = s->root
|
||||
};
|
||||
|
||||
if (add_element(table, "/user/", s->root, r->name, false, NULL))
|
||||
if (add_element(table, &pr, r->name, false))
|
||||
{
|
||||
fprintf(stderr, "%s: add_element failed\n", __func__);
|
||||
return -1;
|
||||
|
@ -2276,13 +2214,13 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
int page_head_resource(struct http_response *const r, const char *const res)
|
||||
int page_thumbnail(struct http_response *const r, const char *const res)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(res, &sb))
|
||||
{
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
if (errno == ENOENT)
|
||||
return page_not_found(r);
|
||||
else
|
||||
{
|
||||
|
@ -2294,17 +2232,11 @@ int page_head_resource(struct http_response *const r, const char *const res)
|
|||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
||||
if (!S_ISDIR(m) && !S_ISREG(m))
|
||||
if (!S_ISREG(m))
|
||||
{
|
||||
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
|
||||
return -1;
|
||||
fprintf(stderr, "%s: only regular files are supported\n", __func__);
|
||||
return 1;
|
||||
}
|
||||
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK,
|
||||
.n = sb.st_size
|
||||
};
|
||||
|
||||
return 0;
|
||||
return serve_file(r, &sb, res, true);
|
||||
}
|
||||
|
|
8
page.h
8
page.h
|
@ -1,7 +1,7 @@
|
|||
#ifndef PAGE_H
|
||||
#define PAGE_H
|
||||
|
||||
#include <libweb/http.h>
|
||||
#include "http.h"
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
@ -13,7 +13,7 @@ struct page_quota
|
|||
struct page_resource
|
||||
{
|
||||
struct http_response *r;
|
||||
const char *dir, *root, *res;
|
||||
const char *adir, *username, *dir, *root, *res;
|
||||
const struct page_quota *q;
|
||||
const struct http_arg *args;
|
||||
size_t n_args;
|
||||
|
@ -26,7 +26,7 @@ struct page_search
|
|||
char *name;
|
||||
} *results;
|
||||
|
||||
const char *root;
|
||||
const char *root, *adir, *username;
|
||||
size_t n;
|
||||
bool limit_exceeded;
|
||||
};
|
||||
|
@ -43,12 +43,12 @@ 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);
|
||||
int page_thumbnail(struct http_response *r, const char *res);
|
||||
|
||||
#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;
|
||||
struct server_client *prev, *next;
|
||||
} *c;
|
||||
};
|
||||
|
||||
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 (struct server_client *ref = s->c; ref; ref = ref->next)
|
||||
{
|
||||
if (c == ref)
|
||||
{
|
||||
struct server_client *const next = ref->next;
|
||||
|
||||
if ((ret = close(c->fd)))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2): %s\n",
|
||||
__func__, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
else if (ref->prev)
|
||||
ref->prev->next = next;
|
||||
else
|
||||
s->c = next;
|
||||
|
||||
if (next)
|
||||
next->prev = ref->prev;
|
||||
|
||||
free(ref);
|
||||
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;
|
||||
}
|
||||
|
||||
struct server_client *const c = malloc(sizeof *c);
|
||||
|
||||
if (!c)
|
||||
{
|
||||
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*c = (const struct server_client)
|
||||
{
|
||||
.fd = fd
|
||||
};
|
||||
|
||||
if (!s->c)
|
||||
s->c = c;
|
||||
else
|
||||
for (struct server_client *ref = s->c; ref; ref = ref->next)
|
||||
if (!ref->next)
|
||||
{
|
||||
ref->next = c;
|
||||
c->prev = ref;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t get_clients(const struct server *const s)
|
||||
{
|
||||
size_t ret = 0;
|
||||
|
||||
for (const struct server_client *c = s->c; c; c = c->next)
|
||||
ret++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct server_client *server_poll(struct server *const s, bool *const io,
|
||||
bool *const exit)
|
||||
{
|
||||
struct server_client *ret = NULL;
|
||||
const size_t n_clients = get_clients(s);
|
||||
const nfds_t n = n_clients + 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 (struct {const struct server_client *c; size_t j;}
|
||||
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
|
||||
{
|
||||
struct pollfd *const p = &fds[_.j];
|
||||
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;
|
||||
}
|
||||
else if (sfd->revents)
|
||||
{
|
||||
ret = alloc_client(s);
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (struct {struct server_client *c; size_t j;}
|
||||
_ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++)
|
||||
{
|
||||
const struct pollfd *const p = &fds[_.j];
|
||||
|
||||
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 */
|
|
@ -0,0 +1,76 @@
|
|||
#! /bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "$0 [-s <size>] [-h] -d <dir> <path>"
|
||||
}
|
||||
|
||||
SIZE=96
|
||||
|
||||
while getopts s:d:h arg
|
||||
do
|
||||
case $arg in
|
||||
d) DIR="$OPTARG"
|
||||
;;
|
||||
s) SIZE="$OPTARG"
|
||||
;;
|
||||
h) usage
|
||||
exit 0
|
||||
;;
|
||||
?) usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$DIR" ]
|
||||
then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
if [ $# != 1 ]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
F="$1"
|
||||
|
||||
gen()
|
||||
{
|
||||
IN="$1"
|
||||
OUT="$2"
|
||||
|
||||
if [ -z "$OUT" ]
|
||||
then
|
||||
echo Expected output filename >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
|
||||
if convert -thumbnail x$SIZE "$IN" "$OUT"
|
||||
then
|
||||
echo Created $OUT
|
||||
else
|
||||
echo Failed to create $OUT >&2
|
||||
fi
|
||||
}
|
||||
|
||||
while read f; do
|
||||
if [ "$f" != "" ]
|
||||
then
|
||||
THUMBNAIL=$(echo "$f" | sed "s,$DIR/user/,$DIR/thumbnails/,")
|
||||
gen "$f" "$THUMBNAIL"
|
||||
fi
|
||||
done <<-EOF
|
||||
$(find "$F" -type f \
|
||||
-a -iname '*.jpeg' \
|
||||
-o -iname '*.jpg' \
|
||||
-o -iname '*.png' \
|
||||
-o -iname '*.jpeg')
|
||||
EOF
|
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"
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
#! /bin/sh
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "$0 [-s <size>] [-r] [-h] [-d <subdir>] <dir>"
|
||||
}
|
||||
|
||||
REGEN=0
|
||||
SIZE=96
|
||||
|
||||
while getopts rs:d:h arg
|
||||
do
|
||||
case $arg in
|
||||
r) REGEN=1
|
||||
;;
|
||||
s) SIZE="$OPTARG"
|
||||
;;
|
||||
h) usage
|
||||
exit 0
|
||||
;;
|
||||
d) SUBDIR="$OPTARG"
|
||||
;;
|
||||
?) usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
if [ $# != 1 ]; then
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIR="$1"
|
||||
|
||||
[ "$REGEN" -eq 1 ] && "$(dirname $0)/tngen" \
|
||||
${SIZE:+-s$SIZE} \
|
||||
-d "$DIR" \
|
||||
"$DIR/user"
|
||||
|
||||
while :
|
||||
do
|
||||
F="$(inotifywait -e modify,move,create,delete \
|
||||
--format "%w%f" -qr "$DIR/user/")"
|
||||
sleep 1 # TODO: revisit this
|
||||
"$(dirname $0)/tngen" \
|
||||
${SIZE:+-s$SIZE} \
|
||||
-d "$DIR" \
|
||||
"$F"
|
||||
done
|
Loading…
Reference in New Issue