Compare commits

..

1 Commits

Author SHA1 Message Date
Xavier Del Campo Romero d54e3ad322
Implement file/directory removal
The following workflow has been implemented:

- A new checkbox for each object inside a directory is shown.
- When one or more objects are selected, the user submits a request
through a HTML5 form.
- Then, slcl will ask the user for confirmation, listing the selected
objects, while reminding the user about the effects.
- The user confirms the selection.
- slcl removes the selected objects. All objects from non-empty
directories are removed, too.
- Finally, slcl redirects the user to the directory the request was
made from.
2023-07-08 03:06:13 +02:00
35 changed files with 3461 additions and 1204 deletions

1
.gitignore vendored
View File

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

6
.gitmodules vendored
View File

@ -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

View File

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

37
Makefile Normal file
View File

@ -0,0 +1,37 @@
.POSIX:
PROJECT = slcl
O = -Og
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
CFLAGS = $(O) $(CDEFS) -g -Wall -Idynstr/include -MD -MF $(@:.o=.d)
LIBS = -lcjson -lssl -lm -lcrypto
LDFLAGS = $(LIBS)
DEPS = $(OBJECTS:.o=.d)
DYNSTR = dynstr/libdynstr.a
DYNSTR_FLAGS = -Ldynstr -ldynstr
OBJECTS = \
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
server.o \
wildcard_cmp.o \
all: $(PROJECT)
clean:
rm -f $(OBJECTS) $(DEPS)
$(PROJECT): $(OBJECTS) $(DYNSTR)
$(CC) $(OBJECTS) $(LDFLAGS) $(DYNSTR_FLAGS) -o $@
$(DYNSTR):
+cd dynstr && $(MAKE)
-include $(DEPS)

View File

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

7
auth.c
View File

@ -1,7 +1,7 @@
#include "auth.h"
#include "hex.h"
#include "http.h"
#include "jwt.h"
#include <libweb/http.h>
#include <cjson/cJSON.h>
#include <dynstr.h>
#include <openssl/sha.h>
@ -9,7 +9,6 @@
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
@ -422,7 +421,7 @@ int auth_quota(const struct auth *const a, const char *const user,
*available = true;
*quota = strtoull(qs, &end, 10);
const unsigned long long mul = 1024ul * 1024ul;
const unsigned long long mul = 1024 * 1024;
if (errno || *end != '\0')
{
@ -436,7 +435,7 @@ int auth_quota(const struct auth *const a, const char *const user,
goto end;
}
*quota *= mul;
*quota *= 1024 * 1024;
break;
}
}

2
auth.h
View File

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

20
cftw.c
View File

@ -9,8 +9,8 @@
#include <stdint.h>
#include <string.h>
static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), bool *const done, void *const user)
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, void *), void *const user)
{
int ret = -1;
DIR *const d = opendir(dirpath);
@ -58,20 +58,20 @@ static int do_cftw(const char *const dirpath, int (*const fn)(const char *,
__func__, path, strerror(errno));
else if (S_ISDIR(sb.st_mode))
{
if ((ret = do_cftw(d.str, fn, done, user)))
if ((ret = cftw(d.str, fn, user)))
;
else if ((ret = fn(d.str, &sb, done, user)))
else if ((ret = fn(d.str, &sb, user)))
;
}
else if (S_ISREG(sb.st_mode))
ret = fn(d.str, &sb, done, user);
ret = fn(d.str, &sb, user);
else
fprintf(stderr, "%s: unexpected st_mode %ju\n",
__func__, (uintmax_t)sb.st_mode);
dynstr_free(&d);
if (ret || *done)
if (ret)
goto end;
}
@ -87,11 +87,3 @@ end:
return ret;
}
int cftw(const char *const dirpath, int (*const fn)(const char *,
const struct stat *, bool *, void *), void *const user)
{
bool done = false;
return do_cftw(dirpath, fn, &done, user);
}

3
cftw.h
View File

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

View File

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

168
configure vendored
View File

@ -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

View File

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

View File

@ -1,8 +0,0 @@
.POSIX:
PREFIX = /usr/local
all:
install: all
+cd man1 && $(MAKE) PREFIX=$(PREFIX) install

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

1
dynstr Submodule

@ -0,0 +1 @@
Subproject commit 5c13c9b8385cb2742f342d4d2f64d4e2d21108ba

315
handler.c Normal file
View File

@ -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;
}

24
handler.h Normal file
View File

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

301
html.c Normal file
View File

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

15
html.h Normal file
View File

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

1956
http.c Normal file

File diff suppressed because it is too large Load Diff

106
http.h Normal file
View File

@ -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 +0,0 @@
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f

565
main.c
View File

@ -2,12 +2,11 @@
#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>
@ -25,8 +24,6 @@
#include <stdlib.h>
#include <string.h>
#define STYLE_PATH "style.css"
struct form
{
char *key, *value;
@ -62,32 +59,7 @@ static int serve_index(const struct http_payload *const p,
static int serve_style(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const char *const dir = auth_dir(a);
struct dynstr d;
dynstr_init(&d);
if (!dir)
{
fprintf(stderr, "%s: auth_dir failed\n", __func__);
goto end;
}
else if (dynstr_append(&d, "%s/" STYLE_PATH, dir))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if ((ret = page_style(r, d.str)))
{
fprintf(stderr, "%s: page_style failed\n", __func__);
goto end;
}
end:
dynstr_free(&d);
return ret;
return page_style(r);
}
static char *alloc_form_data(const char *const s, const char **const end)
@ -135,8 +107,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 +133,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 +150,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 +180,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 +212,8 @@ static int get_forms(const struct http_payload *const pl,
ret = 0;
end:
free(dup);
if (ret)
forms_free(f, *outn);
@ -393,50 +358,24 @@ 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;
}
static bool path_invalid(const char *const path)
{
return path_isrel(path) || strchr(path, '*');
}
static bool filename_invalid(const char *const path)
{
return path_invalid(path) || strchr(path, '/');
}
static bool dirname_invalid(const char *const path)
{
return *path != '/' || path[strlen(path) - 1] != '/' || path_invalid(path);
}
static int getpublic(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
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,14 +385,7 @@ 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))
else if (path_isrel(p->resource))
{
fprintf(stderr, "%s: illegal relative path %s\n",
__func__, p->resource);
@ -588,14 +520,14 @@ static int check_search_input(const struct http_payload *const p,
*f = page_bad_request;
goto end;
}
else if (dirname_invalid(tdir))
else if (path_isrel(tdir) || *tdir != '/' || tdir[strlen(tdir) - 1] != '/')
{
fprintf(stderr, "%s: invalid directory %s\n", __func__, tdir);
ret = 1;
*f = page_bad_request;
goto end;
}
else if (path_isrel(tres))
else if (strchr(tres, '/') || path_isrel(tres))
{
fprintf(stderr, "%s: invalid resource %s\n", __func__, tres);
ret = 1;
@ -644,9 +576,8 @@ struct search_args
};
static int search_fn(const char *const fpath, const struct stat *const sb,
bool *const done, void *const user)
void *const user)
{
static const size_t limit = 200;
const struct search_args *const sa = user;
const char *rel = fpath + strlen(sa->root);
struct page_search *const res = sa->s;
@ -676,13 +607,7 @@ static int search_fn(const char *const fpath, const struct stat *const sb,
}
res->results = results;
if (++res->n >= limit)
{
sa->s->limit_exceeded = true;
*done = true;
}
res->n++;
return 0;
failure:
@ -816,7 +741,7 @@ static int share(const struct http_payload *const p,
const char *const path = forms->value, *const username = p->cookie.field;
if (path_invalid(path))
if (path_isrel(path))
{
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
ret = page_bad_request(r);
@ -842,7 +767,7 @@ end:
}
static int add_length(const char *const fpath, const struct stat *const sb,
bool *const done, void *const user)
void *const user)
{
if (!S_ISREG(sb->st_mode))
return 0;
@ -911,16 +836,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, &quota))
if (auth_quota(a, username, &has_quota, &quota))
{
fprintf(stderr, "%s: auth_quota failed\n", __func__);
return -1;
@ -954,7 +870,7 @@ static int getnode(const struct http_payload *const p,
const char *const username = p->cookie.field,
*const resource = p->resource + strlen("/user/");
if (path_invalid(resource))
if (path_isrel(resource))
{
fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
return page_forbidden(r);
@ -1022,113 +938,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 +992,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 +1019,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 +1032,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 +1046,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 +1088,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)
{
@ -1326,23 +1124,14 @@ static int upload_files(const struct http_payload *const p,
return 0;
}
else if (dirname_invalid(dir))
{
fprintf(stderr, "%s: invalid directory %s\n", __func__, dir);
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 +1147,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 +1168,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 +1194,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++)
{
@ -1430,13 +1218,13 @@ static int createdir(const struct http_payload *const p,
ret = page_bad_request(r);
goto end;
}
else if (filename_invalid(name))
else if (path_isrel(name) || strpbrk(name, "/*"))
{
fprintf(stderr, "%s: invalid directory name %s\n", __func__, dir);
ret = page_bad_request(r);
goto end;
}
else if (dirname_invalid(dir))
else if (path_isrel(dir) || strchr(dir, '*'))
{
fprintf(stderr, "%s: invalid name %s\n", __func__, name);
ret = page_bad_request(r);
@ -1493,12 +1281,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 +1294,6 @@ end:
forms_free(forms, n);
dynstr_free(&userd);
dynstr_free(&d);
free(encurl);
return ret;
}
@ -1536,15 +1318,6 @@ static int check_rm_input(const struct form *const forms, const size_t n,
}
else if (!strcmp(f->key, "path"))
{
const char *const path = f->value;
if (path_isrel(path))
{
fprintf(stderr, "%s: invalid path %s\n", __func__, rm->dir);
*cb = page_bad_request;
return 1;
}
const char **tmp = realloc(rm->items, (rm->n + 1) * sizeof *tmp);
if (!tmp)
@ -1554,7 +1327,7 @@ static int check_rm_input(const struct form *const forms, const size_t n,
return -1;
}
tmp[rm->n++] = path;
tmp[rm->n++] = f->value;
rm->items = tmp;
}
}
@ -1565,7 +1338,7 @@ static int check_rm_input(const struct form *const forms, const size_t n,
*cb = page_bad_request;
return 1;
}
else if (dirname_invalid(rm->dir))
else if (*rm->dir != '/' || path_isrel(rm->dir))
{
fprintf(stderr, "%s: invalid directory %s\n", __func__, rm->dir);
*cb = page_bad_request;
@ -1651,7 +1424,7 @@ static const char *find_rm_dir(const struct form *const forms, const size_t n,
}
static int rm_dir_contents(const char *const fpath,
const struct stat *const sb, bool *const done, void *const user)
const struct stat *const sb, void *const user)
{
if (S_ISDIR(sb->st_mode) && rmdir(fpath))
{
@ -1753,20 +1526,10 @@ static int do_rm(const struct form *const forms, const size_t n,
{
const struct form *const f = &forms[i];
if (!strcmp(f->key, "path"))
if (!strcmp(f->key, "path") && rm_item(dir, f->value))
{
const char *const path = f->value;
if (path_isrel(path))
{
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
return 1;
}
else if (rm_item(dir, f->value))
{
fprintf(stderr, "%s: rm_item failed\n", __func__);
return -1;
}
fprintf(stderr, "%s: rm_item failed\n", __func__);
return -1;
}
}
@ -1817,7 +1580,7 @@ static int rm(const struct http_payload *const p,
ret = f(r);
goto end;
}
else if (dirname_invalid(dir))
else if (*dir != '/' || path_isrel(dir))
{
fprintf(stderr, "%s: invalid directory %s\n", __func__, dir);
ret = page_bad_request(r);
@ -1994,120 +1757,6 @@ end:
return ret;
}
static int dump_default_style(const char *const path)
{
int ret = -1;
FILE *const f = fopen(path, "wb");
if (!f)
{
fprintf(stderr, "%s: fopen(3) %s: %s\n",
__func__, path, strerror(errno));
goto end;
}
else if (!fwrite(style_default, style_default_len, 1, f))
{
fprintf(stderr, "%s: fwrite(3): %s\n", __func__, strerror(errno));
goto end;
}
printf("Dumped default stylesheet into %s\n", path);
ret = 0;
end:
if (f && fclose(f))
{
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
ret = -1;
}
return ret;
}
static int ensure_style(const char *const dir)
{
int ret = -1;
struct dynstr d;
struct stat sb;
dynstr_init(&d);
if (dynstr_append(&d, "%s/" STYLE_PATH, dir))
{
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
goto end;
}
else if (stat(d.str, &sb))
{
switch (errno)
{
case ENOENT:
if (dump_default_style(d.str))
{
fprintf(stderr, "%s: dump_default_style failed\n",
__func__);
goto end;
}
break;
default:
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
goto end;
}
}
else if (!S_ISREG(sb.st_mode))
{
fprintf(stderr, "%s: %s not a regular file\n", __func__, d.str);
return -1;
}
ret = 0;
end:
dynstr_free(&d);
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;
@ -2118,7 +1767,6 @@ int main(int argc, char *argv[])
if (parse_args(argc, argv, &dir, &port, &tmpdir)
|| init_dirs(dir)
|| ensure_style(dir)
|| !(a = auth_alloc(dir)))
goto end;
@ -2126,31 +1774,26 @@ 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, NULL)
|| 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_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:

468
page.c
View File

@ -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)
{
@ -108,7 +102,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 +116,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 +137,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 +297,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 +309,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,13 +329,11 @@ 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)
const char *const res, const char *const name)
{
int ret = -1;
enum {RM_CHECKBOX, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
@ -392,7 +363,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,15 +370,7 @@ 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 (strcmp(name, "..")
&& prepare_rm_checkbox(td[RM_CHECKBOX], &sb, name))
{
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
@ -971,7 +933,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&nbsp;"))
else if (dynstr_append(&d, "Powered by "))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
@ -1073,12 +1035,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 +1051,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 +1071,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;
@ -1152,8 +1118,7 @@ end:
}
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)
const char *const dir, struct html_node *const table)
{
int ret = -1;
struct dirent **pde = NULL;
@ -1165,8 +1130,6 @@ 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];
@ -1175,7 +1138,7 @@ static int add_elements(const char *const root, const char *const res,
if (!strcmp(name, ".")
|| (!strcmp(name, "..") && !strcmp(root, res)))
continue;
else if (add_element(table, dir, res, name, true, st))
else if (add_element(table, dir, res, name))
{
fprintf(stderr, "%s: add_element failed\n", __func__);
goto end;
@ -1193,47 +1156,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 +1170,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->root, pr->res, pr->dir, 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 +1212,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 +1237,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 +1320,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 +1384,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 +1410,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 +1419,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;
}
@ -1671,45 +1535,73 @@ int page_bad_request(struct http_response *const r)
return 0;
}
int page_style(struct http_response *const r, const char *const path)
int page_style(struct http_response *const r)
{
FILE *f = NULL;
struct stat sb;
if (stat(path, &sb))
{
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
goto failure;
}
else if (!(f = fopen(path, "rb")))
{
fprintf(stderr, "%s: fopen(3) %s: %s\n",
__func__, path, strerror(errno));
goto failure;
}
static const char body[] =
"body\n"
"{\n"
" font-family: 'Courier New', Courier, monospace;\n"
"}\n"
"td\n"
"{\n"
" font-size: 14px;\n"
"}\n"
"a\n"
"{\n"
" text-decoration: none;\n"
"}\n"
".userform\n"
"{\n"
" padding: 4px;\n"
"}\n"
".loginform\n"
"{\n"
" display: grid;\n"
"}\n"
"form, label, table\n"
"{\n"
" margin: auto;\n"
"}\n"
"div\n"
"{\n"
" align-items: center;\n"
" display: grid;\n"
"}\n"
"input, .abutton\n"
"{\n"
" margin: auto;\n"
" border: 1px solid;\n"
" border-radius: 8px;\n"
"}\n"
"header, footer\n"
"{\n"
" display: flex;\n"
" justify-content: center;\n"
" text-decoration: auto;\n"
"}\n"
"table\n"
"{\n"
" max-width: 50%;\n"
"}\n"
"tr:nth-child(even)\n"
"{\n"
" background-color: lightgray;\n"
"}\n";
*r = (const struct http_response)
{
.status = HTTP_STATUS_OK,
.f = f,
.n = sb.st_size
.buf.ro = body,
.n = sizeof body - 1
};
if (http_response_add_header(r, "Content-Type", "text/css"))
{
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
goto failure;
return -1;
}
return 0;
failure:
if (f && fclose(f))
fprintf(stderr, "%s: fclose(3) %s: %s\n",
__func__, path, strerror(errno));
return -1;
}
int page_share(struct http_response *const r, const char *const path)
@ -1892,7 +1784,7 @@ static int add_search_results(struct html_node *const n,
{
const struct page_search_result *const r = &s->results[i];
if (add_element(table, "/user/", s->root, r->name, false, NULL))
if (add_element(table, "/user/", s->root, r->name))
{
fprintf(stderr, "%s: add_element failed\n", __func__);
return -1;
@ -1902,20 +1794,36 @@ static int add_search_results(struct html_node *const n,
return 0;
}
static int prepare_search_limit_exceeded(struct html_node *const n,
const struct page_search *const s)
static int prepare_back_button(struct html_node *const n)
{
static const char msg[] = "Too many results were found. Please "
"set up a more specific search term in order to provide "
"fewer and more relevant results";
struct html_node *const p = html_node_add_child(n, "p");
struct html_node *div, *a;
if (!p)
if (!(div = html_node_add_child(n, "div")))
{
fprintf(stderr, "%s: html_node_add_child p failed\n", __func__);
fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
return -1;
}
else if (html_node_set_value(p, msg))
else if (!(a = html_node_add_child(div, "a")))
{
fprintf(stderr, "%s: html_node_add_child a failed\n", __func__);
return -1;
}
else if (html_node_add_attr(div, "class", "userform"))
{
fprintf(stderr, "%s: html_node_add_attr div failed\n", __func__);
return -1;
}
else if (html_node_add_attr(a, "class", "abutton"))
{
fprintf(stderr, "%s: html_node_add_attr a class failed\n", __func__);
return -1;
}
else if (html_node_add_attr(a, "href", "/user/"))
{
fprintf(stderr, "%s: html_node_add_attr a href failed\n", __func__);
return -1;
}
else if (html_node_set_value(a, "Back"))
{
fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
return -1;
@ -1924,54 +1832,6 @@ static int prepare_search_limit_exceeded(struct html_node *const n,
return 0;
}
static int prepare_search_result_count(struct html_node *const n,
const struct page_search *const s)
{
int ret = -1;
struct html_node *const p = html_node_add_child(n, "p");
struct dynstr d;
dynstr_init(&d);
if (!p)
{
fprintf(stderr, "%s: html_node_add_child p failed\n", __func__);
goto end;
}
else if (s->limit_exceeded && prepare_search_limit_exceeded(n, s))
{
fprintf(stderr, "%s: prepare_search_limit_exceeded msg failed\n",
__func__);
goto end;
}
else if (!s->n && html_node_set_value(p, "No results found"))
{
fprintf(stderr, "%s: html_node_set_value msg failed\n", __func__);
goto end;
}
else if (s->n)
{
const char *const results = s->n == 1 ? "result" : "results";
if (dynstr_append(&d, "%zu %s found", s->n, results))
{
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 p failed\n", __func__);
goto end;
}
}
ret = 0;
end:
dynstr_free(&d);
return ret;
}
int page_search(struct http_response *const r,
const struct page_search *const s)
{
@ -2006,9 +1866,14 @@ int page_search(struct http_response *const r,
fprintf(stderr, "%s: add_search_results failed\n", __func__);
goto end;
}
else if (prepare_search_result_count(body, s))
else if (!s->n && html_node_set_value(body, "No results found"))
{
fprintf(stderr, "%s: prepare_limit_exceeded failed\n", __func__);
fprintf(stderr, "%s: html_node_set_value msg failed\n", __func__);
goto end;
}
else if (prepare_back_button(body))
{
fprintf(stderr, "%s: prepare_back_button failed\n", __func__);
goto end;
}
else if (prepare_footer(body))
@ -2275,36 +2140,3 @@ end:
return ret;
}
int page_head_resource(struct http_response *const r, const char *const res)
{
struct stat sb;
if (stat(res, &sb))
{
if (errno == ENOENT || errno == ENOTDIR)
return page_not_found(r);
else
{
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, res, strerror(errno));
return -1;
}
}
const mode_t m = sb.st_mode;
if (!S_ISDIR(m) && !S_ISREG(m))
{
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
return -1;
}
*r = (const struct http_response)
{
.status = HTTP_STATUS_OK,
.n = sb.st_size
};
return 0;
}

7
page.h
View File

@ -1,8 +1,7 @@
#ifndef PAGE_H
#define PAGE_H
#include <libweb/http.h>
#include <stdbool.h>
#include "http.h"
#include <stddef.h>
struct page_quota
@ -28,7 +27,6 @@ struct page_search
const char *root;
size_t n;
bool limit_exceeded;
};
struct page_rm
@ -38,12 +36,11 @@ struct page_rm
};
int page_login(struct http_response *r);
int page_style(struct http_response *r, const char *path);
int page_style(struct http_response *r);
int page_failed_login(struct http_response *r);
int page_forbidden(struct http_response *r);
int page_bad_request(struct http_response *r);
int page_resource(const struct page_resource *r);
int page_head_resource(struct http_response *r, const char *res);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,

373
server.c Normal file
View File

@ -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;
}

15
server.h Normal file
View File

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

55
style.c
View File

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

View File

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

32
usergen
View File

@ -7,23 +7,6 @@ usage()
echo "$0 <dir>"
}
to_hex()
{
od -An -t x1 | tr -d ' ' | tr -d '\n'
}
to_bin()
{
sed -e 's,\([0-9a-f]\{2\}\),\\\\\\x\1,g' | xargs printf
}
mktemp_posix()
{
m4 <<EOF
mkstemp(${TMPDIR:-/tmp}/tmp.XXXXXX)
EOF
}
if [ $# != 1 ]; then
usage >&2
exit 1
@ -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"

View File

@ -30,8 +30,7 @@ int wildcard_cmp(const char *s, const char *p, const bool casecmp)
return r;
}
const size_t auxn = wc - p, rem = strlen(s),
n = auxn > rem ? rem : auxn;
const size_t n = wc - p;
if (n)
{