diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-07-08 00:54:59 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2023-07-08 03:06:13 +0200 |
| commit | d54e3ad322f8ed4219f9cdb7b78ae210861031d1 (patch) | |
| tree | a90cca183ad8ec5376d891b88aad45798e5288a3 /main.c | |
| parent | 74ca76a58fca114140d0d0cc13a1216d6dffbee0 (diff) | |
Implement file/directory removalremove
The following workflow has been implemented:
- A new checkbox for each object inside a directory is shown.
- When one or more objects are selected, the user submits a request
through a HTML5 form.
- Then, slcl will ask the user for confirmation, listing the selected
objects, while reminding the user about the effects.
- The user confirms the selection.
- slcl removes the selected objects. All objects from non-empty
directories are removed, too.
- Finally, slcl redirects the user to the directory the request was
made from.
Diffstat (limited to 'main.c')
| -rw-r--r-- | main.c | 332 |
1 files changed, 332 insertions, 0 deletions
@@ -10,6 +10,7 @@ #include <openssl/err.h> #include <openssl/rand.h> #include <dynstr.h> +#include <dirent.h> #include <libgen.h> #include <fcntl.h> #include <sys/stat.h> @@ -1296,6 +1297,335 @@ end: return ret; } +static int check_rm_input(const struct form *const forms, const size_t n, + struct page_rm *const rm, int (**const cb)(struct http_response *)) +{ + for (size_t i = 0; i < n; i++) + { + const struct form *const f = &forms[i]; + + if (!strcmp(f->key, "dir")) + { + if (!rm->dir) + rm->dir = f->value; + else + { + fprintf(stderr, "%s: directory defined more than once\n", + __func__); + *cb = page_bad_request; + return 1; + } + } + else if (!strcmp(f->key, "path")) + { + const char **tmp = realloc(rm->items, (rm->n + 1) * sizeof *tmp); + + if (!tmp) + { + fprintf(stderr, "%s: realloc(3): %s\n", + __func__, strerror(errno)); + return -1; + } + + tmp[rm->n++] = f->value; + rm->items = tmp; + } + } + + if (!rm->dir) + { + fprintf(stderr, "%s: expected non-null dir\n", __func__); + *cb = page_bad_request; + return 1; + } + else if (*rm->dir != '/' || path_isrel(rm->dir)) + { + fprintf(stderr, "%s: invalid directory %s\n", __func__, rm->dir); + *cb = page_bad_request; + return 1; + } + + return 0; +} + +static int confirm_rm(const struct http_payload *const p, + struct http_response *const r, void *const user) +{ + int ret = -1; + struct auth *const a = user; + struct form *forms = NULL; + const char **items = NULL; + size_t n = 0; + int (*f)(struct http_response *); + struct page_rm rm = {0}; + + if (auth_cookie(a, &p->cookie)) + { + fprintf(stderr, "%s: auth_cookie failed\n", __func__); + ret = page_forbidden(r); + goto end; + } + else if ((ret = get_forms(p, &forms, &n))) + { + if (ret < 0) + fprintf(stderr, "%s: get_forms failed\n", __func__); + else + ret = page_bad_request(r); + + goto end; + } + else if ((ret = check_rm_input(forms, n, &rm, &f))) + { + if (ret < 0) + fprintf(stderr, "%s: check_rm_input failed\n", __func__); + else if ((ret = f(r))) + fprintf(stderr, "%s: check_rm_input callback failed\n", + __func__); + + goto end; + } + else if ((ret = page_rm(r, &rm))) + { + fprintf(stderr, "%s: page_rm failed\n", __func__); + goto end; + } + +end: + forms_free(forms, n); + free(items); + free(rm.items); + return ret; +} + +static const char *find_rm_dir(const struct form *const forms, const size_t n, + int (**const cb)(struct http_response *)) +{ + const char *dir = NULL; + + for (size_t i = 0; i < n; i++) + { + const struct form *const f = &forms[i]; + + if (!strcmp(f->key, "dir")) + { + if (!dir) + dir = f->value; + else + { + fprintf(stderr, "%s: directory defined more than once\n", + __func__); + *cb = page_bad_request; + return NULL; + } + } + } + + return dir; +} + +static int rm_dir_contents(const char *const fpath, + const struct stat *const sb, void *const user) +{ + if (S_ISDIR(sb->st_mode) && rmdir(fpath)) + { + fprintf(stderr, "%s: rmdir(2) %s: %s\n", + __func__, fpath, strerror(errno)); + return -1; + } + else if (S_ISREG(sb->st_mode) && remove(fpath)) + { + fprintf(stderr, "%s: remove(3) %s: %s\n", + __func__, fpath, strerror(errno)); + return -1; + } + + return 0; +} + +static int rmdir_r(const char *const path) +{ + int ret = -1; + DIR *const d = opendir(path); + + if (!d) + { + fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (cftw(path, rm_dir_contents, NULL)) + { + fprintf(stderr, "%s: rm_dir_contents failed\n", __func__); + goto end; + } + else if (rmdir(path)) + { + fprintf(stderr, "%s: rmdir(2) %s: %s\n", + __func__, path, strerror(errno)); + goto end; + } + + ret = 0; + +end: + if (d && closedir(d)) + { + fprintf(stderr, "%s: closedir(3): %s\n", __func__, strerror(errno)); + ret = -1; + } + + return ret; +} + +static int rm_item(const char *const root, const char *const item) +{ + int ret = -1; + struct stat sb; + struct dynstr d; + + dynstr_init(&d); + + if (dynstr_append(&d, "%s/%s", root, item)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (stat(d.str, &sb)) + { + fprintf(stderr, "%s: stat(2) %s: %s\n", + __func__, d.str, strerror(errno)); + + /* This might have already been removed from another request, + * and thus this situation should not be considered an error. */ + if (errno == ENOENT || errno == ENOTDIR) + ret = 0; + + goto end; + } + else if (S_ISDIR(sb.st_mode) && rmdir_r(d.str)) + { + fprintf(stderr, "%s: rmdir_r failed\n", __func__); + goto end; + } + else if (S_ISREG(sb.st_mode) && remove(d.str)) + { + fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno)); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + return ret; +} + +static int do_rm(const struct form *const forms, const size_t n, + const char *const dir) +{ + for (size_t i = 0; i < n; i++) + { + const struct form *const f = &forms[i]; + + if (!strcmp(f->key, "path") && rm_item(dir, f->value)) + { + fprintf(stderr, "%s: rm_item failed\n", __func__); + return -1; + } + } + + return 0; +} + +static int rm(const struct http_payload *const p, + struct http_response *const r, void *const user) +{ + int ret = -1; + struct auth *const a = user; + struct form *forms = NULL; + size_t n = 0; + const struct http_cookie *const c = &p->cookie; + const char *username = c->field, *adir; + struct dynstr d, userdir; + + dynstr_init(&d); + dynstr_init(&userdir); + + if (auth_cookie(a, c)) + { + fprintf(stderr, "%s: auth_cookie failed\n", __func__); + ret = page_forbidden(r); + goto end; + } + else if (!(adir = auth_dir(a))) + { + fprintf(stderr, "%s: auth_dir failed\n", __func__); + goto end; + } + else if ((ret = get_forms(p, &forms, &n))) + { + if (ret < 0) + fprintf(stderr, "%s: get_forms failed\n", __func__); + else + ret = page_bad_request(r); + + goto end; + } + + int (*f)(struct http_response *); + const char *const dir = find_rm_dir(forms, n, &f); + + if (!dir) + { + fprintf(stderr, "%s: expected non-null directory\n", __func__); + ret = f(r); + goto end; + } + else if (*dir != '/' || path_isrel(dir)) + { + fprintf(stderr, "%s: invalid directory %s\n", __func__, dir); + ret = page_bad_request(r); + goto end; + } + else if (dynstr_append(&userdir, "%s/user/%s%s", adir, username, dir)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if ((ret = do_rm(forms, n, userdir.str))) + { + if (ret < 0) + fprintf(stderr, "%s: do_rm failed\n", __func__); + else if ((ret = f(r))) + fprintf(stderr, "%s: rm callback failed\n", __func__); + + goto end; + } + else if (dynstr_append(&d, "/user%s", dir)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + + *r = (const struct http_response) + { + .status = HTTP_STATUS_SEE_OTHER + }; + + if (http_response_add_header(r, "Location", d.str)) + { + fprintf(stderr, "%s: http_response_add_header failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + dynstr_free(&userdir); + forms_free(forms, n); + return ret; +} + static void usage(char *const argv[]) { fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv); @@ -1459,6 +1789,8 @@ int main(int argc, char *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) + || handler_add(h, "/confirm/rm", HTTP_OP_POST, confirm_rm, a) + || handler_add(h, "/rm", HTTP_OP_POST, rm, a) || handler_listen(h, port)) goto end; |
