all repos — cgit @ 016364d4edef261fb55257e36d8a47828d398f96

a hyperfast web frontend for git written in c

ui-snapshot.c (view raw)

  1/* ui-snapshot.c: generate snapshot of a commit
  2 *
  3 * Copyright (C) 2006 Lars Hjemli
  4 * Copyright (C) 2012 Jason A. Donenfeld <Jason@zx2c4.com>
  5 *
  6 * Licensed under GNU General Public License v2
  7 *   (see COPYING for full license text)
  8 */
  9
 10#include "cgit.h"
 11#include "ui-snapshot.h"
 12#include "html.h"
 13#include "ui-shared.h"
 14
 15static int write_archive_type(const char *format, const char *hex, const char *prefix)
 16{
 17	struct argv_array argv = ARGV_ARRAY_INIT;
 18	const char **nargv;
 19	int result;
 20	argv_array_push(&argv, "snapshot");
 21	argv_array_push(&argv, format);
 22	if (prefix) {
 23		struct strbuf buf = STRBUF_INIT;
 24		strbuf_addstr(&buf, prefix);
 25		strbuf_addch(&buf, '/');
 26		argv_array_push(&argv, "--prefix");
 27		argv_array_push(&argv, buf.buf);
 28		strbuf_release(&buf);
 29	}
 30	argv_array_push(&argv, hex);
 31	/*
 32	 * Now we need to copy the pointers to arguments into a new
 33	 * structure because write_archive will rearrange its arguments
 34	 * which may result in duplicated/missing entries causing leaks
 35	 * or double-frees in argv_array_clear.
 36	 */
 37	nargv = xmalloc(sizeof(char *) * (argv.argc + 1));
 38	/* argv_array guarantees a trailing NULL entry. */
 39	memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1));
 40
 41	result = write_archive(argv.argc, nargv, NULL, 1, NULL, 0);
 42	argv_array_clear(&argv);
 43	free(nargv);
 44	return result;
 45}
 46
 47static int write_tar_archive(const char *hex, const char *prefix)
 48{
 49	return write_archive_type("--format=tar", hex, prefix);
 50}
 51
 52static int write_zip_archive(const char *hex, const char *prefix)
 53{
 54	return write_archive_type("--format=zip", hex, prefix);
 55}
 56
 57static int write_compressed_tar_archive(const char *hex,
 58					const char *prefix,
 59					char *filter_argv[])
 60{
 61	int rv;
 62	struct cgit_filter f;
 63
 64	f.cmd = filter_argv[0];
 65	f.argv = filter_argv;
 66	cgit_open_filter(&f);
 67	rv = write_tar_archive(hex, prefix);
 68	cgit_close_filter(&f);
 69	return rv;
 70}
 71
 72static int write_tar_gzip_archive(const char *hex, const char *prefix)
 73{
 74	char *argv[] = { "gzip", "-n", NULL };
 75	return write_compressed_tar_archive(hex, prefix, argv);
 76}
 77
 78static int write_tar_bzip2_archive(const char *hex, const char *prefix)
 79{
 80	char *argv[] = { "bzip2", NULL };
 81	return write_compressed_tar_archive(hex, prefix, argv);
 82}
 83
 84static int write_tar_xz_archive(const char *hex, const char *prefix)
 85{
 86	char *argv[] = { "xz", NULL };
 87	return write_compressed_tar_archive(hex, prefix, argv);
 88}
 89
 90const struct cgit_snapshot_format cgit_snapshot_formats[] = {
 91	{ ".zip", "application/x-zip", write_zip_archive, 0x01 },
 92	{ ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 },
 93	{ ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 },
 94	{ ".tar", "application/x-tar", write_tar_archive, 0x08 },
 95	{ ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 },
 96	{ NULL }
 97};
 98
 99static const struct cgit_snapshot_format *get_format(const char *filename)
100{
101	const struct cgit_snapshot_format *fmt;
102	int fl, sl;
103
104	fl = strlen(filename);
105	for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
106		sl = strlen(fmt->suffix);
107		if (sl >= fl)
108			continue;
109		if (!strcmp(fmt->suffix, filename + fl - sl))
110			return fmt;
111	}
112	return NULL;
113}
114
115static int make_snapshot(const struct cgit_snapshot_format *format,
116			 const char *hex, const char *prefix,
117			 const char *filename)
118{
119	unsigned char sha1[20];
120
121	if (get_sha1(hex, sha1)) {
122		cgit_print_error("Bad object id: %s", hex);
123		return 1;
124	}
125	if (!lookup_commit_reference(sha1)) {
126		cgit_print_error("Not a commit reference: %s", hex);
127		return 1;
128	}
129	ctx.page.mimetype = xstrdup(format->mimetype);
130	ctx.page.filename = xstrdup(filename);
131	cgit_print_http_headers(&ctx);
132	format->write_func(hex, prefix);
133	return 0;
134}
135
136/* Try to guess the requested revision from the requested snapshot name.
137 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
138 * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
139 * Otherwise, if the snapshot name has a prefix matching the result from
140 * repo_basename(), we strip the basename and any following '-' and '_'
141 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
142 * more. If this still isn't a valid commit object name, we check if pre-
143 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives
144 * us something valid.
145 */
146static const char *get_ref_from_filename(const char *url, const char *filename,
147					 const struct cgit_snapshot_format *format)
148{
149	const char *reponame;
150	unsigned char sha1[20];
151	struct strbuf snapshot = STRBUF_INIT;
152	int result = 1;
153
154	strbuf_addstr(&snapshot, filename);
155	strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
156
157	if (get_sha1(snapshot.buf, sha1) == 0)
158		goto out;
159
160	reponame = cgit_repobasename(url);
161	if (prefixcmp(snapshot.buf, reponame) == 0) {
162		const char *new_start = snapshot.buf;
163		new_start += strlen(reponame);
164		while (new_start && (*new_start == '-' || *new_start == '_'))
165			new_start++;
166		strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
167	}
168
169	if (get_sha1(snapshot.buf, sha1) == 0)
170		goto out;
171
172	strbuf_insert(&snapshot, 0, "v", 1);
173	if (get_sha1(snapshot.buf, sha1) == 0)
174		goto out;
175
176	result = 0;
177	strbuf_release(&snapshot);
178
179out:
180	return result ? strbuf_detach(&snapshot, NULL) : NULL;
181}
182
183__attribute__((format (printf, 1, 2)))
184static void show_error(char *fmt, ...)
185{
186	va_list ap;
187
188	ctx.page.mimetype = "text/html";
189	cgit_print_http_headers(&ctx);
190	cgit_print_docstart(&ctx);
191	cgit_print_pageheader(&ctx);
192	va_start(ap, fmt);
193	cgit_vprint_error(fmt, ap);
194	va_end(ap);
195	cgit_print_docend();
196}
197
198void cgit_print_snapshot(const char *head, const char *hex,
199			 const char *filename, int snapshots, int dwim)
200{
201	const struct cgit_snapshot_format* f;
202	char *prefix = NULL;
203
204	if (!filename) {
205		show_error("No snapshot name specified");
206		return;
207	}
208
209	f = get_format(filename);
210	if (!f) {
211		show_error("Unsupported snapshot format: %s", filename);
212		return;
213	}
214
215	if (!hex && dwim) {
216		hex = get_ref_from_filename(ctx.repo->url, filename, f);
217		if (hex == NULL) {
218			html_status(404, "Not found", 0);
219			return;
220		}
221		prefix = xstrdup(filename);
222		prefix[strlen(filename) - strlen(f->suffix)] = '\0';
223	}
224
225	if (!hex)
226		hex = head;
227
228	if (!prefix)
229		prefix = xstrdup(cgit_repobasename(ctx.repo->url));
230
231	make_snapshot(f, hex, prefix, filename);
232	free(prefix);
233}