diff --git a/main.c b/main.c index 82af0e6..05e7d1b 100644 --- a/main.c +++ b/main.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -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; diff --git a/page.c b/page.c index 3139184..3268b9a 100644 --- a/page.c +++ b/page.c @@ -45,6 +45,55 @@ " \n" \ " \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; +} diff --git a/page.h b/page.h index 2554bdc..748380d 100644 --- a/page.h +++ b/page.h @@ -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 */