From fa997aa2c153cef50941b4d4036694485259ac2a Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Sun, 23 Apr 2023 05:19:27 +0200 Subject: [PATCH] Implement file previews When using HTTP "Content-Disposition: attachment;", users are forced to download files in order to use them, whereas others might prefer to open them in the browser. Therefore, now that URL parameters are supported by http.h, previews can be forced by adding "preview=1" or "preview=true" (case-insensitive) as a URL parameters. Any other parameters are ignored by slcl. For users, a "Preview" link has been added next to the "Share" button for each file. --- main.c | 20 ++++++++---- page.c | 101 +++++++++++++++++++++++++++++++++++++++++++++++---------- page.h | 12 +++++-- 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/main.c b/main.c index 66328a8..7fca16d 100644 --- a/main.c +++ b/main.c @@ -700,14 +700,20 @@ static int getnode(const struct http_payload *const p, goto end; } - const struct page_quota *const ppq = available ? - &(const struct page_quota) - { - .cur = cur, - .max = max - } : NULL; + const struct page_resource pr = + { + .r = r, + .args = p->args, + .n_args = p->n_args, + .dir = dir.str, + .root = root.str, + .res = d.str, + .q = available ? + &(const struct page_quota) {.cur = cur, .max = max } + : NULL + }; - ret = page_resource(r, dir.str, root.str, d.str, ppq); + ret = page_resource(&pr); end: dynstr_free(&dir); diff --git a/page.c b/page.c index e731177..6c67484 100644 --- a/page.c +++ b/page.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #define PROJECT_NAME "slcl" @@ -243,11 +244,50 @@ end: return ret; } +static int prepare_preview(struct html_node *const n, + const struct stat *const sb, const char *const dir, const char *const name) +{ + int ret = -1; + struct html_node *a; + struct dynstr d; + + dynstr_init(&d); + + if (!S_ISREG(sb->st_mode)) + return 0; + else if (!(a = html_node_add_child(n, "a"))) + { + fprintf(stderr, "%s: html_node_add_child form failed\n", __func__); + goto end; + } + else if (dynstr_append(&d, "%s%s?preview=1", dir, name)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_node_add_attr(a, "href", d.str)) + { + fprintf(stderr, "%s: html_node_add_attr failed\n", __func__); + goto end; + } + else if (html_node_set_value(a, "Preview")) + { + fprintf(stderr, "%s: html_node_set_value value failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&d); + return ret; +} + 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, COLUMNS}; + enum {NAME, SIZE, DATE, SHARE, PREVIEW, COLUMNS}; struct html_node *tr, *td[COLUMNS]; struct dynstr path; const char *const sep = res[strlen(res) - 1] != '/' ? "/" : ""; @@ -301,6 +341,11 @@ static int add_element(struct html_node *const n, const char *const dir, fprintf(stderr, "%s: prepare_date failed\n", __func__); goto end; } + else if (prepare_preview(td[PREVIEW], &sb, dir, name)) + { + fprintf(stderr, "%s: prepare_date failed\n", __func__); + goto end; + } ret = 0; @@ -867,13 +912,12 @@ end: return ret; } -static int list_dir(struct http_response *const r, const char *const dir, - const char *const root, const char *const res, - const struct page_quota *const q) +static int list_dir(const struct page_resource *const pr) { int ret = -1; struct dynstr out; - struct html_node *table, *const html = resource_layout(dir, q, &table); + struct html_node *table, + *const html = resource_layout(pr->dir, pr->q, &table); dynstr_init(&out); @@ -882,7 +926,7 @@ static int list_dir(struct http_response *const r, const char *const dir, fprintf(stderr, "%s: resource_layout failed\n", __func__); goto end; } - else if (add_elements(root, res, dir, table)) + else if (add_elements(pr->root, pr->res, pr->dir, table)) { fprintf(stderr, "%s: read_elements failed\n", __func__); goto end; @@ -898,7 +942,7 @@ static int list_dir(struct http_response *const r, const char *const dir, goto end; } - *r = (const struct http_response) + *pr->r = (const struct http_response) { .status = HTTP_STATUS_OK, .buf.rw = out.str, @@ -906,7 +950,7 @@ static int list_dir(struct http_response *const r, const char *const dir, .free = free }; - if (http_response_add_header(r, "Content-Type", "text/html")) + if (http_response_add_header(pr->r, "Content-Type", "text/html")) { fprintf(stderr, "%s: http_response_add_header failed\n", __func__); goto end; @@ -924,7 +968,7 @@ end: } static int serve_file(struct http_response *const r, - const struct stat *const sb, const char *const res) + const struct stat *const sb, const char *const res, const bool preview) { int ret = -1; FILE *const f = fopen(res, "rb"); @@ -949,6 +993,14 @@ static int serve_file(struct http_response *const r, fprintf(stderr, "%s: basename(3) failed\n", __func__); goto end; } + else if (preview) + { + if (dynstr_append(&d, "inline")) + { + fprintf(stderr, "%s: dynstr_append inline failed\n", __func__); + goto end; + } + } else if (dynstr_append(&d, "attachment; filename=\"%s\"", bn)) { fprintf(stderr, "%s: dynstr_append attachment failed\n", __func__); @@ -1007,19 +1059,32 @@ static int page_not_found(struct http_response *const r) return 0; } -int page_resource(struct http_response *const r, const char *const dir, - const char *const root, const char *const res, - const struct page_quota *const q) +static bool preview(const struct page_resource *const pr) +{ + for (size_t i = 0; i < pr->n_args; i++) + { + const struct http_arg *const a = &pr->args[i]; + + if (!strcmp(a->key, "preview") + && (!strcmp(a->value, "1") + || !strcasecmp(a->value, "true"))) + return true; + } + + return false; +} + +int page_resource(const struct page_resource *const pr) { struct stat sb; - if (stat(res, &sb)) + if (stat(pr->res, &sb)) { fprintf(stderr, "%s: stat(2) %s: %s\n", - __func__, res, strerror(errno)); + __func__, pr->res, strerror(errno)); if (errno == ENOENT) - return page_not_found(r); + return page_not_found(pr->r); else return -1; } @@ -1027,9 +1092,9 @@ int page_resource(struct http_response *const r, const char *const dir, const mode_t m = sb.st_mode; if (S_ISDIR(m)) - return list_dir(r, dir, root, res, q); + return list_dir(pr); else if (S_ISREG(m)) - return serve_file(r, &sb, res); + return serve_file(pr->r, &sb, pr->res, preview(pr)); fprintf(stderr, "%s: unexpected st_mode %jd\n", __func__, (intmax_t)m); return -1; @@ -1101,7 +1166,7 @@ int page_public(struct http_response *const r, const char *const res) fprintf(stderr, "%s: resolve_link failed\n", __func__); goto end; } - else if (serve_file(r, &sb, path)) + else if (serve_file(r, &sb, path, false)) { fprintf(stderr, "%s: serve_file failed\n", __func__); goto end; diff --git a/page.h b/page.h index 094a21e..bd47138 100644 --- a/page.h +++ b/page.h @@ -8,13 +8,21 @@ struct page_quota unsigned long long cur, max; }; +struct page_resource +{ + struct http_response *r; + const char *dir, *root, *res; + const struct page_quota *q; + const struct http_arg *args; + size_t n_args; +}; + int page_login(struct http_response *r); int page_style(struct http_response *r); int page_failed_login(struct http_response *r); int page_forbidden(struct http_response *r); int page_bad_request(struct http_response *r); -int page_resource(struct http_response *r, const char *dir, const char *root, - const char *res, const struct page_quota *q); +int page_resource(const struct page_resource *r); 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,