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.
This commit is contained in:
Xavier Del Campo Romero 2023-06-06 03:49:36 +02:00
parent 6e9ce3a25b
commit 5a6c92d69b
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
3 changed files with 476 additions and 7 deletions

230
main.c
View File

@ -465,19 +465,237 @@ end:
return ret;
}
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)
{
int ret = auth_cookie(a, &p->cookie);
struct form *forms = NULL;
size_t n = 0;
if (ret < 0)
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
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;
}
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)
{
struct auth *const a = 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;
if (auth_cookie(a, &p->cookie))
dynstr_init(&userd);
dynstr_init(&d);
dynstr_init(&res);
if (!root)
{
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
return page_forbidden(r);
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;
}
fprintf(stderr, "%s: TODO\n", __func__);
return -1;
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,

240
page.c
View File

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

13
page.h
View File

@ -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 */