aboutsummaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-07-08 00:54:59 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-07-08 03:06:13 +0200
commitd54e3ad322f8ed4219f9cdb7b78ae210861031d1 (patch)
treea90cca183ad8ec5376d891b88aad45798e5288a3 /main.c
parent74ca76a58fca114140d0d0cc13a1216d6dffbee0 (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.c332
1 files changed, 332 insertions, 0 deletions
diff --git a/main.c b/main.c
index 82af0e6..05e7d1b 100644
--- a/main.c
+++ b/main.c
@@ -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;