diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Restyle the sources and fix the audit's findings
Diffstat (limited to 'source/ui-snapshot.c')
| -rw-r--r-- | source/ui-snapshot.c | 294 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 171 insertions, 123 deletions
diff --git a/source/ui-snapshot.c b/source/ui-snapshot.c index 97472c5..0bab8ea 100644 --- a/source/ui-snapshot.c +++ b/source/ui-snapshot.c @@ -1,27 +1,37 @@ -/* ui-snapshot.c: generate snapshot of a commit - * - * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> - * - * Licensed under GNU General Public License v2 - * (see LICENSE.txt for full license text) +/* + * Serves a commit as a downloadable archive. Writing the archive is git's + * work, so each format here is a thin wrapper that either calls write_archive + * directly or pipes its tar output through an external compressor. The table + * of formats also lives here, and since a repository's snapshots mask is one + * bit per table position, that order is part of what cgitrc means. When the + * request carries no revision the file name is worked backwards to find one, + * so that a link to cgit-1.2.tar.gz can serve the tag it was named after. A + * name ending in .asc asks for the detached signature kept under refs/notes + * instead of the archive itself. */ #define USE_THE_REPOSITORY_VARIABLE #include "cgit.h" -#include "ui-snapshot.h" +#include "filter.h" #include "html.h" #include "ui-shared.h" +#include "ui-snapshot.h" + +#define SIG_SUFFIX ".asc" -static int write_archive_type(const char *format, const char *hex, const char *prefix) +static int write_archive_format(const char *format_arg, const char *hex, + const char *prefix) { struct strvec argv = STRVEC_INIT; - const char **nargv; + const char **args; int result; + strvec_push(&argv, "snapshot"); - strvec_push(&argv, format); + strvec_push(&argv, format_arg); if (prefix) { struct strbuf buf = STRBUF_INIT; + strbuf_addstr(&buf, prefix); strbuf_addch(&buf, '/'); strvec_push(&argv, "--prefix"); @@ -29,44 +39,40 @@ static int write_archive_type(const char *format, const char *hex, const char *p strbuf_release(&buf); } strvec_push(&argv, hex); - /* - * Now we need to copy the pointers to arguments into a new - * structure because write_archive will rearrange its arguments - * which may result in duplicated/missing entries causing leaks - * or double-frees in strvec_clear. - */ - nargv = xmalloc(sizeof(char *) * (argv.nr + 1)); - /* strvec guarantees a trailing NULL entry. */ - memcpy(nargv, argv.v, sizeof(char *) * (argv.nr + 1)); - result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0); + // write_archive rearranges the argv it is handed, which would leave + // strvec_clear leaking or double freeing, so it gets a copy of the + // pointers, one entry longer than argv.nr for the trailing NULL. + args = xmalloc(sizeof(char *) * (argv.nr + 1)); + memcpy(args, argv.v, sizeof(char *) * (argv.nr + 1)); + + result = write_archive(argv.nr, args, NULL, the_repository, NULL, 0); strvec_clear(&argv); - free(nargv); + free(args); return result; } static int write_tar_archive(const char *hex, const char *prefix) { - return write_archive_type("--format=tar", hex, prefix); + return write_archive_format("--format=tar", hex, prefix); } static int write_zip_archive(const char *hex, const char *prefix) { - return write_archive_type("--format=zip", hex, prefix); + return write_archive_format("--format=zip", hex, prefix); } -static int write_compressed_tar_archive(const char *hex, - const char *prefix, - char *filter_argv[]) +static int write_compressed_tar_archive(const char *hex, const char *prefix, + char *argv[]) { - int rv; - struct cgit_exec_filter f; - cgit_exec_filter_init(&f, filter_argv[0], filter_argv); + struct cgit_exec_filter filter; + int result; - cgit_open_filter(&f.base); - rv = write_tar_archive(hex, prefix); - cgit_close_filter(&f.base); - return rv; + cgit_exec_filter_init(&filter, argv[0], argv); + cgit_open_filter(&filter.base); + result = write_tar_archive(hex, prefix); + cgit_close_filter(&filter.base); + return result; } static int write_tar_gzip_archive(const char *hex, const char *prefix) @@ -95,14 +101,15 @@ static int write_tar_xz_archive(const char *hex, const char *prefix) static int write_tar_zstd_archive(const char *hex, const char *prefix) { - // Stay single-threaded like the other compressors so one request - // cannot pin every core. -T0 would fan out across all of them. + // Single-threaded like the other compressors, since -T0 would let one + // request pin every core. char *argv[] = { "zstd", NULL }; return write_compressed_tar_archive(hex, prefix, argv); } +// ui-shared.c reads entry zero as the tar whose signature stands in for every +// tar variant, so this order cannot change. const struct cgit_snapshot_format cgit_snapshot_formats[] = { - /* .tar must remain the 0 index */ { ".tar", "application/x-tar", write_tar_archive }, { ".tar.gz", "application/x-gzip", write_tar_gzip_archive }, { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive }, @@ -113,48 +120,77 @@ const struct cgit_snapshot_format cgit_snapshot_formats[] = { { NULL } }; -static struct notes_tree snapshot_sig_notes[ARRAY_SIZE(cgit_snapshot_formats)]; +// Each tree is read the first time a signature for that format is asked for, +// then kept for the life of the process. +static struct notes_tree sig_notes[ARRAY_SIZE(cgit_snapshot_formats)]; -const struct object_id *cgit_snapshot_get_sig(const char *ref, - const struct cgit_snapshot_format *f) +static size_t format_index(const struct cgit_snapshot_format *f) { - struct notes_tree *tree; - struct object_id oid; - - if (repo_get_oid(the_repository, ref, &oid)) - return NULL; - - tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]]; - if (!tree->initialized) { - struct strbuf notes_ref = STRBUF_INIT; + return f - cgit_snapshot_formats; +} - strbuf_addf(¬es_ref, "refs/notes/signatures/%s", - f->suffix + 1); +static const struct cgit_snapshot_format *find_format(const char *filename) +{ + const struct cgit_snapshot_format *f; - init_notes(tree, notes_ref.buf, combine_notes_ignore, 0); - strbuf_release(¬es_ref); + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (ends_with(filename, f->suffix)) + return f; } - - return get_note(tree, &oid); + return NULL; } -static const struct cgit_snapshot_format *get_format(const char *filename) +static int resolves(const char *rev) { - const struct cgit_snapshot_format *fmt; + struct object_id oid; - for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { - if (ends_with(filename, fmt->suffix)) - return fmt; - } - return NULL; + return repo_get_oid(the_repository, rev, &oid) == 0; } -unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f) +static const char *ref_from_filename(const struct cgit_repo *repo, + const char *filename, + const struct cgit_snapshot_format *format) { - return BIT(f - &cgit_snapshot_formats[0]); + struct strbuf rev = STRBUF_INIT; + const char *repo_prefix; + int found = 1; + + strbuf_addstr(&rev, filename); + strbuf_setlen(&rev, rev.len - strlen(format->suffix)); + + if (resolves(rev.buf)) + goto out; + + repo_prefix = cgit_snapshot_prefix(repo); + if (starts_with(rev.buf, repo_prefix)) { + const char *rest = rev.buf + strlen(repo_prefix); + + while (rest && (*rest == '-' || *rest == '_')) + rest++; + strbuf_splice(&rev, 0, rest - rev.buf, "", 0); + } + + if (resolves(rev.buf)) + goto out; + + // A tag is often written with a leading v while the file named after it + // is not, so that is tried last. + strbuf_insert(&rev, 0, "v", 1); + if (resolves(rev.buf)) + goto out; + + strbuf_splice(&rev, 0, 1, "V", 1); + if (resolves(rev.buf)) + goto out; + + found = 0; + strbuf_release(&rev); + +out: + return found ? strbuf_detach(&rev, NULL) : NULL; } -static int make_snapshot(const struct cgit_snapshot_format *format, +static int send_snapshot(const struct cgit_snapshot_format *format, const char *hex, const char *prefix, const char *filename) { @@ -179,9 +215,9 @@ static int make_snapshot(const struct cgit_snapshot_format *format, return 0; } -static int write_sig(const struct cgit_snapshot_format *format, - const char *hex, const char *archive, - const char *filename) +static int send_sig(const struct cgit_snapshot_format *format, + const char *hex, const char *archive_name, + const char *sig_filename) { const struct object_id *note = cgit_snapshot_get_sig(hex, format); enum object_type type; @@ -190,7 +226,7 @@ static int write_sig(const struct cgit_snapshot_format *format, if (!note) { cgit_print_error_page(404, "Not found", - "No signature for %s", archive); + "No signature for %s", archive_name); return 0; } @@ -200,11 +236,14 @@ static int write_sig(const struct cgit_snapshot_format *format, return 0; } + // The body is whatever bytes the note holds, so these go out ahead of + // the usual headers to stop a browser sniffing it into a type it will + // act on. html("X-Content-Type-Options: nosniff\n"); html("Content-Security-Policy: default-src 'none'\n"); ctx.page.etag = oid_to_hex(note); ctx.page.mimetype = xstrdup("application/pgp-signature"); - ctx.page.filename = xstrdup(filename); + ctx.page.filename = xstrdup(sig_filename); cgit_print_http_headers(); html_raw(buf, size); @@ -212,64 +251,74 @@ static int write_sig(const struct cgit_snapshot_format *format, return 0; } -/* Try to guess the requested revision from the requested snapshot name. - * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become - * "cgit-0.7.2". If this is a valid commit object name we've got a winner. - * Otherwise, if the snapshot name has a prefix matching the result from - * repo_basename(), we strip the basename and any following '-' and '_' - * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once - * more. If this still isn't a valid commit object name, we check if pre- - * pending a 'v' or a 'V' to the remaining snapshot name ("0.7.2" -> - * "v0.7.2") gives us something valid. - */ -static const char *get_ref_from_filename(const struct cgit_repo *repo, - const char *filename, - const struct cgit_snapshot_format *format) +const struct object_id *cgit_snapshot_get_sig(const char *ref, + const struct cgit_snapshot_format *f) { - const char *reponame; + struct notes_tree *tree; struct object_id oid; - struct strbuf snapshot = STRBUF_INIT; - int result = 1; - strbuf_addstr(&snapshot, filename); - strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix)); + if (repo_get_oid(the_repository, ref, &oid)) + return NULL; - if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) - goto out; + tree = &sig_notes[format_index(f)]; + if (!tree->initialized) { + struct strbuf notes_ref = STRBUF_INIT; + + // Signatures live under the format suffix with the leading dot + // dropped, so plain tar is refs/notes/signatures/tar. + strbuf_addf(¬es_ref, "refs/notes/signatures/%s", + f->suffix + 1); - reponame = cgit_snapshot_prefix(repo); - if (starts_with(snapshot.buf, reponame)) { - const char *new_start = snapshot.buf; - new_start += strlen(reponame); - while (new_start && (*new_start == '-' || *new_start == '_')) - new_start++; - strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0); + init_notes(tree, notes_ref.buf, combine_notes_ignore, 0); + strbuf_release(¬es_ref); } - if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) - goto out; + return get_note(tree, &oid); +} - strbuf_insert(&snapshot, 0, "v", 1); - if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) - goto out; +unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f) +{ + return 1U << format_index(f); +} - strbuf_splice(&snapshot, 0, 1, "V", 1); - if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) - goto out; +int cgit_parse_snapshots_mask(const char *str) +{ + struct string_list tokens = STRING_LIST_INIT_DUP; + struct string_list_item *item; + const struct cgit_snapshot_format *f; + int mask = 0; - result = 0; - strbuf_release(&snapshot); + // A number is the legacy form of this setting and is still taken + // ahead of the named forms below. + if (atoi(str)) + return 1; -out: - return result ? strbuf_detach(&snapshot, NULL) : NULL; + if (strcmp(str, "all") == 0) + return INT_MAX; + + string_list_split(&tokens, str, " ", -1); + string_list_remove_empty_items(&tokens, 0); + + for_each_string_list_item(item, &tokens) { + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (!strcmp(item->string, f->suffix) || + !strcmp(item->string, f->suffix + 1)) { + mask |= cgit_snapshot_format_bit(f); + break; + } + } + } + + string_list_clear(&tokens, 0); + return mask; } void cgit_print_snapshot(const char *head, const char *hex, const char *filename, int dwim) { - const struct cgit_snapshot_format* f; + const struct cgit_snapshot_format *f; const char *sig_filename = NULL; - char *adj_filename = NULL; + char *archive_name = NULL; char *prefix = NULL; if (!filename) { @@ -278,25 +327,24 @@ void cgit_print_snapshot(const char *head, const char *hex, return; } - if (ends_with(filename, ".asc")) { + if (ends_with(filename, SIG_SUFFIX)) { sig_filename = filename; - - /* Strip ".asc" from filename for common format processing */ - adj_filename = xstrdup(filename); - adj_filename[strlen(adj_filename) - 4] = '\0'; - filename = adj_filename; + archive_name = xstrdup(filename); + archive_name[strlen(archive_name) - strlen(SIG_SUFFIX)] = '\0'; + filename = archive_name; } - f = get_format(filename); - if (!f || (!sig_filename && !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) { + f = find_format(filename); + if (!f || (!sig_filename && + !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) { cgit_print_error_page(400, "Bad request", "Unsupported snapshot format: %s", filename); return; } if (!hex && dwim) { - hex = get_ref_from_filename(ctx.repo, filename, f); - if (hex == NULL) { + hex = ref_from_filename(ctx.repo, filename, f); + if (!hex) { cgit_print_error_page(404, "Not found", "Not found"); return; } @@ -311,10 +359,10 @@ void cgit_print_snapshot(const char *head, const char *hex, prefix = xstrdup(cgit_snapshot_prefix(ctx.repo)); if (sig_filename) - write_sig(f, hex, filename, sig_filename); + send_sig(f, hex, filename, sig_filename); else - make_snapshot(f, hex, prefix, filename); + send_snapshot(f, hex, prefix, filename); free(prefix); - free(adj_filename); + free(archive_name); } |
