/* imports */ #include #include #include #include #include #include #include #include #include #include #include /* macros */ #define LENGTH(X) (sizeof(X) / sizeof((X)[0])) #define HOUR 3600L #define DAY (24 * HOUR) #define MAXTIME 253402300799LL /* enums */ typedef struct { const char *name; const char *url; } Service; /* function declarations */ static int bprintf(char *buf, size_t len, const char *fmt, ...); static int commitdiff(git_diff **diff, git_commit *commit, int renames); static void commitpage(const char *id); static const char *envor(const char *name, const char *fallback); static void filepage(const char *path, const git_tree_entry *e); static int findref(char *spec, char **rest); static void httpheader(int status, const char *type); static void indexpage(void); static void indexrow(const char *name); static int isrepo(const char *name); static int loadcommit(const char *id); static int loadref(git_reference *r); static void logpage(void); static void logrow(const git_oid *oid); static const char *modestr(git_filemode_t mode); static void notfound(void); static int openrepo(git_repository **r, const char *name); static void pagebegin(int status, const char *pagetitle); static void pageend(void); static void printage(const git_time *t); static void printdiff(git_diff *diff, git_patch **patches); static void printname(const char *name); static void printpath(const char *path); static void printstat(git_diff *diff, git_patch **patches); static void printtime(const git_time *t, const char *fmt); static void printurl(const char *page, const char *r, const char *path); static void rawpage(const char *path); static void readfile(const char *name, const char *file, char *buf, size_t len); static void refrows(const char *glob); static void refspage(void); static void repoheader(const char *tab); static void route(char *path); static void treepage(const char *path); static void treerow(const git_tree_entry *e, const char *path); static void treetable(const git_tree *tree, const char *path); static void urlencode(const char *s); static int validname(const char *s); static int validtime(const git_time *t); static void walk(const char *rel, int depth); static void xmlencode(const char *s); static void xmlencoden(const char *s, size_t len); /* variables */ static const char refshead[] = "NameLast commit" "Author\n\n"; static const char *base; static const char *reponame; static git_repository *repo; static git_odb *odb; static char ref[PATH_MAX]; static git_oid tip; static long ofs; #include "config.h" /* function implementations */ int bprintf(char *buf, size_t len, const char *fmt, ...) { va_list ap; int n; va_start(ap, fmt); n = vsnprintf(buf, len, fmt, ap); va_end(ap); if (n < 0 || (size_t)n >= len) return -1; return 0; } int commitdiff(git_diff **diff, git_commit *commit, int renames) { git_commit *parent = NULL; git_tree *tree = NULL, *ptree = NULL; git_diff_options opts; git_diff_find_options fopts; int err; *diff = NULL; git_diff_options_init(&opts, GIT_DIFF_OPTIONS_VERSION); git_diff_find_options_init(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION); opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; opts.flags |= GIT_DIFF_IGNORE_SUBMODULES; fopts.flags |= GIT_DIFF_FIND_RENAMES; if ((err = git_commit_tree(&tree, commit)) < 0) goto end; if (git_commit_parentcount(commit)) { if ((err = git_commit_parent(&parent, commit, 0)) < 0) goto end; if ((err = git_commit_tree(&ptree, parent)) < 0) goto end; } if ((err = git_diff_tree_to_tree(diff, repo, ptree, tree, &opts)) < 0) goto end; if (renames) err = git_diff_find_similar(*diff, &fopts); end: if (err < 0) { git_diff_free(*diff); *diff = NULL; } git_tree_free(ptree); git_tree_free(tree); git_commit_free(parent); return err; } void commitpage(const char *id) { git_commit *c; git_diff *diff; git_patch **patches = NULL; const git_signature *a; const char *body, *pid; size_t i, n = 0; if (loadcommit(id) < 0 || git_commit_lookup(&c, repo, &tip) < 0) { notfound(); return; } a = git_commit_author(c); pagebegin(200, git_commit_summary(c)); repoheader("log"); fputs("

", stdout); xmlencode(git_commit_summary(c)); fputs("

\n", stdout); if ((body = git_commit_body(c))) { fputs("
", stdout);
		xmlencode(body);
		fputs("
\n", stdout); } printf("\n\n", git_oid_tostr_s(git_commit_id(c))); for (i = 0; i < git_commit_parentcount(c); i++) { pid = git_oid_tostr_s(git_commit_parent_id(c, i)); fputs("\n", pid); } fputs("\n\n
commit%s
parent%s
author", stdout); xmlencode(a->name); fputs(" <", stdout); xmlencode(a->email); fputs(">
date", stdout); printtime(&a->when, "%Y-%m-%d %H:%M"); if (validtime(&a->when)) printf(" %c%02d%02d", a->when.sign, abs(a->when.offset) / 60, abs(a->when.offset) % 60); fputs("
\n", stdout); if (commitdiff(&diff, c, 1) >= 0) { n = git_diff_num_deltas(diff); patches = calloc(n + 1, sizeof(*patches)); } if (patches) { for (i = 0; i < n; i++) if (git_patch_from_diff(&patches[i], diff, i) < 0) patches[i] = NULL; printstat(diff, patches); printdiff(diff, patches); for (i = 0; i < n; i++) git_patch_free(patches[i]); free(patches); } pageend(); git_diff_free(diff); git_commit_free(c); } const char * envor(const char *name, const char *fallback) { const char *v; v = getenv(name); return v && *v ? v : fallback; } void filepage(const char *path, const git_tree_entry *e) { git_blob *blob = NULL; git_object_t type; const char *s = NULL, *nl; size_t len, i, l, n, lines = 0; if (git_odb_read_header(&len, &type, odb, git_tree_entry_id(e)) < 0 || (len <= (size_t)maxblob && git_blob_lookup(&blob, repo, git_tree_entry_id(e)) < 0)) { notfound(); return; } if (blob && !git_blob_is_binary(blob)) { s = git_blob_rawcontent(blob); for (i = 0; i < len; i++) if (s[i] == '\n') lines++; if (len && s[len - 1] != '\n') lines++; } pagebegin(200, reponame); repoheader("tree"); printpath(path); fputs("", stdout); if (s) printf("%zu lines · ", lines); printf("%zuB · raw

\n", stdout); if (!blob) { fputs("

File too large.

\n", stdout); } else if (!s) { fputs("

Binary file.

\n", stdout); } else { fputs("
", stdout);
		for (i = 0, n = 1; i < len; i += l + 1, n++) {
			nl = memchr(s + i, '\n', len - i);
			l = nl ? (size_t)(nl - s) - i : len - i;
			printf("%zu",
				n, n, n);
			xmlencoden(s + i, l);
			putchar('\n');
		}
		fputs("
\n", stdout); } pageend(); git_blob_free(blob); } int findref(char *spec, char **rest) { git_reference *r; char *cut = NULL, *p; for (;;) { if ((git_reference_dwim(&r, repo, spec) >= 0 && loadref(r) >= 0) || loadcommit(spec) >= 0) { *rest = cut ? cut + 1 : spec + strlen(spec); return 0; } if (cut) *cut = '/'; p = cut ? cut : spec + strlen(spec); while (p > spec && *--p != '/') ; if (p == spec) return -1; *p = '\0'; cut = p; } } void httpheader(int status, const char *type) { if (status == 404) fputs("Status: 404 Not Found\r\n", stdout); printf("Content-Type: %s\r\n", type); fputs("Content-Security-Policy: default-src 'none'; " "style-src 'self'; img-src 'self'\r\n" "X-Content-Type-Options: nosniff\r\n\r\n", stdout); } void indexpage(void) { pagebegin(200, title); fputs("
\n\n" "" "\n\n", stdout); walk("", 0); fputs("\n
NameDescriptionOwnerUpdated
\n", stdout); pageend(); } void indexrow(const char *name) { char desc[256], owner[256]; git_repository *r = NULL; git_object *obj = NULL; readfile(name, "description", desc, sizeof(desc)); readfile(name, "owner", owner, sizeof(owner)); fputs("", stdout); printname(name); fputs("", stdout); xmlencode(desc); fputs("", stdout); xmlencode(owner); fputs("", stdout); if (openrepo(&r, name) >= 0 && git_revparse_single(&obj, r, "HEAD^{commit}") < 0) obj = NULL; printage(obj ? &git_commit_committer((git_commit *)obj)->when : NULL); fputs("\n", stdout); git_object_free(obj); git_repository_free(r); } int isrepo(const char *name) { char path[PATH_MAX]; struct stat st; if (!validname(name)) return 0; if (bprintf(path, sizeof(path), "%s/%s.git/git-daemon-export-ok", root, name) < 0) return 0; return stat(path, &st) >= 0; } int loadcommit(const char *id) { git_oid oid; git_commit *c; size_t len; len = strlen(id); if (len < 4 || len > GIT_OID_SHA1_HEXSIZE || git_oid_fromstrn(&oid, id, len) < 0 || git_commit_lookup_prefix(&c, repo, &oid, len) < 0) return -1; git_oid_cpy(&tip, git_commit_id(c)); git_oid_tostr(ref, sizeof(ref), &tip); git_commit_free(c); return 0; } int loadref(git_reference *r) { git_object *obj; int err; if ((err = git_reference_peel(&obj, r, GIT_OBJECT_COMMIT)) >= 0) { git_oid_cpy(&tip, git_object_id(obj)); err = bprintf(ref, sizeof(ref), "%s", git_reference_shorthand(r)); git_object_free(obj); } git_reference_free(r); return err; } void logpage(void) { git_revwalk *w = NULL; git_oid oid; long i; int n = 0, more = 0; pagebegin(200, reponame); repoheader("log"); fputs("\n" "" "" "\n\n", stdout); if (*ref && git_revwalk_new(&w, repo) >= 0 && git_revwalk_push(w, &tip) >= 0) { git_revwalk_sorting(w, GIT_SORT_TIME); for (i = 0; i < ofs && git_revwalk_next(&oid, w) >= 0; i++) ; for (; n < loglimit && git_revwalk_next(&oid, w) >= 0; n++) logrow(&oid); more = git_revwalk_next(&oid, w) >= 0; } fputs("\n
DateMessageAuthorFilesLines
\n", stdout); if (!n) { fputs(*ref ? "

No commits.

\n" : "

Empty repository.

\n", stdout); } else if (ofs || more) { fputs("\n", stdout); } git_revwalk_free(w); pageend(); } void logrow(const git_oid *oid) { git_commit *commit; git_diff *diff; git_diff_stats *stats = NULL; const git_signature *author; if (git_commit_lookup(&commit, repo, oid) < 0) return; author = git_commit_author(commit); fputs("when, "%Y-%m-%d %H:%M"); fputs("\">", stdout); printtime(&author->when, "%Y-%m-%d"); fputs("", stdout); xmlencode(git_commit_summary(commit)); fputs("", stdout); xmlencode(author->name); if (commitdiff(&diff, commit, 0) >= 0) git_diff_get_stats(&stats, diff); if (stats) printf("%zu" "-%zu/" "+%zu\n", git_diff_stats_files_changed(stats), git_diff_stats_deletions(stats), git_diff_stats_insertions(stats)); else fputs("\n", stdout); git_diff_stats_free(stats); git_diff_free(diff); git_commit_free(commit); } const char * modestr(git_filemode_t mode) { switch (mode) { case GIT_FILEMODE_TREE: return "d---------"; case GIT_FILEMODE_BLOB_EXECUTABLE: return "-rwxr-xr-x"; case GIT_FILEMODE_LINK: return "lrwxrwxrwx"; case GIT_FILEMODE_COMMIT: return "m---------"; default: return "-rw-r--r--"; } } void notfound(void) { pagebegin(404, "Not found"); fputs("
\n

Not found.

\n", stdout); pageend(); } int openrepo(git_repository **r, const char *name) { char path[PATH_MAX]; if (bprintf(path, sizeof(path), "%s/%s.git", root, name) < 0) return -1; return git_repository_open_ext(r, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); } void pagebegin(int status, const char *pagetitle) { size_t i; httpheader(status, "text/html; charset=utf-8"); fputs("\n\n\n\n" "\n", stdout); xmlencode(pagetitle); fputs("\n\n\n\n\n
\"\" ", stdout); xmlencode(title); fputs("
\n", stdout); } void pageend(void) { fputs("
\n\n\n", stdout); } void printage(const git_time *t) { static const char *units[] = { "minute", "hour", "day", "week", "month", "year" }; static const long secs[] = { 60, HOUR, DAY, 7 * DAY, 30 * DAY, 365 * DAY }; static const long upto[] = { HOUR, DAY, 14 * DAY, 60 * DAY, 730 * DAY, LONG_MAX }; static const long fade[] = { 7 * DAY, 30 * DAY, 180 * DAY, 730 * DAY }; long d, n; size_t i, level; if (!t || !validtime(t)) { fputs("—", stdout); return; } if ((d = (long)(time(NULL) - (time_t)t->time)) < 0) d = 0; for (i = 0; d >= upto[i]; i++) ; if ((n = d / secs[i]) < 1) n = 1; for (level = 0; level < LENGTH(fade) && d >= fade[level]; level++) ; printf("%ld %s%s", n, units[i], n == 1 ? "" : "s"); } void printdiff(git_diff *diff, git_patch **patches) { const git_diff_delta *delta; const git_diff_hunk *hunk; const git_diff_line *line; git_patch *p; size_t i, h, l, nlines, len, total = 0; for (i = 0; i < git_diff_num_deltas(diff); i++) { delta = git_diff_get_delta(diff, i); p = patches[i]; printf("

", i); if (strcmp(delta->old_file.path, delta->new_file.path)) { xmlencode(delta->old_file.path); fputs(" → ", stdout); } xmlencode(delta->new_file.path); fputs("

\n
", stdout);
		if (delta->flags & GIT_DIFF_FLAG_BINARY)
			fputs("Binary files differ.", stdout);
		for (h = 0; p && h < git_patch_num_hunks(p); h++) {
			if (git_patch_get_hunk(&hunk, &nlines, p, h) < 0)
				break;
			len = hunk->header_len;
			while (len && hunk->header[len - 1] == '\n')
				len--;
			fputs("", stdout);
			xmlencoden(hunk->header, len);
			fputs("", stdout);
			for (l = 0; l < nlines; l++) {
				if (total++ >= (size_t)maxdiff)
					goto cut;
				if (git_patch_get_line_in_hunk(&line, p, h,
				    l) < 0)
					break;
				if (line->origin == GIT_DIFF_LINE_ADDITION)
					fputs("+", stdout);
				else if (line->origin ==
				    GIT_DIFF_LINE_DELETION)
					fputs("-", stdout);
				else if (line->origin == GIT_DIFF_LINE_CONTEXT)
					fputs(" ", stdout);
				else
					continue;
				len = line->content_len;
				if (len && line->content[len - 1] == '\n')
					len--;
				xmlencoden(line->content, len);
				fputs("", stdout);
			}
		}
		fputs("
\n", stdout); } return; cut: fputs("\n

Diff truncated.

\n", stdout); } void printname(const char *name) { const char *p; if ((p = strrchr(name, '/'))) { fputs("", stdout); xmlencoden(name, p - name + 1); fputs("", stdout); name = p + 1; } xmlencode(name); } void printpath(const char *path) { char prefix[PATH_MAX]; const char *p, *q, *s; fputs("

", stdout); if ((s = strrchr(reponame, '/'))) s++; else s = reponame; if (!*path) { xmlencode(s); return; } fputs("", stdout); xmlencode(s); fputs("", stdout); for (p = path; (q = strchr(p, '/')); p = q + 1) { if (bprintf(prefix, sizeof(prefix), "%.*s", (int)(q - path), path) < 0) return; fputs(" / ", stdout); xmlencoden(p, q - p); fputs("", stdout); } fputs(" / ", stdout); xmlencode(p); } void printstat(git_diff *diff, git_patch **patches) { size_t i, j, n, add, del, w, max = 0, tadd = 0, tdel = 0; n = git_diff_num_deltas(diff); for (i = 0; i < n; i++) { add = del = 0; if (patches[i]) git_patch_line_stats(NULL, &add, &del, patches[i]); if (add + del > max) max = add + del; } fputs("\n", stdout); for (i = 0; i < n; i++) { add = del = 0; if (patches[i]) git_patch_line_stats(NULL, &add, &del, patches[i]); tadd += add; tdel += del; printf("" "\n", stdout); } printf("
", i); xmlencode(git_diff_get_delta(diff, i)->new_file.path); printf("%zu", add + del); if (add + del && max > (size_t)barwidth) { w = ((add + del) * barwidth + max - 1) / max; add = (add * w + (add + del) / 2) / (add + del); del = w - add; } for (j = 0; j < add; j++) putchar('+'); fputs("", stdout); for (j = 0; j < del; j++) putchar('-'); fputs("
\n

%zu file%s changed, " "-%zu/" "+%zu

\n", n, n == 1 ? "" : "s", tdel, tadd); } void printtime(const git_time *t, const char *fmt) { struct tm tm; time_t secs; char buf[32]; if (!validtime(t)) return; secs = (time_t)t->time + (time_t)t->offset * 60; if (!gmtime_r(&secs, &tm)) return; if (strftime(buf, sizeof(buf), fmt, &tm)) fputs(buf, stdout); } void printurl(const char *page, const char *r, const char *path) { xmlencode(base); putchar('/'); urlencode(reponame); printf("/%s", page); if (r && *r) { putchar('/'); urlencode(r); } if (path && *path) { putchar('/'); urlencode(path); } } void rawpage(const char *path) { git_commit *c = NULL; git_tree *tree = NULL; git_tree_entry *e = NULL; git_blob *blob = NULL; if (git_commit_lookup(&c, repo, &tip) < 0 || git_commit_tree(&tree, c) < 0 || git_tree_entry_bypath(&e, tree, path) < 0 || git_tree_entry_type(e) != GIT_OBJECT_BLOB || git_blob_lookup(&blob, repo, git_tree_entry_id(e)) < 0) { notfound(); goto end; } httpheader(200, git_blob_is_binary(blob) ? "application/octet-stream" : "text/plain; charset=utf-8"); fwrite(git_blob_rawcontent(blob), 1, (size_t)git_blob_rawsize(blob), stdout); end: git_blob_free(blob); git_tree_entry_free(e); git_tree_free(tree); git_commit_free(c); } void readfile(const char *name, const char *file, char *buf, size_t len) { char path[PATH_MAX]; FILE *fp; buf[0] = '\0'; if (bprintf(path, sizeof(path), "%s/%s.git/%s", root, name, file) < 0) return; if (!(fp = fopen(path, "r"))) return; if (fgets(buf, (int)len, fp)) buf[strcspn(buf, "\r\n")] = '\0'; fclose(fp); if (!strncmp(buf, "Unnamed repository", 18)) buf[0] = '\0'; } void refrows(const char *glob) { git_reference_iterator *it; git_reference *r; git_object *obj; const git_signature *author; if (git_reference_iterator_glob_new(&it, repo, glob) < 0) return; while (git_reference_next(&r, it) >= 0) { fputs("", stdout); xmlencode(git_reference_shorthand(r)); fputs("", stdout); if (git_reference_peel(&obj, r, GIT_OBJECT_COMMIT) >= 0) { author = git_commit_author((git_commit *)obj); printtime(&author->when, "%Y-%m-%d %H:%M"); fputs("", stdout); xmlencode(author->name); git_object_free(obj); } else { fputs("", stdout); } fputs("\n", stdout); git_reference_free(r); } git_reference_iterator_free(it); } void refspage(void) { pagebegin(200, reponame); repoheader("refs"); printf("

Branches

\n\n%s", refshead); refrows("refs/heads/*"); printf("\n
\n

Tags

\n" "\n%s", refshead); refrows("refs/tags/*"); fputs("\n
\n", stdout); pageend(); } void repoheader(const char *tab) { static const char *tabs[] = { "log", "tree", "refs" }; char desc[256]; size_t i; readfile(reponame, "description", desc, sizeof(desc)); fputs("
\n

", stdout); printname(reponame); fputs("

\n", stdout); if (*desc) { fputs("

", stdout); xmlencode(desc); fputs("

\n", stdout); } fputs("

git clone ", stdout); xmlencode(clone); xmlencode(reponame); fputs(".git

\n\n
\n
\n", stdout); } void route(char *path) { git_reference *r; char *rest = NULL, *page, *arg, *cut; while (*path == '/') path++; for (cut = path + strlen(path); cut > path && cut[-1] == '/'; cut--) cut[-1] = '\0'; if (!*path) { indexpage(); return; } while (!isrepo(path)) { if (!(cut = strrchr(path, '/'))) { notfound(); return; } if (rest) rest[-1] = '/'; *cut = '\0'; rest = cut + 1; } if (openrepo(&repo, path) < 0 || git_repository_odb(&odb, repo) < 0) { notfound(); goto end; } reponame = path; if (git_repository_head(&r, repo) >= 0) loadref(r); page = rest ? rest : "log"; if ((arg = strchr(page, '/'))) *arg++ = '\0'; else arg = ""; if (!strcmp(page, "log")) { if (*arg && (findref(arg, &rest) < 0 || *rest)) notfound(); else logpage(); } else if (!strcmp(page, "tree")) { if (*arg && findref(arg, &rest) < 0) notfound(); else treepage(*arg ? rest : ""); } else if (!strcmp(page, "raw")) { if (findref(arg, &rest) < 0 || !*rest) notfound(); else rawpage(rest); } else if (!strcmp(page, "commit") && *arg) { commitpage(arg); } else if (!strcmp(page, "refs") && !*arg) { refspage(); } else { notfound(); } end: git_odb_free(odb); git_repository_free(repo); } void treepage(const char *path) { git_commit *c = NULL; git_tree *root = NULL, *tree = NULL; git_tree_entry *e = NULL; if (*ref && (git_commit_lookup(&c, repo, &tip) < 0 || git_commit_tree(&root, c) < 0)) goto fail; if (*path) { if (!root || git_tree_entry_bypath(&e, root, path) < 0) goto fail; if (git_tree_entry_type(e) == GIT_OBJECT_BLOB) { filepage(path, e); goto end; } if (git_tree_entry_type(e) != GIT_OBJECT_TREE || git_tree_lookup(&tree, repo, git_tree_entry_id(e)) < 0) goto fail; } pagebegin(200, reponame); repoheader("tree"); printpath(path); fputs("

\n", stdout); if (root) treetable(tree ? tree : root, path); else fputs("

Empty repository.

\n", stdout); pageend(); goto end; fail: notfound(); end: git_tree_entry_free(e); git_tree_free(tree); git_tree_free(root); git_commit_free(c); } void treerow(const git_tree_entry *e, const char *path) { char child[PATH_MAX]; const char *name; git_object_t type, otype; size_t len; name = git_tree_entry_name(e); type = git_tree_entry_type(e); if (bprintf(child, sizeof(child), "%s%s%s", path, *path ? "/" : "", name) < 0) return; printf("%s", modestr(git_tree_entry_filemode(e)), type == GIT_OBJECT_TREE ? "dirent" : "name"); if (type == GIT_OBJECT_COMMIT) { xmlencode(name); } else { fputs("", stdout); xmlencode(name); fputs(type == GIT_OBJECT_TREE ? "/" : "", stdout); } fputs("", stdout); if (type == GIT_OBJECT_BLOB && git_odb_read_header(&len, &otype, odb, git_tree_entry_id(e)) >= 0) printf("%zuB", len); fputs("\n", stdout); } void treetable(const git_tree *tree, const char *path) { const git_tree_entry *e; size_t i, n; int pass; fputs("\n" "\n" "\n", stdout); n = git_tree_entrycount(tree); for (pass = 0; pass < 2; pass++) { for (i = 0; i < n; i++) { e = git_tree_entry_byindex(tree, i); if ((git_tree_entry_type(e) == GIT_OBJECT_TREE) != pass) treerow(e, path); } } fputs("\n
ModeNameSize
\n", stdout); } void urlencode(const char *s) { for (; *s; s++) { if (isalnum((unsigned char)*s) || strchr("-._~/", *s)) putchar(*s); else printf("%%%02X", (unsigned char)*s); } } int validname(const char *s) { unsigned char c; int start = 1; if (!*s) return 0; for (; *s; s++) { c = *s; if (c == '/') { if (start) return 0; start = 1; continue; } if (start && !(isalnum(c) || c == '_')) return 0; if (!(isalnum(c) || c == '.' || c == '_' || c == '-')) return 0; start = 0; } return !start; } int validtime(const git_time *t) { return t->time >= 0 && t->time <= MAXTIME && t->offset >= -1440 && t->offset <= 1440; } void walk(const char *rel, int depth) { char path[PATH_MAX], name[PATH_MAX]; struct dirent **ents; struct stat st; const char *e; size_t len; int i, n; if (depth > maxdepth || bprintf(path, sizeof(path), "%s/%s", root, rel) < 0 || (n = scandir(path, &ents, NULL, alphasort)) < 0) return; for (i = 0; i < n; i++) { e = ents[i]->d_name; len = strlen(e); if (e[0] != '.' && bprintf(name, sizeof(name), "%s%s%s", rel, *rel ? "/" : "", e) >= 0 && bprintf(path, sizeof(path), "%s/%s", root, name) >= 0 && lstat(path, &st) >= 0 && S_ISDIR(st.st_mode)) { if (len > 4 && !strcmp(e + len - 4, ".git")) { name[strlen(name) - 4] = '\0'; if (isrepo(name)) indexrow(name); } else { walk(name, depth + 1); } } free(ents[i]); } free(ents); } void xmlencode(const char *s) { if (s) xmlencoden(s, strlen(s)); } void xmlencoden(const char *s, size_t len) { for (; len; s++, len--) { switch (*s) { case '<': fputs("<", stdout); break; case '>': fputs(">", stdout); break; case '&': fputs("&", stdout); break; case '"': fputs(""", stdout); break; case '\'': fputs("'", stdout); break; default: putchar(*s); } } } /* main */ int main(void) { char path[1024]; const char *q; struct rlimit rl; rl.rlim_cur = rl.rlim_max = maxcpu; setrlimit(RLIMIT_CPU, &rl); root = envor("PETAL_ROOT", root); css = envor("PETAL_CSS", css); icon = envor("PETAL_ICON", icon); clone = envor("PETAL_CLONE", clone); title = envor("PETAL_TITLE", title); base = envor("SCRIPT_NAME", ""); q = envor("QUERY_STRING", ""); if (!strncmp(q, "ofs=", 4) && (ofs = strtol(q + 4, NULL, 10)) < 0) ofs = 0; git_libgit2_init(); if (bprintf(path, sizeof(path), "%s", envor("PATH_INFO", "")) < 0) notfound(); else route(path); git_libgit2_shutdown(); return 0; }