/* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli * Copyright (C) 2010-2013 Jason A. Donenfeld * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cache.h" #include "cmd.h" #include "configfile.h" #include "html.h" #include "ui-shared.h" #include "ui-stats.h" #include "ui-blob.h" #include "ui-summary.h" #include "scan-tree.h" const char *cgit_version = CGIT_VERSION; static void add_mimetype(const char *name, const char *value) { struct string_list_item *item; item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); item->util = xstrdup(value); } static struct cgit_filter *new_filter(const char *cmd, filter_type filtertype) { struct cgit_filter *f; int args_size = 0; int extra_args; if (!cmd || !cmd[0]) return NULL; switch (filtertype) { case SOURCE: case ABOUT: extra_args = 1; break; case COMMIT: default: extra_args = 0; break; } f = xmalloc(sizeof(struct cgit_filter)); f->cmd = xstrdup(cmd); args_size = (2 + extra_args) * sizeof(char *); f->argv = xmalloc(args_size); memset(f->argv, 0, args_size); f->argv[0] = f->cmd; return f; } static void process_cached_repolist(const char *path); static void repo_config(struct cgit_repo *repo, const char *name, const char *value) { struct string_list_item *item; if (!strcmp(name, "name")) repo->name = xstrdup(value); else if (!strcmp(name, "clone-url")) repo->clone_url = xstrdup(value); else if (!strcmp(name, "desc")) repo->desc = xstrdup(value); else if (!strcmp(name, "owner")) repo->owner = xstrdup(value); else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-commit-graph")) repo->enable_commit_graph = atoi(value); else if (!strcmp(name, "enable-log-filecount")) repo->enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) repo->enable_log_linecount = atoi(value); else if (!strcmp(name, "enable-remote-branches")) repo->enable_remote_branches = atoi(value); else if (!strcmp(name, "enable-subject-links")) repo->enable_subject_links = atoi(value); else if (!strcmp(name, "branch-sort")) { if (!strcmp(value, "age")) repo->branch_sort = 1; if (!strcmp(value, "name")) repo->branch_sort = 0; } else if (!strcmp(name, "commit-sort")) { if (!strcmp(value, "date")) repo->commit_sort = 1; if (!strcmp(value, "topo")) repo->commit_sort = 2; } else if (!strcmp(name, "max-stats")) repo->max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "module-link")) repo->module_link= xstrdup(value); else if (!prefixcmp(name, "module-link.")) { item = string_list_append(&repo->submodules, xstrdup(name + 12)); item->util = xstrdup(value); } else if (!strcmp(name, "section")) repo->section = xstrdup(value); else if (!strcmp(name, "readme") && value != NULL) { if (repo->readme.items == ctx.cfg.readme.items) memset(&repo->readme, 0, sizeof(repo->readme)); string_list_append(&repo->readme, xstrdup(value)); } else if (!strcmp(name, "logo") && value != NULL) repo->logo = xstrdup(value); else if (!strcmp(name, "logo-link") && value != NULL) repo->logo_link = xstrdup(value); else if (ctx.cfg.enable_filter_overrides) { if (!strcmp(name, "about-filter")) repo->about_filter = new_filter(value, ABOUT); else if (!strcmp(name, "commit-filter")) repo->commit_filter = new_filter(value, COMMIT); else if (!strcmp(name, "source-filter")) repo->source_filter = new_filter(value, SOURCE); } } static void config_cb(const char *name, const char *value) { if (!strcmp(name, "section") || !strcmp(name, "repo.group")) ctx.cfg.section = xstrdup(value); else if (!strcmp(name, "repo.url")) ctx.repo = cgit_add_repo(value); else if (ctx.repo && !strcmp(name, "repo.path")) ctx.repo->path = trim_end(value, '/'); else if (ctx.repo && !prefixcmp(name, "repo.")) repo_config(ctx.repo, name + 5, value); else if (!strcmp(name, "readme") && value != NULL) string_list_append(&ctx.cfg.readme, xstrdup(value)); else if (!strcmp(name, "root-title")) ctx.cfg.root_title = xstrdup(value); else if (!strcmp(name, "root-desc")) ctx.cfg.root_desc = xstrdup(value); else if (!strcmp(name, "root-readme")) ctx.cfg.root_readme = xstrdup(value); else if (!strcmp(name, "css")) ctx.cfg.css = xstrdup(value); else if (!strcmp(name, "favicon")) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); else if (!strcmp(name, "head-include")) ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); else if (!strcmp(name, "logo")) ctx.cfg.logo = xstrdup(value); else if (!strcmp(name, "index-header")) ctx.cfg.index_header = xstrdup(value); else if (!strcmp(name, "index-info")) ctx.cfg.index_info = xstrdup(value); else if (
/* ui-blob.c: show blob content
 *
 * Copyright (C) 2008 Lars Hjemli
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 */

#include "cgit.h"
#include "html.h"
#include "ui-shared.h"

static char *match_path;
static unsigned char *matched_sha1;

static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
	const char *pathname, unsigned mode, int stage, void *cbdata) {
	if(strncmp(base,match_path,baselen)
		|| strcmp(match_path+baselen,pathname) )
		return READ_TREE_RECURSIVE;
	memmove(matched_sha1,sha1,20);
	return 0;
}

void cgit_print_blob(const char *hex, char *path, const char *head)
{

	unsigned char sha1[20];
	enum object_type type;
	unsigned char *buf;
	unsigned long size;
	struct commit *commit;
	const char *paths[] = {path, NULL};

	if (hex) {
		if (get_sha1_hex(hex, sha1)){
			cgit_print_error(fmt("Bad hex value: %s", hex));
			return;
		}
	} else {
		if (get_sha1(head,sha1)) {
			cgit_print_error(fmt("Bad ref: %s", head));
			return;
		}
	}

	type = sha1_object_info(sha1, &size);

	if((!hex) && type == OBJ_COMMIT && path) {
		commit = lookup_commit_reference(sha1);
		match_path = path;
		matched_sha1 = sha1;
		read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
		type = sha1_object_info(sha1,&size);
	}

	if (type == OBJ_BAD) {
		cgit_print_error(fmt("Bad object name: %s", hex));
		return;
	}

	buf = read_sha1_file(sha1, &type, &size);
	if (!buf) {
		cgit_print_error(fmt("Error reading object %s", hex));
		return;
	}

	buf[size] = '\0';
	ctx.page.mimetype = ctx.qry.mimetype;
	ctx.page.filename = path;
	cgit_print_http_headers(&ctx);
	write(htmlfd, buf, size);
}
the resulting repolist in 'cached_rc' * and return 0 on success. */ static int generate_cached_repolist(const char *path, const char *cached_rc) { struct strbuf locked_rc = STRBUF_INIT; int result = 0; int idx; FILE *f; strbuf_addf(&locked_rc, "%s.lock", cached_rc); f = fopen(locked_rc.buf, "wx"); if (!f) { /* Inform about the error unless the lockfile already existed, * since that only means we've got concurrent requests. */ result = errno; if (result != EEXIST) fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", locked_rc.buf, strerror(result), result); goto out; } idx = cgit_repolist.count; if (ctx.cfg.project_list) scan_projects(path, ctx.cfg.project_list, repo_config); else scan_tree(path, repo_config); print_repolist(f, &cgit_repolist, idx); if (rename(locked_rc.buf, cached_rc)) fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", locked_rc.buf, cached_rc, strerror(errno), errno); fclose(f); out: strbuf_release(&locked_rc); return result; } static void process_cached_repolist(const char *path) { struct stat st; struct strbuf cached_rc = STRBUF_INIT; time_t age; unsigned long hash; hash = hash_str(path); if (ctx.cfg.project_list) hash += hash_str(ctx.cfg.project_list); strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash); if (stat(cached_rc.buf, &st)) { /* Nothing is cached, we need to scan without forking. And * if we fail to generate a cached repolist, we need to * invoke scan_tree manually. */ if (generate_cached_repolist(path, cached_rc.buf)) { if (ctx.cfg.project_list) scan_projects(path, ctx.cfg.project_list, repo_config); else scan_tree(path, repo_config); } goto out; } parse_configfile(cached_rc.buf, config_cb); /* If the cached configfile hasn't expired, lets exit now */ age = time(NULL) - st.st_mtime; if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) goto out; /* The cached repolist has been parsed, but it was old. So lets * rescan the specified path and generate a new cached repolist * in a child-process to avoid latency for the current request. */ if (fork()) goto out; exit(generate_cached_repolist(path, cached_rc.buf)); out: strbuf_release(&cached_rc); } static void cgit_parse_args(int argc, const char **argv) { int i; int scan = 0; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "--cache=", 8)) { ctx.cfg.cache_root = xstrdup(argv[i] + 8); } if (!strcmp(argv[i], "--nocache")) { ctx.cfg.nocache = 1; } if (!strcmp(argv[i], "--nohttp")) { ctx.env.no_http = "1"; } if (!strncmp(argv[i], "--query=", 8)) { ctx.qry.raw = xstrdup(argv[i] + 8); } if (!strncmp(argv[i], "--repo=", 7)) { ctx.qry.repo = xstrdup(argv[i] + 7); } if (!strncmp(argv[i], "--page=", 7)) { ctx.qry.page = xstrdup(argv[i] + 7); } if (!strncmp(argv[i], "--head=", 7)) { ctx.qry.head = xstrdup(argv[i] + 7); ctx.qry.has_symref = 1; } if (!strncmp(argv[i], "--sha1=", 7)) { ctx.qry.sha1 = xstrdup(argv[i] + 7); ctx.qry.has_sha1 = 1; } if (!strncmp(argv[i], "--ofs=", 6)) { ctx.qry.ofs = atoi(argv[i] + 6); } if (!strncmp(argv[i], "--scan-tree=", 12) || !strncmp(argv[i], "--scan-path=", 12)) { /* HACK: the global snapshot bitmask defines the * set of allowed snapshot formats, but the config * file hasn't been parsed yet so the mask is * currently 0. By setting all bits high before * scanning we make sure that any in-repo cgitrc * snapshot setting is respected by scan_tree(). * BTW: we assume that there'll never be more than * 255 different snapshot formats supported by cgit... */ ctx.cfg.snapshots = 0xFF; scan++; scan_tree(argv[i] + 12, repo_config); } } if (scan) { qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), cmp_repos); print_repolist(stdout, &cgit_repolist, 0); exit(0); } } static int calc_ttl() { if (!ctx.repo) return ctx.cfg.cache_root_ttl; if (!ctx.qry.page) return ctx.cfg.cache_repo_ttl; if (!strcmp(ctx.qry.page, "about")) return ctx.cfg.cache_about_ttl; if (ctx.qry.has_sha1) return ctx.cfg.cache_static_ttl; if (ctx.qry.has_symref) return ctx.cfg.cache_dynamic_ttl; return ctx.cfg.cache_repo_ttl; } int main(int argc, const char **argv) { const char *path; int err, ttl; prepare_context(&ctx); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_parse_args(argc, argv); parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); /* If virtual-root isn't specified in cgitrc, lets pretend * that virtual-root equals SCRIPT_NAME, minus any possibly * trailing slashes. */ if (!ctx.cfg.virtual_root && ctx.cfg.script_name) ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/'); /* If no url parameter is specified on the querystring, lets * use PATH_INFO as url. This allows cgit to work with virtual * urls without the need for rewriterules in the webserver (as * long as PATH_INFO is included in the cache lookup key). */ path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') path++; ctx.qry.url = xstrdup(path); if (ctx.qry.raw) { char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw); free(ctx.qry.raw); ctx.qry.raw = newqry; } else ctx.qry.raw = xstrdup(ctx.qry.url); cgit_parse_url(ctx.qry.url); } ttl = calc_ttl(); if (ttl < 0) ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */ else ctx.page.expires += ttl * 60; if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) ctx.cfg.nocache = 1; if (ctx.cfg.nocache) ctx.cfg.cache_size = 0; err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, ctx.qry.raw, ttl, process_request, &ctx); if (err) cgit_print_error("Error processing page: %s (%d)", strerror(err), err); return err; }