Compare commits
40 Commits
v0.1.0-rc3
...
master
|
@ -2,3 +2,4 @@ build/
|
|||
slcl
|
||||
*.o
|
||||
*.d
|
||||
./Makefile
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(slcl C)
|
||||
project(slcl LANGUAGES C VERSION 0.2.0)
|
||||
add_executable(${PROJECT_NAME}
|
||||
auth.c
|
||||
base64.c
|
||||
|
@ -12,10 +12,20 @@ add_executable(${PROJECT_NAME}
|
|||
)
|
||||
target_compile_options(${PROJECT_NAME} PRIVATE -Wall)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64)
|
||||
add_subdirectory(libweb)
|
||||
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()
|
||||
|
||||
find_package(cJSON 1.0 REQUIRED)
|
||||
find_package(OpenSSL 2.0 REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE libweb dynstr cjson OpenSSL::SSL)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE web dynstr cjson OpenSSL::SSL)
|
||||
install(TARGETS ${PROJECT_NAME})
|
||||
install(FILES usergen
|
||||
TYPE BIN
|
||||
|
|
51
Makefile
51
Makefile
|
@ -1,51 +0,0 @@
|
|||
.POSIX:
|
||||
|
||||
PREFIX = /usr/local
|
||||
DST = $(PREFIX)/bin
|
||||
PROJECT = slcl
|
||||
O = -Og
|
||||
CDEFS = -D_FILE_OFFSET_BITS=64 # Required for large file support on 32-bit.
|
||||
CFLAGS = $(O) $(CDEFS) -g -Wall -Ilibweb/include -Ilibweb/dynstr/include \
|
||||
-MD -MF $(@:.o=.d)
|
||||
LIBS = -lcjson -lssl -lm -lcrypto
|
||||
DEPS = $(OBJECTS:.o=.d)
|
||||
DYNSTR = libweb/dynstr/libdynstr.a
|
||||
DYNSTR_FLAGS = -Llibweb/dynstr -ldynstr
|
||||
LIBWEB = libweb/libslweb.a
|
||||
SLWEB_FLAGS = -Llibweb -lweb
|
||||
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
|
||||
|
||||
clean:
|
||||
rm -f $(OBJECTS) $(DEPS)
|
||||
+cd libweb && $(MAKE) clean
|
||||
+cd libweb/dynstr && $(MAKE) clean
|
||||
|
||||
FORCE:
|
||||
|
||||
$(PROJECT): $(OBJECTS) $(DYNSTR) $(LIBWEB)
|
||||
$(CC) $(OBJECTS) $(LDFLAGS) $(LIBS) $(SLWEB_FLAGS) $(DYNSTR_FLAGS) -o $@
|
||||
|
||||
$(DYNSTR): FORCE
|
||||
+cd libweb/dynstr && $(MAKE)
|
||||
|
||||
$(LIBWEB): FORCE
|
||||
+cd libweb && $(MAKE)
|
||||
|
||||
-include $(DEPS)
|
17
README.md
17
README.md
|
@ -59,13 +59,13 @@ to `slcl`. If required, encryption should be done before uploading e.g.: using
|
|||
#### Mandatory packages
|
||||
|
||||
```sh
|
||||
sudo apt install build-essential libcjson-dev libssl-dev
|
||||
sudo apt install build-essential libcjson-dev libssl-dev m4 jq
|
||||
```
|
||||
|
||||
#### Optional packages
|
||||
|
||||
```sh
|
||||
sudo apt install cmake xxd jq
|
||||
sudo apt install cmake
|
||||
```
|
||||
|
||||
## How to use
|
||||
|
@ -74,7 +74,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 and mostly POSIX-compliant
|
||||
[`Makefile`](Makefile).
|
||||
- A [`CMakeLists.txt`](CMakeLists.txt).
|
||||
|
||||
`slcl` can be built using the standard build process:
|
||||
|
@ -82,15 +83,15 @@ them:
|
|||
#### Make
|
||||
|
||||
```sh
|
||||
$ ./configure
|
||||
$ make
|
||||
```
|
||||
|
||||
#### CMake
|
||||
|
||||
```sh
|
||||
$ mkdir build/
|
||||
$ cmake ..
|
||||
$ cmake --build .
|
||||
$ cmake -B build
|
||||
$ cmake --build build/
|
||||
```
|
||||
|
||||
### Setting up
|
||||
|
@ -181,9 +182,7 @@ 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` (which is common
|
||||
among Unix-like operating systems such as GNU/Linux, although not defined
|
||||
by POSIX) if undefined.
|
||||
`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:
|
||||
|
|
4
auth.c
4
auth.c
|
@ -422,7 +422,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 = 1024 * 1024;
|
||||
const unsigned long long mul = 1024ul * 1024ul;
|
||||
|
||||
if (errno || *end != '\0')
|
||||
{
|
||||
|
@ -436,7 +436,7 @@ int auth_quota(const struct auth *const a, const char *const user,
|
|||
goto end;
|
||||
}
|
||||
|
||||
*quota *= 1024 * 1024;
|
||||
*quota *= mul;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
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()
|
|
@ -0,0 +1,168 @@
|
|||
#! /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
|
2
libweb
2
libweb
|
@ -1 +1 @@
|
|||
Subproject commit 0222b75e8554796548e079aa3393c512ae30ac24
|
||||
Subproject commit b4930f72bb9026c5a0871f4fa4cabe20cb0e6a9f
|
290
main.c
290
main.c
|
@ -135,7 +135,8 @@ 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), *key = NULL, *value = NULL;
|
||||
char *const data = alloc_form_data(*s, &end), *enckey = NULL,
|
||||
*encvalue = NULL, *key = NULL, *value = NULL;
|
||||
struct form *f = NULL, *fs = NULL;
|
||||
|
||||
if (!data)
|
||||
|
@ -161,14 +162,16 @@ static int append_form(struct form **const forms, const char **const s,
|
|||
|
||||
const size_t keylen = sep - data;
|
||||
|
||||
if (!(key = strndup(data, keylen)))
|
||||
if (!(enckey = strndup(data, keylen)))
|
||||
{
|
||||
fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: strndup(3) enckey: %s\n",
|
||||
__func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (!(value = strdup(sep + 1)))
|
||||
else if (!(encvalue = strdup(sep + 1)))
|
||||
{
|
||||
fprintf(stderr, "%s: strdup(3) value: %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: strdup(3) encvalue: %s\n",
|
||||
__func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (!(fs = realloc(*forms, (*n + 1) * sizeof **forms)))
|
||||
|
@ -178,27 +181,39 @@ static int append_form(struct form **const forms, const char **const s,
|
|||
}
|
||||
|
||||
*forms = fs;
|
||||
f = &(*forms)[(*n)++];
|
||||
|
||||
/* HTML input forms use '+' for whitespace, rather than %20. */
|
||||
*f = (const struct form)
|
||||
if ((ret = http_decode_url(enckey, true, &key)))
|
||||
{
|
||||
.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__);
|
||||
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)++];
|
||||
|
||||
*f = (const struct form)
|
||||
{
|
||||
.key = key,
|
||||
.value = value
|
||||
};
|
||||
|
||||
*s = end;
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
free(key);
|
||||
free(value);
|
||||
if (ret)
|
||||
{
|
||||
free(key);
|
||||
free(value);
|
||||
}
|
||||
|
||||
free(enckey);
|
||||
free(encvalue);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
@ -378,14 +393,24 @@ end:
|
|||
|
||||
static bool path_isrel(const char *const path)
|
||||
{
|
||||
if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
|
||||
if (!strcmp(path, "..")
|
||||
|| !strcmp(path, ".")
|
||||
|| !strncmp(path, "./", strlen("./"))
|
||||
|| !strncmp(path, "../", strlen("../"))
|
||||
|| strstr(path, "/./")
|
||||
|| strstr(path, "/../"))
|
||||
return true;
|
||||
|
||||
static const char suffix[] = "/..";
|
||||
const size_t n = strlen(path), sn = strlen(suffix);
|
||||
static const char *const suffixes[] = {"/.", "/.."};
|
||||
|
||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||
return true;
|
||||
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;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -410,7 +435,8 @@ 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 char *const adir = auth_dir(a),
|
||||
*const file = p->resource + strlen("/public/");
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&d);
|
||||
|
@ -420,6 +446,13 @@ 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",
|
||||
|
@ -878,7 +911,16 @@ static int check_length(const unsigned long long len,
|
|||
bool has_quota;
|
||||
unsigned long long quota;
|
||||
|
||||
if (auth_quota(a, username, &has_quota, "a))
|
||||
if (auth_cookie(a, c))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
||||
|
||||
if (page_forbidden(r))
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
else if (auth_quota(a, username, &has_quota, "a))
|
||||
{
|
||||
fprintf(stderr, "%s: auth_quota failed\n", __func__);
|
||||
return -1;
|
||||
|
@ -980,52 +1022,113 @@ 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;
|
||||
FILE *const f = fopen(old, "rb");
|
||||
const int fd = open(new, O_WRONLY | O_CREAT, 0600);
|
||||
const int fd_old = open(old, O_RDONLY),
|
||||
fd_new = open(new, O_WRONLY | O_CREAT, 0600);
|
||||
struct stat sb;
|
||||
|
||||
if (!f)
|
||||
if (fd_old < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: open(2) fd_old: %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (fd < 0)
|
||||
else if (fd_new < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if (stat(old, &sb))
|
||||
else if (fstat(fd_old, &sb))
|
||||
{
|
||||
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: fstat(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
for (off_t i = 0; i < sb.st_size;)
|
||||
{
|
||||
char buf[1024];
|
||||
char buf[BUFSIZ];
|
||||
const off_t left = sb.st_size - i;
|
||||
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
||||
ssize_t w;
|
||||
const ssize_t r = read(fd_old, buf, rem);
|
||||
|
||||
if (!fread(buf, rem, 1, f))
|
||||
if (r < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: fread(3) failed, feof=%d, ferror=%d\n",
|
||||
__func__, feof(f), ferror(f));
|
||||
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
else if ((w = write(fd, buf, rem)) < 0)
|
||||
|
||||
size_t wrem = r;
|
||||
const void *p = buf;
|
||||
|
||||
while (wrem)
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
i += rem;
|
||||
|
@ -1034,15 +1137,15 @@ static int move_file(const char *const old, const char *const new)
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
if (fd >= 0 && close(fd))
|
||||
if (fd_old >= 0 && close(fd_old))
|
||||
{
|
||||
fprintf(stderr, "%s: close(2): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: close(2) fd_old: %s\n", __func__, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if (f && fclose(f))
|
||||
if (fd_new >= 0 && close(fd_new))
|
||||
{
|
||||
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
||||
fprintf(stderr, "%s: close(2) fd_new: %s\n", __func__, strerror(errno));
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
|
@ -1061,12 +1164,43 @@ 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 d;
|
||||
struct dynstr dird, d;
|
||||
|
||||
dynstr_init(&dird);
|
||||
dynstr_init(&d);
|
||||
|
||||
if (!root)
|
||||
|
@ -1074,9 +1208,21 @@ 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(&d, "%s/user/%s/%s%s", root, user, dir, f->filename))
|
||||
else if (dynstr_append(&dird, "%s/user/%s/%s", root, user, dir))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
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__);
|
||||
goto end;
|
||||
}
|
||||
else if (rename_or_move(f->tmpname, d.str))
|
||||
|
@ -1088,6 +1234,7 @@ static int upload_file(const struct http_post_file *const f,
|
|||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&dird);
|
||||
dynstr_free(&d);
|
||||
return ret;
|
||||
}
|
||||
|
@ -1187,11 +1334,15 @@ static int upload_files(const struct http_payload *const p,
|
|||
|
||||
for (size_t i = 0; i < po->nfiles; i++)
|
||||
{
|
||||
if (upload_file(&po->files[i], user, root, dir))
|
||||
const int ret = upload_file(&po->files[i], user, root, dir);
|
||||
|
||||
if (ret < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: upload_file failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
else if (ret)
|
||||
return page_bad_request(r);
|
||||
}
|
||||
|
||||
return redirect_to_dir(dir, r);
|
||||
|
@ -1207,7 +1358,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->u.post.expect_continue)
|
||||
else if (p->expect_continue)
|
||||
{
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
|
@ -1228,6 +1379,7 @@ 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);
|
||||
|
@ -1254,7 +1406,7 @@ static int createdir(const struct http_payload *const p,
|
|||
goto end;
|
||||
}
|
||||
|
||||
char *name = NULL, *dir = NULL;
|
||||
const char *name = NULL, *dir = NULL;
|
||||
|
||||
for (size_t i = 0; i < n; i++)
|
||||
{
|
||||
|
@ -1341,7 +1493,12 @@ static int createdir(const struct http_payload *const p,
|
|||
.status = HTTP_STATUS_SEE_OTHER
|
||||
};
|
||||
|
||||
if (http_response_add_header(r, "Location", userd.str))
|
||||
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))
|
||||
{
|
||||
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1354,6 +1511,7 @@ end:
|
|||
forms_free(forms, n);
|
||||
dynstr_free(&userd);
|
||||
dynstr_free(&d);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1924,6 +2082,7 @@ static int add_urls(struct handler *const h, void *const user)
|
|||
{.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},
|
||||
|
@ -1967,14 +2126,31 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
.length = check_length,
|
||||
.tmpdir = tmpdir,
|
||||
.user = a
|
||||
.user = a,
|
||||
.post =
|
||||
{
|
||||
/* Arbitrary limit. */
|
||||
.max_files = 10000,
|
||||
/* File upload only requires one pair. */
|
||||
.max_pairs = 1
|
||||
}
|
||||
};
|
||||
|
||||
unsigned short outport;
|
||||
|
||||
if (!(h = handler_alloc(&cfg))
|
||||
|| add_urls(h, a)
|
||||
|| handler_listen(h, port))
|
||||
|| handler_listen(h, port, &outport))
|
||||
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:
|
||||
|
|
175
page.c
175
page.c
|
@ -108,6 +108,7 @@ 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);
|
||||
|
@ -122,7 +123,12 @@ 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 (html_node_add_attr(a, "href", d.str))
|
||||
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))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr href failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -143,6 +149,7 @@ static int prepare_name(struct html_node *const n, struct stat *const sb,
|
|||
end:
|
||||
dynstr_free(&d);
|
||||
dynstr_free(&dname);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -303,6 +310,7 @@ 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;
|
||||
|
||||
|
@ -315,9 +323,22 @@ 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?preview=1", dir, name))
|
||||
else if (dynstr_append(&d, "%s%s", dir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
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__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(a, "href", d.str))
|
||||
|
@ -335,6 +356,7 @@ static int prepare_preview(struct html_node *const n,
|
|||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
free(encurl);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -1272,20 +1294,24 @@ end:
|
|||
}
|
||||
|
||||
static int serve_file(struct http_response *const r,
|
||||
const struct stat *const sb, const char *const res, const bool preview)
|
||||
const struct stat *const sb, const char *const res, const bool preview,
|
||||
const int fd, bool *const fdopened)
|
||||
{
|
||||
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 (preview)
|
||||
{
|
||||
fprintf(stderr, "%s: fopen(3): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
if (dynstr_append(&d, "inline"))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append inline failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
else if (dynstr_append(&b, "%s", res))
|
||||
{
|
||||
|
@ -1297,20 +1323,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,
|
||||
|
@ -1380,28 +1406,63 @@ 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 (stat(pr->res, &sb))
|
||||
if (fd < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, pr->res, strerror(errno));
|
||||
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
return page_not_found(pr->r);
|
||||
ret = page_not_found(pr->r);
|
||||
else
|
||||
return -1;
|
||||
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",
|
||||
__func__, pr->res, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
||||
if (S_ISDIR(m))
|
||||
return list_dir(pr);
|
||||
{
|
||||
if (list_dir(pr))
|
||||
{
|
||||
fprintf(stderr, "%s: list_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
else if (S_ISREG(m))
|
||||
return serve_file(pr->r, &sb, pr->res, preview(pr));
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
|
||||
return -1;
|
||||
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;
|
||||
}
|
||||
|
||||
static char *resolve_link(const char *const res)
|
||||
|
@ -1444,18 +1505,26 @@ 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 (stat(res, &sb))
|
||||
if (fd < 0)
|
||||
{
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
|
||||
if (errno == ENOENT)
|
||||
return page_not_found(r);
|
||||
ret = page_not_found(r);
|
||||
else
|
||||
goto end;
|
||||
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",
|
||||
__func__, res, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
@ -1470,7 +1539,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))
|
||||
else if (serve_file(r, &sb, path, false, fd, &fdopened))
|
||||
{
|
||||
fprintf(stderr, "%s: serve_file failed\n", __func__);
|
||||
goto end;
|
||||
|
@ -1479,6 +1548,13 @@ 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;
|
||||
}
|
||||
|
@ -2199,3 +2275,36 @@ end:
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int page_head_resource(struct http_response *const r, const char *const res)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(res, &sb))
|
||||
{
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
return page_not_found(r);
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
const mode_t m = sb.st_mode;
|
||||
|
||||
if (!S_ISDIR(m) && !S_ISREG(m))
|
||||
{
|
||||
fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK,
|
||||
.n = sb.st_size
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
1
page.h
1
page.h
|
@ -43,6 +43,7 @@ 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,
|
||||
|
|
13
usergen
13
usergen
|
@ -9,7 +9,7 @@ usage()
|
|||
|
||||
to_hex()
|
||||
{
|
||||
od -An -t x1 | tr -d ' '
|
||||
od -An -t x1 | tr -d ' ' | tr -d '\n'
|
||||
}
|
||||
|
||||
to_bin()
|
||||
|
@ -17,6 +17,13 @@ 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
|
||||
|
@ -68,7 +75,7 @@ do
|
|||
done
|
||||
|
||||
echo >&2
|
||||
TMP=$(mktemp)
|
||||
TMP=$(mktemp_posix)
|
||||
|
||||
cleanup()
|
||||
{
|
||||
|
@ -86,5 +93,5 @@ jq ".users += [
|
|||
\"quota\": \"$QUOTA\"
|
||||
}]" "$DB" > $TMP
|
||||
|
||||
mkdir -p "$DIR/user/$USER"
|
||||
mv $TMP "$DB"
|
||||
mkdir "$DIR/user/$USER"
|
||||
|
|
Loading…
Reference in New Issue