Implement file/directory removal

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.
This commit is contained in:
Xavier Del Campo Romero 2023-07-08 00:54:59 +02:00
parent 74ca76a58f
commit 0822a982ef
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
3 changed files with 698 additions and 1 deletions

332
main.c
View File

@ -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;

360
page.c
View File

@ -45,6 +45,55 @@
" <input type=\"submit\" value=\"Submit\">\n" \
" </form>\n"
#define MAXSIZEFMT "18446744073709551615.0 XiB"
#define RM_FORM_ID "rm"
static int prepare_rm_checkbox(struct html_node *const n,
const struct stat *const sb, const char *const name)
{
int ret = -1;
const char *const sep = S_ISDIR(sb->st_mode) ? "/" : "";
struct html_node *input;
struct dynstr d;
dynstr_init(&d);
if (dynstr_append(&d, "%s%s", name, sep))
{
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
goto end;
}
else if (!(input = html_node_add_child(n, "input")))
{
fprintf(stderr, "%s: html_node_add_child input failed\n", __func__);
goto end;
}
else if (html_node_add_attr(input, "type", "checkbox"))
{
fprintf(stderr, "%s: html_node_add_attr type failed\n", __func__);
goto end;
}
else if (html_node_add_attr(input, "form", RM_FORM_ID))
{
fprintf(stderr, "%s: html_node_add_attr form failed\n", __func__);
goto end;
}
else if (html_node_add_attr(input, "name", "path"))
{
fprintf(stderr, "%s: html_node_add_attr name failed\n", __func__);
goto end;
}
else if (html_node_add_attr(input, "value", d.str))
{
fprintf(stderr, "%s: html_node_add_attr value failed\n", __func__);
goto end;
}
ret = 0;
end:
dynstr_free(&d);
return ret;
}
static int prepare_name(struct html_node *const n, struct stat *const sb,
const char *const dir, const char *const name)
@ -287,7 +336,7 @@ 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, SHARE, PREVIEW, COLUMNS};
enum {RM_CHECKBOX, NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS};
struct html_node *tr, *td[COLUMNS];
struct dynstr path;
const char *const sep = res[strlen(res) - 1] != '/' ? "/" : "";
@ -321,6 +370,12 @@ static int add_element(struct html_node *const n, const char *const dir,
__func__, path.str, strerror(errno));
goto end;
}
else if (strcmp(name, "..")
&& prepare_rm_checkbox(td[RM_CHECKBOX], &sb, name))
{
fprintf(stderr, "%s: prepare_rm_checkbox failed\n", __func__);
goto end;
}
else if (prepare_name(td[NAME], &sb, dir, name))
{
fprintf(stderr, "%s: prepare_name failed\n", __func__);
@ -618,6 +673,80 @@ static int prepare_mkdir_form(struct html_node *const n, const char *const dir)
return 0;
}
static int prepare_rm_form(struct html_node *const n, const char *const dir)
{
struct html_node *div, *form, *hidden, *submit;
if (!(div = html_node_add_child(n, "div")))
{
fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
return -1;
}
else if (!(form = html_node_add_child(div, "form")))
{
fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
return -1;
}
else if (!(hidden = html_node_add_child(form, "input")))
{
fprintf(stderr, "%s: html_node_add_child hidden failed\n", __func__);
return -1;
}
else if (!(submit = html_node_add_child(form, "input")))
{
fprintf(stderr, "%s: html_node_add_child submit failed\n", __func__);
return -1;
}
else if (html_node_add_attr(div, "class", "userform"))
{
fprintf(stderr, "%s: html_node_add_attr div failed\n", __func__);
return -1;
}
else if (html_node_add_attr(form, "id", RM_FORM_ID))
{
fprintf(stderr, "%s: html_node_add_attr id failed\n", __func__);
return -1;
}
else if (html_node_add_attr(form, "method", "post"))
{
fprintf(stderr, "%s: html_node_add_attr method failed\n", __func__);
return -1;
}
else if (html_node_add_attr(form, "action", "/confirm/rm"))
{
fprintf(stderr, "%s: html_node_add_attr method failed\n", __func__);
return -1;
}
else if (html_node_add_attr(hidden, "type", "hidden"))
{
fprintf(stderr, "%s: html_node_add_attr hidden failed\n", __func__);
return -1;
}
else if (html_node_add_attr(hidden, "name", "dir"))
{
fprintf(stderr, "%s: html_node_add_attr name failed\n", __func__);
return -1;
}
else if (html_node_add_attr(hidden, "value", dir))
{
fprintf(stderr, "%s: html_node_add_attr value failed\n", __func__);
return -1;
}
else if (html_node_add_attr(submit, "type", "submit"))
{
fprintf(stderr, "%s: html_node_add_attr submit failed\n", __func__);
return -1;
}
else if (html_node_add_attr(submit, "value", "Remove selected files"))
{
fprintf(stderr, "%s: html_node_add_attr submit value failed\n",
__func__);
return -1;
}
return 0;
}
static int prepare_quota_form(struct html_node *const n,
const struct page_quota *const q)
{
@ -957,6 +1086,11 @@ static struct html_node *resource_layout(const char *const dir,
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
goto end;
}
else if (prepare_rm_form(body, fdir))
{
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
goto end;
}
else if (q && prepare_quota_form(body, q))
{
fprintf(stderr, "%s: prepare_quota_form failed\n", __func__);
@ -1782,3 +1916,227 @@ end:
return ret;
}
static int append_rm_items(struct html_node *const n,
const struct page_rm *const rm)
{
struct html_node *div, *form, *table, *hidden, *submit;
if (!(table = html_node_add_child(n, "table")))
{
fprintf(stderr, "%s: html_node_add_child table failed\n", __func__);
return -1;
}
else if (!(div = html_node_add_child(n, "div")))
{
fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
return -1;
}
else if (!(form = html_node_add_child(div, "form")))
{
fprintf(stderr, "%s: html_node_add_child form failed\n", __func__);
return -1;
}
else if (!(hidden = html_node_add_child(form, "input")))
{
fprintf(stderr, "%s: html_node_add_child hidden failed\n", __func__);
return -1;
}
else if (!(submit = html_node_add_child(form, "input")))
{
fprintf(stderr, "%s: html_node_add_child submit failed\n", __func__);
return -1;
}
else if (html_node_add_attr(form, "method", "post"))
{
fprintf(stderr, "%s: html_node_add_attr method failed\n", __func__);
return -1;
}
else if (html_node_add_attr(form, "action", "/rm"))
{
fprintf(stderr, "%s: html_node_add_attr action failed\n", __func__);
return -1;
}
else if (html_node_add_attr(div, "class", "userform"))
{
fprintf(stderr, "%s: html_node_add_attr class failed\n", __func__);
return -1;
}
else if (html_node_add_attr(hidden, "type", "hidden"))
{
fprintf(stderr, "%s: html_node_add_attr hidden type failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(hidden, "name", "dir"))
{
fprintf(stderr, "%s: html_node_add_attr hidden name failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(hidden, "value", rm->dir))
{
fprintf(stderr, "%s: html_node_add_attr hidden value failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(submit, "type", "submit"))
{
fprintf(stderr, "%s: html_node_add_attr submit type failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(submit, "value", "Confirm"))
{
fprintf(stderr, "%s: html_node_add_attr submit value failed\n",
__func__);
return -1;
}
for (size_t i = 0; i < rm->n; i++)
{
const char *const item = rm->items[i];
struct html_node *const tr = html_node_add_child(table, "tr"),
*const path = html_node_add_child(form, "input"), *td;
if (!tr)
{
fprintf(stderr, "%s: html_node_add_child tr failed\n", __func__);
return -1;
}
else if (!path)
{
fprintf(stderr, "%s: html_node_add_child path failed\n", __func__);
return -1;
}
else if (!(td = html_node_add_child(tr, "td")))
{
fprintf(stderr, "%s: html_node_add_child td failed\n", __func__);
return -1;
}
else if (html_node_set_value(td, item))
{
fprintf(stderr, "%s: html_node_set_value td failed\n", __func__);
return -1;
}
else if (html_node_add_attr(path, "type", "hidden"))
{
fprintf(stderr, "%s: html_node_add_attr path type failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(path, "name", "path"))
{
fprintf(stderr, "%s: html_node_add_attr path name failed\n",
__func__);
return -1;
}
else if (html_node_add_attr(path, "value", item))
{
fprintf(stderr, "%s: html_node_add_attr path value failed\n",
__func__);
return -1;
}
}
return 0;
}
int page_rm(struct http_response *r, const struct page_rm *const rm)
{
int ret = -1;
struct dynstr out;
struct html_node *const html = html_node_alloc("html"),
*p, *p2, *head, *body;
static const char question[] = "Are you sure to remove the "
"following files and/or directories? Remember that removing a "
"directory will remove all of the files and directories inside it.",
reminder[] = "This action cannot be undone.";
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 (!(body = html_node_add_child(html, "body")))
{
fprintf(stderr, "%s: html_node_add_child body failed\n", __func__);
goto end;
}
else if (!(p = html_node_add_child(body, "p")))
{
fprintf(stderr, "%s: html_node_add_child p failed\n", __func__);
goto end;
}
else if (!(p2 = html_node_add_child(body, "p")))
{
fprintf(stderr, "%s: html_node_add_child p2 failed\n", __func__);
goto end;
}
else if (common_head(head, NULL))
{
fprintf(stderr, "%s: common_head failed\n", __func__);
goto end;
}
else if (html_node_set_value(p, question))
{
fprintf(stderr, "%s: html_node_set_value question failed\n", __func__);
goto end;
}
else if (html_node_set_value(p2, reminder))
{
fprintf(stderr, "%s: html_node_set_value reminder failed\n", __func__);
goto end;
}
else if (append_rm_items(body, rm))
{
fprintf(stderr, "%s: append_rm_items failed\n", __func__);
goto end;
}
else if (prepare_footer(body))
{
fprintf(stderr, "%s: prepare_footer failed\n", __func__);
goto end;
}
else if (dynstr_append(&out, DOCTYPE_TAG))
{
fprintf(stderr, "%s: dynstr_append out 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;
}

7
page.h
View File

@ -29,6 +29,12 @@ struct page_search
size_t n;
};
struct page_rm
{
const char *dir, **items;
size_t n;
};
int page_login(struct http_response *r);
int page_style(struct http_response *r);
int page_failed_login(struct http_response *r);
@ -40,5 +46,6 @@ int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
int page_search(struct http_response *r, const struct page_search *s);
int page_rm(struct http_response *r, const struct page_rm *rm);
#endif /* PAGE_H */