Implement public file sharing
An HTML form is now added next to each regular file, that generates a POST request. Then, slcl replies with a HTML document with a link to the public resource (which are implemented as symlinks). Limitations: - For now, only regular files can be shared i.e., sharing directories is not possible. While feasible, it still requires a larger refactor to list_dir and resource_layout, so that read-only access to the directory is provided to anonymous users.
This commit is contained in:
parent
13f96054f6
commit
2e1b131396
196
main.c
196
main.c
|
@ -1,8 +1,11 @@
|
|||
#include "auth.h"
|
||||
#include "cftw.h"
|
||||
#include "handler.h"
|
||||
#include "hex.h"
|
||||
#include "http.h"
|
||||
#include "page.h"
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <dynstr.h>
|
||||
#include <libgen.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -352,6 +355,116 @@ end:
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool path_isrel(const char *const path)
|
||||
{
|
||||
if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
|
||||
return true;
|
||||
|
||||
static const char suffix[] = "/..";
|
||||
const size_t n = strlen(path), sn = strlen(suffix);
|
||||
|
||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int getpublic(const struct http_payload *const p,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
int ret = -1;
|
||||
struct auth *const a = user;
|
||||
const char *const adir = auth_dir(a);
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&d);
|
||||
|
||||
if (!adir)
|
||||
{
|
||||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (path_isrel(p->resource))
|
||||
{
|
||||
fprintf(stderr, "%s: illegal relative path %s\n",
|
||||
__func__, p->resource);
|
||||
ret = page_forbidden(r);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%s%s", adir, p->resource))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (page_public(r, d.str))
|
||||
{
|
||||
fprintf(stderr, "%s: page_public failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *create_symlink(const char *const username, const char *const dir,
|
||||
const char *const path)
|
||||
{
|
||||
char *ret = NULL;
|
||||
unsigned char buf[16];
|
||||
char dbuf[1 + 2 * sizeof buf];
|
||||
struct dynstr user, abs, rel;
|
||||
|
||||
dynstr_init(&user);
|
||||
dynstr_init(&abs);
|
||||
dynstr_init(&rel);
|
||||
|
||||
if (RAND_bytes(buf, sizeof buf) != 1)
|
||||
{
|
||||
fprintf(stderr, "%s: RAND_bytes failed with %lu\n",
|
||||
__func__, ERR_get_error());
|
||||
goto end;
|
||||
}
|
||||
else if (hex_encode(buf, dbuf, sizeof buf, sizeof dbuf))
|
||||
{
|
||||
fprintf(stderr, "%s: hex_encode failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&user, "%s/user/%s%s", dir, username, path))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&rel, "/public/%s", dbuf))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&abs, "%s%s", dir, rel.str))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append abs failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (symlink(user.str, abs.str))
|
||||
{
|
||||
fprintf(stderr, "%s: symlink(2): %s\n", __func__, strerror(errno));
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = rel.str;
|
||||
|
||||
end:
|
||||
dynstr_free(&user);
|
||||
dynstr_free(&abs);
|
||||
|
||||
if (!ret)
|
||||
dynstr_free(&rel);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int search(const struct http_payload *const p,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
|
@ -367,6 +480,73 @@ static int search(const struct http_payload *const p,
|
|||
return -1;
|
||||
}
|
||||
|
||||
static int share(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 adir = auth_dir(a);
|
||||
|
||||
if (!adir)
|
||||
{
|
||||
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = -1;
|
||||
struct form *forms = NULL;
|
||||
size_t n;
|
||||
char *sympath = NULL;
|
||||
|
||||
if (!(forms = get_forms(p, &n)))
|
||||
{
|
||||
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
||||
ret = page_bad_request(r);
|
||||
goto end;
|
||||
}
|
||||
else if (n != 1)
|
||||
{
|
||||
fprintf(stderr, "%s: expected 1 form, got %zu\n", __func__, n);
|
||||
ret = page_bad_request(r);
|
||||
goto end;
|
||||
}
|
||||
|
||||
const char *const path = forms->value, *const username = p->cookie.field;
|
||||
|
||||
if (path_isrel(path))
|
||||
{
|
||||
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
|
||||
ret = page_bad_request(r);
|
||||
goto end;
|
||||
}
|
||||
else if (!(sympath = create_symlink(username, adir, path)))
|
||||
{
|
||||
fprintf(stderr, "%s: create_symlink failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (page_share(r, sympath))
|
||||
{
|
||||
fprintf(stderr, "%s: page_share failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
if (forms)
|
||||
for (size_t i = 0; i < n; i++)
|
||||
form_free(&forms[i]);
|
||||
|
||||
free(forms);
|
||||
free(sympath);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int add_length(const char *const fpath, const struct stat *const sb,
|
||||
void *const user)
|
||||
|
@ -445,20 +625,6 @@ static int check_length(const unsigned long long len,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static bool path_isrel(const char *const path)
|
||||
{
|
||||
if (!strcmp(path, "..") || !strcmp(path, ".") || strstr(path, "/../"))
|
||||
return true;
|
||||
|
||||
static const char suffix[] = "/..";
|
||||
const size_t n = strlen(path), sn = strlen(suffix);
|
||||
|
||||
if (n >= sn && !strcmp(path + n - sn, suffix))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int getnode(const struct http_payload *const p,
|
||||
struct http_response *const r, void *const user)
|
||||
{
|
||||
|
@ -976,7 +1142,9 @@ int main(const int argc, char *const argv[])
|
|||
|| handler_add(h, "/user/*", HTTP_OP_GET, getnode, a)
|
||||
|| handler_add(h, "/login", HTTP_OP_POST, login, a)
|
||||
|| handler_add(h, "/logout", HTTP_OP_POST, logout, a)
|
||||
|| handler_add(h, "/public/*", HTTP_OP_GET, getpublic, a)
|
||||
|| handler_add(h, "/search", HTTP_OP_POST, search, a)
|
||||
|| handler_add(h, "/share", HTTP_OP_POST, share, a)
|
||||
|| handler_add(h, "/upload", HTTP_OP_POST, upload, a)
|
||||
|| handler_add(h, "/mkdir", HTTP_OP_POST, createdir, a)
|
||||
|| handler_listen(h, port))
|
||||
|
|
186
page.c
186
page.c
|
@ -161,11 +161,89 @@ static int prepare_date(struct html_node *const n, const struct stat *const sb)
|
|||
return 0;
|
||||
}
|
||||
|
||||
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");
|
||||
struct html_node *form, *file, *submit;
|
||||
struct dynstr d;
|
||||
|
||||
dynstr_init(&d);
|
||||
|
||||
if (!S_ISREG(sb->st_mode))
|
||||
return 0;
|
||||
|
||||
if (!(form = html_node_add_child(n, "form")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(file = html_node_add_child(form, "input")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child file failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(submit = html_node_add_child(form, "input")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child file failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(form, "method", "post"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr method failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(form, "action", "/share"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr action failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(file, "type", "hidden"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr file type failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(file, "name", "name"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr file name failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&d, "%s%s", fdir, name))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(file, "value", d.str))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr file value failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(submit, "type", "submit"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr submit type failed\n",
|
||||
__func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(submit, "value", "Share"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr submit value failed\n",
|
||||
__func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
dynstr_free(&d);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int add_element(struct html_node *const n, const char *const dir,
|
||||
const char *const res, const char *const name)
|
||||
{
|
||||
int ret = -1;
|
||||
enum {NAME, SIZE, DATE, COLUMNS};
|
||||
enum {NAME, SIZE, DATE, SHARE, COLUMNS};
|
||||
struct html_node *tr, *td[COLUMNS];
|
||||
struct dynstr path;
|
||||
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
|
||||
|
@ -214,6 +292,11 @@ 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))
|
||||
{
|
||||
fprintf(stderr, "%s: prepare_date failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
|
@ -892,6 +975,32 @@ int page_resource(struct http_response *const r, const char *const dir,
|
|||
return -1;
|
||||
}
|
||||
|
||||
int page_public(struct http_response *const r, const char *const res)
|
||||
{
|
||||
struct stat sb;
|
||||
|
||||
if (stat(res, &sb))
|
||||
{
|
||||
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
||||
__func__, res, strerror(errno));
|
||||
|
||||
if (errno == ENOENT)
|
||||
return page_not_found(r);
|
||||
else
|
||||
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);
|
||||
}
|
||||
|
||||
int page_failed_login(struct http_response *const r)
|
||||
{
|
||||
static const char index[] =
|
||||
|
@ -1036,3 +1145,78 @@ int page_style(struct http_response *const r)
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int page_share(struct http_response *const r, const char *const path)
|
||||
{
|
||||
int ret = -1;
|
||||
struct dynstr out;
|
||||
struct html_node *const html = html_node_alloc("html"),
|
||||
*head, *a;
|
||||
|
||||
dynstr_init(&out);
|
||||
|
||||
if (!html)
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(head = html_node_add_child(html, "head")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child head failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (common_head(head, NULL))
|
||||
{
|
||||
fprintf(stderr, "%s: common_head failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (!(a = html_node_add_child(html, "a")))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_child failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_add_attr(a, "href", path))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_add_attr href failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_node_set_value(a, "Copy this link"))
|
||||
{
|
||||
fprintf(stderr, "%s: html_node_set_value href failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (dynstr_append(&out, DOCTYPE_TAG))
|
||||
{
|
||||
fprintf(stderr, "%s: dynstr_prepend failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
else if (html_serialize(html, &out))
|
||||
{
|
||||
fprintf(stderr, "%s: html_serialize failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
*r = (const struct http_response)
|
||||
{
|
||||
.status = HTTP_STATUS_OK,
|
||||
.buf.rw = out.str,
|
||||
.n = out.len,
|
||||
.free = free
|
||||
};
|
||||
|
||||
if (http_response_add_header(r, "Content-Type", "text/html"))
|
||||
{
|
||||
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
||||
goto end;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
end:
|
||||
html_node_free(html);
|
||||
|
||||
if (ret)
|
||||
dynstr_free(&out);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
2
page.h
2
page.h
|
@ -15,5 +15,7 @@ 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_public(struct http_response *r, const char *res);
|
||||
int page_share(struct http_response *r, const char *path);
|
||||
|
||||
#endif /* PAGE_H */
|
||||
|
|
Loading…
Reference in New Issue