diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-03-09 01:29:48 +0100 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-03-09 02:02:40 +0100 |
| commit | 2e1b1313962d979b6e15491c63d316d829638bf0 (patch) | |
| tree | e2b74911503568d539ff4f377b80759257590635 | |
| parent | 13f96054f6fb68aae464363e283e32f01d2da1a3 (diff) | |
| download | slcl-2e1b1313962d979b6e15491c63d316d829638bf0.tar.gz | |
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.
| -rw-r--r-- | main.c | 196 | ||||
| -rw-r--r-- | page.c | 186 | ||||
| -rw-r--r-- | page.h | 2 |
3 files changed, 369 insertions, 15 deletions
@@ -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)) @@ -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; +} @@ -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 */ |
