aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CMakeLists.txt20
-rw-r--r--Makefile36
-rw-r--r--README.md11
-rwxr-xr-xconfigure131
-rw-r--r--doc/Makefile14
-rw-r--r--main.c214
-rw-r--r--page.c254
-rw-r--r--page.h10
-rw-r--r--thumbnail.c143
-rw-r--r--thumbnail.h15
-rw-r--r--thumbnail_stub.c17
-rwxr-xr-xusergen1
13 files changed, 726 insertions, 141 deletions
diff --git a/.gitignore b/.gitignore
index 339e89f..6f7f1b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ build/
slcl
*.o
*.d
+/Makefile
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 83c25b1..deb3ba2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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")
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 36bc50f..0000000
--- a/Makefile
+++ /dev/null
@@ -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)
diff --git a/README.md b/README.md
index 9f37f75..a6feb02 100644
--- a/README.md
+++ b/README.md
@@ -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
```
diff --git a/configure b/configure
new file mode 100755
index 0000000..18f3986
--- /dev/null
+++ b/configure
@@ -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
diff --git a/doc/Makefile b/doc/Makefile
new file mode 100644
index 0000000..ca581a3
--- /dev/null
+++ b/doc/Makefile
@@ -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
diff --git a/main.c b/main.c
index 66328a8..b411a0f 100644
--- a/main.c
+++ b/main.c
@@ -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);
- }
-
- const char *const username = p->cookie.field,
- *const resource = p->resource + strlen("/user/");
-
- if (path_isrel(resource))
- {
- fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
- return page_forbidden(r);
+ 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 (path_isrel(p->resource))
{
- fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ fprintf(stderr, "%s: illegal relative path %s\n",
+ __func__, p->resource);
+ ret = page_forbidden(r);
goto end;
}
- else if (dynstr_append(&dir, "%s%s", p->resource, sep))
+ else if (!(adir = auth_dir(a)))
{
- fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
+ fprintf(stderr, "%s: auth_dir 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;
- }
+ fprintf(stderr, "%s: auth_cookie failed\n", __func__);
+ ret = page_forbidden(r);
+ goto end;
+ }
- printf("Created empty directory at %s\n", dir);
- break;
+ const char *const adir = auth_dir(a);
- default:
- fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
- return -1;
- }
+ if (!adir)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ goto end;
}
- else if (!S_ISDIR(sb.st_mode))
+ else if (dynstr_append(&d, "%s%s", adir, p->resource))
{
- fprintf(stderr, "%s: %s not a directory\n", __func__, dir);
- return -1;
+ 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;
}
- return 0;
+ 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;
diff --git a/page.c b/page.c
index 9776349..970a5bf 100644
--- a/page.c
+++ b/page.c
@@ -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);
+}
diff --git a/page.h b/page.h
index 094a21e..3b09c75 100644
--- a/page.h
+++ b/page.h
@@ -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 */
diff --git a/thumbnail.c b/thumbnail.c
new file mode 100644
index 0000000..9e425cb
--- /dev/null
+++ b/thumbnail.c
@@ -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;
+}
diff --git a/thumbnail.h b/thumbnail.h
new file mode 100644
index 0000000..f4cfc58
--- /dev/null
+++ b/thumbnail.h
@@ -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 */
diff --git a/thumbnail_stub.c b/thumbnail_stub.c
new file mode 100644
index 0000000..6319942
--- /dev/null
+++ b/thumbnail_stub.c
@@ -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;
+}
diff --git a/usergen b/usergen
index 42c18fe..735196a 100755
--- a/usergen
+++ b/usergen
@@ -64,3 +64,4 @@ jq ".users += [
mv $TMP "$DB"
mkdir "$DIR/user/$USER"
+test -d "$DIR/thumbnails" && mkdir -p "$DIR/thumbnails/$USER"