diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce 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.c294
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(&notes_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(&notes_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(&notes_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(&notes_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);
}