Compare commits

...

2 Commits

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

1
.gitignore vendored
View File

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

3
.gitmodules vendored
View File

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

View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.13)
project(slcl)
option(THUMBNAILS "Enables thumbnail generation for images." OFF)
add_executable(${PROJECT_NAME}
auth.c
base64.c
@ -13,9 +14,26 @@ add_executable(${PROJECT_NAME}
page.c
server.c
)
if(NOT DEFINED THUMBNAIL_HEIGHT)
set(THUMBNAIL_HEIGHT 96)
endif()
if(THUMBNAILS)
find_package(ImageMagick REQUIRED)
target_sources(${PROJECT_NAME} PRIVATE thumbnail.c)
target_compile_definitions(${PROJECT_NAME}
PRIVATE THUMBNAIL_HEIGHT=${THUMBNAIL_HEIGHT})
elseif(THUMBNAIL_HEIGHT)
target_sources(${PROJECT_NAME} PRIVATE thumbnail_stub.c)
endif()
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
add_subdirectory(dynstr)
add_subdirectory(mkdir_r)
find_package(cJSON 1.0 REQUIRED)
find_package(OpenSSL 3.0 REQUIRED)
target_link_libraries(${PROJECT_NAME} PRIVATE dynstr cjson OpenSSL::SSL)
target_link_libraries(${PROJECT_NAME}
PRIVATE dynstr mkdir_r cjson OpenSSL::SSL ImageMagick)
#message(FATAL_ERROR "TODO")

View File

@ -1,36 +0,0 @@
.POSIX:
.SUFFIXES: .c .o
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 -
LIBS = -lcjson -lssl -lm -lcrypto
LDFLAGS = $(LIBS)
DEPS = $(OBJECTS:.o=.d)
OBJECTS = \
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
server.o \
dynstr/dynstr.o
all: $(PROJECT)
clean:
rm -f $(OBJECTS) $(DEPS)
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) -o $@
.c.o:
$(CC) $(CFLAGS) -c $< -o $@ > $(@:.o=.d)
-include $(DEPS)

View File

@ -16,6 +16,8 @@ simplicity and efficiency.
- Its own, tiny HTTP/1.0 and 1.1-compatible server.
- A simple JSON file as the credentials database.
- No JavaScript.
- Optional, resizable thumbnails (made available by
[GraphicsMagick](http://www.graphicsmagick.org/)).
### TLS
@ -42,9 +44,12 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
- cJSON >= 1.7.15.
- [`dynstr`](https://gitea.privatedns.org/xavi92/dynstr)
(provided as a `git` submodule).
- [`mkdir_r`](https://gitea.privatedns.org/xavi92/mkdir_r)
(provided as a `git` submodule).
- `xxd` (for [`usergen`](usergen) only).
- `jq` (for [`usergen`](usergen) only).
- CMake (optional).
- [GraphicsMagick](http://www.graphicsmagick.org/) >= 1.4 (optional).
### Ubuntu / Debian
@ -57,7 +62,7 @@ sudo apt install build-essential libcjson-dev libssl-dev
#### Optional packages
```sh
sudo apt install cmake xxd jq
sudo apt install cmake xxd jq libgraphicsmagick1-dev
```
## How to use
@ -66,7 +71,8 @@ sudo apt install cmake xxd jq
Two build environments are provided for `slcl` - feel free to choose any of
them:
- A mostly POSIX-compliant [`Makefile`](Makefile).
- A [`configure`](configure) POSIX shell script that generates a mostly
POSIX-compliant `Makefile`.
- A [`CMakeLists.txt`](CMakeLists.txt).
`slcl` can be built using the standard build process:
@ -74,6 +80,7 @@ them:
#### Make
```sh
$ ./configure
$ make
```

131
configure vendored Executable file
View File

@ -0,0 +1,131 @@
#! /bin/sh
set -e
DEFAULT_PREFIX=/usr/local
DEFAULT_THUMBNAIL_HEIGHT=96
usage()
{
printf "$0 [OPTION]...\n\n"
printf "Configuration:\n"
printf "%s\t\t\t" "--prefix=PREFIX"
printf "Sets installation prefix (default: $DEFAULT_PREFIX).\n"
printf "%s\t\t\t" "--thumbnails"
printf "Enables thumbnail generation for images.\n"
printf "%s\t" "--thumbnail-height=HEIGHT"
printf "Sets thumbnail height, in pixels "
printf "(default: $DEFAULT_THUMBNAIL_HEIGHT). "
printf "Requires --thumbnails.\n"
printf "%s\t\t\t" "-h|--help"
printf "Prints this help page.\n"
}
CC=${CC:-cc}
CFLAGS=${CFLAGS:-}
for arg
do
case "$arg" in
--thumbnails)
THUMBNAILS=1
;;
--thumbnail-height=*)
THUMBNAIL_HEIGHT=${arg#*=}
;;
--prefix=*)
PREFIX=${arg#*=}
;;
-h | --help)
usage
exit 0
;;
*)
echo "Invalid argument $arg" >&2
usage >&2
exit 1
;;
esac
done
THUMBNAILS=${THUMBNAILS:-0}
THUMBNAIL_HEIGHT=${THUMBNAIL_HEIGHT:-$DEFAULT_THUMBNAIL_HEIGHT}
PREFIX=${PREFIX:-$DEFAULT_PREFIX}
OBJECTS="\
auth.o \
base64.o \
cftw.o \
handler.o \
hex.o \
html.o \
http.o \
jwt.o \
main.o \
page.o \
server.o \
dynstr/dynstr.o"
LIBS="\
-lcjson \
-lssl \
-lm \
-lcrypto"
CFLAGS=${CFLAGS:-'$(O) $(CDEFS) -g -Wall -Idynstr/include -Imkdir_r -MD -MF -'}
if [ $THUMBNAILS -ne 0 ]
then
LIBS="$LIBS $(GraphicsMagick-config --libs)"
CFLAGS="$CFLAGS $(GraphicsMagick-config --cppflags |
tr '\n' ' ')"
CFLAGS="$CFLAGS -DTHUMBNAIL_HEIGHT=$THUMBNAIL_HEIGHT"
CFLAGS="$CFLAGS -Imkdir_r/private_include"
LDFLAGS="$LDFLAGS $(GraphicsMagick-config --ldflags)"
OBJECTS="$OBJECTS \
thumbnail.o \
mkdir_r/mkdir_r.o \
mkdir_r/posix.o"
else
OBJECTS="$OBJECTS thumbnail_stub.o mkdir_r_stub.o"
fi
cat > Makefile <<"EOF"
.POSIX:
.SUFFIXES: .c .o
PROJECT = slcl
O = -Og
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
DEPS = $(OBJECTS:.o=.d)
EOF
cat >> Makefile <<EOF
CFLAGS = $CFLAGS
LIBS = $LIBS
LDFLAGS = $LDFLAGS
OBJECTS = $OBJECTS
PREFIX = $PREFIX
EOF
cat >> Makefile <<"EOF"
all: $(PROJECT)
clean:
rm -f $(OBJECTS) $(DEPS)
install: $(PROJECT)
mkdir -p $(PREFIX) $(PREFIX)/bin
cp $(PROJECT) $(PREFIX)/bin
chmod 0755 $(PREFIX)/bin/$(PROJECT)
+cd doc && $(MAKE) install PREFIX=$(PREFIX)
$(PROJECT): $(OBJECTS)
$(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) -o $@
.c.o:
$(CC) $(CFLAGS) -c $< -o $@ > $(@:.o=.d)
-include $(DEPS)
EOF

14
doc/Makefile Normal file
View File

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

222
main.c
View File

@ -5,7 +5,9 @@
#include "handler.h"
#include "hex.h"
#include "http.h"
#include "mkdir_r.h"
#include "page.h"
#include "thumbnail.h"
#include <openssl/err.h>
#include <openssl/rand.h>
#include <dynstr.h>
@ -643,58 +645,54 @@ static int check_length(const unsigned long long len,
static int getnode(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
int ret = -1;
struct auth *const a = user;
const char *adir, *const username = p->cookie.field;
struct dynstr d;
dynstr_init(&d);
if (auth_cookie(a, &p->cookie))
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
return page_forbidden(r);
ret = page_forbidden(r);
goto end;
}
const char *const username = p->cookie.field,
*const resource = p->resource + strlen("/user/");
if (path_isrel(resource))
else if (path_isrel(p->resource))
{
fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
return page_forbidden(r);
fprintf(stderr, "%s: illegal relative path %s\n",
__func__, p->resource);
ret = page_forbidden(r);
goto end;
}
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)
else if (!(adir = auth_dir(a)))
{
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))
else if (dynstr_append(&d, "%s/user/%s/", adir, username))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
const struct page_resource res =
{
.root = adir,
.username = username,
.user_root = d.str,
.res = p->resource + strlen("/user")
};
bool available;
unsigned long long cur, max;
if (auth_quota(a, username, &available, &max))
if (auth_quota(a, res.username, &available, &max))
{
fprintf(stderr, "%s: quota_available failed\n", __func__);
goto end;
}
else if (available && quota_current(a, username, &cur))
else if (available && quota_current(a, res.username, &cur))
{
fprintf(stderr, "%s: quota_current failed\n", __func__);
goto end;
@ -707,12 +705,10 @@ static int getnode(const struct http_payload *const p,
.max = max
} : NULL;
ret = page_resource(r, dir.str, root.str, d.str, ppq);
ret = page_resource(r, &res, ppq);
end:
dynstr_free(&dir);
dynstr_free(&d);
dynstr_free(&root);
return ret;
}
@ -797,25 +793,84 @@ static int rename_or_move(const char *const old, const char *const new)
return res;
}
static int ensure_dir(const char *const dir)
{
struct stat sb;
if (stat(dir, &sb))
{
switch (errno)
{
case ENOENT:
if (mkdir(dir, S_IRWXU))
{
fprintf(stderr, "%s: mkdir(2) %s: %s\n",
__func__, dir, strerror(errno));
return -1;
}
printf("Created empty directory at %s\n", dir);
break;
default:
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
return -1;
}
}
else if (!S_ISDIR(sb.st_mode))
{
fprintf(stderr, "%s: %s not a directory\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 d;
struct dynstr d, dird, th;
dynstr_init(&d);
dynstr_init(&dird);
dynstr_init(&th);
if (!root)
{
fprintf(stderr, "%s: auth_dir failed\n", __func__);
goto end;
}
else if (dynstr_append(&d, "%s/user/%s/%s%s", root, user, dir, f->filename))
else if (dynstr_append(&d, "%s/user/%s%s%s", root, user, dir, f->filename))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if (rename_or_move(f->tmpname, d.str))
else if (thumbnail_configured())
{
if (dynstr_append(&dird, "%s/thumbnails/%s%s", root, user, dir))
{
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
goto end;
}
else if (dynstr_append(&th, "%s%s", dird.str, f->filename))
{
fprintf(stderr, "%s: dynstr_append th failed\n", __func__);
goto end;
}
else if (mkdir_r(dird.str, S_IRWXU))
{
fprintf(stderr, "%s: ensure_dir failed\n", __func__);
goto end;
}
else if (thumbnail_create(f->tmpname, th.str) < 0)
{
fprintf(stderr, "%s: thumbnail_create failed\n", __func__);
goto end;
}
}
if (rename_or_move(f->tmpname, d.str))
{
fprintf(stderr, "%s: rename_or_move failed\n", __func__);
goto end;
@ -825,6 +880,8 @@ static int upload_file(const struct http_post_file *const f,
end:
dynstr_free(&d);
dynstr_free(&dird);
dynstr_free(&th);
return ret;
}
@ -1129,47 +1186,74 @@ static int parse_args(const int argc, char *const argv[],
return 0;
}
static int ensure_dir(const char *const dir)
static int getthumbnail(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
struct stat sb;
int ret = -1;
struct auth *const a = user;
struct dynstr d, dir;
char *name;
if (stat(dir, &sb))
dynstr_init(&d);
dynstr_init(&dir);
if (auth_cookie(a, &p->cookie))
{
switch (errno)
{
case ENOENT:
if (mkdir(dir, S_IRWXU))
{
fprintf(stderr, "%s: mkdir(2) %s: %s\n",
__func__, dir, strerror(errno));
return -1;
}
printf("Created empty directory at %s\n", dir);
break;
default:
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
return -1;
}
}
else if (!S_ISDIR(sb.st_mode))
{
fprintf(stderr, "%s: %s not a directory\n", __func__, dir);
return -1;
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
ret = page_forbidden(r);
goto end;
}
return 0;
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 (!(name = dirname(dir.str)))
{
fprintf(stderr, "%s: dirname(3) failed\n", __func__);
goto end;
}
else if (ensure_dir(name))
{
fprintf(stderr, "%s: ensure_dir 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 int init_dirs(const char *const dir)
{
int ret = -1;
struct dynstr user, public;
struct dynstr user, public, thumbnails;
struct sb;
dynstr_init(&user);
dynstr_init(&public);
dynstr_init(&thumbnails);
if (dynstr_append(&user, "%s/user", dir))
{
@ -1196,12 +1280,26 @@ static int init_dirs(const char *const dir)
fprintf(stderr, "%s: ensure_dir public failed\n", __func__);
goto end;
}
if (thumbnail_configured())
{
if (dynstr_append(&thumbnails, "%s/thumbnails", dir))
{
fprintf(stderr, "%s: dynstr_append thumbnails failed\n", __func__);
goto end;
}
else if (ensure_dir(thumbnails.str))
{
fprintf(stderr, "%s: ensure_dir thumbnails failed\n", __func__);
goto end;
}
}
ret = 0;
end:
dynstr_free(&user);
dynstr_free(&public);
dynstr_free(&thumbnails);
return ret;
}
@ -1237,6 +1335,8 @@ int main(const int argc, char *const argv[])
|| 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)
|| (thumbnail_configured()
&& handler_add(h, "/thumbnails/*", HTTP_OP_GET, getthumbnail, a))
|| handler_listen(h, port))
goto end;

1
mkdir_r Submodule

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

254
page.c
View File

@ -3,6 +3,7 @@
#include "page.h"
#include "http.h"
#include "html.h"
#include "thumbnail.h"
#include <dynstr.h>
#include <dirent.h>
#include <fcntl.h>
@ -45,7 +46,102 @@
" </form>\n"
#define MAXSIZEFMT "18446744073709551615.0 XiB"
static int prepare_name(struct html_node *const n, struct stat *const sb,
static const char *dir_separator(const char *const dir)
{
return dir[strlen(dir) - 1] != '/' ? "/" : "";
}
static int prepare_thumbnail(struct html_node *const n,
const struct stat *const sb,
const struct page_resource *const r, const char *const name)
{
int ret = -1, res;
const char *const sep = dir_separator(r->res);
struct html_node *img;
struct dynstr abs, rel;
struct stat tsb;
struct thumbnail_dim dim;
char w[sizeof "18446744073709551615"], h[sizeof w];
dynstr_init(&abs);
dynstr_init(&rel);
if (!S_ISREG(sb->st_mode))
{
ret = 0;
goto end;
}
else if (dynstr_append(&rel, "/thumbnails/%s%s%s%s",
r->username, r->res, sep, name))
{
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
goto end;
}
else if (dynstr_append(&abs, "%s%s", r->root, 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 (thumbnail_dim(abs.str, &dim))
{
fprintf(stderr, "%s: thumbnail_dim failed\n", __func__);
goto end;
}
res = snprintf(w, sizeof w, "%lu", dim.w);
if (res < 0 || res >= sizeof w)
{
fprintf(stderr, "%s: snprintf(3) w failed with %d\n", __func__, res);
goto end;
}
res = snprintf(h, sizeof h, "%lu", dim.h);
if (res < 0 || res >= sizeof h)
{
fprintf(stderr, "%s: snprintf(3) h failed with %d\n", __func__, res);
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;
}
else if (html_node_add_attr(img, "width", w))
{
fprintf(stderr, "%s: html_node_add_attr w failed\n", __func__);
goto end;
}
else if (html_node_add_attr(img, "height", h))
{
fprintf(stderr, "%s: html_node_add_attr h 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, const struct stat *const sb,
const char *const dir, const char *const name)
{
int ret = -1;
@ -169,7 +265,7 @@ static int prepare_share(struct html_node *const n,
const struct stat *const sb, const char *const dir, const char *const name)
{
int ret = -1;
const char *const fdir = dir + strlen("/user");
const char *const sep = dir_separator(dir);
struct html_node *form, *file, *submit;
struct dynstr d;
@ -213,7 +309,7 @@ static int prepare_share(struct html_node *const n,
fprintf(stderr, "%s: html_node_add_attr file name failed\n", __func__);
goto end;
}
else if (dynstr_append(&d, "%s%s", fdir, name))
else if (dynstr_append(&d, "%s%s%s", dir, sep, name))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
@ -243,18 +339,37 @@ end:
return ret;
}
static int add_element(struct html_node *const n, const char *const dir,
const char *const res, const char *const name)
static int user_resource(const struct page_resource *const r,
struct dynstr *const d)
{
if (dynstr_append(d, "%s/user/%s%s", r->root, r->username, r->res))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
return -1;
}
return 0;
}
static int add_element(const struct page_resource *const r,
struct html_node *const n, const char *const absres,
const char *const name)
{
int ret = -1;
enum {NAME, SIZE, DATE, SHARE, COLUMNS};
enum {THUMBNAIL, NAME, SIZE, DATE, SHARE, COLUMNS};
const char *const sep = dir_separator(r->res);
struct html_node *tr, *td[COLUMNS];
struct dynstr path;
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
struct dynstr path, rel;
dynstr_init(&path);
dynstr_init(&rel);
if (dynstr_append(&path, "%s%s%s", res, sep, name))
if (dynstr_append(&path, "%s%s%s", absres, sep, name))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if (dynstr_append(&rel, "/user%s%s", r->res, sep))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
@ -266,7 +381,9 @@ static int add_element(struct html_node *const n, const char *const dir,
}
for (size_t i = 0; i < sizeof td / sizeof *td; i++)
if (!(td[i] = html_node_add_child(tr, "td")))
if (i == THUMBNAIL && !thumbnail_configured())
continue;
else if (!(td[i] = html_node_add_child(tr, "td")))
{
fprintf(stderr, "%s: html_node_add_child td[%zu] failed\n",
__func__, i);
@ -281,7 +398,13 @@ static int add_element(struct html_node *const n, const char *const dir,
__func__, path.str, strerror(errno));
goto end;
}
else if (prepare_name(td[NAME], &sb, dir, name))
else if (thumbnail_configured()
&& prepare_thumbnail(td[THUMBNAIL], &sb, r, name))
{
fprintf(stderr, "%s: prepare_thumbnail failed\n", __func__);
goto end;
}
else if (prepare_name(td[NAME], &sb, rel.str, name))
{
fprintf(stderr, "%s: prepare_name failed\n", __func__);
goto end;
@ -296,7 +419,7 @@ 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, rel.str, name))
{
fprintf(stderr, "%s: prepare_date failed\n", __func__);
goto end;
@ -306,6 +429,7 @@ static int add_element(struct html_node *const n, const char *const dir,
end:
dynstr_free(&path);
dynstr_free(&rel);
return ret;
}
@ -743,7 +867,6 @@ end:
static struct html_node *resource_layout(const char *const dir,
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, *body;
@ -767,17 +890,17 @@ static struct html_node *resource_layout(const char *const dir,
fprintf(stderr, "%s: html_node_add_child table failed\n", __func__);
goto end;
}
else if (common_head(head, fdir))
else if (common_head(head, dir))
{
fprintf(stderr, "%s: common_head failed\n", __func__);
goto end;
}
else if (prepare_upload_form(body, fdir))
else if (prepare_upload_form(body, dir))
{
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
goto end;
}
else if (prepare_mkdir_form(body, fdir))
else if (prepare_mkdir_form(body, dir))
{
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
goto end;
@ -808,14 +931,21 @@ end:
return ret;
}
static int add_elements(const char *const root, const char *const res,
const char *const dir, struct html_node *const table)
static int add_elements(const struct page_resource *const r,
struct html_node *const table)
{
int ret = -1;
int ret = -1, n = 0;
struct dynstr d;
struct dirent **pde = NULL;
const int n = scandir(res, &pde, NULL, alphasort);
if (n < 0)
dynstr_init(&d);
if (user_resource(r, &d))
{
fprintf(stderr, "%s: user_resource failed\n", __func__);
goto end;
}
else if ((n = scandir(d.str, &pde, NULL, alphasort)) < 0)
{
fprintf(stderr, "%s: scandir(3): %s\n", __func__, strerror(errno));
goto end;
@ -827,9 +957,9 @@ static int add_elements(const char *const root, const char *const res,
const char *const name = de->d_name;
if (!strcmp(name, ".")
|| (!strcmp(name, "..") && !strcmp(root, res)))
|| (!strcmp(name, "..") && !strcmp(r->user_root, d.str)))
continue;
else if (add_element(table, dir, res, name))
else if (add_element(r, table, d.str, name))
{
fprintf(stderr, "%s: add_element failed\n", __func__);
goto end;
@ -844,25 +974,25 @@ end:
free(pde[i]);
free(pde);
dynstr_free(&d);
return ret;
}
static int list_dir(struct http_response *const r, const char *const dir,
const char *const root, const char *const res,
const struct page_quota *const q)
static int list_dir(struct http_response *const r,
const struct page_resource *const res, const struct page_quota *const q)
{
int ret = -1;
struct dynstr out;
struct html_node *table, *const html = resource_layout(dir, q, &table);
struct html_node *table, *html = NULL;
dynstr_init(&out);
if (!html)
if (!(html = resource_layout(res->res, q, &table)))
{
fprintf(stderr, "%s: resource_layout failed\n", __func__);
goto end;
}
else if (add_elements(root, res, dir, table))
else if (add_elements(res, table))
{
fprintf(stderr, "%s: read_elements failed\n", __func__);
goto end;
@ -907,14 +1037,14 @@ static int serve_file(struct http_response *const r,
const struct stat *const sb, const char *const res)
{
int ret = -1;
FILE *const f = fopen(res, "rb");
FILE *f = NULL;
struct dynstr b, d;
char *bn;
dynstr_init(&b);
dynstr_init(&d);
if (!f)
if (!(f = fopen(res, "rb")))
{
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
goto end;
@ -987,32 +1117,43 @@ static int page_not_found(struct http_response *const r)
return 0;
}
int page_resource(struct http_response *const r, const char *const dir,
const char *const root, const char *const res,
const struct page_quota *const q)
int page_resource(struct http_response *const r,
const struct page_resource *const res, const struct page_quota *const q)
{
int ret = -1;
struct stat sb;
struct dynstr d;
if (stat(res, &sb))
dynstr_init(&d);
if (user_resource(res, &d))
{
fprintf(stderr, "%s: user_resource failed\n", __func__);
goto end;
}
else if (stat(d.str, &sb))
{
fprintf(stderr, "%s: stat(2) %s: %s\n",
__func__, res, strerror(errno));
__func__, res->res, strerror(errno));
if (errno == ENOENT)
return page_not_found(r);
else
return -1;
ret = page_not_found(r);
goto end;
}
const mode_t m = sb.st_mode;
if (S_ISDIR(m))
return list_dir(r, dir, root, res, q);
ret = list_dir(r, res, q);
else if (S_ISREG(m))
return serve_file(r, &sb, res);
ret = serve_file(r, &sb, d.str);
else
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
return -1;
end:
dynstr_free(&d);
return ret;
}
static char *resolve_link(const char *const res)
@ -1403,3 +1544,30 @@ end:
return ret;
}
int page_thumbnail(struct http_response *const r, const char *const res)
{
struct stat sb;
if (stat(res, &sb))
{
if (errno == ENOENT)
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_ISREG(m))
{
fprintf(stderr, "%s: only regular files are supported\n", __func__);
return 1;
}
return serve_file(r, &sb, res);
}

10
page.h
View File

@ -8,16 +8,22 @@ struct page_quota
unsigned long long cur, max;
};
struct page_resource
{
const char *root, *user_root, *username, *res;
};
int page_login(struct http_response *r);
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(struct http_response *r, const char *dir, const char *root,
const char *res, const struct page_quota *q);
int page_resource(struct http_response *r, const struct page_resource *res,
const struct page_quota *q);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
int page_thumbnail(struct http_response *r, const char *res);
#endif /* PAGE_H */

143
thumbnail.c Normal file
View File

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

15
thumbnail.h Normal file
View File

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

17
thumbnail_stub.c Normal file
View File

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

View File

@ -64,3 +64,4 @@ jq ".users += [
mv $TMP "$DB"
mkdir "$DIR/user/$USER"
test -d "$DIR/thumbnails" && mkdir -p "$DIR/thumbnails/$USER"