aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-06-06 03:49:36 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2023-06-06 03:52:16 +0200
commit5a6c92d69bbb307ca09ac2ecbad54df4f2394bea (patch)
tree4ae024ff5218b5483731467b2e8ddd14c37057df
parent6e9ce3a25b438a94ad870ba85dd8c9bf8a28e043 (diff)
downloadslcl-5a6c92d69bbb307ca09ac2ecbad54df4f2394bea.tar.gz
Implement search
This new feature adds a HTML form on each directory listing that allows to search files recursively, starting from the current user directory. Wildcard patterns are also allowed.
-rw-r--r--main.c230
-rw-r--r--page.c240
-rw-r--r--page.h13
3 files changed, 476 insertions, 7 deletions
diff --git a/main.c b/main.c
index 1ca7a3c..2313fec 100644
--- a/main.c
+++ b/main.c
@@ -465,21 +465,239 @@ end:
return ret;
}
-static int search(const struct http_payload *const p,
- struct http_response *const r, void *const user)
+static int check_search_input(const struct http_payload *const p,
+ int (**const f)(struct http_response *), const struct auth *const a,
+ char **const dir, struct dynstr *const res)
{
- struct auth *const a = user;
+ int ret = auth_cookie(a, &p->cookie);
+ struct form *forms = NULL;
+ size_t n = 0;
- if (auth_cookie(a, &p->cookie))
+ if (ret < 0)
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
- return page_forbidden(r);
+ goto end;
+ }
+ else if (ret)
+ {
+ *f = page_forbidden;
+ goto end;
+ }
+ else if ((ret = get_forms(p, &forms, &n)))
+ {
+ if (ret < 0)
+ fprintf(stderr, "%s: get_forms failed\n", __func__);
+ else
+ *f = page_bad_request;
+
+ goto end;
+ }
+
+ const char *tdir = NULL, *tres = NULL;
+
+ for (size_t i = 0; i < n; i++)
+ {
+ const struct form *const f = &forms[i];
+
+ if (!strcmp(f->key, "dir"))
+ tdir = f->value;
+ else if (!strcmp(f->key, "name"))
+ tres = f->value;
}
- fprintf(stderr, "%s: TODO\n", __func__);
+ if (!tdir)
+ {
+ fprintf(stderr, "%s: expected non-null directory\n", __func__);
+ ret = 1;
+ *f = page_bad_request;
+ goto end;
+ }
+ else if (!tres)
+ {
+ fprintf(stderr, "%s: expected non-null resource\n", __func__);
+ ret = 1;
+ *f = page_bad_request;
+ goto end;
+ }
+ else if (path_isrel(tdir) || *tdir != '/' || tdir[strlen(tdir) - 1] != '/')
+ {
+ fprintf(stderr, "%s: invalid directory %s\n", __func__, tdir);
+ ret = 1;
+ *f = page_bad_request;
+ goto end;
+ }
+ else if (strchr(tres, '/') || path_isrel(tres))
+ {
+ fprintf(stderr, "%s: invalid resource %s\n", __func__, tres);
+ ret = 1;
+ *f = page_bad_request;
+ goto end;
+ }
+ else if (!(*dir = strdup(tdir)))
+ {
+ fprintf(stderr, "%s: strdup(3) dir: %s\n", __func__, strerror(errno));
+ ret = -1;
+ goto end;
+ }
+ else if (dynstr_append(res, "*%s*", tres))
+ {
+ fprintf(stderr, "%s: dynstr_append failed\n", __func__);
+ ret = -1;
+ goto end;
+ }
+
+end:
+ forms_free(forms, n);
+ return ret;
+}
+
+static void search_result_free(struct page_search_result *const r)
+{
+ if (r)
+ free(r->name);
+}
+
+static void search_results_free(struct page_search *const s)
+{
+ if (s)
+ {
+ for (size_t i = 0; i < s->n; i++)
+ search_result_free(&s->results[i]);
+
+ free(s->results);
+ }
+}
+
+struct search_args
+{
+ const char *root, *res;
+ struct page_search *s;
+};
+
+static int search_fn(const char *const fpath, const struct stat *const sb,
+ void *const user)
+{
+ const struct search_args *const sa = user;
+ const char *rel = fpath + strlen(sa->root);
+ struct page_search *const res = sa->s;
+ struct page_search_result *results = NULL, *r = NULL;
+
+ rel += strspn(rel, "/");
+
+ if (wildcard_cmp(rel, sa->res, false))
+ return 0;
+ else if (!(results = realloc(res->results,
+ (res->n + 1) * sizeof *res->results)))
+ {
+ fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
+ goto failure;
+ }
+
+ r = &results[res->n];
+ *r = (const struct page_search_result)
+ {
+ .name = strdup(rel)
+ };
+
+ if (!r->name)
+ {
+ fprintf(stderr, "%s: strdup(3): %s", __func__, strerror(errno));
+ goto failure;
+ }
+
+ res->results = results;
+ res->n++;
+ return 0;
+
+failure:
+ free(results);
+ search_result_free(r);
return -1;
}
+static int do_search(const char *const abs, const char *const root,
+ const char *const res, struct page_search *const s)
+{
+ struct search_args sa =
+ {
+ .root = root,
+ .res = res,
+ .s = s
+ };
+
+ s->root = root;
+
+ if (cftw(abs, search_fn, &sa))
+ {
+ fprintf(stderr, "%s: cftw failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int search(const struct http_payload *const p,
+ struct http_response *const r, void *const user)
+{
+ int ret = -1;
+ const struct auth *const a = user;
+ const char *const username = p->cookie.field, *const root = auth_dir(a);
+ struct page_search s = {0};
+ int (*f)(struct http_response *);
+ char *dir = NULL;
+ struct dynstr userd, d, res;
+
+ dynstr_init(&userd);
+ dynstr_init(&d);
+ dynstr_init(&res);
+
+ if (!root)
+ {
+ fprintf(stderr, "%s: auth_dir failed\n", __func__);
+ goto end;
+ }
+ else if ((ret = check_search_input(p, &f, a, &dir, &res)))
+ {
+ if (ret < 0)
+ fprintf(stderr, "%s: check_search_input failed\n", __func__);
+ else if ((ret = f(r)))
+ fprintf(stderr, "%s: check_search_input callback failed\n",
+ __func__);
+
+ goto end;
+ }
+ else if (dynstr_append(&userd, "%s/user/%s", root, username))
+ {
+ fprintf(stderr, "%s: dynstr_append userd failed\n", __func__);
+ goto end;
+ }
+ else if (dynstr_append(&d, "%s/%s", userd.str, dir + strspn(dir, "/")))
+ {
+ fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
+ goto end;
+ }
+ else if ((ret = do_search(d.str, userd.str, res.str, &s)))
+ {
+ if (ret < 0)
+ fprintf(stderr, "%s: do_search failed\n", __func__);
+
+ goto end;
+ }
+ else if ((ret = page_search(r, &s)))
+ {
+ fprintf(stderr, "%s: page_search failed\n", __func__);
+ goto end;
+ }
+
+end:
+ free(dir);
+ dynstr_free(&userd);
+ dynstr_free(&d);
+ dynstr_free(&res);
+ search_results_free(&s);
+ return ret;
+}
+
static int share(const struct http_payload *const p,
struct http_response *const r, void *const user)
{
diff --git a/page.c b/page.c
index 6e25647..1b0c2eb 100644
--- a/page.c
+++ b/page.c
@@ -354,6 +354,91 @@ end:
return ret;
}
+static int prepare_search_form(struct html_node *const n, const char *const dir)
+{
+ struct html_node *div, *form, *hidden, *submit, *input;
+
+ 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 (!(input = html_node_add_child(form, "input")))
+ {
+ fprintf(stderr, "%s: html_node_add_child input 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 method 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", "/search"))
+ {
+ 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 dir failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(hidden, "value", 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 failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(submit, "value", "Search"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr submit value failed\n",
+ __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(input, "type", "text"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr text failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(input, "name", "name"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr name failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
static int prepare_upload_form(struct html_node *const n, const char *const dir)
{
struct html_node *div, *hidden, *form, *submit, *input;
@@ -857,6 +942,11 @@ static struct html_node *resource_layout(const char *const dir,
fprintf(stderr, "%s: common_head failed\n", __func__);
goto end;
}
+ else if (prepare_search_form(body, fdir))
+ {
+ fprintf(stderr, "%s: prepare_search_form failed\n", __func__);
+ goto end;
+ }
else if (prepare_upload_form(body, fdir))
{
fprintf(stderr, "%s: prepare_upload_form failed\n", __func__);
@@ -1343,7 +1433,7 @@ int page_style(struct http_response *const r)
" align-items: center;\n"
" display: grid;\n"
"}\n"
- "input\n"
+ "input, .abutton\n"
"{\n"
" margin: auto;\n"
" border: 1px solid;\n"
@@ -1544,3 +1634,151 @@ end:
return ret;
}
+
+static int add_search_results(struct html_node *const n,
+ const struct page_search *const s)
+{
+ struct html_node *table;
+
+ if (!(table = html_node_add_child(n, "table")))
+ {
+ fprintf(stderr, "%s: html_node_add_child table failed\n", __func__);
+ return -1;
+ }
+
+ for (size_t i = 0; i < s->n; i++)
+ {
+ const struct page_search_result *const r = &s->results[i];
+
+ if (add_element(table, "/user/", s->root, r->name))
+ {
+ fprintf(stderr, "%s: add_element failed\n", __func__);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int prepare_back_button(struct html_node *const n)
+{
+ struct html_node *div, *a;
+
+ if (!(div = html_node_add_child(n, "div")))
+ {
+ fprintf(stderr, "%s: html_node_add_child div failed\n", __func__);
+ return -1;
+ }
+ else if (!(a = html_node_add_child(div, "a")))
+ {
+ fprintf(stderr, "%s: html_node_add_child a 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(a, "class", "abutton"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr a class failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_add_attr(a, "href", "/user/"))
+ {
+ fprintf(stderr, "%s: html_node_add_attr a href failed\n", __func__);
+ return -1;
+ }
+ else if (html_node_set_value(a, "Back"))
+ {
+ fprintf(stderr, "%s: html_node_set_value failed\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+int page_search(struct http_response *const r,
+ const struct page_search *const s)
+{
+ int ret = -1;
+ struct dynstr out;
+ struct html_node *const html = html_node_alloc("html"), *head, *body;
+
+ 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 (common_head(head, NULL))
+ {
+ fprintf(stderr, "%s: common_head failed\n", __func__);
+ goto end;
+ }
+ else if (s->n && add_search_results(body, s))
+ {
+ fprintf(stderr, "%s: add_search_results failed\n", __func__);
+ goto end;
+ }
+ else if (!s->n && html_node_set_value(body, "No results found"))
+ {
+ fprintf(stderr, "%s: html_node_set_value msg failed\n", __func__);
+ goto end;
+ }
+ else if (prepare_back_button(body))
+ {
+ fprintf(stderr, "%s: prepare_back_button 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_PAYLOAD_TOO_LARGE,
+ .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 bd47138..2554bdc 100644
--- a/page.h
+++ b/page.h
@@ -2,6 +2,7 @@
#define PAGE_H
#include "http.h"
+#include <stddef.h>
struct page_quota
{
@@ -17,6 +18,17 @@ struct page_resource
size_t n_args;
};
+struct page_search
+{
+ struct page_search_result
+ {
+ char *name;
+ } *results;
+
+ const char *root;
+ 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);
@@ -27,5 +39,6 @@ int page_public(struct http_response *r, const char *res);
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);
#endif /* PAGE_H */