diff --git a/CMakeLists.txt b/CMakeLists.txt index 83c25b1..1b4a381 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(${PROJECT_NAME} main.c page.c server.c + wildcard_cmp.c ) target_compile_options(${PROJECT_NAME} PRIVATE -Wall) target_compile_definitions(${PROJECT_NAME} PRIVATE _FILE_OFFSET_BITS=64) diff --git a/Makefile b/Makefile index 5e330be..09f3cb2 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,8 @@ OBJECTS = \ jwt.o \ main.o \ page.o \ - server.o + server.o \ + wildcard_cmp.o \ all: $(PROJECT) diff --git a/cftw.c b/cftw.c index c4986fd..d11cb87 100644 --- a/cftw.c +++ b/cftw.c @@ -29,12 +29,13 @@ int cftw(const char *const dirpath, int (*const fn)(const char *, if (!strcmp(path, ".") || !strcmp(path, "..")) continue; + const char *const sep = dirpath[strlen(dirpath) - 1] == '/' ? "" : "/"; struct stat sb; struct dynstr d; dynstr_init(&d); - if (dynstr_append(&d, "%s/%s", dirpath, path)) + if (dynstr_append(&d, "%s%s%s", dirpath, sep, path)) { fprintf(stderr, "%s: dynstr_append failed\n", __func__); return -1; diff --git a/handler.c b/handler.c index 795d503..1d7e922 100644 --- a/handler.c +++ b/handler.c @@ -3,6 +3,7 @@ #include "handler.h" #include "http.h" #include "server.h" +#include "wildcard_cmp.h" #include #include #include @@ -47,48 +48,6 @@ static int on_write(const void *const buf, const size_t n, void *const user) return server_write(buf, n, c->c); } -static int wildcard_cmp(const char *s, const char *p) -{ - while (*p && *s) - { - const char *const wc = strchr(p, '*'); - - if (!wc) - return strcmp(s, p); - - const size_t n = wc - p; - - if (n) - { - const int r = strncmp(s, p, n); - - if (r) - return r; - - p += n; - s += n; - } - else if (*(wc + 1) == *s) - { - p = wc + 1; - s += n; - } - else if (*(wc + 1) == '*') - p++; - else - { - s++; - p += n; - } - } - - while (*p) - if (*p++ != '*') - return -1; - - return 0; -} - static int on_payload(const struct http_payload *const p, struct http_response *const r, void *const user) { @@ -99,7 +58,7 @@ static int on_payload(const struct http_payload *const p, { const struct elem *const e = &h->elem[i]; - if (e->op == p->op && !wildcard_cmp(p->resource, e->url)) + if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true)) return e->f(p, r, e->user); } diff --git a/main.c b/main.c index 4a9dfa5..5707042 100644 --- a/main.c +++ b/main.c @@ -6,6 +6,7 @@ #include "hex.h" #include "http.h" #include "page.h" +#include "wildcard_cmp.h" #include #include #include @@ -464,19 +465,248 @@ 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 != '/') + { + 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) +{ + int ret = -1; + const struct search_args *const sa = user; + const char *const rel = fpath + strlen(sa->root) + 1; + struct page_search *const res = sa->s; + struct page_search_result *results = NULL, *r = NULL; + + if (wildcard_cmp(rel, sa->res, false)) + { + ret = 0; + goto end; + } + else if (!(results = realloc(res->results, + (res->n + 1) * sizeof *res->results))) + { + fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); + goto end; + } + + r = &results[res->n]; + + /* Call strdup(3) again to ensure a pointer to the heap (remember + * dirname(3) and basename(3) might return a pointer to static data). */ + *r = (const struct page_search_result) + { + .name = strdup(rel) + }; + + if (!r->name) + { + fprintf(stderr, "%s: strdup(3) r->name: %s", + __func__, strerror(errno)); + goto end; + } + + res->results = results; + res->n++; + ret = 0; + +end: + + if (ret) + { + free(results); + search_result_free(r); + } + + return ret; +} + +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)) + { + 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, diff --git a/page.c b/page.c index c080e95..dab3f17 100644 --- a/page.c +++ b/page.c @@ -283,6 +283,9 @@ end: return ret; } +/* dir: /user/test */ +/* res: /home/xavier/db/user/a/test/ */ +/* name: FB_IMG_1539173215088.jpg */ static int add_element(struct html_node *const n, const char *const dir, const char *const res, const char *const name) { @@ -354,6 +357,86 @@ 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(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 +940,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__); @@ -1103,7 +1191,7 @@ int page_resource(const struct page_resource *const pr) fprintf(stderr, "%s: stat(2) %s: %s\n", __func__, pr->res, strerror(errno)); - if (errno == ENOENT) + if (errno == ENOENT || errno == ENOTDIR) return page_not_found(pr->r); else return -1; @@ -1314,7 +1402,7 @@ int page_bad_request(struct http_response *const r) int page_style(struct http_response *const r) { static const char body[] = - "body, input\n" + "body\n" "{\n" " font-family: 'Courier New', Courier, monospace;\n" "}\n" @@ -1328,13 +1416,9 @@ int page_style(struct http_response *const r) "}\n" ".userform\n" "{\n" - " padding: 4px;\n" + " padding: 10px;\n" "}\n" - ".loginform\n" - "{\n" - " display: grid;\n" - "}\n" - "form, label, table, input\n" + "form, label, table\n" "{\n" " margin: auto;\n" "}\n" @@ -1343,7 +1427,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 +1628,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 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 */ diff --git a/wildcard_cmp.c b/wildcard_cmp.c new file mode 100644 index 0000000..c4743d3 --- /dev/null +++ b/wildcard_cmp.c @@ -0,0 +1,57 @@ +#include "wildcard_cmp.h" +#include +#include +#include +#include + +int wildcard_cmp(const char *s, const char *p, const bool casecmp) +{ + int (*const cmp)(const char *, const char *) = + casecmp ? strcmp : strcasecmp; + int (*const ncmp)(const char *, const char *, size_t) = + casecmp ? strncmp : strncasecmp; + + while (*p && *s) + { + const char *const wc = strchr(p, '*'); + + if (!wc) + return cmp(s, p); + + const size_t n = wc - p; + + if (n) + { + const int r = ncmp(s, p, n); + + if (r) + return r; + + p += n; + s += n; + } + else + { + const char next = *(wc + 1), wca[2] = {next}, sa[sizeof wca] = {*s}; + + if (!cmp(wca, sa)) + { + p = wc + 1; + s += n; + } + else if (next == '*') + p++; + else + { + s++; + p += n; + } + } + } + + while (*p) + if (*p++ != '*') + return -1; + + return 0; +} diff --git a/wildcard_cmp.h b/wildcard_cmp.h new file mode 100644 index 0000000..fa09913 --- /dev/null +++ b/wildcard_cmp.h @@ -0,0 +1,8 @@ +#ifndef WILDCARD_CMP_H +#define WILDCARD_CMP_H + +#include + +int wildcard_cmp(const char *s, const char *p, bool casecmp); + +#endif /* WILDCARD_CMP_H */