Compare commits

..

2 Commits

Author SHA1 Message Date
Xavier Del Campo Romero 05a76f2899
WIP add thumbnail CLI tools 2023-07-11 13:30:17 +02:00
Xavier Del Campo Romero aae26c510d
WIP thumbnail code 2023-07-11 13:30:17 +02:00
32 changed files with 3636 additions and 857 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,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)

38
Makefile Normal file
View File

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

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,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
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);

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 */

1964
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

436
main.c
View File

@ -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, &quota))
if (auth_quota(a, username, &has_quota, &quota))
{
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
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)
{
@ -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&nbsp;"))
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
View File

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

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 */

76
tngen Executable file
View File

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

52
watchdir Executable file
View File

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