├── .gitignore ├── .gitmodules ├── .mailmap ├── AUTHORS ├── COPYING ├── Makefile ├── README ├── cache.c ├── cache.h ├── cgit.c ├── cgit.css ├── cgit.h ├── cgit.js ├── cgit.mk ├── cgit.png ├── cgitrc.5.txt ├── cmd.c ├── cmd.h ├── configfile.c ├── configfile.h ├── contrib └── hooks │ └── post-receive.agefile ├── favicon.ico ├── filter.c ├── filters ├── about-formatting.sh ├── commit-links.sh ├── email-gravatar.lua ├── email-gravatar.py ├── email-libravatar.lua ├── file-authentication.lua ├── gentoo-ldap-authentication.lua ├── html-converters │ ├── man2html │ ├── md2html │ ├── rst2html │ └── txt2html ├── owner-example.lua ├── simple-authentication.lua ├── syntax-highlighting.py └── syntax-highlighting.sh ├── gen-version.sh ├── html.c ├── html.h ├── parsing.c ├── robots.txt ├── scan-tree.c ├── scan-tree.h ├── shared.c ├── tests ├── .gitignore ├── Makefile ├── filters │ ├── dump.lua │ └── dump.sh ├── setup.sh ├── t0001-validate-git-versions.sh ├── t0010-validate-html.sh ├── t0020-validate-cache.sh ├── t0101-index.sh ├── t0102-summary.sh ├── t0103-log.sh ├── t0104-tree.sh ├── t0105-commit.sh ├── t0106-diff.sh ├── t0107-snapshot.sh ├── t0108-patch.sh ├── t0109-gitconfig.sh ├── t0110-rawdiff.sh ├── t0111-filter.sh └── valgrind │ └── bin │ └── cgit ├── ui-atom.c ├── ui-atom.h ├── ui-blame.c ├── ui-blame.h ├── ui-blob.c ├── ui-blob.h ├── ui-clone.c ├── ui-clone.h ├── ui-commit.c ├── ui-commit.h ├── ui-diff.c ├── ui-diff.h ├── ui-log.c ├── ui-log.h ├── ui-patch.c ├── ui-patch.h ├── ui-plain.c ├── ui-plain.h ├── ui-refs.c ├── ui-refs.h ├── ui-repolist.c ├── ui-repolist.h ├── ui-shared.c ├── ui-shared.h ├── ui-snapshot.c ├── ui-snapshot.h ├── ui-ssdiff.c ├── ui-ssdiff.h ├── ui-stats.c ├── ui-stats.h ├── ui-summary.c ├── ui-summary.h ├── ui-tag.c ├── ui-tag.h ├── ui-tree.c └── ui-tree.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Files I don't care to see in git-status/commit 2 | /cgit 3 | cgit.conf 4 | CGIT-CFLAGS 5 | VERSION 6 | cgitrc.5 7 | cgitrc.5.fo 8 | cgitrc.5.html 9 | cgitrc.5.pdf 10 | cgitrc.5.xml 11 | *.o 12 | *.d 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "git"] 2 | url = https://git.kernel.org/pub/scm/git/git.git 3 | path = git 4 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Florian Pritz 2 | Harley Laue 3 | John Keeping 4 | Lars Hjemli 5 | Lars Hjemli 6 | Lars Hjemli 7 | Lars Hjemli 8 | Lukas Fleischer 9 | Lukas Fleischer 10 | Stefan Bühler 11 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Maintainer: 2 | Jason A. Donenfeld 3 | 4 | Contributors: 5 | Jason A. Donenfeld 6 | Lukas Fleischer 7 | Johan Herland 8 | Lars Hjemli 9 | Ferry Huberts 10 | John Keeping 11 | 12 | Previous Maintainer: 13 | Lars Hjemli 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all:: 2 | 3 | CGIT_VERSION = v1.2.3 4 | CGIT_SCRIPT_NAME = cgit.cgi 5 | CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 6 | CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 7 | CGIT_CONFIG = /etc/cgitrc 8 | CACHE_ROOT = /var/cache/cgit 9 | prefix = /usr/local 10 | libdir = $(prefix)/lib 11 | filterdir = $(libdir)/cgit/filters 12 | docdir = $(prefix)/share/doc/cgit 13 | htmldir = $(docdir) 14 | pdfdir = $(docdir) 15 | mandir = $(prefix)/share/man 16 | SHA1_HEADER = 17 | GIT_VER = 2.46.0 18 | GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz 19 | INSTALL = install 20 | COPYTREE = cp -r 21 | MAN5_TXT = $(wildcard *.5.txt) 22 | MAN_TXT = $(MAN5_TXT) 23 | DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT)) 24 | DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) 25 | DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT)) 26 | 27 | ASCIIDOC = asciidoc 28 | ASCIIDOC_EXTRA = 29 | ASCIIDOC_HTML = xhtml11 30 | ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA) 31 | TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML) 32 | 33 | # Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) 34 | # do not support the 'size specifiers' introduced by C99, namely ll, hh, 35 | # j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). 36 | # some C compilers supported these specifiers prior to C99 as an extension. 37 | # 38 | # Define HAVE_LINUX_SENDFILE to use sendfile() 39 | 40 | #-include config.mak 41 | 42 | -include git/config.mak.uname 43 | # 44 | # Let the user override the above settings. 45 | # 46 | -include cgit.conf 47 | 48 | export CGIT_VERSION CGIT_SCRIPT_NAME CGIT_SCRIPT_PATH CGIT_DATA_PATH CGIT_CONFIG CACHE_ROOT 49 | 50 | # 51 | # Define a way to invoke make in subdirs quietly, shamelessly ripped 52 | # from git.git 53 | # 54 | QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 55 | QUIET_SUBDIR1 = 56 | 57 | ifneq ($(findstring w,$(MAKEFLAGS)),w) 58 | PRINT_DIR = --no-print-directory 59 | else # "make -w" 60 | NO_SUBDIR = : 61 | endif 62 | 63 | ifndef V 64 | QUIET_SUBDIR0 = +@subdir= 65 | QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 66 | $(MAKE) $(PRINT_DIR) -C $$subdir 67 | QUIET_TAGS = @echo ' ' TAGS $@; 68 | export V 69 | endif 70 | 71 | .SUFFIXES: 72 | 73 | all:: cgit 74 | 75 | cgit: 76 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1 77 | 78 | sparse: 79 | $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse 80 | 81 | test: 82 | @$(MAKE) --no-print-directory cgit EXTRA_GIT_TARGETS=all 83 | $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 84 | 85 | install: all 86 | $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 87 | $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 88 | $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 89 | $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 90 | $(INSTALL) -m 0644 cgit.js $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js 91 | $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 92 | $(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico 93 | $(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt 94 | $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir) 95 | $(COPYTREE) filters/* $(DESTDIR)$(filterdir) 96 | 97 | install-doc: install-man install-html install-pdf 98 | 99 | install-man: doc-man 100 | $(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5 101 | $(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5 102 | 103 | install-html: doc-html 104 | $(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir) 105 | $(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir) 106 | 107 | install-pdf: doc-pdf 108 | $(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir) 109 | $(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir) 110 | 111 | uninstall: 112 | rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 113 | rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 114 | rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 115 | rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico 116 | 117 | uninstall-doc: uninstall-man uninstall-html uninstall-pdf 118 | 119 | uninstall-man: 120 | @for i in $(DOC_MAN5); do \ 121 | rm -fv $(DESTDIR)$(mandir)/man5/$$i; \ 122 | done 123 | 124 | uninstall-html: 125 | @for i in $(DOC_HTML); do \ 126 | rm -fv $(DESTDIR)$(htmldir)/$$i; \ 127 | done 128 | 129 | uninstall-pdf: 130 | @for i in $(DOC_PDF); do \ 131 | rm -fv $(DESTDIR)$(pdfdir)/$$i; \ 132 | done 133 | 134 | doc: doc-man doc-html doc-pdf 135 | doc-man: doc-man5 136 | doc-man5: $(DOC_MAN5) 137 | doc-html: $(DOC_HTML) 138 | doc-pdf: $(DOC_PDF) 139 | 140 | %.5 : %.5.txt 141 | a2x -f manpage $< 142 | 143 | $(DOC_HTML): %.html : %.txt 144 | $(TXT_TO_HTML) -o $@+ $< && \ 145 | mv $@+ $@ 146 | 147 | $(DOC_PDF): %.pdf : %.txt 148 | a2x -f pdf cgitrc.5.txt 149 | 150 | clean: clean-doc 151 | $(RM) cgit VERSION CGIT-CFLAGS *.o tags 152 | $(RM) -r .deps 153 | 154 | cleanall: clean 155 | $(MAKE) -C git clean 156 | 157 | clean-doc: 158 | $(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo 159 | 160 | get-git: 161 | curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git 162 | 163 | tags: 164 | $(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags 165 | 166 | .PHONY: all cgit git get-git 167 | .PHONY: clean clean-doc cleanall 168 | .PHONY: doc doc-html doc-man doc-pdf 169 | .PHONY: install install-doc install-html install-man install-pdf 170 | .PHONY: tags test 171 | .PHONY: uninstall uninstall-doc uninstall-html uninstall-man uninstall-pdf 172 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | cgit - CGI for Git 2 | ================== 3 | 4 | This is an attempt to create a fast web interface for the Git SCM, using a 5 | built-in cache to decrease server I/O pressure. 6 | 7 | Installation 8 | ------------ 9 | 10 | Building cgit involves building a proper version of Git. How to do this 11 | depends on how you obtained the cgit sources: 12 | 13 | a) If you're working in a cloned cgit repository, you first need to 14 | initialize and update the Git submodule: 15 | 16 | $ git submodule init # register the Git submodule in .git/config 17 | $ $EDITOR .git/config # if you want to specify a different url for git 18 | $ git submodule update # clone/fetch and checkout correct git version 19 | 20 | b) If you're building from a cgit tarball, you can download a proper git 21 | version like this: 22 | 23 | $ make get-git 24 | 25 | When either a) or b) has been performed, you can build and install cgit like 26 | this: 27 | 28 | $ make 29 | $ sudo make install 30 | 31 | This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You 32 | can configure this location (and a few other things) by providing a `cgit.conf` 33 | file (see the Makefile for details). 34 | 35 | If you'd like to compile without Lua support, you may use: 36 | 37 | $ make NO_LUA=1 38 | 39 | And if you'd like to specify a Lua implementation, you may use: 40 | 41 | $ make LUA_PKGCONFIG=lua5.1 42 | 43 | If this is not specified, the Lua implementation will be auto-detected, 44 | preferring LuaJIT if many are present. Acceptable values are generally "lua", 45 | "luajit", "lua5.1", and "lua5.2". 46 | 47 | 48 | Dependencies 49 | ------------ 50 | 51 | * libzip 52 | * libcrypto (OpenSSL) 53 | * libssl (OpenSSL) 54 | * optional: luajit or lua, most reliably used when pkg-config is available 55 | 56 | Apache configuration 57 | -------------------- 58 | 59 | A new `Directory` section must probably be added for cgit, possibly something 60 | like this: 61 | 62 | 63 | AllowOverride None 64 | Options +ExecCGI 65 | Order allow,deny 66 | Allow from all 67 | 68 | 69 | 70 | Runtime configuration 71 | --------------------- 72 | 73 | The file `/etc/cgitrc` is read by cgit before handling a request. In addition 74 | to runtime parameters, this file may also contain a list of repositories 75 | displayed by cgit (see `cgitrc.5.txt` for further details). 76 | 77 | The cache 78 | --------- 79 | 80 | When cgit is invoked it looks for a cache file matching the request and 81 | returns it to the client. If no such cache file exists (or if it has expired), 82 | the content for the request is written into the proper cache file before the 83 | file is returned. 84 | 85 | If the cache file has expired but cgit is unable to obtain a lock for it, the 86 | stale cache file is returned to the client. This is done to favour page 87 | throughput over page freshness. 88 | 89 | The generated content contains the complete response to the client, including 90 | the HTTP headers `Modified` and `Expires`. 91 | 92 | Online presence 93 | --------------- 94 | 95 | * The cgit homepage is hosted by cgit at 96 | 97 | * Patches, bug reports, discussions and support should go to the cgit 98 | mailing list: . To sign up, visit 99 | 100 | -------------------------------------------------------------------------------- /cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Since git has it's own cache.h which we include, 3 | * lets test on CGIT_CACHE_H to avoid confusion 4 | */ 5 | 6 | #ifndef CGIT_CACHE_H 7 | #define CGIT_CACHE_H 8 | 9 | typedef void (*cache_fill_fn)(void); 10 | 11 | 12 | /* Print cached content to stdout, generate the content if necessary. 13 | * 14 | * Parameters 15 | * size max number of cache files 16 | * path directory used to store cache files 17 | * key the key used to lookup cache files 18 | * ttl max cache time in seconds for this key 19 | * fn content generator function for this key 20 | * 21 | * Return value 22 | * 0 indicates success, everything else is an error 23 | */ 24 | extern int cache_process(int size, const char *path, const char *key, int ttl, 25 | cache_fill_fn fn); 26 | 27 | 28 | /* List info about all cache entries on stdout */ 29 | extern int cache_ls(const char *path); 30 | 31 | /* Print a message to stdout */ 32 | __attribute__((format (printf,1,2))) 33 | extern void cache_log(const char *format, ...); 34 | 35 | extern unsigned long hash_str(const char *str); 36 | 37 | #endif /* CGIT_CACHE_H */ 38 | -------------------------------------------------------------------------------- /cgit.js: -------------------------------------------------------------------------------- 1 | /* cgit.js: javacript functions for cgit 2 | * 3 | * Copyright (C) 2006-2018 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | (function () { 10 | 11 | /* This follows the logic and suffixes used in ui-shared.c */ 12 | 13 | var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ]; 14 | var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ]; 15 | var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ]; 16 | var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ]; 17 | var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ]; 18 | 19 | function render_age(e, age) { 20 | var t, n; 21 | 22 | for (n = 0; n < age_classes.length; n++) 23 | if (age < age_limit[n]) 24 | break; 25 | 26 | t = Math.round(age / age_next[n]) + " " + age_suffix[n]; 27 | 28 | if (e.textContent != t) { 29 | e.textContent = t; 30 | if (n == age_classes.length) 31 | n--; 32 | if (e.className != age_classes[n]) 33 | e.className = age_classes[n]; 34 | } 35 | } 36 | 37 | function aging() { 38 | var n, next = 24 * 3600, 39 | now_ut = Math.round((new Date().getTime() / 1000)); 40 | 41 | for (n = 0; n < age_classes.length; n++) { 42 | var m, elems = document.getElementsByClassName(age_classes[n]); 43 | 44 | if (elems.length && update_next[n] < next) 45 | next = update_next[n]; 46 | 47 | for (m = 0; m < elems.length; m++) { 48 | var age = now_ut - elems[m].getAttribute("data-ut"); 49 | 50 | render_age(elems[m], age); 51 | } 52 | } 53 | 54 | /* 55 | * We only need to come back when the age might have changed. 56 | * Eg, if everything is counted in hours already, once per 57 | * 5 minutes is accurate enough. 58 | */ 59 | 60 | window.setTimeout(aging, next * 1000); 61 | } 62 | 63 | document.addEventListener("DOMContentLoaded", function() { 64 | /* we can do the aging on DOM content load since no layout dependency */ 65 | aging(); 66 | }, false); 67 | 68 | })(); 69 | -------------------------------------------------------------------------------- /cgit.mk: -------------------------------------------------------------------------------- 1 | # This Makefile is run in the "git" directory in order to re-use Git's 2 | # build variables and operating system detection. Hence all files in 3 | # CGit's directory must be prefixed with "../". 4 | include Makefile 5 | 6 | CGIT_PREFIX = ../ 7 | 8 | -include $(CGIT_PREFIX)cgit.conf 9 | 10 | # The CGIT_* variables are inherited when this file is called from the 11 | # main Makefile - they are defined there. 12 | 13 | $(CGIT_PREFIX)VERSION: force-version 14 | @cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)" 15 | -include $(CGIT_PREFIX)VERSION 16 | .PHONY: force-version 17 | 18 | # CGIT_CFLAGS is a separate variable so that we can track it separately 19 | # and avoid rebuilding all of Git when these variables change. 20 | CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 21 | CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 22 | CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 23 | 24 | PKG_CONFIG ?= pkg-config 25 | 26 | ifdef NO_C99_FORMAT 27 | CFLAGS += -DNO_C99_FORMAT 28 | endif 29 | 30 | ifdef NO_LUA 31 | LUA_MESSAGE := linking without specified Lua support 32 | CGIT_CFLAGS += -DNO_LUA 33 | else 34 | ifeq ($(LUA_PKGCONFIG),) 35 | LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \ 36 | $(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \ 37 | done) 38 | LUA_MODE := autodetected 39 | else 40 | LUA_MODE := specified 41 | endif 42 | ifneq ($(LUA_PKGCONFIG),) 43 | LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG) 44 | LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null) 45 | LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null) 46 | CGIT_LIBS += $(LUA_LIBS) 47 | CGIT_CFLAGS += $(LUA_CFLAGS) 48 | else 49 | LUA_MESSAGE := linking without autodetected Lua support 50 | NO_LUA := YesPlease 51 | CGIT_CFLAGS += -DNO_LUA 52 | endif 53 | 54 | endif 55 | 56 | # Add -ldl to linker flags on systems that commonly use GNU libc. 57 | ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) 58 | CGIT_LIBS += -ldl 59 | endif 60 | 61 | # glibc 2.1+ offers sendfile which the most common C library on Linux 62 | ifeq ($(uname_S),Linux) 63 | HAVE_LINUX_SENDFILE = YesPlease 64 | endif 65 | 66 | ifdef HAVE_LINUX_SENDFILE 67 | CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE 68 | endif 69 | 70 | CGIT_OBJ_NAMES += cgit.o 71 | CGIT_OBJ_NAMES += cache.o 72 | CGIT_OBJ_NAMES += cmd.o 73 | CGIT_OBJ_NAMES += configfile.o 74 | CGIT_OBJ_NAMES += filter.o 75 | CGIT_OBJ_NAMES += html.o 76 | CGIT_OBJ_NAMES += parsing.o 77 | CGIT_OBJ_NAMES += scan-tree.o 78 | CGIT_OBJ_NAMES += shared.o 79 | CGIT_OBJ_NAMES += ui-atom.o 80 | CGIT_OBJ_NAMES += ui-blame.o 81 | CGIT_OBJ_NAMES += ui-blob.o 82 | CGIT_OBJ_NAMES += ui-clone.o 83 | CGIT_OBJ_NAMES += ui-commit.o 84 | CGIT_OBJ_NAMES += ui-diff.o 85 | CGIT_OBJ_NAMES += ui-log.o 86 | CGIT_OBJ_NAMES += ui-patch.o 87 | CGIT_OBJ_NAMES += ui-plain.o 88 | CGIT_OBJ_NAMES += ui-refs.o 89 | CGIT_OBJ_NAMES += ui-repolist.o 90 | CGIT_OBJ_NAMES += ui-shared.o 91 | CGIT_OBJ_NAMES += ui-snapshot.o 92 | CGIT_OBJ_NAMES += ui-ssdiff.o 93 | CGIT_OBJ_NAMES += ui-stats.o 94 | CGIT_OBJ_NAMES += ui-summary.o 95 | CGIT_OBJ_NAMES += ui-tag.o 96 | CGIT_OBJ_NAMES += ui-tree.o 97 | 98 | CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES)) 99 | 100 | # Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the 101 | # version changes. 102 | CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp) 103 | $(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION 104 | $(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \ 105 | -DCGIT_VERSION='"$(CGIT_VERSION)"' 106 | 107 | # Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not 108 | # handled by that and we must handle them ourselves. 109 | cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d) 110 | cgit_dep_files_present := $(wildcard $(cgit_dep_files)) 111 | ifneq ($(cgit_dep_files_present),) 112 | include $(cgit_dep_files_present) 113 | endif 114 | 115 | ifeq ($(wildcard $(CGIT_PREFIX).depend),) 116 | missing_dep_dirs += $(CGIT_PREFIX).depend 117 | endif 118 | 119 | $(CGIT_PREFIX).depend: 120 | @mkdir -p $@ 121 | 122 | $(CGIT_PREFIX)CGIT-CFLAGS: FORCE 123 | @FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \ 124 | if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \ 125 | echo 1>&2 " * new CGit build flags"; \ 126 | echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \ 127 | fi 128 | 129 | $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) 130 | $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< 131 | 132 | $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) 133 | @echo 1>&1 " * $(LUA_MESSAGE)" 134 | $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) 135 | 136 | CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS)) 137 | 138 | $(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE 139 | $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $< 140 | 141 | cgit-sparse: $(CGIT_SP_OBJS) 142 | -------------------------------------------------------------------------------- /cgit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zx2c4/cgit/09d24d7cd0b7e85633f2f43808b12871bb209d69/cgit.png -------------------------------------------------------------------------------- /cmd.c: -------------------------------------------------------------------------------- 1 | /* cmd.c: the cgit command dispatcher 2 | * 3 | * Copyright (C) 2006-2017 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include "cgit.h" 10 | #include "cmd.h" 11 | #include "cache.h" 12 | #include "ui-shared.h" 13 | #include "ui-atom.h" 14 | #include "ui-blame.h" 15 | #include "ui-blob.h" 16 | #include "ui-clone.h" 17 | #include "ui-commit.h" 18 | #include "ui-diff.h" 19 | #include "ui-log.h" 20 | #include "ui-patch.h" 21 | #include "ui-plain.h" 22 | #include "ui-refs.h" 23 | #include "ui-repolist.h" 24 | #include "ui-snapshot.h" 25 | #include "ui-stats.h" 26 | #include "ui-summary.h" 27 | #include "ui-tag.h" 28 | #include "ui-tree.h" 29 | 30 | static void HEAD_fn(void) 31 | { 32 | cgit_clone_head(); 33 | } 34 | 35 | static void atom_fn(void) 36 | { 37 | cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items); 38 | } 39 | 40 | static void about_fn(void) 41 | { 42 | if (ctx.repo) { 43 | size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0; 44 | if (!ctx.qry.path && 45 | ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' && 46 | (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) { 47 | char *currenturl = cgit_currenturl(); 48 | char *redirect = fmtalloc("%s/", currenturl); 49 | cgit_redirect(redirect, true); 50 | free(currenturl); 51 | free(redirect); 52 | } else if (ctx.repo->readme.nr) 53 | cgit_print_repo_readme(ctx.qry.path); 54 | else if (ctx.repo->homepage) 55 | cgit_redirect(ctx.repo->homepage, false); 56 | else { 57 | char *currenturl = cgit_currenturl(); 58 | char *redirect = fmtalloc("%s../", currenturl); 59 | cgit_redirect(redirect, false); 60 | free(currenturl); 61 | free(redirect); 62 | } 63 | } else 64 | cgit_print_site_readme(); 65 | } 66 | 67 | static void blame_fn(void) 68 | { 69 | if (ctx.repo->enable_blame) 70 | cgit_print_blame(); 71 | else 72 | cgit_print_error_page(403, "Forbidden", "Blame is disabled"); 73 | } 74 | 75 | static void blob_fn(void) 76 | { 77 | cgit_print_blob(ctx.qry.oid, ctx.qry.path, ctx.qry.head, 0); 78 | } 79 | 80 | static void commit_fn(void) 81 | { 82 | cgit_print_commit(ctx.qry.oid, ctx.qry.path); 83 | } 84 | 85 | static void diff_fn(void) 86 | { 87 | cgit_print_diff(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path, 1, 0); 88 | } 89 | 90 | static void rawdiff_fn(void) 91 | { 92 | cgit_print_diff(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path, 1, 1); 93 | } 94 | 95 | static void info_fn(void) 96 | { 97 | cgit_clone_info(); 98 | } 99 | 100 | static void log_fn(void) 101 | { 102 | cgit_print_log(ctx.qry.oid, ctx.qry.ofs, ctx.cfg.max_commit_count, 103 | ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1, 104 | ctx.repo->enable_commit_graph, 105 | ctx.repo->commit_sort); 106 | } 107 | 108 | static void ls_cache_fn(void) 109 | { 110 | ctx.page.mimetype = "text/plain"; 111 | ctx.page.filename = "ls-cache.txt"; 112 | cgit_print_http_headers(); 113 | cache_ls(ctx.cfg.cache_root); 114 | } 115 | 116 | static void objects_fn(void) 117 | { 118 | cgit_clone_objects(); 119 | } 120 | 121 | static void repolist_fn(void) 122 | { 123 | cgit_print_repolist(); 124 | } 125 | 126 | static void patch_fn(void) 127 | { 128 | cgit_print_patch(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path); 129 | } 130 | 131 | static void plain_fn(void) 132 | { 133 | cgit_print_plain(); 134 | } 135 | 136 | static void refs_fn(void) 137 | { 138 | cgit_print_refs(); 139 | } 140 | 141 | static void snapshot_fn(void) 142 | { 143 | cgit_print_snapshot(ctx.qry.head, ctx.qry.oid, ctx.qry.path, 144 | ctx.qry.nohead); 145 | } 146 | 147 | static void stats_fn(void) 148 | { 149 | cgit_show_stats(); 150 | } 151 | 152 | static void summary_fn(void) 153 | { 154 | cgit_print_summary(); 155 | } 156 | 157 | static void tag_fn(void) 158 | { 159 | cgit_print_tag(ctx.qry.oid); 160 | } 161 | 162 | static void tree_fn(void) 163 | { 164 | cgit_print_tree(ctx.qry.oid, ctx.qry.path); 165 | } 166 | 167 | #define def_cmd(name, want_repo, want_vpath, is_clone) \ 168 | {#name, name##_fn, want_repo, want_vpath, is_clone} 169 | 170 | struct cgit_cmd *cgit_get_cmd(void) 171 | { 172 | static struct cgit_cmd cmds[] = { 173 | def_cmd(HEAD, 1, 0, 1), 174 | def_cmd(atom, 1, 0, 0), 175 | def_cmd(about, 0, 0, 0), 176 | def_cmd(blame, 1, 1, 0), 177 | def_cmd(blob, 1, 0, 0), 178 | def_cmd(commit, 1, 1, 0), 179 | def_cmd(diff, 1, 1, 0), 180 | def_cmd(info, 1, 0, 1), 181 | def_cmd(log, 1, 1, 0), 182 | def_cmd(ls_cache, 0, 0, 0), 183 | def_cmd(objects, 1, 0, 1), 184 | def_cmd(patch, 1, 1, 0), 185 | def_cmd(plain, 1, 0, 0), 186 | def_cmd(rawdiff, 1, 1, 0), 187 | def_cmd(refs, 1, 0, 0), 188 | def_cmd(repolist, 0, 0, 0), 189 | def_cmd(snapshot, 1, 0, 0), 190 | def_cmd(stats, 1, 1, 0), 191 | def_cmd(summary, 1, 0, 0), 192 | def_cmd(tag, 1, 0, 0), 193 | def_cmd(tree, 1, 1, 0), 194 | }; 195 | int i; 196 | 197 | if (ctx.qry.page == NULL) { 198 | if (ctx.repo) 199 | ctx.qry.page = "summary"; 200 | else 201 | ctx.qry.page = "repolist"; 202 | } 203 | 204 | for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 205 | if (!strcmp(ctx.qry.page, cmds[i].name)) 206 | return &cmds[i]; 207 | return NULL; 208 | } 209 | -------------------------------------------------------------------------------- /cmd.h: -------------------------------------------------------------------------------- 1 | #ifndef CMD_H 2 | #define CMD_H 3 | 4 | typedef void (*cgit_cmd_fn)(void); 5 | 6 | struct cgit_cmd { 7 | const char *name; 8 | cgit_cmd_fn fn; 9 | unsigned int want_repo:1, 10 | want_vpath:1, 11 | is_clone:1; 12 | }; 13 | 14 | extern struct cgit_cmd *cgit_get_cmd(void); 15 | 16 | #endif /* CMD_H */ 17 | -------------------------------------------------------------------------------- /configfile.c: -------------------------------------------------------------------------------- 1 | /* configfile.c: parsing of config files 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include 10 | #include "configfile.h" 11 | 12 | static int next_char(FILE *f) 13 | { 14 | int c = fgetc(f); 15 | if (c == '\r') { 16 | c = fgetc(f); 17 | if (c != '\n') { 18 | ungetc(c, f); 19 | c = '\r'; 20 | } 21 | } 22 | return c; 23 | } 24 | 25 | static void skip_line(FILE *f) 26 | { 27 | int c; 28 | 29 | while ((c = next_char(f)) && c != '\n' && c != EOF) 30 | ; 31 | } 32 | 33 | static int read_config_line(FILE *f, struct strbuf *name, struct strbuf *value) 34 | { 35 | int c = next_char(f); 36 | 37 | strbuf_reset(name); 38 | strbuf_reset(value); 39 | 40 | /* Skip comments and preceding spaces. */ 41 | for(;;) { 42 | if (c == EOF) 43 | return 0; 44 | else if (c == '#' || c == ';') 45 | skip_line(f); 46 | else if (!isspace(c)) 47 | break; 48 | c = next_char(f); 49 | } 50 | 51 | /* Read variable name. */ 52 | while (c != '=') { 53 | if (c == '\n' || c == EOF) 54 | return 0; 55 | strbuf_addch(name, c); 56 | c = next_char(f); 57 | } 58 | 59 | /* Read variable value. */ 60 | c = next_char(f); 61 | while (c != '\n' && c != EOF) { 62 | strbuf_addch(value, c); 63 | c = next_char(f); 64 | } 65 | 66 | return 1; 67 | } 68 | 69 | int parse_configfile(const char *filename, configfile_value_fn fn) 70 | { 71 | static int nesting; 72 | struct strbuf name = STRBUF_INIT; 73 | struct strbuf value = STRBUF_INIT; 74 | FILE *f; 75 | 76 | /* cancel deeply nested include-commands */ 77 | if (nesting > 8) 78 | return -1; 79 | if (!(f = fopen(filename, "r"))) 80 | return -1; 81 | nesting++; 82 | while (read_config_line(f, &name, &value)) 83 | fn(name.buf, value.buf); 84 | nesting--; 85 | fclose(f); 86 | strbuf_release(&name); 87 | strbuf_release(&value); 88 | return 0; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /configfile.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIGFILE_H 2 | #define CONFIGFILE_H 3 | 4 | #include "cgit.h" 5 | 6 | typedef void (*configfile_value_fn)(const char *name, const char *value); 7 | 8 | extern int parse_configfile(const char *filename, configfile_value_fn fn); 9 | 10 | #endif /* CONFIGFILE_H */ 11 | -------------------------------------------------------------------------------- /contrib/hooks/post-receive.agefile: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # An example hook to update the "agefile" for CGit's idle time calculation. 4 | # 5 | # This hook assumes that you are using the default agefile location of 6 | # "info/web/last-modified". If you change the value in your cgitrc then you 7 | # must also change it here. 8 | # 9 | # To install the hook, copy (or link) it to the file "hooks/post-receive" in 10 | # each of your repositories. 11 | # 12 | 13 | agefile="$(git rev-parse --git-dir)"/info/web/last-modified 14 | 15 | mkdir -p "$(dirname "$agefile")" && 16 | git for-each-ref \ 17 | --sort=-authordate --count=1 \ 18 | --format='%(authordate:iso8601)' \ 19 | >"$agefile" 20 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zx2c4/cgit/09d24d7cd0b7e85633f2f43808b12871bb209d69/favicon.ico -------------------------------------------------------------------------------- /filters/about-formatting.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This may be used with the about-filter or repo.about-filter setting in cgitrc. 4 | # It passes formatting of about pages to differing programs, depending on the usage. 5 | 6 | # Markdown support requires python and markdown-python. 7 | # RestructuredText support requires python and docutils. 8 | # Man page support requires groff. 9 | 10 | # The following environment variables can be used to retrieve the configuration 11 | # of the repository for which this script is called: 12 | # CGIT_REPO_URL ( = repo.url setting ) 13 | # CGIT_REPO_NAME ( = repo.name setting ) 14 | # CGIT_REPO_PATH ( = repo.path setting ) 15 | # CGIT_REPO_OWNER ( = repo.owner setting ) 16 | # CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) 17 | # CGIT_REPO_SECTION ( = section setting ) 18 | # CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) 19 | 20 | cd "$(dirname $0)/html-converters/" 21 | case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in 22 | *.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;; 23 | *.rst) exec ./rst2html; ;; 24 | *.[1-9]) exec ./man2html; ;; 25 | *.htm|*.html) exec cat; ;; 26 | *.txt|*) exec ./txt2html; ;; 27 | esac 28 | -------------------------------------------------------------------------------- /filters/commit-links.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script can be used to generate links in commit messages. 3 | # 4 | # To use this script, refer to this file with either the commit-filter or the 5 | # repo.commit-filter options in cgitrc. 6 | # 7 | # The following environment variables can be used to retrieve the configuration 8 | # of the repository for which this script is called: 9 | # CGIT_REPO_URL ( = repo.url setting ) 10 | # CGIT_REPO_NAME ( = repo.name setting ) 11 | # CGIT_REPO_PATH ( = repo.path setting ) 12 | # CGIT_REPO_OWNER ( = repo.owner setting ) 13 | # CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) 14 | # CGIT_REPO_SECTION ( = section setting ) 15 | # CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) 16 | # 17 | 18 | regex='' 19 | 20 | # This expression generates links to commits referenced by their SHA1. 21 | regex=$regex' 22 | s|\b([0-9a-fA-F]{7,64})\b|\1|g' 23 | 24 | # This expression generates links to a fictional bugtracker. 25 | regex=$regex' 26 | s|#([0-9]+)\b|#\1|g' 27 | 28 | sed -re "$regex" 29 | -------------------------------------------------------------------------------- /filters/email-gravatar.lua: -------------------------------------------------------------------------------- 1 | -- This script may be used with the email-filter or repo.email-filter settings in cgitrc. 2 | -- It adds gravatar icons to author names. It is designed to be used with the lua: 3 | -- prefix in filters. It is much faster than the corresponding python script. 4 | -- 5 | -- Requirements: 6 | -- luaossl 7 | -- 8 | -- 9 | 10 | local digest = require("openssl.digest") 11 | 12 | function md5_hex(input) 13 | local b = digest.new("md5"):final(input) 14 | local x = "" 15 | for i = 1, #b do 16 | x = x .. string.format("%.2x", string.byte(b, i)) 17 | end 18 | return x 19 | end 20 | 21 | function filter_open(email, page) 22 | buffer = "" 23 | md5 = md5_hex(email:sub(2, -2):lower()) 24 | end 25 | 26 | function filter_close() 27 | html("Gravatar " .. buffer) 28 | return 0 29 | end 30 | 31 | function filter_write(str) 32 | buffer = buffer .. str 33 | end 34 | 35 | 36 | -------------------------------------------------------------------------------- /filters/email-gravatar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Please prefer the email-gravatar.lua using lua: as a prefix over this script. This 4 | # script is very slow, in comparison. 5 | # 6 | # This script may be used with the email-filter or repo.email-filter settings in cgitrc. 7 | # 8 | # The following environment variables can be used to retrieve the configuration 9 | # of the repository for which this script is called: 10 | # CGIT_REPO_URL ( = repo.url setting ) 11 | # CGIT_REPO_NAME ( = repo.name setting ) 12 | # CGIT_REPO_PATH ( = repo.path setting ) 13 | # CGIT_REPO_OWNER ( = repo.owner setting ) 14 | # CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) 15 | # CGIT_REPO_SECTION ( = section setting ) 16 | # CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) 17 | # 18 | # It receives an email address on argv[1] and text on stdin. It prints 19 | # to stdout that text prepended by a gravatar at 10pt. 20 | 21 | import sys 22 | import hashlib 23 | import codecs 24 | 25 | email = sys.argv[1].lower().strip() 26 | if email[0] == '<': 27 | email = email[1:] 28 | if email[-1] == '>': 29 | email = email[0:-1] 30 | 31 | page = sys.argv[2] 32 | 33 | sys.stdin = codecs.getreader("utf-8")(sys.stdin.detach()) 34 | sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) 35 | 36 | md5 = hashlib.md5(email.encode()).hexdigest() 37 | text = sys.stdin.read().strip() 38 | 39 | print("Gravatar " + text) 40 | -------------------------------------------------------------------------------- /filters/email-libravatar.lua: -------------------------------------------------------------------------------- 1 | -- This script may be used with the email-filter or repo.email-filter settings in cgitrc. 2 | -- It adds libravatar icons to author names. It is designed to be used with the lua: 3 | -- prefix in filters. 4 | -- 5 | -- Requirements: 6 | -- luaossl 7 | -- 8 | -- 9 | 10 | local digest = require("openssl.digest") 11 | 12 | function md5_hex(input) 13 | local b = digest.new("md5"):final(input) 14 | local x = "" 15 | for i = 1, #b do 16 | x = x .. string.format("%.2x", string.byte(b, i)) 17 | end 18 | return x 19 | end 20 | 21 | function filter_open(email, page) 22 | buffer = "" 23 | md5 = md5_hex(email:sub(2, -2):lower()) 24 | end 25 | 26 | function filter_close() 27 | baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/" 28 | html("Libravatar " .. buffer) 29 | return 0 30 | end 31 | 32 | function filter_write(str) 33 | buffer = buffer .. str 34 | end 35 | 36 | 37 | -------------------------------------------------------------------------------- /filters/file-authentication.lua: -------------------------------------------------------------------------------- 1 | -- This script may be used with the auth-filter. 2 | -- 3 | -- Requirements: 4 | -- luaossl 5 | -- 6 | -- luaposix 7 | -- 8 | -- 9 | local sysstat = require("posix.sys.stat") 10 | local unistd = require("posix.unistd") 11 | local rand = require("openssl.rand") 12 | local hmac = require("openssl.hmac") 13 | 14 | -- This file should contain a series of lines in the form of: 15 | -- username1:hash1 16 | -- username2:hash2 17 | -- username3:hash3 18 | -- ... 19 | -- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`. 20 | -- This file should not be world-readable. 21 | local users_filename = "/etc/cgit-auth/users" 22 | 23 | -- This file should contain a series of lines in the form of: 24 | -- groupname1:username1,username2,username3,... 25 | -- ... 26 | local groups_filename = "/etc/cgit-auth/groups" 27 | 28 | -- This file should contain a series of lines in the form of: 29 | -- reponame1:groupname1,groupname2,groupname3,... 30 | -- ... 31 | local repos_filename = "/etc/cgit-auth/repos" 32 | 33 | -- Set this to a path this script can write to for storing a persistent 34 | -- cookie secret, which should not be world-readable. 35 | local secret_filename = "/var/cache/cgit/auth-secret" 36 | 37 | -- 38 | -- 39 | -- Authentication functions follow below. Swap these out if you want different authentication semantics. 40 | -- 41 | -- 42 | 43 | -- Looks up a hash for a given user. 44 | function lookup_hash(user) 45 | local line 46 | for line in io.lines(users_filename) do 47 | local u, h = string.match(line, "(.-):(.+)") 48 | if u:lower() == user:lower() then 49 | return h 50 | end 51 | end 52 | return nil 53 | end 54 | 55 | -- Looks up users for a given repo. 56 | function lookup_users(repo) 57 | local users = nil 58 | local groups = nil 59 | local line, group, user 60 | for line in io.lines(repos_filename) do 61 | local r, g = string.match(line, "(.-):(.+)") 62 | if r == repo then 63 | groups = { } 64 | for group in string.gmatch(g, "([^,]+)") do 65 | groups[group:lower()] = true 66 | end 67 | break 68 | end 69 | end 70 | if groups == nil then 71 | return nil 72 | end 73 | for line in io.lines(groups_filename) do 74 | local g, u = string.match(line, "(.-):(.+)") 75 | if groups[g:lower()] then 76 | if users == nil then 77 | users = { } 78 | end 79 | for user in string.gmatch(u, "([^,]+)") do 80 | users[user:lower()] = true 81 | end 82 | end 83 | end 84 | return users 85 | end 86 | 87 | 88 | -- Sets HTTP cookie headers based on post and sets up redirection. 89 | function authenticate_post() 90 | local hash = lookup_hash(post["username"]) 91 | local redirect = validate_value("redirect", post["redirect"]) 92 | 93 | if redirect == nil then 94 | not_found() 95 | return 0 96 | end 97 | 98 | redirect_to(redirect) 99 | 100 | if hash == nil or hash ~= unistd.crypt(post["password"], hash) then 101 | set_cookie("cgitauth", "") 102 | else 103 | -- One week expiration time 104 | local username = secure_value("username", post["username"], os.time() + 604800) 105 | set_cookie("cgitauth", username) 106 | end 107 | 108 | html("\n") 109 | return 0 110 | end 111 | 112 | 113 | -- Returns 1 if the cookie is valid and 0 if it is not. 114 | function authenticate_cookie() 115 | accepted_users = lookup_users(cgit["repo"]) 116 | if accepted_users == nil then 117 | -- We return as valid if the repo is not protected. 118 | return 1 119 | end 120 | 121 | local username = validate_value("username", get_cookie(http["cookie"], "cgitauth")) 122 | if username == nil or not accepted_users[username:lower()] then 123 | return 0 124 | else 125 | return 1 126 | end 127 | end 128 | 129 | -- Prints the html for the login form. 130 | function body() 131 | html("

Authentication Required

") 132 | html("
") 135 | html("") 138 | html("") 139 | html("") 140 | html("") 141 | html("") 142 | html("
") 143 | 144 | return 0 145 | end 146 | 147 | 148 | 149 | -- 150 | -- 151 | -- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. 152 | -- 153 | -- 154 | 155 | local actions = {} 156 | actions["authenticate-post"] = authenticate_post 157 | actions["authenticate-cookie"] = authenticate_cookie 158 | actions["body"] = body 159 | 160 | function filter_open(...) 161 | action = actions[select(1, ...)] 162 | 163 | http = {} 164 | http["cookie"] = select(2, ...) 165 | http["method"] = select(3, ...) 166 | http["query"] = select(4, ...) 167 | http["referer"] = select(5, ...) 168 | http["path"] = select(6, ...) 169 | http["host"] = select(7, ...) 170 | http["https"] = select(8, ...) 171 | 172 | cgit = {} 173 | cgit["repo"] = select(9, ...) 174 | cgit["page"] = select(10, ...) 175 | cgit["url"] = select(11, ...) 176 | cgit["login"] = select(12, ...) 177 | 178 | end 179 | 180 | function filter_close() 181 | return action() 182 | end 183 | 184 | function filter_write(str) 185 | post = parse_qs(str) 186 | end 187 | 188 | 189 | -- 190 | -- 191 | -- Utility functions based on keplerproject/wsapi. 192 | -- 193 | -- 194 | 195 | function url_decode(str) 196 | if not str then 197 | return "" 198 | end 199 | str = string.gsub(str, "+", " ") 200 | str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) 201 | str = string.gsub(str, "\r\n", "\n") 202 | return str 203 | end 204 | 205 | function url_encode(str) 206 | if not str then 207 | return "" 208 | end 209 | str = string.gsub(str, "\n", "\r\n") 210 | str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) 211 | str = string.gsub(str, " ", "+") 212 | return str 213 | end 214 | 215 | function parse_qs(qs) 216 | local tab = {} 217 | for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do 218 | tab[url_decode(key)] = url_decode(val) 219 | end 220 | return tab 221 | end 222 | 223 | function get_cookie(cookies, name) 224 | cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") 225 | return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) 226 | end 227 | 228 | function tohex(b) 229 | local x = "" 230 | for i = 1, #b do 231 | x = x .. string.format("%.2x", string.byte(b, i)) 232 | end 233 | return x 234 | end 235 | 236 | -- 237 | -- 238 | -- Cookie construction and validation helpers. 239 | -- 240 | -- 241 | 242 | local secret = nil 243 | 244 | -- Loads a secret from a file, creates a secret, or returns one from memory. 245 | function get_secret() 246 | if secret ~= nil then 247 | return secret 248 | end 249 | local secret_file = io.open(secret_filename, "r") 250 | if secret_file == nil then 251 | local old_umask = sysstat.umask(63) 252 | local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) 253 | local temporary_file = io.open(temporary_filename, "w") 254 | if temporary_file == nil then 255 | os.exit(177) 256 | end 257 | temporary_file:write(tohex(rand.bytes(32))) 258 | temporary_file:close() 259 | unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. 260 | unistd.unlink(temporary_filename) 261 | sysstat.umask(old_umask) 262 | secret_file = io.open(secret_filename, "r") 263 | end 264 | if secret_file == nil then 265 | os.exit(177) 266 | end 267 | secret = secret_file:read() 268 | secret_file:close() 269 | if secret:len() ~= 64 then 270 | os.exit(177) 271 | end 272 | return secret 273 | end 274 | 275 | -- Returns value of cookie if cookie is valid. Otherwise returns nil. 276 | function validate_value(expected_field, cookie) 277 | local i = 0 278 | local value = "" 279 | local field = "" 280 | local expiration = 0 281 | local salt = "" 282 | local chmac = "" 283 | 284 | if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then 285 | return nil 286 | end 287 | 288 | for component in string.gmatch(cookie, "[^|]+") do 289 | if i == 0 then 290 | field = component 291 | elseif i == 1 then 292 | value = component 293 | elseif i == 2 then 294 | expiration = tonumber(component) 295 | if expiration == nil then 296 | expiration = -1 297 | end 298 | elseif i == 3 then 299 | salt = component 300 | elseif i == 4 then 301 | chmac = component 302 | else 303 | break 304 | end 305 | i = i + 1 306 | end 307 | 308 | if chmac == nil or chmac:len() == 0 then 309 | return nil 310 | end 311 | 312 | -- Lua hashes strings, so these comparisons are time invariant. 313 | if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then 314 | return nil 315 | end 316 | 317 | if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then 318 | return nil 319 | end 320 | 321 | if url_decode(field) ~= expected_field then 322 | return nil 323 | end 324 | 325 | return url_decode(value) 326 | end 327 | 328 | function secure_value(field, value, expiration) 329 | if value == nil or value:len() <= 0 then 330 | return "" 331 | end 332 | 333 | local authstr = "" 334 | local salt = tohex(rand.bytes(16)) 335 | value = url_encode(value) 336 | field = url_encode(field) 337 | authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt 338 | authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) 339 | return authstr 340 | end 341 | 342 | function set_cookie(cookie, value) 343 | html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") 344 | if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then 345 | html("; secure") 346 | end 347 | html("\n") 348 | end 349 | 350 | function redirect_to(url) 351 | html("Status: 302 Redirect\n") 352 | html("Cache-Control: no-cache, no-store\n") 353 | html("Location: " .. url .. "\n") 354 | end 355 | 356 | function not_found() 357 | html("Status: 404 Not Found\n") 358 | html("Cache-Control: no-cache, no-store\n\n") 359 | end 360 | -------------------------------------------------------------------------------- /filters/gentoo-ldap-authentication.lua: -------------------------------------------------------------------------------- 1 | -- This script may be used with the auth-filter. Be sure to configure it as you wish. 2 | -- 3 | -- Requirements: 4 | -- luaossl 5 | -- 6 | -- lualdap >= 1.2 7 | -- 8 | -- luaposix 9 | -- 10 | -- 11 | local sysstat = require("posix.sys.stat") 12 | local unistd = require("posix.unistd") 13 | local lualdap = require("lualdap") 14 | local rand = require("openssl.rand") 15 | local hmac = require("openssl.hmac") 16 | 17 | -- 18 | -- 19 | -- Configure these variables for your settings. 20 | -- 21 | -- 22 | 23 | -- A list of password protected repositories, with which gentooAccess 24 | -- group is allowed to access each one. 25 | local protected_repos = { 26 | glouglou = "infra", 27 | portage = "dev" 28 | } 29 | 30 | -- Set this to a path this script can write to for storing a persistent 31 | -- cookie secret, which should be guarded. 32 | local secret_filename = "/var/cache/cgit/auth-secret" 33 | 34 | 35 | -- 36 | -- 37 | -- Authentication functions follow below. Swap these out if you want different authentication semantics. 38 | -- 39 | -- 40 | 41 | -- Sets HTTP cookie headers based on post and sets up redirection. 42 | function authenticate_post() 43 | local redirect = validate_value("redirect", post["redirect"]) 44 | 45 | if redirect == nil then 46 | not_found() 47 | return 0 48 | end 49 | 50 | redirect_to(redirect) 51 | 52 | local groups = gentoo_ldap_user_groups(post["username"], post["password"]) 53 | if groups == nil then 54 | set_cookie("cgitauth", "") 55 | else 56 | -- One week expiration time 57 | set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800)) 58 | end 59 | 60 | html("\n") 61 | return 0 62 | end 63 | 64 | 65 | -- Returns 1 if the cookie is valid and 0 if it is not. 66 | function authenticate_cookie() 67 | local required_group = protected_repos[cgit["repo"]] 68 | if required_group == nil then 69 | -- We return as valid if the repo is not protected. 70 | return 1 71 | end 72 | 73 | local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth")) 74 | if user_groups == nil or user_groups == "" then 75 | return 0 76 | end 77 | for group in string.gmatch(user_groups, "[^,]+") do 78 | if group == required_group then 79 | return 1 80 | end 81 | end 82 | return 0 83 | end 84 | 85 | -- Prints the html for the login form. 86 | function body() 87 | html("

Gentoo LDAP Authentication Required

") 88 | html("
") 91 | html("") 94 | html("") 95 | html("") 96 | html("") 97 | html("") 98 | html("
") 99 | 100 | return 0 101 | end 102 | 103 | -- 104 | -- 105 | -- Gentoo LDAP support. 106 | -- 107 | -- 108 | 109 | function gentoo_ldap_user_groups(username, password) 110 | -- Ensure the user is alphanumeric 111 | if username == nil or username:match("%W") then 112 | return nil 113 | end 114 | 115 | local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org" 116 | 117 | local ldap, err = lualdap.open_simple { 118 | uri = "ldap://ldap1.gentoo.org", 119 | who = who, 120 | password = password, 121 | starttls = true, 122 | certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt", 123 | keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key", 124 | cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem" 125 | } 126 | if ldap == nil then 127 | return nil 128 | end 129 | 130 | local group_suffix = ".group" 131 | local group_suffix_len = group_suffix:len() 132 | local groups = {} 133 | for dn, attribs in ldap:search { base = who, scope = "subtree" } do 134 | local access = attribs["gentooAccess"] 135 | if dn == who and access ~= nil then 136 | for i, v in ipairs(access) do 137 | local vlen = v:len() 138 | if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then 139 | table.insert(groups, v:sub(1, vlen - group_suffix_len)) 140 | end 141 | end 142 | end 143 | end 144 | 145 | ldap:close() 146 | 147 | return groups 148 | end 149 | 150 | -- 151 | -- 152 | -- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. 153 | -- 154 | -- 155 | 156 | local actions = {} 157 | actions["authenticate-post"] = authenticate_post 158 | actions["authenticate-cookie"] = authenticate_cookie 159 | actions["body"] = body 160 | 161 | function filter_open(...) 162 | action = actions[select(1, ...)] 163 | 164 | http = {} 165 | http["cookie"] = select(2, ...) 166 | http["method"] = select(3, ...) 167 | http["query"] = select(4, ...) 168 | http["referer"] = select(5, ...) 169 | http["path"] = select(6, ...) 170 | http["host"] = select(7, ...) 171 | http["https"] = select(8, ...) 172 | 173 | cgit = {} 174 | cgit["repo"] = select(9, ...) 175 | cgit["page"] = select(10, ...) 176 | cgit["url"] = select(11, ...) 177 | cgit["login"] = select(12, ...) 178 | 179 | end 180 | 181 | function filter_close() 182 | return action() 183 | end 184 | 185 | function filter_write(str) 186 | post = parse_qs(str) 187 | end 188 | 189 | 190 | -- 191 | -- 192 | -- Utility functions based on keplerproject/wsapi. 193 | -- 194 | -- 195 | 196 | function url_decode(str) 197 | if not str then 198 | return "" 199 | end 200 | str = string.gsub(str, "+", " ") 201 | str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) 202 | str = string.gsub(str, "\r\n", "\n") 203 | return str 204 | end 205 | 206 | function url_encode(str) 207 | if not str then 208 | return "" 209 | end 210 | str = string.gsub(str, "\n", "\r\n") 211 | str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) 212 | str = string.gsub(str, " ", "+") 213 | return str 214 | end 215 | 216 | function parse_qs(qs) 217 | local tab = {} 218 | for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do 219 | tab[url_decode(key)] = url_decode(val) 220 | end 221 | return tab 222 | end 223 | 224 | function get_cookie(cookies, name) 225 | cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") 226 | return string.match(cookies, ";" .. name .. "=(.-);") 227 | end 228 | 229 | function tohex(b) 230 | local x = "" 231 | for i = 1, #b do 232 | x = x .. string.format("%.2x", string.byte(b, i)) 233 | end 234 | return x 235 | end 236 | 237 | -- 238 | -- 239 | -- Cookie construction and validation helpers. 240 | -- 241 | -- 242 | 243 | local secret = nil 244 | 245 | -- Loads a secret from a file, creates a secret, or returns one from memory. 246 | function get_secret() 247 | if secret ~= nil then 248 | return secret 249 | end 250 | local secret_file = io.open(secret_filename, "r") 251 | if secret_file == nil then 252 | local old_umask = sysstat.umask(63) 253 | local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) 254 | local temporary_file = io.open(temporary_filename, "w") 255 | if temporary_file == nil then 256 | os.exit(177) 257 | end 258 | temporary_file:write(tohex(rand.bytes(32))) 259 | temporary_file:close() 260 | unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. 261 | unistd.unlink(temporary_filename) 262 | sysstat.umask(old_umask) 263 | secret_file = io.open(secret_filename, "r") 264 | end 265 | if secret_file == nil then 266 | os.exit(177) 267 | end 268 | secret = secret_file:read() 269 | secret_file:close() 270 | if secret:len() ~= 64 then 271 | os.exit(177) 272 | end 273 | return secret 274 | end 275 | 276 | -- Returns value of cookie if cookie is valid. Otherwise returns nil. 277 | function validate_value(expected_field, cookie) 278 | local i = 0 279 | local value = "" 280 | local field = "" 281 | local expiration = 0 282 | local salt = "" 283 | local chmac = "" 284 | 285 | if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then 286 | return nil 287 | end 288 | 289 | for component in string.gmatch(cookie, "[^|]+") do 290 | if i == 0 then 291 | field = component 292 | elseif i == 1 then 293 | value = component 294 | elseif i == 2 then 295 | expiration = tonumber(component) 296 | if expiration == nil then 297 | expiration = -1 298 | end 299 | elseif i == 3 then 300 | salt = component 301 | elseif i == 4 then 302 | chmac = component 303 | else 304 | break 305 | end 306 | i = i + 1 307 | end 308 | 309 | if chmac == nil or chmac:len() == 0 then 310 | return nil 311 | end 312 | 313 | -- Lua hashes strings, so these comparisons are time invariant. 314 | if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then 315 | return nil 316 | end 317 | 318 | if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then 319 | return nil 320 | end 321 | 322 | if url_decode(field) ~= expected_field then 323 | return nil 324 | end 325 | 326 | return url_decode(value) 327 | end 328 | 329 | function secure_value(field, value, expiration) 330 | if value == nil or value:len() <= 0 then 331 | return "" 332 | end 333 | 334 | local authstr = "" 335 | local salt = tohex(rand.bytes(16)) 336 | value = url_encode(value) 337 | field = url_encode(field) 338 | authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt 339 | authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) 340 | return authstr 341 | end 342 | 343 | function set_cookie(cookie, value) 344 | html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") 345 | if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then 346 | html("; secure") 347 | end 348 | html("\n") 349 | end 350 | 351 | function redirect_to(url) 352 | html("Status: 302 Redirect\n") 353 | html("Cache-Control: no-cache, no-store\n") 354 | html("Location: " .. url .. "\n") 355 | end 356 | 357 | function not_found() 358 | html("Status: 404 Not Found\n") 359 | html("Cache-Control: no-cache, no-store\n\n") 360 | end 361 | -------------------------------------------------------------------------------- /filters/html-converters/man2html: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "
" 3 | groff -mandoc -T html -P -r -P -l | egrep -v '(||||||||" 5 | -------------------------------------------------------------------------------- /filters/html-converters/md2html: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import markdown 3 | import sys 4 | import io 5 | from pygments.formatters import HtmlFormatter 6 | from markdown.extensions.toc import TocExtension 7 | sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') 8 | sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') 9 | sys.stdout.write(''' 10 | 290 | ''') 291 | sys.stdout.write("
") 292 | sys.stdout.flush() 293 | # Note: you may want to run this through bleach for sanitization 294 | markdown.markdownFromFile( 295 | output_format="html5", 296 | extensions=[ 297 | "markdown.extensions.fenced_code", 298 | "markdown.extensions.codehilite", 299 | "markdown.extensions.tables", 300 | "markdown.extensions.sane_lists", 301 | TocExtension(anchorlink=True)], 302 | extension_configs={ 303 | "markdown.extensions.codehilite":{"css_class":"highlight"}}) 304 | sys.stdout.write("
") 305 | -------------------------------------------------------------------------------- /filters/html-converters/rst2html: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s") 3 | -------------------------------------------------------------------------------- /filters/html-converters/txt2html: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "
"
3 | sed "s|&|\\&|g;s|'|\\'|g;s|\"|\\"|g;s|<|\\<|g;s|>|\\>|g"
4 | echo "
" 5 | -------------------------------------------------------------------------------- /filters/owner-example.lua: -------------------------------------------------------------------------------- 1 | -- This script is an example of an owner-filter. It replaces the 2 | -- usual query link with one to a fictional homepage. This script may 3 | -- be used with the owner-filter or repo.owner-filter settings in 4 | -- cgitrc with the `lua:` prefix. 5 | 6 | function filter_open() 7 | buffer = "" 8 | end 9 | 10 | function filter_close() 11 | html(string.format("%s", "http://wiki.example.com/about/" .. buffer, buffer)) 12 | return 0 13 | end 14 | 15 | function filter_write(str) 16 | buffer = buffer .. str 17 | end 18 | -------------------------------------------------------------------------------- /filters/simple-authentication.lua: -------------------------------------------------------------------------------- 1 | -- This script may be used with the auth-filter. Be sure to configure it as you wish. 2 | -- 3 | -- Requirements: 4 | -- luaossl 5 | -- 6 | -- luaposix 7 | -- 8 | -- 9 | local sysstat = require("posix.sys.stat") 10 | local unistd = require("posix.unistd") 11 | local rand = require("openssl.rand") 12 | local hmac = require("openssl.hmac") 13 | 14 | -- 15 | -- 16 | -- Configure these variables for your settings. 17 | -- 18 | -- 19 | 20 | -- A list of password protected repositories along with the users who can access them. 21 | local protected_repos = { 22 | glouglou = { laurent = true, jason = true }, 23 | qt = { jason = true, bob = true } 24 | } 25 | 26 | -- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`. 27 | local users = { 28 | jason = "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1", 29 | laurent = "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.", 30 | bob = "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC" 31 | } 32 | 33 | -- Set this to a path this script can write to for storing a persistent 34 | -- cookie secret, which should be guarded. 35 | local secret_filename = "/var/cache/cgit/auth-secret" 36 | 37 | -- 38 | -- 39 | -- Authentication functions follow below. Swap these out if you want different authentication semantics. 40 | -- 41 | -- 42 | 43 | -- Sets HTTP cookie headers based on post and sets up redirection. 44 | function authenticate_post() 45 | local hash = users[post["username"]] 46 | local redirect = validate_value("redirect", post["redirect"]) 47 | 48 | if redirect == nil then 49 | not_found() 50 | return 0 51 | end 52 | 53 | redirect_to(redirect) 54 | 55 | if hash == nil or hash ~= unistd.crypt(post["password"], hash) then 56 | set_cookie("cgitauth", "") 57 | else 58 | -- One week expiration time 59 | local username = secure_value("username", post["username"], os.time() + 604800) 60 | set_cookie("cgitauth", username) 61 | end 62 | 63 | html("\n") 64 | return 0 65 | end 66 | 67 | 68 | -- Returns 1 if the cookie is valid and 0 if it is not. 69 | function authenticate_cookie() 70 | accepted_users = protected_repos[cgit["repo"]] 71 | if accepted_users == nil then 72 | -- We return as valid if the repo is not protected. 73 | return 1 74 | end 75 | 76 | local username = validate_value("username", get_cookie(http["cookie"], "cgitauth")) 77 | if username == nil or not accepted_users[username:lower()] then 78 | return 0 79 | else 80 | return 1 81 | end 82 | end 83 | 84 | -- Prints the html for the login form. 85 | function body() 86 | html("

Authentication Required

") 87 | html("
") 90 | html("") 93 | html("") 94 | html("") 95 | html("") 96 | html("") 97 | html("
") 98 | 99 | return 0 100 | end 101 | 102 | 103 | 104 | -- 105 | -- 106 | -- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. 107 | -- 108 | -- 109 | 110 | local actions = {} 111 | actions["authenticate-post"] = authenticate_post 112 | actions["authenticate-cookie"] = authenticate_cookie 113 | actions["body"] = body 114 | 115 | function filter_open(...) 116 | action = actions[select(1, ...)] 117 | 118 | http = {} 119 | http["cookie"] = select(2, ...) 120 | http["method"] = select(3, ...) 121 | http["query"] = select(4, ...) 122 | http["referer"] = select(5, ...) 123 | http["path"] = select(6, ...) 124 | http["host"] = select(7, ...) 125 | http["https"] = select(8, ...) 126 | 127 | cgit = {} 128 | cgit["repo"] = select(9, ...) 129 | cgit["page"] = select(10, ...) 130 | cgit["url"] = select(11, ...) 131 | cgit["login"] = select(12, ...) 132 | 133 | end 134 | 135 | function filter_close() 136 | return action() 137 | end 138 | 139 | function filter_write(str) 140 | post = parse_qs(str) 141 | end 142 | 143 | 144 | -- 145 | -- 146 | -- Utility functions based on keplerproject/wsapi. 147 | -- 148 | -- 149 | 150 | function url_decode(str) 151 | if not str then 152 | return "" 153 | end 154 | str = string.gsub(str, "+", " ") 155 | str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) 156 | str = string.gsub(str, "\r\n", "\n") 157 | return str 158 | end 159 | 160 | function url_encode(str) 161 | if not str then 162 | return "" 163 | end 164 | str = string.gsub(str, "\n", "\r\n") 165 | str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) 166 | str = string.gsub(str, " ", "+") 167 | return str 168 | end 169 | 170 | function parse_qs(qs) 171 | local tab = {} 172 | for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do 173 | tab[url_decode(key)] = url_decode(val) 174 | end 175 | return tab 176 | end 177 | 178 | function get_cookie(cookies, name) 179 | cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") 180 | return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) 181 | end 182 | 183 | function tohex(b) 184 | local x = "" 185 | for i = 1, #b do 186 | x = x .. string.format("%.2x", string.byte(b, i)) 187 | end 188 | return x 189 | end 190 | 191 | -- 192 | -- 193 | -- Cookie construction and validation helpers. 194 | -- 195 | -- 196 | 197 | local secret = nil 198 | 199 | -- Loads a secret from a file, creates a secret, or returns one from memory. 200 | function get_secret() 201 | if secret ~= nil then 202 | return secret 203 | end 204 | local secret_file = io.open(secret_filename, "r") 205 | if secret_file == nil then 206 | local old_umask = sysstat.umask(63) 207 | local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) 208 | local temporary_file = io.open(temporary_filename, "w") 209 | if temporary_file == nil then 210 | os.exit(177) 211 | end 212 | temporary_file:write(tohex(rand.bytes(32))) 213 | temporary_file:close() 214 | unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. 215 | unistd.unlink(temporary_filename) 216 | sysstat.umask(old_umask) 217 | secret_file = io.open(secret_filename, "r") 218 | end 219 | if secret_file == nil then 220 | os.exit(177) 221 | end 222 | secret = secret_file:read() 223 | secret_file:close() 224 | if secret:len() ~= 64 then 225 | os.exit(177) 226 | end 227 | return secret 228 | end 229 | 230 | -- Returns value of cookie if cookie is valid. Otherwise returns nil. 231 | function validate_value(expected_field, cookie) 232 | local i = 0 233 | local value = "" 234 | local field = "" 235 | local expiration = 0 236 | local salt = "" 237 | local chmac = "" 238 | 239 | if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then 240 | return nil 241 | end 242 | 243 | for component in string.gmatch(cookie, "[^|]+") do 244 | if i == 0 then 245 | field = component 246 | elseif i == 1 then 247 | value = component 248 | elseif i == 2 then 249 | expiration = tonumber(component) 250 | if expiration == nil then 251 | expiration = -1 252 | end 253 | elseif i == 3 then 254 | salt = component 255 | elseif i == 4 then 256 | chmac = component 257 | else 258 | break 259 | end 260 | i = i + 1 261 | end 262 | 263 | if chmac == nil or chmac:len() == 0 then 264 | return nil 265 | end 266 | 267 | -- Lua hashes strings, so these comparisons are time invariant. 268 | if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then 269 | return nil 270 | end 271 | 272 | if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then 273 | return nil 274 | end 275 | 276 | if url_decode(field) ~= expected_field then 277 | return nil 278 | end 279 | 280 | return url_decode(value) 281 | end 282 | 283 | function secure_value(field, value, expiration) 284 | if value == nil or value:len() <= 0 then 285 | return "" 286 | end 287 | 288 | local authstr = "" 289 | local salt = tohex(rand.bytes(16)) 290 | value = url_encode(value) 291 | field = url_encode(field) 292 | authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt 293 | authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) 294 | return authstr 295 | end 296 | 297 | function set_cookie(cookie, value) 298 | html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") 299 | if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then 300 | html("; secure") 301 | end 302 | html("\n") 303 | end 304 | 305 | function redirect_to(url) 306 | html("Status: 302 Redirect\n") 307 | html("Cache-Control: no-cache, no-store\n") 308 | html("Location: " .. url .. "\n") 309 | end 310 | 311 | function not_found() 312 | html("Status: 404 Not Found\n") 313 | html("Cache-Control: no-cache, no-store\n\n") 314 | end 315 | -------------------------------------------------------------------------------- /filters/syntax-highlighting.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This script uses Pygments and Python3. You must have both installed 4 | # for this to work. 5 | # 6 | # http://pygments.org/ 7 | # http://python.org/ 8 | # 9 | # It may be used with the source-filter or repo.source-filter settings 10 | # in cgitrc. 11 | # 12 | # The following environment variables can be used to retrieve the 13 | # configuration of the repository for which this script is called: 14 | # CGIT_REPO_URL ( = repo.url setting ) 15 | # CGIT_REPO_NAME ( = repo.name setting ) 16 | # CGIT_REPO_PATH ( = repo.path setting ) 17 | # CGIT_REPO_OWNER ( = repo.owner setting ) 18 | # CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) 19 | # CGIT_REPO_SECTION ( = section setting ) 20 | # CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) 21 | 22 | 23 | import sys 24 | import io 25 | from pygments import highlight 26 | from pygments.util import ClassNotFound 27 | from pygments.lexers import TextLexer 28 | from pygments.lexers import guess_lexer 29 | from pygments.lexers import guess_lexer_for_filename 30 | from pygments.formatters import HtmlFormatter 31 | 32 | 33 | sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace') 34 | sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') 35 | data = sys.stdin.read() 36 | filename = sys.argv[1] 37 | formatter = HtmlFormatter(style='pastie', nobackground=True) 38 | 39 | try: 40 | lexer = guess_lexer_for_filename(filename, data) 41 | except ClassNotFound: 42 | # check if there is any shebang 43 | if data[0:2] == '#!': 44 | lexer = guess_lexer(data) 45 | else: 46 | lexer = TextLexer() 47 | except TypeError: 48 | lexer = TextLexer() 49 | 50 | # highlight! :-) 51 | # printout pygments' css definitions as well 52 | sys.stdout.write('') 55 | sys.stdout.write(highlight(data, lexer, formatter, outfile=None)) 56 | -------------------------------------------------------------------------------- /filters/syntax-highlighting.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This script can be used to implement syntax highlighting in the cgit 3 | # tree-view by referring to this file with the source-filter or repo.source- 4 | # filter options in cgitrc. 5 | # 6 | # This script requires a shell supporting the ${var##pattern} syntax. 7 | # It is supported by at least dash and bash, however busybox environments 8 | # might have to use an external call to sed instead. 9 | # 10 | # Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 11 | # highlighting, so you'll probably want something like the following included 12 | # in your css file: 13 | # 14 | # Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/ 15 | # 16 | # table.blob .num { color:#2928ff; } 17 | # table.blob .esc { color:#ff00ff; } 18 | # table.blob .str { color:#ff0000; } 19 | # table.blob .dstr { color:#818100; } 20 | # table.blob .slc { color:#838183; font-style:italic; } 21 | # table.blob .com { color:#838183; font-style:italic; } 22 | # table.blob .dir { color:#008200; } 23 | # table.blob .sym { color:#000000; } 24 | # table.blob .kwa { color:#000000; font-weight:bold; } 25 | # table.blob .kwb { color:#830000; } 26 | # table.blob .kwc { color:#000000; font-weight:bold; } 27 | # table.blob .kwd { color:#010181; } 28 | # 29 | # 30 | # Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/ 31 | # 32 | # body.hl { background-color:#ffffff; } 33 | # pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';} 34 | # .hl.num { color:#2928ff; } 35 | # .hl.esc { color:#ff00ff; } 36 | # .hl.str { color:#ff0000; } 37 | # .hl.dstr { color:#818100; } 38 | # .hl.slc { color:#838183; font-style:italic; } 39 | # .hl.com { color:#838183; font-style:italic; } 40 | # .hl.dir { color:#008200; } 41 | # .hl.sym { color:#000000; } 42 | # .hl.line { color:#555555; } 43 | # .hl.mark { background-color:#ffffbb;} 44 | # .hl.kwa { color:#000000; font-weight:bold; } 45 | # .hl.kwb { color:#830000; } 46 | # .hl.kwc { color:#000000; font-weight:bold; } 47 | # .hl.kwd { color:#010181; } 48 | # 49 | # 50 | # Style definition file generated by highlight 3.8, http://www.andre-simon.de/ 51 | # 52 | # body.hl { background-color:#e0eaee; } 53 | # pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';} 54 | # .hl.num { color:#b07e00; } 55 | # .hl.esc { color:#ff00ff; } 56 | # .hl.str { color:#bf0303; } 57 | # .hl.pps { color:#818100; } 58 | # .hl.slc { color:#838183; font-style:italic; } 59 | # .hl.com { color:#838183; font-style:italic; } 60 | # .hl.ppc { color:#008200; } 61 | # .hl.opt { color:#000000; } 62 | # .hl.lin { color:#555555; } 63 | # .hl.kwa { color:#000000; font-weight:bold; } 64 | # .hl.kwb { color:#0057ae; } 65 | # .hl.kwc { color:#000000; font-weight:bold; } 66 | # .hl.kwd { color:#010181; } 67 | # 68 | # 69 | # Style definition file generated by highlight 3.13, http://www.andre-simon.de/ 70 | # 71 | # body.hl { background-color:#e0eaee; } 72 | # pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;} 73 | # .hl.num { color:#b07e00; } 74 | # .hl.esc { color:#ff00ff; } 75 | # .hl.str { color:#bf0303; } 76 | # .hl.pps { color:#818100; } 77 | # .hl.slc { color:#838183; font-style:italic; } 78 | # .hl.com { color:#838183; font-style:italic; } 79 | # .hl.ppc { color:#008200; } 80 | # .hl.opt { color:#000000; } 81 | # .hl.ipl { color:#0057ae; } 82 | # .hl.lin { color:#555555; } 83 | # .hl.kwa { color:#000000; font-weight:bold; } 84 | # .hl.kwb { color:#0057ae; } 85 | # .hl.kwc { color:#000000; font-weight:bold; } 86 | # .hl.kwd { color:#010181; } 87 | # 88 | # 89 | # The following environment variables can be used to retrieve the configuration 90 | # of the repository for which this script is called: 91 | # CGIT_REPO_URL ( = repo.url setting ) 92 | # CGIT_REPO_NAME ( = repo.name setting ) 93 | # CGIT_REPO_PATH ( = repo.path setting ) 94 | # CGIT_REPO_OWNER ( = repo.owner setting ) 95 | # CGIT_REPO_DEFBRANCH ( = repo.defbranch setting ) 96 | # CGIT_REPO_SECTION ( = section setting ) 97 | # CGIT_REPO_CLONE_URL ( = repo.clone-url setting ) 98 | # 99 | 100 | # store filename and extension in local vars 101 | BASENAME="$1" 102 | EXTENSION="${BASENAME##*.}" 103 | 104 | [ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt 105 | [ -z "${EXTENSION}" ] && EXTENSION=txt 106 | 107 | # map Makefile and Makefile.* to .mk 108 | [ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk 109 | 110 | # highlight versions 2 and 3 have different commandline options. Specifically, 111 | # the -X option that is used for version 2 is replaced by the -O xhtml option 112 | # for version 3. 113 | # 114 | # Version 2 can be found (for example) on EPEL 5, while version 3 can be 115 | # found (for example) on EPEL 6. 116 | # 117 | # This is for version 2 118 | exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null 119 | 120 | # This is for version 3 121 | #exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null 122 | -------------------------------------------------------------------------------- /gen-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Get version-info specified in Makefile 4 | V=$1 5 | 6 | # Use `git describe` to get current version if we're inside a git repo 7 | if test "$(git rev-parse --git-dir 2>/dev/null)" = '.git' 8 | then 9 | V=$(git describe --abbrev=4 HEAD 2>/dev/null) 10 | fi 11 | 12 | new="CGIT_VERSION = $V" 13 | old=$(cat VERSION 2>/dev/null) 14 | 15 | # Exit if VERSION is uptodate 16 | test "$old" = "$new" && exit 0 17 | 18 | # Update VERSION with new version-info 19 | echo "$new" > VERSION 20 | cat VERSION 21 | -------------------------------------------------------------------------------- /html.c: -------------------------------------------------------------------------------- 1 | /* html.c: helper functions for html output 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include "cgit.h" 10 | #include "html.h" 11 | #include "url.h" 12 | 13 | /* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */ 14 | static const char* url_escape_table[256] = { 15 | "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", 16 | "%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", 17 | "%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17", 18 | "%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f", 19 | "%20", NULL, "%22", "%23", NULL, "%25", "%26", "%27", 20 | NULL, NULL, NULL, "%2b", NULL, NULL, NULL, NULL, 21 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 22 | NULL, NULL, NULL, NULL, "%3c", "%3d", "%3e", "%3f", 23 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 24 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 25 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 26 | NULL, NULL, NULL, NULL, "%5c", NULL, "%5e", NULL, 27 | "%60", NULL, NULL, NULL, NULL, NULL, NULL, NULL, 28 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 29 | NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 30 | NULL, NULL, NULL, "%7b", "%7c", "%7d", NULL, "%7f", 31 | "%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87", 32 | "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", 33 | "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", 34 | "%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", 35 | "%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7", 36 | "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af", 37 | "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", 38 | "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", 39 | "%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", 40 | "%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf", 41 | "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7", 42 | "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", 43 | "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", 44 | "%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", 45 | "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7", 46 | "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff" 47 | }; 48 | 49 | char *fmt(const char *format, ...) 50 | { 51 | static char buf[8][1024]; 52 | static int bufidx; 53 | int len; 54 | va_list args; 55 | 56 | bufidx++; 57 | bufidx &= 7; 58 | 59 | va_start(args, format); 60 | len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 61 | va_end(args); 62 | if (len >= sizeof(buf[bufidx])) { 63 | fprintf(stderr, "[html.c] string truncated: %s\n", format); 64 | exit(1); 65 | } 66 | return buf[bufidx]; 67 | } 68 | 69 | char *fmtalloc(const char *format, ...) 70 | { 71 | struct strbuf sb = STRBUF_INIT; 72 | va_list args; 73 | 74 | va_start(args, format); 75 | strbuf_vaddf(&sb, format, args); 76 | va_end(args); 77 | 78 | return strbuf_detach(&sb, NULL); 79 | } 80 | 81 | void html_raw(const char *data, size_t size) 82 | { 83 | if (write(STDOUT_FILENO, data, size) != size) 84 | die_errno("write error on html output"); 85 | } 86 | 87 | void html(const char *txt) 88 | { 89 | html_raw(txt, strlen(txt)); 90 | } 91 | 92 | void htmlf(const char *format, ...) 93 | { 94 | va_list args; 95 | struct strbuf buf = STRBUF_INIT; 96 | 97 | va_start(args, format); 98 | strbuf_vaddf(&buf, format, args); 99 | va_end(args); 100 | html(buf.buf); 101 | strbuf_release(&buf); 102 | } 103 | 104 | void html_txtf(const char *format, ...) 105 | { 106 | va_list args; 107 | 108 | va_start(args, format); 109 | html_vtxtf(format, args); 110 | va_end(args); 111 | } 112 | 113 | void html_vtxtf(const char *format, va_list ap) 114 | { 115 | va_list cp; 116 | struct strbuf buf = STRBUF_INIT; 117 | 118 | va_copy(cp, ap); 119 | strbuf_vaddf(&buf, format, cp); 120 | va_end(cp); 121 | html_txt(buf.buf); 122 | strbuf_release(&buf); 123 | } 124 | 125 | void html_txt(const char *txt) 126 | { 127 | if (txt) 128 | html_ntxt(txt, strlen(txt)); 129 | } 130 | 131 | ssize_t html_ntxt(const char *txt, size_t len) 132 | { 133 | const char *t = txt; 134 | ssize_t slen; 135 | 136 | if (len > SSIZE_MAX) 137 | return -1; 138 | 139 | slen = (ssize_t) len; 140 | while (t && *t && slen--) { 141 | int c = *t; 142 | if (c == '<' || c == '>' || c == '&') { 143 | html_raw(txt, t - txt); 144 | if (c == '>') 145 | html(">"); 146 | else if (c == '<') 147 | html("<"); 148 | else if (c == '&') 149 | html("&"); 150 | txt = t + 1; 151 | } 152 | t++; 153 | } 154 | if (t != txt) 155 | html_raw(txt, t - txt); 156 | return slen; 157 | } 158 | 159 | void html_attrf(const char *fmt, ...) 160 | { 161 | va_list ap; 162 | struct strbuf sb = STRBUF_INIT; 163 | 164 | va_start(ap, fmt); 165 | strbuf_vaddf(&sb, fmt, ap); 166 | va_end(ap); 167 | 168 | html_attr(sb.buf); 169 | strbuf_release(&sb); 170 | } 171 | 172 | void html_attr(const char *txt) 173 | { 174 | const char *t = txt; 175 | while (t && *t) { 176 | int c = *t; 177 | if (c == '<' || c == '>' || c == '\'' || c == '\"' || c == '&') { 178 | html_raw(txt, t - txt); 179 | if (c == '>') 180 | html(">"); 181 | else if (c == '<') 182 | html("<"); 183 | else if (c == '\'') 184 | html("'"); 185 | else if (c == '"') 186 | html("""); 187 | else if (c == '&') 188 | html("&"); 189 | txt = t + 1; 190 | } 191 | t++; 192 | } 193 | if (t != txt) 194 | html(txt); 195 | } 196 | 197 | void html_url_path(const char *txt) 198 | { 199 | const char *t = txt; 200 | while (t && *t) { 201 | unsigned char c = *t; 202 | const char *e = url_escape_table[c]; 203 | if (e && c != '+' && c != '&') { 204 | html_raw(txt, t - txt); 205 | html(e); 206 | txt = t + 1; 207 | } 208 | t++; 209 | } 210 | if (t != txt) 211 | html(txt); 212 | } 213 | 214 | void html_url_arg(const char *txt) 215 | { 216 | const char *t = txt; 217 | while (t && *t) { 218 | unsigned char c = *t; 219 | const char *e = url_escape_table[c]; 220 | if (c == ' ') 221 | e = "+"; 222 | if (e) { 223 | html_raw(txt, t - txt); 224 | html(e); 225 | txt = t + 1; 226 | } 227 | t++; 228 | } 229 | if (t != txt) 230 | html(txt); 231 | } 232 | 233 | void html_header_arg_in_quotes(const char *txt) 234 | { 235 | const char *t = txt; 236 | while (t && *t) { 237 | unsigned char c = *t; 238 | const char *e = NULL; 239 | if (c == '\\') 240 | e = "\\\\"; 241 | else if (c == '\r') 242 | e = "\\r"; 243 | else if (c == '\n') 244 | e = "\\n"; 245 | else if (c == '"') 246 | e = "\\\""; 247 | if (e) { 248 | html_raw(txt, t - txt); 249 | html(e); 250 | txt = t + 1; 251 | } 252 | t++; 253 | } 254 | if (t != txt) 255 | html(txt); 256 | 257 | } 258 | 259 | void html_hidden(const char *name, const char *value) 260 | { 261 | html(""); 266 | } 267 | 268 | void html_option(const char *value, const char *text, const char *selected_value) 269 | { 270 | html("\n"); 278 | } 279 | 280 | void html_intoption(int value, const char *text, int selected_value) 281 | { 282 | htmlf(""); 286 | } 287 | 288 | void html_link_open(const char *url, const char *title, const char *class) 289 | { 290 | html(""); 301 | } 302 | 303 | void html_link_close(void) 304 | { 305 | html(""); 306 | } 307 | 308 | void html_fileperm(unsigned short mode) 309 | { 310 | htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), 311 | (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); 312 | } 313 | 314 | int html_include(const char *filename) 315 | { 316 | FILE *f; 317 | char buf[4096]; 318 | size_t len; 319 | 320 | if (!(f = fopen(filename, "r"))) { 321 | fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 322 | filename, strerror(errno), errno); 323 | return -1; 324 | } 325 | while ((len = fread(buf, 1, 4096, f)) > 0) 326 | html_raw(buf, len); 327 | fclose(f); 328 | return 0; 329 | } 330 | 331 | void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value)) 332 | { 333 | const char *t = txt; 334 | 335 | while (t && *t) { 336 | char *name = url_decode_parameter_name(&t); 337 | if (*name) { 338 | char *value = url_decode_parameter_value(&t); 339 | fn(name, value); 340 | free(value); 341 | } 342 | free(name); 343 | } 344 | } 345 | -------------------------------------------------------------------------------- /html.h: -------------------------------------------------------------------------------- 1 | #ifndef HTML_H 2 | #define HTML_H 3 | 4 | #include "cgit.h" 5 | 6 | extern void html_raw(const char *txt, size_t size); 7 | extern void html(const char *txt); 8 | 9 | __attribute__((format (printf,1,2))) 10 | extern void htmlf(const char *format,...); 11 | 12 | __attribute__((format (printf,1,2))) 13 | extern void html_txtf(const char *format,...); 14 | 15 | __attribute__((format (printf,1,0))) 16 | extern void html_vtxtf(const char *format, va_list ap); 17 | 18 | __attribute__((format (printf,1,2))) 19 | extern void html_attrf(const char *format,...); 20 | 21 | extern void html_txt(const char *txt); 22 | extern ssize_t html_ntxt(const char *txt, size_t len); 23 | extern void html_attr(const char *txt); 24 | extern void html_url_path(const char *txt); 25 | extern void html_url_arg(const char *txt); 26 | extern void html_header_arg_in_quotes(const char *txt); 27 | extern void html_hidden(const char *name, const char *value); 28 | extern void html_option(const char *value, const char *text, const char *selected_value); 29 | extern void html_intoption(int value, const char *text, int selected_value); 30 | extern void html_link_open(const char *url, const char *title, const char *class); 31 | extern void html_link_close(void); 32 | extern void html_fileperm(unsigned short mode); 33 | extern int html_include(const char *filename); 34 | 35 | extern void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value)); 36 | 37 | #endif /* HTML_H */ 38 | -------------------------------------------------------------------------------- /parsing.c: -------------------------------------------------------------------------------- 1 | /* parsing.c: parsing of config files 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | 13 | /* 14 | * url syntax: [repo ['/' cmd [ '/' path]]] 15 | * repo: any valid repo url, may contain '/' 16 | * cmd: log | commit | diff | tree | view | blob | snapshot 17 | * path: any valid path, may contain '/' 18 | * 19 | */ 20 | void cgit_parse_url(const char *url) 21 | { 22 | char *c, *cmd, *p; 23 | struct cgit_repo *repo; 24 | 25 | if (!url || url[0] == '\0') 26 | return; 27 | 28 | ctx.qry.page = NULL; 29 | ctx.repo = cgit_get_repoinfo(url); 30 | if (ctx.repo) { 31 | ctx.qry.repo = ctx.repo->url; 32 | return; 33 | } 34 | 35 | cmd = NULL; 36 | c = strchr(url, '/'); 37 | while (c) { 38 | c[0] = '\0'; 39 | repo = cgit_get_repoinfo(url); 40 | if (repo) { 41 | ctx.repo = repo; 42 | cmd = c; 43 | } 44 | c[0] = '/'; 45 | c = strchr(c + 1, '/'); 46 | } 47 | 48 | if (ctx.repo) { 49 | ctx.qry.repo = ctx.repo->url; 50 | p = strchr(cmd + 1, '/'); 51 | if (p) { 52 | p[0] = '\0'; 53 | if (p[1]) 54 | ctx.qry.path = trim_end(p + 1, '/'); 55 | } 56 | if (cmd[1]) 57 | ctx.qry.page = xstrdup(cmd + 1); 58 | } 59 | } 60 | 61 | static char *substr(const char *head, const char *tail) 62 | { 63 | char *buf; 64 | 65 | if (tail < head) 66 | return xstrdup(""); 67 | buf = xmalloc(tail - head + 1); 68 | strlcpy(buf, head, tail - head + 1); 69 | return buf; 70 | } 71 | 72 | static void parse_user(const char *t, char **name, char **email, unsigned long *date, int *tz) 73 | { 74 | struct ident_split ident; 75 | unsigned email_len; 76 | 77 | if (!split_ident_line(&ident, t, strchrnul(t, '\n') - t)) { 78 | *name = substr(ident.name_begin, ident.name_end); 79 | 80 | email_len = ident.mail_end - ident.mail_begin; 81 | *email = xmalloc(strlen("<") + email_len + strlen(">") + 1); 82 | xsnprintf(*email, email_len + 3, "<%.*s>", email_len, ident.mail_begin); 83 | 84 | if (ident.date_begin) 85 | *date = strtoul(ident.date_begin, NULL, 10); 86 | if (ident.tz_begin) 87 | *tz = atoi(ident.tz_begin); 88 | } 89 | } 90 | 91 | #ifdef NO_ICONV 92 | #define reencode(a, b, c) 93 | #else 94 | static const char *reencode(char **txt, const char *src_enc, const char *dst_enc) 95 | { 96 | char *tmp; 97 | 98 | if (!txt) 99 | return NULL; 100 | 101 | if (!*txt || !src_enc || !dst_enc) 102 | return *txt; 103 | 104 | /* no encoding needed if src_enc equals dst_enc */ 105 | if (!strcasecmp(src_enc, dst_enc)) 106 | return *txt; 107 | 108 | tmp = reencode_string(*txt, dst_enc, src_enc); 109 | if (tmp) { 110 | free(*txt); 111 | *txt = tmp; 112 | } 113 | return *txt; 114 | } 115 | #endif 116 | 117 | static const char *next_header_line(const char *p) 118 | { 119 | p = strchr(p, '\n'); 120 | if (!p) 121 | return NULL; 122 | return p + 1; 123 | } 124 | 125 | static int end_of_header(const char *p) 126 | { 127 | return !p || (*p == '\n'); 128 | } 129 | 130 | struct commitinfo *cgit_parse_commit(struct commit *commit) 131 | { 132 | struct commitinfo *ret; 133 | const char *p = repo_get_commit_buffer(the_repository, commit, NULL); 134 | const char *t; 135 | 136 | ret = xcalloc(1, sizeof(struct commitinfo)); 137 | ret->commit = commit; 138 | 139 | if (!p) 140 | return ret; 141 | 142 | if (!skip_prefix(p, "tree ", &p)) 143 | die("Bad commit: %s", oid_to_hex(&commit->object.oid)); 144 | p += the_hash_algo->hexsz + 1; 145 | 146 | while (skip_prefix(p, "parent ", &p)) 147 | p += the_hash_algo->hexsz + 1; 148 | 149 | if (p && skip_prefix(p, "author ", &p)) { 150 | parse_user(p, &ret->author, &ret->author_email, 151 | &ret->author_date, &ret->author_tz); 152 | p = next_header_line(p); 153 | } 154 | 155 | if (p && skip_prefix(p, "committer ", &p)) { 156 | parse_user(p, &ret->committer, &ret->committer_email, 157 | &ret->committer_date, &ret->committer_tz); 158 | p = next_header_line(p); 159 | } 160 | 161 | if (p && skip_prefix(p, "encoding ", &p)) { 162 | t = strchr(p, '\n'); 163 | if (t) { 164 | ret->msg_encoding = substr(p, t + 1); 165 | p = t + 1; 166 | } 167 | } 168 | 169 | if (!ret->msg_encoding) 170 | ret->msg_encoding = xstrdup("UTF-8"); 171 | 172 | while (!end_of_header(p)) 173 | p = next_header_line(p); 174 | while (p && *p == '\n') 175 | p++; 176 | if (!p) 177 | return ret; 178 | 179 | t = strchrnul(p, '\n'); 180 | ret->subject = substr(p, t); 181 | while (*t == '\n') 182 | t++; 183 | ret->msg = xstrdup(t); 184 | 185 | reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING); 186 | reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING); 187 | reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING); 188 | reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING); 189 | reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING); 190 | reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING); 191 | 192 | return ret; 193 | } 194 | 195 | struct taginfo *cgit_parse_tag(struct tag *tag) 196 | { 197 | void *data; 198 | enum object_type type; 199 | unsigned long size; 200 | const char *p; 201 | struct taginfo *ret = NULL; 202 | 203 | data = repo_read_object_file(the_repository, &tag->object.oid, &type, &size); 204 | if (!data || type != OBJ_TAG) 205 | goto cleanup; 206 | 207 | ret = xcalloc(1, sizeof(struct taginfo)); 208 | 209 | for (p = data; !end_of_header(p); p = next_header_line(p)) { 210 | if (skip_prefix(p, "tagger ", &p)) { 211 | parse_user(p, &ret->tagger, &ret->tagger_email, 212 | &ret->tagger_date, &ret->tagger_tz); 213 | } 214 | } 215 | 216 | while (p && *p == '\n') 217 | p++; 218 | 219 | if (p && *p) 220 | ret->msg = xstrdup(p); 221 | 222 | cleanup: 223 | free(data); 224 | return ret; 225 | } 226 | -------------------------------------------------------------------------------- /robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /*/snapshot/* 3 | Disallow: /*/blame/* 4 | Allow: / 5 | -------------------------------------------------------------------------------- /scan-tree.c: -------------------------------------------------------------------------------- 1 | /* scan-tree.c 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include "cgit.h" 10 | #include "scan-tree.h" 11 | #include "configfile.h" 12 | #include "html.h" 13 | #include 14 | 15 | /* return 1 if path contains a objects/ directory and a HEAD file */ 16 | static int is_git_dir(const char *path) 17 | { 18 | struct stat st; 19 | struct strbuf pathbuf = STRBUF_INIT; 20 | int result = 0; 21 | 22 | strbuf_addf(&pathbuf, "%s/objects", path); 23 | if (stat(pathbuf.buf, &st)) { 24 | if (errno != ENOENT) 25 | fprintf(stderr, "Error checking path %s: %s (%d)\n", 26 | path, strerror(errno), errno); 27 | goto out; 28 | } 29 | if (!S_ISDIR(st.st_mode)) 30 | goto out; 31 | 32 | strbuf_reset(&pathbuf); 33 | strbuf_addf(&pathbuf, "%s/HEAD", path); 34 | if (stat(pathbuf.buf, &st)) { 35 | if (errno != ENOENT) 36 | fprintf(stderr, "Error checking path %s: %s (%d)\n", 37 | path, strerror(errno), errno); 38 | goto out; 39 | } 40 | if (!S_ISREG(st.st_mode)) 41 | goto out; 42 | 43 | result = 1; 44 | out: 45 | strbuf_release(&pathbuf); 46 | return result; 47 | } 48 | 49 | static struct cgit_repo *repo; 50 | static repo_config_fn config_fn; 51 | 52 | static void scan_tree_repo_config(const char *name, const char *value) 53 | { 54 | config_fn(repo, name, value); 55 | } 56 | 57 | static int gitconfig_config(const char *key, const char *value, const struct config_context *, void *cb) 58 | { 59 | const char *name; 60 | 61 | if (!strcmp(key, "gitweb.owner")) 62 | config_fn(repo, "owner", value); 63 | else if (!strcmp(key, "gitweb.description")) 64 | config_fn(repo, "desc", value); 65 | else if (!strcmp(key, "gitweb.category")) 66 | config_fn(repo, "section", value); 67 | else if (!strcmp(key, "gitweb.homepage")) 68 | config_fn(repo, "homepage", value); 69 | else if (skip_prefix(key, "cgit.", &name)) 70 | config_fn(repo, name, value); 71 | 72 | return 0; 73 | } 74 | 75 | static char *xstrrchr(char *s, char *from, int c) 76 | { 77 | while (from >= s && *from != c) 78 | from--; 79 | return from < s ? NULL : from; 80 | } 81 | 82 | static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn) 83 | { 84 | struct stat st; 85 | struct passwd *pwd; 86 | size_t pathlen; 87 | struct strbuf rel = STRBUF_INIT; 88 | char *p, *slash; 89 | int n; 90 | size_t size; 91 | 92 | if (stat(path->buf, &st)) { 93 | fprintf(stderr, "Error accessing %s: %s (%d)\n", 94 | path->buf, strerror(errno), errno); 95 | return; 96 | } 97 | 98 | strbuf_addch(path, '/'); 99 | pathlen = path->len; 100 | 101 | if (ctx.cfg.strict_export) { 102 | strbuf_addstr(path, ctx.cfg.strict_export); 103 | if(stat(path->buf, &st)) 104 | return; 105 | strbuf_setlen(path, pathlen); 106 | } 107 | 108 | strbuf_addstr(path, "noweb"); 109 | if (!stat(path->buf, &st)) 110 | return; 111 | strbuf_setlen(path, pathlen); 112 | 113 | if (!starts_with(path->buf, base)) 114 | strbuf_addbuf(&rel, path); 115 | else 116 | strbuf_addstr(&rel, path->buf + strlen(base) + 1); 117 | 118 | if (!strcmp(rel.buf + rel.len - 5, "/.git")) 119 | strbuf_setlen(&rel, rel.len - 5); 120 | else if (rel.len && rel.buf[rel.len - 1] == '/') 121 | strbuf_setlen(&rel, rel.len - 1); 122 | 123 | repo = cgit_add_repo(rel.buf); 124 | config_fn = fn; 125 | if (ctx.cfg.enable_git_config) { 126 | strbuf_addstr(path, "config"); 127 | git_config_from_file(gitconfig_config, path->buf, NULL); 128 | strbuf_setlen(path, pathlen); 129 | } 130 | 131 | if (ctx.cfg.remove_suffix) { 132 | size_t urllen; 133 | strip_suffix(repo->url, ".git", &urllen); 134 | strip_suffix_mem(repo->url, &urllen, "/"); 135 | repo->url[urllen] = '\0'; 136 | } 137 | repo->path = xstrdup(path->buf); 138 | while (!repo->owner) { 139 | if ((pwd = getpwuid(st.st_uid)) == NULL) { 140 | fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 141 | path->buf, strerror(errno), errno); 142 | break; 143 | } 144 | if (pwd->pw_gecos) 145 | if ((p = strchr(pwd->pw_gecos, ','))) 146 | *p = '\0'; 147 | repo->owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name); 148 | } 149 | 150 | if (repo->desc == cgit_default_repo_desc || !repo->desc) { 151 | strbuf_addstr(path, "description"); 152 | if (!stat(path->buf, &st)) 153 | readfile(path->buf, &repo->desc, &size); 154 | strbuf_setlen(path, pathlen); 155 | } 156 | 157 | if (ctx.cfg.section_from_path) { 158 | n = ctx.cfg.section_from_path; 159 | if (n > 0) { 160 | slash = rel.buf - 1; 161 | while (slash && n && (slash = strchr(slash + 1, '/'))) 162 | n--; 163 | } else { 164 | slash = rel.buf + rel.len; 165 | while (slash && n && (slash = xstrrchr(rel.buf, slash - 1, '/'))) 166 | n++; 167 | } 168 | if (slash && !n) { 169 | *slash = '\0'; 170 | repo->section = xstrdup(rel.buf); 171 | *slash = '/'; 172 | if (starts_with(repo->name, repo->section)) { 173 | repo->name += strlen(repo->section); 174 | if (*repo->name == '/') 175 | repo->name++; 176 | } 177 | } 178 | } 179 | 180 | strbuf_addstr(path, "cgitrc"); 181 | if (!stat(path->buf, &st)) 182 | parse_configfile(path->buf, &scan_tree_repo_config); 183 | 184 | strbuf_release(&rel); 185 | } 186 | 187 | static void scan_path(const char *base, const char *path, repo_config_fn fn) 188 | { 189 | DIR *dir = opendir(path); 190 | struct dirent *ent; 191 | struct strbuf pathbuf = STRBUF_INIT; 192 | size_t pathlen = strlen(path); 193 | struct stat st; 194 | 195 | if (!dir) { 196 | fprintf(stderr, "Error opening directory %s: %s (%d)\n", 197 | path, strerror(errno), errno); 198 | return; 199 | } 200 | 201 | strbuf_add(&pathbuf, path, strlen(path)); 202 | if (is_git_dir(pathbuf.buf)) { 203 | add_repo(base, &pathbuf, fn); 204 | goto end; 205 | } 206 | strbuf_addstr(&pathbuf, "/.git"); 207 | if (is_git_dir(pathbuf.buf)) { 208 | add_repo(base, &pathbuf, fn); 209 | goto end; 210 | } 211 | /* 212 | * Add one because we don't want to lose the trailing '/' when we 213 | * reset the length of pathbuf in the loop below. 214 | */ 215 | pathlen++; 216 | while ((ent = readdir(dir)) != NULL) { 217 | if (ent->d_name[0] == '.') { 218 | if (ent->d_name[1] == '\0') 219 | continue; 220 | if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 221 | continue; 222 | if (!ctx.cfg.scan_hidden_path) 223 | continue; 224 | } 225 | strbuf_setlen(&pathbuf, pathlen); 226 | strbuf_addstr(&pathbuf, ent->d_name); 227 | if (stat(pathbuf.buf, &st)) { 228 | fprintf(stderr, "Error checking path %s: %s (%d)\n", 229 | pathbuf.buf, strerror(errno), errno); 230 | continue; 231 | } 232 | if (S_ISDIR(st.st_mode)) 233 | scan_path(base, pathbuf.buf, fn); 234 | } 235 | end: 236 | strbuf_release(&pathbuf); 237 | closedir(dir); 238 | } 239 | 240 | void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn) 241 | { 242 | struct strbuf line = STRBUF_INIT; 243 | FILE *projects; 244 | int err; 245 | 246 | projects = fopen(projectsfile, "r"); 247 | if (!projects) { 248 | fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", 249 | projectsfile, strerror(errno), errno); 250 | return; 251 | } 252 | while (strbuf_getline(&line, projects) != EOF) { 253 | if (!line.len) 254 | continue; 255 | strbuf_insert(&line, 0, "/", 1); 256 | strbuf_insert(&line, 0, path, strlen(path)); 257 | scan_path(path, line.buf, fn); 258 | } 259 | if ((err = ferror(projects))) { 260 | fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n", 261 | projectsfile, strerror(err), err); 262 | } 263 | fclose(projects); 264 | strbuf_release(&line); 265 | } 266 | 267 | void scan_tree(const char *path, repo_config_fn fn) 268 | { 269 | scan_path(path, path, fn); 270 | } 271 | -------------------------------------------------------------------------------- /scan-tree.h: -------------------------------------------------------------------------------- 1 | extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn); 2 | extern void scan_tree(const char *path, repo_config_fn fn); 3 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | trash\ directory.t* 2 | test-results 3 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | include ../git/config.mak.uname 2 | -include ../cgit.conf 3 | 4 | SHELL_PATH ?= $(SHELL) 5 | SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) 6 | 7 | T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh) 8 | 9 | all: $(T) 10 | 11 | $(T): 12 | @'$(SHELL_PATH_SQ)' $@ $(CGIT_TEST_OPTS) 13 | 14 | clean: 15 | $(RM) -rf trash 16 | 17 | .PHONY: $(T) clean 18 | -------------------------------------------------------------------------------- /tests/filters/dump.lua: -------------------------------------------------------------------------------- 1 | function filter_open(...) 2 | buffer = "" 3 | for i = 1, select("#", ...) do 4 | buffer = buffer .. select(i, ...) .. " " 5 | end 6 | end 7 | 8 | function filter_close() 9 | html(buffer) 10 | return 0 11 | end 12 | 13 | function filter_write(str) 14 | buffer = buffer .. string.upper(str) 15 | end 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/filters/dump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | [ "$#" -gt 0 ] && printf "%s " "$*" 4 | tr '[:lower:]' '[:upper:]' 5 | -------------------------------------------------------------------------------- /tests/setup.sh: -------------------------------------------------------------------------------- 1 | # This file should be sourced by all test-scripts 2 | # 3 | # Main functions: 4 | # prepare_tests(description) - setup for testing, i.e. create repos+config 5 | # run_test(description, script) - run one test, i.e. eval script 6 | # 7 | # Helper functions 8 | # cgit_query(querystring) - call cgit with the specified querystring 9 | # cgit_url(url) - call cgit with the specified virtual url 10 | # 11 | # Example script: 12 | # 13 | # . setup.sh 14 | # prepare_tests "html validation" 15 | # run_test 'repo index' 'cgit_url "/" | tidy -e' 16 | # run_test 'repo summary' 'cgit_url "/foo" | tidy -e' 17 | 18 | # We don't want to run Git commands through Valgrind, so we filter out the 19 | # --valgrind option here and handle it ourselves. We copy the arguments 20 | # assuming that none contain a newline, although other whitespace is 21 | # preserved. 22 | LF=' 23 | ' 24 | test_argv= 25 | 26 | while test $# != 0 27 | do 28 | case "$1" in 29 | --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind) 30 | cgit_valgrind=t 31 | test_argv="$test_argv${LF}--verbose" 32 | ;; 33 | *) 34 | test_argv="$test_argv$LF$1" 35 | ;; 36 | esac 37 | shift 38 | done 39 | 40 | OLDIFS=$IFS 41 | IFS=$LF 42 | set -- $test_argv 43 | IFS=$OLDIFS 44 | 45 | : ${TEST_DIRECTORY=$(pwd)/../git/t} 46 | : ${TEST_OUTPUT_DIRECTORY=$(pwd)} 47 | TEST_NO_CREATE_REPO=YesPlease 48 | . "$TEST_DIRECTORY"/test-lib.sh 49 | 50 | # Prepend the directory containing cgit to PATH. 51 | if test -n "$cgit_valgrind" 52 | then 53 | GIT_VALGRIND="$TEST_DIRECTORY/valgrind" 54 | CGIT_VALGRIND=$(cd ../valgrind && pwd) 55 | PATH="$CGIT_VALGRIND/bin:$PATH" 56 | export GIT_VALGRIND CGIT_VALGRIND 57 | else 58 | PATH="$(pwd)/../..:$PATH" 59 | fi 60 | 61 | FILTER_DIRECTORY=$(cd ../filters && pwd) 62 | 63 | if cgit --version | grep -F -q "[+] Lua scripting"; then 64 | export CGIT_HAS_LUA=1 65 | else 66 | export CGIT_HAS_LUA=0 67 | fi 68 | 69 | mkrepo() { 70 | name=$1 71 | count=$2 72 | test_create_repo "$name" 73 | ( 74 | cd "$name" 75 | n=1 76 | while test $n -le $count 77 | do 78 | echo $n >file-$n 79 | git add file-$n 80 | git commit -m "commit $n" 81 | n=$(expr $n + 1) 82 | done 83 | case "$3" in 84 | testplus) 85 | echo "hello" >a+b 86 | git add a+b 87 | git commit -m "add a+b" 88 | git branch "1+2" 89 | ;; 90 | commit-graph) 91 | git commit-graph write 92 | ;; 93 | esac 94 | ) 95 | } 96 | 97 | setup_repos() 98 | { 99 | rm -rf cache 100 | mkdir -p cache 101 | mkrepo repos/foo 5 >/dev/null 102 | mkrepo repos/bar 50 commit-graph >/dev/null 103 | mkrepo repos/foo+bar 10 testplus >/dev/null 104 | mkrepo "repos/with space" 2 >/dev/null 105 | mkrepo repos/filter 5 testplus >/dev/null 106 | cat >cgitrc <>cgitrc <makefile_version 16 | ' 17 | 18 | # Note that Git's GIT-VERSION-GEN script applies "s/-/./g" to the version 19 | # string to produce the internal version in the GIT-VERSION-FILE, so we 20 | # must apply the same transformation to the version in the Makefile before 21 | # comparing them. 22 | test_expect_success 'test Git version matches Makefile' ' 23 | ( cat ../../git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) | 24 | sed -e "s/GIT_VERSION[ ]*=[ ]*//" -e "s/\\.dirty$//" >git_version && 25 | sed -e "s/-/./g" makefile_version >makefile_git_version && 26 | test_cmp git_version makefile_git_version 27 | ' 28 | 29 | test_expect_success 'test submodule version matches Makefile' ' 30 | if ! test -e ../../git/.git 31 | then 32 | echo "git/ is not a Git repository" >&2 33 | else 34 | ( 35 | cd ../.. && 36 | sm_oid=$(git ls-files --stage -- git | 37 | sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9] .*$/\\1/") && 38 | cd git && 39 | git describe --match "v[0-9]*" $sm_oid 40 | ) | sed -e "s/^v//" -e "s/-/./" >sm_version && 41 | test_cmp sm_version makefile_version 42 | fi 43 | ' 44 | 45 | test_done 46 | -------------------------------------------------------------------------------- /tests/t0010-validate-html.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Validate html with tidy' 4 | . ./setup.sh 5 | 6 | 7 | test_url() 8 | { 9 | tidy_opt="-eq" 10 | test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no" 11 | cgit_url "$1" >tidy-$test_count.tmp || return 12 | sed -e "1,4d" tidy-$test_count.tmp >tidy-$test_count || return 13 | "$tidy" $tidy_opt tidy-$test_count 14 | rc=$? 15 | 16 | # tidy returns with exitcode 1 on warnings, 2 on error 17 | if test $rc = 2 18 | then 19 | false 20 | else 21 | : 22 | fi 23 | } 24 | 25 | tidy=`which tidy 2>/dev/null` 26 | test -n "$tidy" || { 27 | skip_all='Skipping html validation tests: tidy not found' 28 | test_done 29 | exit 30 | } 31 | 32 | test_expect_success 'index page' 'test_url ""' 33 | test_expect_success 'foo' 'test_url "foo"' 34 | test_expect_success 'foo/log' 'test_url "foo/log"' 35 | test_expect_success 'foo/tree' 'test_url "foo/tree"' 36 | test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"' 37 | test_expect_success 'foo/commit' 'test_url "foo/commit"' 38 | test_expect_success 'foo/diff' 'test_url "foo/diff"' 39 | 40 | test_done 41 | -------------------------------------------------------------------------------- /tests/t0020-validate-cache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Validate cache' 4 | . ./setup.sh 5 | 6 | test_expect_success 'verify cache-size=0' ' 7 | 8 | rm -f cache/* && 9 | sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp && 10 | mv -f cgitrc.tmp cgitrc && 11 | cgit_url "" && 12 | cgit_url "foo" && 13 | cgit_url "foo/refs" && 14 | cgit_url "foo/tree" && 15 | cgit_url "foo/log" && 16 | cgit_url "foo/diff" && 17 | cgit_url "foo/patch" && 18 | cgit_url "bar" && 19 | cgit_url "bar/refs" && 20 | cgit_url "bar/tree" && 21 | cgit_url "bar/log" && 22 | cgit_url "bar/diff" && 23 | cgit_url "bar/patch" && 24 | ls cache >output && 25 | test_line_count = 0 output 26 | ' 27 | 28 | test_expect_success 'verify cache-size=1' ' 29 | 30 | rm -f cache/* && 31 | sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp && 32 | mv -f cgitrc.tmp cgitrc && 33 | cgit_url "" && 34 | cgit_url "foo" && 35 | cgit_url "foo/refs" && 36 | cgit_url "foo/tree" && 37 | cgit_url "foo/log" && 38 | cgit_url "foo/diff" && 39 | cgit_url "foo/patch" && 40 | cgit_url "bar" && 41 | cgit_url "bar/refs" && 42 | cgit_url "bar/tree" && 43 | cgit_url "bar/log" && 44 | cgit_url "bar/diff" && 45 | cgit_url "bar/patch" && 46 | ls cache >output && 47 | test_line_count = 1 output 48 | ' 49 | 50 | test_expect_success 'verify cache-size=1021' ' 51 | 52 | rm -f cache/* && 53 | sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp && 54 | mv -f cgitrc.tmp cgitrc && 55 | cgit_url "" && 56 | cgit_url "foo" && 57 | cgit_url "foo/refs" && 58 | cgit_url "foo/tree" && 59 | cgit_url "foo/log" && 60 | cgit_url "foo/diff" && 61 | cgit_url "foo/patch" && 62 | cgit_url "bar" && 63 | cgit_url "bar/refs" && 64 | cgit_url "bar/tree" && 65 | cgit_url "bar/log" && 66 | cgit_url "bar/diff" && 67 | cgit_url "bar/patch" && 68 | ls cache >output && 69 | test_line_count = 13 output && 70 | cgit_url "foo/ls_cache" >output.full && 71 | strip_headers output && 72 | test_line_count = 13 output && 73 | # Check that ls_cache output is cached correctly 74 | cgit_url "foo/ls_cache" >output.second && 75 | test_cmp output.full output.second 76 | ' 77 | 78 | test_done 79 | -------------------------------------------------------------------------------- /tests/t0101-index.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on index page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate index page' 'cgit_url "" >tmp' 7 | test_expect_success 'find foo repo' 'grep "foo" tmp' 8 | test_expect_success 'find foo description' 'grep "\[no description\]" tmp' 9 | test_expect_success 'find bar repo' 'grep "bar" tmp' 10 | test_expect_success 'find bar description' 'grep "the bar repo" tmp' 11 | test_expect_success 'find foo+bar repo' 'grep ">foo+bar<" tmp' 12 | test_expect_success 'verify foo+bar link' 'grep "/foo+bar/" tmp' 13 | test_expect_success 'verify "with%20space" link' 'grep "/with%20space/" tmp' 14 | test_expect_success 'no tree-link' '! grep "foo/tree" tmp' 15 | test_expect_success 'no log-link' '! grep "foo/log" tmp' 16 | 17 | test_done 18 | -------------------------------------------------------------------------------- /tests/t0102-summary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on summary page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo summary' 'cgit_url "foo" >tmp' 7 | test_expect_success 'find commit 1' 'grep "commit 1" tmp' 8 | test_expect_success 'find commit 5' 'grep "commit 5" tmp' 9 | test_expect_success 'find branch master' 'grep "master" tmp' 10 | test_expect_success 'no tags' '! grep "tags" tmp' 11 | test_expect_success 'clone-url expanded correctly' ' 12 | grep "git://example.org/foo.git" tmp 13 | ' 14 | 15 | test_expect_success 'generate bar summary' 'cgit_url "bar" >tmp' 16 | test_expect_success 'no commit 45' '! grep "commit 45" tmp' 17 | test_expect_success 'find commit 46' 'grep "commit 46" tmp' 18 | test_expect_success 'find commit 50' 'grep "commit 50" tmp' 19 | test_expect_success 'find branch master' 'grep "master" tmp' 20 | test_expect_success 'no tags' '! grep "tags" tmp' 21 | test_expect_success 'clone-url expanded correctly' ' 22 | grep "git://example.org/bar.git" tmp 23 | ' 24 | 25 | test_done 26 | -------------------------------------------------------------------------------- /tests/t0103-log.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on log page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo/log' 'cgit_url "foo/log" >tmp' 7 | test_expect_success 'find commit 1' 'grep "commit 1" tmp' 8 | test_expect_success 'find commit 5' 'grep "commit 5" tmp' 9 | 10 | test_expect_success 'generate bar/log' 'cgit_url "bar/log" >tmp' 11 | test_expect_success 'find commit 1' 'grep "commit 1" tmp' 12 | test_expect_success 'find commit 50' 'grep "commit 50" tmp' 13 | 14 | test_expect_success 'generate "with%20space/log?qt=grep&q=commit+1"' ' 15 | cgit_url "with+space/log&qt=grep&q=commit+1" >tmp 16 | ' 17 | test_expect_success 'find commit 1' 'grep "commit 1" tmp' 18 | test_expect_success 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" tmp' 19 | test_expect_success 'find link with + in arg' 'grep "/log/?qt=grep&q=commit+1" tmp' 20 | test_expect_success 'no links with space in path' '! grep "href=./with space/" tmp' 21 | test_expect_success 'no links with space in arg' '! grep "q=commit 1" tmp' 22 | test_expect_success 'commit 2 is not visible' '! grep "commit 2" tmp' 23 | 24 | test_done 25 | -------------------------------------------------------------------------------- /tests/t0104-tree.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on tree page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate bar/tree' 'cgit_url "bar/tree" >tmp' 7 | test_expect_success 'find file-1' 'grep "file-1" tmp' 8 | test_expect_success 'find file-50' 'grep "file-50" tmp' 9 | 10 | test_expect_success 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >tmp' 11 | 12 | test_expect_success 'find line 1' ' 13 | grep "1" tmp 14 | ' 15 | 16 | test_expect_success 'no line 2' ' 17 | ! grep "2" tmp 18 | ' 19 | 20 | test_expect_success 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >tmp' 21 | 22 | test_expect_success 'verify a+b link' ' 23 | grep "/foo+bar/tree/a+b" tmp 24 | ' 25 | 26 | test_expect_success 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >tmp' 27 | 28 | test_expect_success 'verify a+b?h=1+2 link' ' 29 | grep "/foo+bar/tree/a+b?h=1%2b2" tmp 30 | ' 31 | 32 | test_done 33 | -------------------------------------------------------------------------------- /tests/t0105-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on commit page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp' 7 | test_expect_success 'find tree link' 'grep "" tmp' 8 | test_expect_success 'find parent link' 'grep -E "" tmp' 9 | 10 | test_expect_success 'find commit subject' ' 11 | grep "
commit 5<" tmp 12 | ' 13 | 14 | test_expect_success 'find commit msg' 'grep "
" tmp' 15 | test_expect_success 'find diffstat' 'grep "" tmp' 16 | 17 | test_expect_success 'find diff summary' ' 18 | grep "1 files changed, 1 insertions, 0 deletions" tmp 19 | ' 20 | 21 | test_expect_success 'get root commit' ' 22 | root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) && 23 | cgit_url "foo/commit&id=$root" >tmp && 24 | grep "" tmp 25 | ' 26 | 27 | test_expect_success 'root commit contains diffstat' ' 28 | grep "file-1" tmp 29 | ' 30 | 31 | test_expect_success 'root commit contains diff' ' 32 | grep ">diff --git a/file-1 b/file-1<" tmp && 33 | grep "
+1
" tmp 34 | ' 35 | 36 | test_done 37 | -------------------------------------------------------------------------------- /tests/t0106-diff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on diff page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo/diff' 'cgit_url "foo/diff" >tmp' 7 | test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp' 8 | test_expect_success 'find blob link' 'grep "@@ -0,0 +1 @@" tmp 13 | ' 14 | 15 | test_expect_success 'find added line' ' 16 | grep "
+5
" tmp 17 | ' 18 | 19 | test_done 20 | -------------------------------------------------------------------------------- /tests/t0107-snapshot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Verify snapshot' 4 | . ./setup.sh 5 | 6 | test_expect_success 'get foo/snapshot/master.tar.gz' ' 7 | cgit_url "foo/snapshot/master.tar.gz" >tmp 8 | ' 9 | 10 | test_expect_success 'check html headers' ' 11 | head -n 1 tmp | 12 | grep "Content-Type: application/x-gzip" && 13 | 14 | head -n 2 tmp | 15 | grep "Content-Disposition: inline; filename=.master.tar.gz." 16 | ' 17 | 18 | test_expect_success 'strip off the header lines' ' 19 | strip_headers master.tar.gz 20 | ' 21 | 22 | test_expect_success 'verify gzip format' ' 23 | gunzip --test master.tar.gz 24 | ' 25 | 26 | test_expect_success 'untar' ' 27 | rm -rf master && 28 | gzip -dc master.tar.gz | tar -xf - 29 | ' 30 | 31 | test_expect_success 'count files' ' 32 | ls master/ >output && 33 | test_line_count = 5 output 34 | ' 35 | 36 | test_expect_success 'verify untarred file-5' ' 37 | grep "^5$" master/file-5 && 38 | test_line_count = 1 master/file-5 39 | ' 40 | 41 | if test -n "$(which lzip 2>/dev/null)"; then 42 | test_set_prereq LZIP 43 | else 44 | say 'Skipping LZIP validation tests: lzip not found' 45 | fi 46 | 47 | test_expect_success LZIP 'get foo/snapshot/master.tar.lz' ' 48 | cgit_url "foo/snapshot/master.tar.lz" >tmp 49 | ' 50 | 51 | test_expect_success LZIP 'check html headers' ' 52 | head -n 1 tmp | 53 | grep "Content-Type: application/x-lzip" && 54 | 55 | head -n 2 tmp | 56 | grep "Content-Disposition: inline; filename=.master.tar.lz." 57 | ' 58 | 59 | test_expect_success LZIP 'strip off the header lines' ' 60 | strip_headers master.tar.lz 61 | ' 62 | 63 | test_expect_success LZIP 'verify lzip format' ' 64 | lzip --test master.tar.lz 65 | ' 66 | 67 | test_expect_success LZIP 'untar' ' 68 | rm -rf master && 69 | lzip -dc master.tar.lz | tar -xf - 70 | ' 71 | 72 | test_expect_success LZIP 'count files' ' 73 | ls master/ >output && 74 | test_line_count = 5 output 75 | ' 76 | 77 | test_expect_success LZIP 'verify untarred file-5' ' 78 | grep "^5$" master/file-5 && 79 | test_line_count = 1 master/file-5 80 | ' 81 | 82 | if test -n "$(which xz 2>/dev/null)"; then 83 | test_set_prereq XZ 84 | else 85 | say 'Skipping XZ validation tests: xz not found' 86 | fi 87 | 88 | test_expect_success XZ 'get foo/snapshot/master.tar.xz' ' 89 | cgit_url "foo/snapshot/master.tar.xz" >tmp 90 | ' 91 | 92 | test_expect_success XZ 'check html headers' ' 93 | head -n 1 tmp | 94 | grep "Content-Type: application/x-xz" && 95 | 96 | head -n 2 tmp | 97 | grep "Content-Disposition: inline; filename=.master.tar.xz." 98 | ' 99 | 100 | test_expect_success XZ 'strip off the header lines' ' 101 | strip_headers master.tar.xz 102 | ' 103 | 104 | test_expect_success XZ 'verify xz format' ' 105 | xz --test master.tar.xz 106 | ' 107 | 108 | test_expect_success XZ 'untar' ' 109 | rm -rf master && 110 | xz -dc master.tar.xz | tar -xf - 111 | ' 112 | 113 | test_expect_success XZ 'count files' ' 114 | ls master/ >output && 115 | test_line_count = 5 output 116 | ' 117 | 118 | test_expect_success XZ 'verify untarred file-5' ' 119 | grep "^5$" master/file-5 && 120 | test_line_count = 1 master/file-5 121 | ' 122 | 123 | if test -n "$(which zstd 2>/dev/null)"; then 124 | test_set_prereq ZSTD 125 | else 126 | say 'Skipping ZSTD validation tests: zstd not found' 127 | fi 128 | 129 | test_expect_success ZSTD 'get foo/snapshot/master.tar.zst' ' 130 | cgit_url "foo/snapshot/master.tar.zst" >tmp 131 | ' 132 | 133 | test_expect_success ZSTD 'check html headers' ' 134 | head -n 1 tmp | 135 | grep "Content-Type: application/x-zstd" && 136 | 137 | head -n 2 tmp | 138 | grep "Content-Disposition: inline; filename=.master.tar.zst." 139 | ' 140 | 141 | test_expect_success ZSTD 'strip off the header lines' ' 142 | strip_headers master.tar.zst 143 | ' 144 | 145 | test_expect_success ZSTD 'verify zstd format' ' 146 | zstd --test master.tar.zst 147 | ' 148 | 149 | test_expect_success ZSTD 'untar' ' 150 | rm -rf master && 151 | zstd -dc master.tar.zst | tar -xf - 152 | ' 153 | 154 | test_expect_success ZSTD 'count files' ' 155 | ls master/ >output && 156 | test_line_count = 5 output 157 | ' 158 | 159 | test_expect_success ZSTD 'verify untarred file-5' ' 160 | grep "^5$" master/file-5 && 161 | test_line_count = 1 master/file-5 162 | ' 163 | 164 | test_expect_success 'get foo/snapshot/master.zip' ' 165 | cgit_url "foo/snapshot/master.zip" >tmp 166 | ' 167 | 168 | test_expect_success 'check HTML headers (zip)' ' 169 | head -n 1 tmp | 170 | grep "Content-Type: application/x-zip" && 171 | 172 | head -n 2 tmp | 173 | grep "Content-Disposition: inline; filename=.master.zip." 174 | ' 175 | 176 | test_expect_success 'strip off the header lines (zip)' ' 177 | strip_headers master.zip 178 | ' 179 | 180 | if test -n "$(which unzip 2>/dev/null)"; then 181 | test_set_prereq UNZIP 182 | else 183 | say 'Skipping ZIP validation tests: unzip not found' 184 | fi 185 | 186 | test_expect_success UNZIP 'verify zip format' ' 187 | unzip -t master.zip 188 | ' 189 | 190 | test_expect_success UNZIP 'unzip' ' 191 | rm -rf master && 192 | unzip master.zip 193 | ' 194 | 195 | test_expect_success UNZIP 'count files (zip)' ' 196 | ls master/ >output && 197 | test_line_count = 5 output 198 | ' 199 | 200 | test_expect_success UNZIP 'verify unzipped file-5' ' 201 | grep "^5$" master/file-5 && 202 | test_line_count = 1 master/file-5 203 | ' 204 | 205 | test_done 206 | -------------------------------------------------------------------------------- /tests/t0108-patch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on patch page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo/patch' ' 7 | cgit_query "url=foo/patch" >tmp 8 | ' 9 | 10 | test_expect_success 'find `From:` line' ' 11 | grep "^From: " tmp 12 | ' 13 | 14 | test_expect_success 'find `Date:` line' ' 15 | grep "^Date: " tmp 16 | ' 17 | 18 | test_expect_success 'find `Subject:` line' ' 19 | grep "^Subject: commit 5" tmp 20 | ' 21 | 22 | test_expect_success 'find `cgit` signature' ' 23 | tail -2 tmp | head -1 | grep "^cgit" 24 | ' 25 | 26 | test_expect_success 'compare with output of git-format-patch(1)' ' 27 | CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) && 28 | git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 && 29 | strip_headers tmp_ && 30 | test_cmp tmp_ tmp2 31 | ' 32 | 33 | test_expect_success 'find initial commit' ' 34 | root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) 35 | ' 36 | 37 | test_expect_success 'generate patch for initial commit' ' 38 | cgit_query "url=foo/patch&id=$root" >tmp 39 | ' 40 | 41 | test_expect_success 'find `cgit` signature' ' 42 | tail -2 tmp | head -1 | grep "^cgit" 43 | ' 44 | 45 | test_expect_success 'generate patches for multiple commits' ' 46 | id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && 47 | id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && 48 | cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp 49 | ' 50 | 51 | test_expect_success 'find `cgit` signature' ' 52 | tail -2 tmp | head -1 | grep "^cgit" 53 | ' 54 | 55 | test_expect_success 'compare with output of git-format-patch(1)' ' 56 | CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) && 57 | git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 && 58 | strip_headers tmp_ && 59 | test_cmp tmp_ tmp2 60 | ' 61 | 62 | test_done 63 | -------------------------------------------------------------------------------- /tests/t0109-gitconfig.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Ensure that git does not access $HOME' 4 | . ./setup.sh 5 | 6 | test -n "$(which strace 2>/dev/null)" || { 7 | skip_all='Skipping access validation tests: strace not found' 8 | test_done 9 | exit 10 | } 11 | 12 | strace true 2>/dev/null || { 13 | skip_all='Skipping access validation tests: strace not functional' 14 | test_done 15 | exit 16 | } 17 | 18 | test_no_home_access () { 19 | non_existent_path="/path/to/some/place/that/does/not/possibly/exist" 20 | while test -d "$non_existent_path"; do 21 | non_existent_path="$non_existent_path/$(date +%N)" 22 | done && 23 | strace \ 24 | -E HOME="$non_existent_path" \ 25 | -E CGIT_CONFIG="$PWD/cgitrc" \ 26 | -E QUERY_STRING="url=$1" \ 27 | -e access -f -o strace.out cgit && 28 | ! grep "$non_existent_path" strace.out 29 | } 30 | 31 | test_no_home_access_success() { 32 | test_expect_success "do not access \$HOME: $1" " 33 | test_no_home_access '$1' 34 | " 35 | } 36 | 37 | test_no_home_access_success 38 | test_no_home_access_success foo 39 | test_no_home_access_success foo/refs 40 | test_no_home_access_success foo/log 41 | test_no_home_access_success foo/tree 42 | test_no_home_access_success foo/tree/file-1 43 | test_no_home_access_success foo/commit 44 | test_no_home_access_success foo/diff 45 | test_no_home_access_success foo/patch 46 | test_no_home_access_success foo/snapshot/master.tar.gz 47 | 48 | test_done 49 | -------------------------------------------------------------------------------- /tests/t0110-rawdiff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check content on rawdiff page' 4 | . ./setup.sh 5 | 6 | test_expect_success 'generate foo/rawdiff' ' 7 | cgit_query "url=foo/rawdiff" >tmp 8 | ' 9 | 10 | test_expect_success 'compare with output of git-diff(1)' ' 11 | git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 && 12 | sed "1,4d" tmp >tmp_ && 13 | cmp tmp_ tmp2 14 | ' 15 | 16 | test_expect_success 'find initial commit' ' 17 | root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) 18 | ' 19 | 20 | test_expect_success 'generate diff for initial commit' ' 21 | cgit_query "url=foo/rawdiff&id=$root" >tmp 22 | ' 23 | 24 | test_expect_success 'compare with output of git-diff-tree(1)' ' 25 | git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 && 26 | sed "1,4d" tmp >tmp_ && 27 | cmp tmp_ tmp2 28 | ' 29 | 30 | test_expect_success 'generate diff for multiple commits' ' 31 | id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) && 32 | id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) && 33 | cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp 34 | ' 35 | 36 | test_expect_success 'compare with output of git-diff(1)' ' 37 | git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 && 38 | sed "1,4d" tmp >tmp_ && 39 | cmp tmp_ tmp2 40 | ' 41 | 42 | test_done 43 | -------------------------------------------------------------------------------- /tests/t0111-filter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | test_description='Check filtered content' 4 | . ./setup.sh 5 | 6 | prefixes="exec" 7 | if [ $CGIT_HAS_LUA -eq 1 ]; then 8 | prefixes="$prefixes lua" 9 | fi 10 | 11 | for prefix in $prefixes 12 | do 13 | test_expect_success "generate filter-$prefix/tree/a%2bb" " 14 | cgit_url 'filter-$prefix/tree/a%2bb' >tmp 15 | " 16 | 17 | test_expect_success "check whether the $prefix source filter works" ' 18 | grep "a+b HELLO$" tmp 19 | ' 20 | 21 | test_expect_success "generate filter-$prefix/about/" " 22 | cgit_url 'filter-$prefix/about/' >tmp 23 | " 24 | 25 | test_expect_success "check whether the $prefix about filter works" ' 26 | grep "
a+b HELLO$" tmp 27 | ' 28 | 29 | test_expect_success "generate filter-$prefix/commit/" " 30 | cgit_url 'filter-$prefix/commit/' >tmp 31 | " 32 | 33 | test_expect_success "check whether the $prefix commit filter works" ' 34 | grep "
ADD A+B" tmp 35 | ' 36 | 37 | test_expect_success "check whether the $prefix email filter works for authors" ' 38 | grep " commit A U THOR <AUTHOR@EXAMPLE.COM>" tmp 39 | ' 40 | 41 | test_expect_success "check whether the $prefix email filter works for committers" ' 42 | grep " commit C O MITTER <COMMITTER@EXAMPLE.COM>" tmp 43 | ' 44 | done 45 | 46 | test_done 47 | -------------------------------------------------------------------------------- /tests/valgrind/bin/cgit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Note that we currently use Git's suppression file and there are variables 4 | # $GIT_VALGRIND and $CGIT_VALGRIND which point to different places. 5 | exec valgrind -q --error-exitcode=126 \ 6 | --suppressions="$GIT_VALGRIND/default.supp" \ 7 | --gen-suppressions=all \ 8 | --leak-check=no \ 9 | --track-origins=yes \ 10 | --log-fd=4 \ 11 | --input-fd=4 \ 12 | "$CGIT_VALGRIND/../../cgit" "$@" 13 | -------------------------------------------------------------------------------- /ui-atom.c: -------------------------------------------------------------------------------- 1 | /* ui-atom.c: functions for atom feeds 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-atom.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | static void add_entry(struct commit *commit, const char *host) 17 | { 18 | char delim = '&'; 19 | char *hex; 20 | char *mail, *t, *t2; 21 | struct commitinfo *info; 22 | 23 | info = cgit_parse_commit(commit); 24 | hex = oid_to_hex(&commit->object.oid); 25 | html("\n"); 26 | html(""); 27 | html_txt(info->subject); 28 | html("\n"); 29 | html(""); 30 | html_txt(show_date(info->committer_date, 0, 31 | date_mode_from_type(DATE_ISO8601_STRICT))); 32 | html("\n"); 33 | html("\n"); 34 | if (info->author) { 35 | html(""); 36 | html_txt(info->author); 37 | html("\n"); 38 | } 39 | if (info->author_email && !ctx.cfg.noplainemail) { 40 | mail = xstrdup(info->author_email); 41 | t = strchr(mail, '<'); 42 | if (t) 43 | t++; 44 | else 45 | t = mail; 46 | t2 = strchr(t, '>'); 47 | if (t2) 48 | *t2 = '\0'; 49 | html(""); 50 | html_txt(t); 51 | html("\n"); 52 | free(mail); 53 | } 54 | html("\n"); 55 | html(""); 56 | html_txt(show_date(info->author_date, 0, 57 | date_mode_from_type(DATE_ISO8601_STRICT))); 58 | html("\n"); 59 | if (host) { 60 | char *pageurl; 61 | html("\n"); 70 | free(pageurl); 71 | } 72 | html(""); 73 | html_txtf("urn:%s:%s", the_hash_algo->name, hex); 74 | html("\n"); 75 | html("\n"); 76 | html_txt(info->msg); 77 | html("\n"); 78 | html("\n"); 79 | cgit_free_commitinfo(info); 80 | } 81 | 82 | 83 | void cgit_print_atom(char *tip, const char *path, int max_count) 84 | { 85 | char *host; 86 | const char *argv[] = {NULL, tip, NULL, NULL, NULL}; 87 | struct commit *commit; 88 | struct rev_info rev; 89 | int argc = 2; 90 | bool first = true; 91 | 92 | if (ctx.qry.show_all) 93 | argv[1] = "--all"; 94 | else if (!tip) 95 | argv[1] = ctx.qry.head; 96 | 97 | if (path) { 98 | argv[argc++] = "--"; 99 | argv[argc++] = path; 100 | } 101 | 102 | repo_init_revisions(the_repository, &rev, NULL); 103 | rev.abbrev = DEFAULT_ABBREV; 104 | rev.commit_format = CMIT_FMT_DEFAULT; 105 | rev.verbose_header = 1; 106 | rev.show_root_diff = 0; 107 | rev.max_count = max_count; 108 | setup_revisions(argc, argv, &rev, NULL); 109 | prepare_revision_walk(&rev); 110 | 111 | host = cgit_hosturl(); 112 | ctx.page.mimetype = "text/xml"; 113 | ctx.page.charset = "utf-8"; 114 | cgit_print_http_headers(); 115 | html("\n"); 116 | html(""); 117 | html_txt(ctx.repo->name); 118 | if (path) { 119 | html("/"); 120 | html_txt(path); 121 | } 122 | if (tip && !ctx.qry.show_all) { 123 | html(", branch "); 124 | html_txt(tip); 125 | } 126 | html("\n"); 127 | html(""); 128 | html_txt(ctx.repo->desc); 129 | html("\n"); 130 | if (host) { 131 | char *fullurl = cgit_currentfullurl(); 132 | char *repourl = cgit_repourl(ctx.repo->url); 133 | html(""); 134 | html_txtf("%s%s%s", cgit_httpscheme(), host, fullurl); 135 | html("\n"); 136 | html("\n"); 139 | html("\n"); 142 | free(fullurl); 143 | free(repourl); 144 | } 145 | while ((commit = get_revision(&rev)) != NULL) { 146 | if (first) { 147 | html(""); 148 | html_txt(show_date(commit->date, 0, 149 | date_mode_from_type(DATE_ISO8601_STRICT))); 150 | html("\n"); 151 | first = false; 152 | } 153 | add_entry(commit, host); 154 | release_commit_memory(the_repository->parsed_objects, commit); 155 | commit->parents = NULL; 156 | } 157 | html("\n"); 158 | free(host); 159 | } 160 | -------------------------------------------------------------------------------- /ui-atom.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_ATOM_H 2 | #define UI_ATOM_H 3 | 4 | extern void cgit_print_atom(char *tip, const char *path, int max_count); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /ui-blame.c: -------------------------------------------------------------------------------- 1 | /* ui-blame.c: functions for blame output 2 | * 3 | * Copyright (C) 2006-2017 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-blame.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | #include "strvec.h" 16 | #include "blame.h" 17 | 18 | 19 | static char *emit_suspect_detail(struct blame_origin *suspect) 20 | { 21 | struct commitinfo *info; 22 | struct strbuf detail = STRBUF_INIT; 23 | 24 | info = cgit_parse_commit(suspect->commit); 25 | 26 | strbuf_addf(&detail, "author %s", info->author); 27 | if (!ctx.cfg.noplainemail) 28 | strbuf_addf(&detail, " %s", info->author_email); 29 | strbuf_addf(&detail, " %s\n", 30 | show_date(info->author_date, info->author_tz, 31 | cgit_date_mode(DATE_ISO8601))); 32 | 33 | strbuf_addf(&detail, "committer %s", info->committer); 34 | if (!ctx.cfg.noplainemail) 35 | strbuf_addf(&detail, " %s", info->committer_email); 36 | strbuf_addf(&detail, " %s\n\n", 37 | show_date(info->committer_date, info->committer_tz, 38 | cgit_date_mode(DATE_ISO8601))); 39 | 40 | strbuf_addstr(&detail, info->subject); 41 | 42 | cgit_free_commitinfo(info); 43 | return strbuf_detach(&detail, NULL); 44 | } 45 | 46 | static void emit_blame_entry_hash(struct blame_entry *ent) 47 | { 48 | struct blame_origin *suspect = ent->suspect; 49 | struct object_id *oid = &suspect->commit->object.oid; 50 | unsigned long line = 0; 51 | 52 | char *detail = emit_suspect_detail(suspect); 53 | html(""); 54 | cgit_commit_link(repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV), detail, 55 | NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); 56 | html(""); 57 | free(detail); 58 | 59 | if (!repo_parse_commit(the_repository, suspect->commit) && suspect->commit->parents) { 60 | struct commit *parent = suspect->commit->parents->item; 61 | 62 | html(" "); 63 | cgit_blame_link("^", "Blame the previous revision", NULL, 64 | ctx.qry.head, oid_to_hex(&parent->object.oid), 65 | suspect->path); 66 | } 67 | 68 | while (line++ < ent->num_lines) 69 | html("\n"); 70 | } 71 | 72 | static void emit_blame_entry_linenumber(struct blame_entry *ent) 73 | { 74 | const char *numberfmt = "%1$d\n"; 75 | 76 | unsigned long lineno = ent->lno; 77 | while (lineno < ent->lno + ent->num_lines) 78 | htmlf(numberfmt, ++lineno); 79 | } 80 | 81 | static void emit_blame_entry_line_background(struct blame_scoreboard *sb, 82 | struct blame_entry *ent) 83 | { 84 | unsigned long line; 85 | size_t len, maxlen = 2; 86 | const char* pos, *endpos; 87 | 88 | for (line = ent->lno; line < ent->lno + ent->num_lines; line++) { 89 | html("\n"); 90 | pos = blame_nth_line(sb, line); 91 | endpos = blame_nth_line(sb, line + 1); 92 | len = 0; 93 | while (pos < endpos) { 94 | len++; 95 | if (*pos++ == '\t') 96 | len = (len + 7) & ~7; 97 | } 98 | if (len > maxlen) 99 | maxlen = len; 100 | } 101 | 102 | for (len = 0; len < maxlen - 1; len++) 103 | html(" "); 104 | } 105 | 106 | struct walk_tree_context { 107 | char *curr_rev; 108 | int match_baselen; 109 | int state; 110 | }; 111 | 112 | static void print_object(const struct object_id *oid, const char *path, 113 | const char *basename, const char *rev) 114 | { 115 | enum object_type type; 116 | char *buf; 117 | unsigned long size; 118 | struct strvec rev_argv = STRVEC_INIT; 119 | struct rev_info revs; 120 | struct blame_scoreboard sb; 121 | struct blame_origin *o; 122 | struct blame_entry *ent = NULL; 123 | 124 | type = oid_object_info(the_repository, oid, &size); 125 | if (type == OBJ_BAD) { 126 | cgit_print_error_page(404, "Not found", "Bad object name: %s", 127 | oid_to_hex(oid)); 128 | return; 129 | } 130 | 131 | buf = repo_read_object_file(the_repository, oid, &type, &size); 132 | if (!buf) { 133 | cgit_print_error_page(500, "Internal server error", 134 | "Error reading object %s", oid_to_hex(oid)); 135 | return; 136 | } 137 | 138 | strvec_push(&rev_argv, "blame"); 139 | strvec_push(&rev_argv, rev); 140 | repo_init_revisions(the_repository, &revs, NULL); 141 | revs.diffopt.flags.allow_textconv = 1; 142 | setup_revisions(rev_argv.nr, rev_argv.v, &revs, NULL); 143 | init_scoreboard(&sb); 144 | sb.revs = &revs; 145 | sb.repo = the_repository; 146 | sb.path = path; 147 | setup_scoreboard(&sb, &o); 148 | o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o); 149 | prio_queue_put(&sb.commits, o->commit); 150 | blame_origin_decref(o); 151 | sb.ent = NULL; 152 | sb.path = path; 153 | assign_blame(&sb, 0); 154 | blame_sort_final(&sb); 155 | blame_coalesce(&sb); 156 | 157 | cgit_set_title_from_path(path); 158 | 159 | cgit_print_layout_start(); 160 | htmlf("blob: %s (", oid_to_hex(oid)); 161 | cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); 162 | html(") ("); 163 | cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path); 164 | html(")\n"); 165 | 166 | if (buffer_is_binary(buf, size)) { 167 | html("
blob is binary.
"); 168 | goto cleanup; 169 | } 170 | if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { 171 | htmlf("
blob size (%ldKB)" 172 | " exceeds display size limit (%dKB).
", 173 | size / 1024, ctx.cfg.max_blob_size); 174 | goto cleanup; 175 | } 176 | 177 | html("
\n\n"); 178 | 179 | /* Commit hashes */ 180 | html("\n"); 187 | 188 | /* Line numbers */ 189 | if (ctx.cfg.enable_tree_linenumbers) { 190 | html("\n"); 197 | } 198 | 199 | html("\n"); 229 | 230 | html("\n
"); 181 | for (ent = sb.ent; ent; ent = ent->next) { 182 | html("
");
183 | 		emit_blame_entry_hash(ent);
184 | 		html("
"); 185 | } 186 | html("
"); 191 | for (ent = sb.ent; ent; ent = ent->next) { 192 | html("
");
193 | 			emit_blame_entry_linenumber(ent);
194 | 			html("
"); 195 | } 196 | html("
"); 200 | 201 | /* Colored bars behind lines */ 202 | html("
"); 203 | for (ent = sb.ent; ent; ) { 204 | struct blame_entry *e = ent->next; 205 | html("
");
206 | 		emit_blame_entry_line_background(&sb, ent);
207 | 		html("
"); 208 | free(ent); 209 | ent = e; 210 | } 211 | html("
"); 212 | 213 | free((void *)sb.final_buf); 214 | 215 | /* Lines */ 216 | html("
");
217 | 	if (ctx.repo->source_filter) {
218 | 		char *filter_arg = xstrdup(basename);
219 | 		cgit_open_filter(ctx.repo->source_filter, filter_arg);
220 | 		html_raw(buf, size);
221 | 		cgit_close_filter(ctx.repo->source_filter);
222 | 		free(filter_arg);
223 | 	} else {
224 | 		html_txt(buf);
225 | 	}
226 | 	html("
"); 227 | 228 | html("
\n"); 231 | 232 | cgit_print_layout_end(); 233 | 234 | cleanup: 235 | free(buf); 236 | } 237 | 238 | static int walk_tree(const struct object_id *oid, struct strbuf *base, 239 | const char *pathname, unsigned mode, void *cbdata) 240 | { 241 | struct walk_tree_context *walk_tree_ctx = cbdata; 242 | 243 | if (base->len == walk_tree_ctx->match_baselen) { 244 | if (S_ISREG(mode)) { 245 | struct strbuf buffer = STRBUF_INIT; 246 | strbuf_addbuf(&buffer, base); 247 | strbuf_addstr(&buffer, pathname); 248 | print_object(oid, buffer.buf, pathname, 249 | walk_tree_ctx->curr_rev); 250 | strbuf_release(&buffer); 251 | walk_tree_ctx->state = 1; 252 | } else if (S_ISDIR(mode)) { 253 | walk_tree_ctx->state = 2; 254 | } 255 | } else if (base->len < INT_MAX 256 | && (int)base->len > walk_tree_ctx->match_baselen) { 257 | walk_tree_ctx->state = 2; 258 | } else if (S_ISDIR(mode)) { 259 | return READ_TREE_RECURSIVE; 260 | } 261 | return 0; 262 | } 263 | 264 | static int basedir_len(const char *path) 265 | { 266 | char *p = strrchr(path, '/'); 267 | if (p) 268 | return p - path + 1; 269 | return 0; 270 | } 271 | 272 | void cgit_print_blame(void) 273 | { 274 | const char *rev = ctx.qry.oid; 275 | struct object_id oid; 276 | struct commit *commit; 277 | struct pathspec_item path_items = { 278 | .match = ctx.qry.path, 279 | .len = ctx.qry.path ? strlen(ctx.qry.path) : 0 280 | }; 281 | struct pathspec paths = { 282 | .nr = 1, 283 | .items = &path_items 284 | }; 285 | struct walk_tree_context walk_tree_ctx = { 286 | .state = 0 287 | }; 288 | 289 | if (!rev) 290 | rev = ctx.qry.head; 291 | 292 | if (repo_get_oid(the_repository, rev, &oid)) { 293 | cgit_print_error_page(404, "Not found", 294 | "Invalid revision name: %s", rev); 295 | return; 296 | } 297 | commit = lookup_commit_reference(the_repository, &oid); 298 | if (!commit || repo_parse_commit(the_repository, commit)) { 299 | cgit_print_error_page(404, "Not found", 300 | "Invalid commit reference: %s", rev); 301 | return; 302 | } 303 | 304 | walk_tree_ctx.curr_rev = xstrdup(rev); 305 | walk_tree_ctx.match_baselen = (path_items.match) ? 306 | basedir_len(path_items.match) : -1; 307 | 308 | read_tree(the_repository, repo_get_commit_tree(the_repository, commit), 309 | &paths, walk_tree, &walk_tree_ctx); 310 | if (!walk_tree_ctx.state) 311 | cgit_print_error_page(404, "Not found", "Not found"); 312 | else if (walk_tree_ctx.state == 2) 313 | cgit_print_error_page(404, "No blame for folders", 314 | "Blame is not available for folders."); 315 | 316 | free(walk_tree_ctx.curr_rev); 317 | } 318 | -------------------------------------------------------------------------------- /ui-blame.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_BLAME_H 2 | #define UI_BLAME_H 3 | 4 | extern void cgit_print_blame(void); 5 | 6 | #endif /* UI_BLAME_H */ 7 | -------------------------------------------------------------------------------- /ui-blob.c: -------------------------------------------------------------------------------- 1 | /* ui-blob.c: show blob content 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-blob.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | struct walk_tree_context { 17 | const char *match_path; 18 | struct object_id *matched_oid; 19 | unsigned int found_path:1; 20 | unsigned int file_only:1; 21 | }; 22 | 23 | static int walk_tree(const struct object_id *oid, struct strbuf *base, 24 | const char *pathname, unsigned mode, void *cbdata) 25 | { 26 | struct walk_tree_context *walk_tree_ctx = cbdata; 27 | 28 | if (walk_tree_ctx->file_only && !S_ISREG(mode)) 29 | return READ_TREE_RECURSIVE; 30 | if (strncmp(base->buf, walk_tree_ctx->match_path, base->len) 31 | || strcmp(walk_tree_ctx->match_path + base->len, pathname)) 32 | return READ_TREE_RECURSIVE; 33 | oidcpy(walk_tree_ctx->matched_oid, oid); 34 | walk_tree_ctx->found_path = 1; 35 | return 0; 36 | } 37 | 38 | int cgit_ref_path_exists(const char *path, const char *ref, int file_only) 39 | { 40 | struct object_id oid; 41 | unsigned long size; 42 | struct pathspec_item path_items = { 43 | .match = xstrdup(path), 44 | .len = strlen(path) 45 | }; 46 | struct pathspec paths = { 47 | .nr = 1, 48 | .items = &path_items 49 | }; 50 | struct walk_tree_context walk_tree_ctx = { 51 | .match_path = path, 52 | .matched_oid = &oid, 53 | .found_path = 0, 54 | .file_only = file_only 55 | }; 56 | 57 | if (repo_get_oid(the_repository, ref, &oid)) 58 | goto done; 59 | if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT) 60 | goto done; 61 | read_tree(the_repository, 62 | repo_get_commit_tree(the_repository, lookup_commit_reference(the_repository, &oid)), 63 | &paths, walk_tree, &walk_tree_ctx); 64 | 65 | done: 66 | free(path_items.match); 67 | return walk_tree_ctx.found_path; 68 | } 69 | 70 | int cgit_print_file(char *path, const char *head, int file_only) 71 | { 72 | struct object_id oid; 73 | enum object_type type; 74 | char *buf; 75 | unsigned long size; 76 | struct commit *commit; 77 | struct pathspec_item path_items = { 78 | .match = path, 79 | .len = strlen(path) 80 | }; 81 | struct pathspec paths = { 82 | .nr = 1, 83 | .items = &path_items 84 | }; 85 | struct walk_tree_context walk_tree_ctx = { 86 | .match_path = path, 87 | .matched_oid = &oid, 88 | .found_path = 0, 89 | .file_only = file_only 90 | }; 91 | 92 | if (repo_get_oid(the_repository, head, &oid)) 93 | return -1; 94 | type = oid_object_info(the_repository, &oid, &size); 95 | if (type == OBJ_COMMIT) { 96 | commit = lookup_commit_reference(the_repository, &oid); 97 | read_tree(the_repository, repo_get_commit_tree(the_repository, commit), 98 | &paths, walk_tree, &walk_tree_ctx); 99 | if (!walk_tree_ctx.found_path) 100 | return -1; 101 | type = oid_object_info(the_repository, &oid, &size); 102 | } 103 | if (type == OBJ_BAD) 104 | return -1; 105 | buf = repo_read_object_file(the_repository, &oid, &type, &size); 106 | if (!buf) 107 | return -1; 108 | buf[size] = '\0'; 109 | html_raw(buf, size); 110 | free(buf); 111 | return 0; 112 | } 113 | 114 | void cgit_print_blob(const char *hex, char *path, const char *head, int file_only) 115 | { 116 | struct object_id oid; 117 | enum object_type type; 118 | char *buf; 119 | unsigned long size; 120 | struct commit *commit; 121 | struct pathspec_item path_items = { 122 | .match = path, 123 | .len = path ? strlen(path) : 0 124 | }; 125 | struct pathspec paths = { 126 | .nr = 1, 127 | .items = &path_items 128 | }; 129 | struct walk_tree_context walk_tree_ctx = { 130 | .match_path = path, 131 | .matched_oid = &oid, 132 | .found_path = 0, 133 | .file_only = file_only 134 | }; 135 | 136 | if (hex) { 137 | if (get_oid_hex(hex, &oid)) { 138 | cgit_print_error_page(400, "Bad request", 139 | "Bad hex value: %s", hex); 140 | return; 141 | } 142 | } else { 143 | if (repo_get_oid(the_repository, head, &oid)) { 144 | cgit_print_error_page(404, "Not found", 145 | "Bad ref: %s", head); 146 | return; 147 | } 148 | } 149 | 150 | type = oid_object_info(the_repository, &oid, &size); 151 | 152 | if ((!hex) && type == OBJ_COMMIT && path) { 153 | commit = lookup_commit_reference(the_repository, &oid); 154 | read_tree(the_repository, repo_get_commit_tree(the_repository, commit), 155 | &paths, walk_tree, &walk_tree_ctx); 156 | type = oid_object_info(the_repository, &oid, &size); 157 | } 158 | 159 | if (type == OBJ_BAD) { 160 | cgit_print_error_page(404, "Not found", 161 | "Bad object name: %s", hex); 162 | return; 163 | } 164 | 165 | buf = repo_read_object_file(the_repository, &oid, &type, &size); 166 | if (!buf) { 167 | cgit_print_error_page(500, "Internal server error", 168 | "Error reading object %s", hex); 169 | return; 170 | } 171 | 172 | buf[size] = '\0'; 173 | if (buffer_is_binary(buf, size)) 174 | ctx.page.mimetype = "application/octet-stream"; 175 | else 176 | ctx.page.mimetype = "text/plain"; 177 | ctx.page.filename = path; 178 | 179 | html("X-Content-Type-Options: nosniff\n"); 180 | html("Content-Security-Policy: default-src 'none'\n"); 181 | cgit_print_http_headers(); 182 | html_raw(buf, size); 183 | free(buf); 184 | } 185 | -------------------------------------------------------------------------------- /ui-blob.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_BLOB_H 2 | #define UI_BLOB_H 3 | 4 | extern int cgit_ref_path_exists(const char *path, const char *ref, int file_only); 5 | extern int cgit_print_file(char *path, const char *head, int file_only); 6 | extern void cgit_print_blob(const char *hex, char *path, const char *head, int file_only); 7 | 8 | #endif /* UI_BLOB_H */ 9 | -------------------------------------------------------------------------------- /ui-clone.c: -------------------------------------------------------------------------------- 1 | /* ui-clone.c: functions for http cloning, based on 2 | * git's http-backend.c by Shawn O. Pearce 3 | * 4 | * Copyright (C) 2006-2014 cgit Development Team 5 | * 6 | * Licensed under GNU General Public License v2 7 | * (see COPYING for full license text) 8 | */ 9 | 10 | #define USE_THE_REPOSITORY_VARIABLE 11 | 12 | #include "cgit.h" 13 | #include "ui-clone.h" 14 | #include "html.h" 15 | #include "ui-shared.h" 16 | #include "packfile.h" 17 | #include "object-store.h" 18 | 19 | static int print_ref_info(const char *refname, const struct object_id *oid, 20 | int flags, void *cb_data) 21 | { 22 | struct object *obj; 23 | 24 | if (!(obj = parse_object(the_repository, oid))) 25 | return 0; 26 | 27 | htmlf("%s\t%s\n", oid_to_hex(oid), refname); 28 | if (obj->type == OBJ_TAG) { 29 | if (!(obj = deref_tag(the_repository, obj, refname, 0))) 30 | return 0; 31 | htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), refname); 32 | } 33 | return 0; 34 | } 35 | 36 | static void print_pack_info(void) 37 | { 38 | struct packed_git *pack; 39 | char *offset; 40 | 41 | ctx.page.mimetype = "text/plain"; 42 | ctx.page.filename = "objects/info/packs"; 43 | cgit_print_http_headers(); 44 | reprepare_packed_git(the_repository); 45 | for (pack = get_packed_git(the_repository); pack; pack = pack->next) { 46 | if (pack->pack_local) { 47 | offset = strrchr(pack->pack_name, '/'); 48 | if (offset && offset[1] != '\0') 49 | ++offset; 50 | else 51 | offset = pack->pack_name; 52 | htmlf("P %s\n", offset); 53 | } 54 | } 55 | } 56 | 57 | static void send_file(const char *path) 58 | { 59 | struct stat st; 60 | 61 | if (stat(path, &st)) { 62 | switch (errno) { 63 | case ENOENT: 64 | cgit_print_error_page(404, "Not found", "Not found"); 65 | break; 66 | case EACCES: 67 | cgit_print_error_page(403, "Forbidden", "Forbidden"); 68 | break; 69 | default: 70 | cgit_print_error_page(400, "Bad request", "Bad request"); 71 | } 72 | return; 73 | } 74 | ctx.page.mimetype = "application/octet-stream"; 75 | ctx.page.filename = path; 76 | skip_prefix(path, ctx.repo->path, &ctx.page.filename); 77 | skip_prefix(ctx.page.filename, "/", &ctx.page.filename); 78 | cgit_print_http_headers(); 79 | html_include(path); 80 | } 81 | 82 | void cgit_clone_info(void) 83 | { 84 | if (!ctx.qry.path || strcmp(ctx.qry.path, "refs")) { 85 | cgit_print_error_page(400, "Bad request", "Bad request"); 86 | return; 87 | } 88 | 89 | ctx.page.mimetype = "text/plain"; 90 | ctx.page.filename = "info/refs"; 91 | cgit_print_http_headers(); 92 | refs_for_each_ref(get_main_ref_store(the_repository), 93 | print_ref_info, NULL); 94 | } 95 | 96 | void cgit_clone_objects(void) 97 | { 98 | char *p; 99 | 100 | if (!ctx.qry.path) 101 | goto err; 102 | 103 | if (!strcmp(ctx.qry.path, "info/packs")) { 104 | print_pack_info(); 105 | return; 106 | } 107 | 108 | /* Avoid directory traversal by forbidding "..", but also work around 109 | * other funny business by just specifying a fairly strict format. For 110 | * example, now we don't have to stress out about the Cygwin port. 111 | */ 112 | for (p = ctx.qry.path; *p; ++p) { 113 | if (*p == '.' && *(p + 1) == '.') 114 | goto err; 115 | if (!isalnum(*p) && *p != '/' && *p != '.' && *p != '-') 116 | goto err; 117 | } 118 | 119 | send_file(git_path("objects/%s", ctx.qry.path)); 120 | return; 121 | 122 | err: 123 | cgit_print_error_page(400, "Bad request", "Bad request"); 124 | } 125 | 126 | void cgit_clone_head(void) 127 | { 128 | send_file(git_path("%s", "HEAD")); 129 | } 130 | -------------------------------------------------------------------------------- /ui-clone.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_CLONE_H 2 | #define UI_CLONE_H 3 | 4 | void cgit_clone_info(void); 5 | void cgit_clone_objects(void); 6 | void cgit_clone_head(void); 7 | 8 | #endif /* UI_CLONE_H */ 9 | -------------------------------------------------------------------------------- /ui-commit.c: -------------------------------------------------------------------------------- 1 | /* ui-commit.c: generate commit view 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-commit.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | #include "ui-diff.h" 16 | #include "ui-log.h" 17 | 18 | void cgit_print_commit(char *hex, const char *prefix) 19 | { 20 | struct commit *commit, *parent; 21 | struct commitinfo *info, *parent_info; 22 | struct commit_list *p; 23 | struct strbuf notes = STRBUF_INIT; 24 | struct object_id oid; 25 | char *tmp, *tmp2; 26 | int parents = 0; 27 | 28 | if (!hex) 29 | hex = ctx.qry.head; 30 | 31 | if (repo_get_oid(the_repository, hex, &oid)) { 32 | cgit_print_error_page(400, "Bad request", 33 | "Bad object id: %s", hex); 34 | return; 35 | } 36 | commit = lookup_commit_reference(the_repository, &oid); 37 | if (!commit) { 38 | cgit_print_error_page(404, "Not found", 39 | "Bad commit reference: %s", hex); 40 | return; 41 | } 42 | info = cgit_parse_commit(commit); 43 | 44 | format_display_notes(&oid, ¬es, PAGE_ENCODING, 1); 45 | 46 | load_ref_decorations(NULL, DECORATE_FULL_REFS); 47 | 48 | ctx.page.title = fmtalloc("%s - %s", info->subject, ctx.page.title); 49 | cgit_print_layout_start(); 50 | cgit_print_diff_ctrls(); 51 | html("\n"); 52 | html("\n"); 64 | html("\n"); 76 | html("\n"); 82 | html("\n"); 92 | for (p = commit->parents; p; p = p->next) { 93 | parent = lookup_commit_reference(the_repository, &p->item->object.oid); 94 | if (!parent) { 95 | html(""); 98 | continue; 99 | } 100 | html("" 101 | ""); 112 | parents++; 113 | } 114 | if (ctx.repo->snapshots) { 115 | html(""); 118 | } 119 | html("
author"); 53 | cgit_open_filter(ctx.repo->email_filter, info->author_email, "commit"); 54 | html_txt(info->author); 55 | if (!ctx.cfg.noplainemail) { 56 | html(" "); 57 | html_txt(info->author_email); 58 | } 59 | cgit_close_filter(ctx.repo->email_filter); 60 | html(""); 61 | html_txt(show_date(info->author_date, info->author_tz, 62 | cgit_date_mode(DATE_ISO8601))); 63 | html("
committer"); 65 | cgit_open_filter(ctx.repo->email_filter, info->committer_email, "commit"); 66 | html_txt(info->committer); 67 | if (!ctx.cfg.noplainemail) { 68 | html(" "); 69 | html_txt(info->committer_email); 70 | } 71 | cgit_close_filter(ctx.repo->email_filter); 72 | html(""); 73 | html_txt(show_date(info->committer_date, info->committer_tz, 74 | cgit_date_mode(DATE_ISO8601))); 75 | html("
commit"); 77 | tmp = oid_to_hex(&commit->object.oid); 78 | cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix); 79 | html(" ("); 80 | cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix); 81 | html(")
tree"); 83 | tmp = xstrdup(hex); 84 | cgit_tree_link(oid_to_hex(get_commit_tree_oid(commit)), NULL, NULL, 85 | ctx.qry.head, tmp, NULL); 86 | if (prefix) { 87 | html(" /"); 88 | cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix); 89 | } 90 | free(tmp); 91 | html("
"); 96 | cgit_print_error("Error reading parent commit"); 97 | html("
parent"); 102 | tmp = tmp2 = oid_to_hex(&p->item->object.oid); 103 | if (ctx.repo->enable_subject_links) { 104 | parent_info = cgit_parse_commit(parent); 105 | tmp2 = parent_info->subject; 106 | } 107 | cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix); 108 | html(" ("); 109 | cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 110 | oid_to_hex(&p->item->object.oid), prefix); 111 | html(")
download"); 116 | cgit_print_snapshot_links(ctx.repo, hex, "
"); 117 | html("
\n"); 120 | html("
"); 121 | cgit_open_filter(ctx.repo->commit_filter); 122 | html_txt(info->subject); 123 | cgit_close_filter(ctx.repo->commit_filter); 124 | show_commit_decorations(commit); 125 | html("
"); 126 | html("
"); 127 | cgit_open_filter(ctx.repo->commit_filter); 128 | html_txt(info->msg); 129 | cgit_close_filter(ctx.repo->commit_filter); 130 | html("
"); 131 | if (notes.len != 0) { 132 | html("
Notes
"); 133 | html("
"); 134 | cgit_open_filter(ctx.repo->commit_filter); 135 | html_txt(notes.buf); 136 | cgit_close_filter(ctx.repo->commit_filter); 137 | html("
"); 138 | html(""); 139 | } 140 | if (parents < 3) { 141 | if (parents) 142 | tmp = oid_to_hex(&commit->parents->item->object.oid); 143 | else 144 | tmp = NULL; 145 | cgit_print_diff(ctx.qry.oid, tmp, prefix, 0, 0); 146 | } 147 | strbuf_release(¬es); 148 | cgit_free_commitinfo(info); 149 | cgit_print_layout_end(); 150 | } 151 | -------------------------------------------------------------------------------- /ui-commit.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_COMMIT_H 2 | #define UI_COMMIT_H 3 | 4 | extern void cgit_print_commit(char *hex, const char *prefix); 5 | 6 | #endif /* UI_COMMIT_H */ 7 | -------------------------------------------------------------------------------- /ui-diff.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_DIFF_H 2 | #define UI_DIFF_H 3 | 4 | extern void cgit_print_diff_ctrls(void); 5 | 6 | extern void cgit_print_diff(const char *new_hex, const char *old_hex, 7 | const char *prefix, int show_ctrls, int raw); 8 | 9 | extern struct diff_filespec *cgit_get_current_old_file(void); 10 | extern struct diff_filespec *cgit_get_current_new_file(void); 11 | 12 | extern struct object_id old_rev_oid[1]; 13 | extern struct object_id new_rev_oid[1]; 14 | 15 | #endif /* UI_DIFF_H */ 16 | -------------------------------------------------------------------------------- /ui-log.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_LOG_H 2 | #define UI_LOG_H 3 | 4 | extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, 5 | char *pattern, const char *path, int pager, 6 | int commit_graph, int commit_sort); 7 | extern void show_commit_decorations(struct commit *commit); 8 | 9 | #endif /* UI_LOG_H */ 10 | -------------------------------------------------------------------------------- /ui-patch.c: -------------------------------------------------------------------------------- 1 | /* ui-patch.c: generate patch view 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-patch.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | /* two commit hashes with two dots in between and termination */ 17 | #define REV_RANGE_LEN 2 * GIT_MAX_HEXSZ + 3 18 | 19 | void cgit_print_patch(const char *new_rev, const char *old_rev, 20 | const char *prefix) 21 | { 22 | struct rev_info rev; 23 | struct commit *commit; 24 | struct object_id new_rev_oid, old_rev_oid; 25 | char rev_range[REV_RANGE_LEN]; 26 | const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix, NULL }; 27 | int rev_argc = ARRAY_SIZE(rev_argv) - 1; 28 | char *patchname; 29 | 30 | if (!prefix) 31 | rev_argc--; 32 | 33 | if (!new_rev) 34 | new_rev = ctx.qry.head; 35 | 36 | if (repo_get_oid(the_repository, new_rev, &new_rev_oid)) { 37 | cgit_print_error_page(404, "Not found", 38 | "Bad object id: %s", new_rev); 39 | return; 40 | } 41 | commit = lookup_commit_reference(the_repository, &new_rev_oid); 42 | if (!commit) { 43 | cgit_print_error_page(404, "Not found", 44 | "Bad commit reference: %s", new_rev); 45 | return; 46 | } 47 | 48 | if (old_rev) { 49 | if (repo_get_oid(the_repository, old_rev, &old_rev_oid)) { 50 | cgit_print_error_page(404, "Not found", 51 | "Bad object id: %s", old_rev); 52 | return; 53 | } 54 | if (!lookup_commit_reference(the_repository, &old_rev_oid)) { 55 | cgit_print_error_page(404, "Not found", 56 | "Bad commit reference: %s", old_rev); 57 | return; 58 | } 59 | } else if (commit->parents && commit->parents->item) { 60 | oidcpy(&old_rev_oid, &commit->parents->item->object.oid); 61 | } else { 62 | oidclr(&old_rev_oid, the_repository->hash_algo); 63 | } 64 | 65 | if (is_null_oid(&old_rev_oid)) { 66 | memcpy(rev_range, oid_to_hex(&new_rev_oid), the_hash_algo->hexsz + 1); 67 | } else { 68 | xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_rev_oid), 69 | oid_to_hex(&new_rev_oid)); 70 | } 71 | 72 | patchname = fmt("%s.patch", rev_range); 73 | ctx.page.mimetype = "text/plain"; 74 | ctx.page.filename = patchname; 75 | cgit_print_http_headers(); 76 | 77 | if (ctx.cfg.noplainemail) { 78 | rev_argv[2] = "--format=format:From %H Mon Sep 17 00:00:00 " 79 | "2001%nFrom: %an%nDate: %aD%n%w(78,0,1)Subject: " 80 | "%s%n%n%w(0)%b"; 81 | } 82 | 83 | repo_init_revisions(the_repository, &rev, NULL); 84 | rev.abbrev = DEFAULT_ABBREV; 85 | rev.verbose_header = 1; 86 | rev.diff = 1; 87 | rev.show_root_diff = 1; 88 | rev.max_parents = 1; 89 | rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT | 90 | DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY; 91 | if (prefix) 92 | rev.diffopt.stat_sep = fmt("(limited to '%s')\n\n", prefix); 93 | setup_revisions(rev_argc, rev_argv, &rev, NULL); 94 | prepare_revision_walk(&rev); 95 | 96 | while ((commit = get_revision(&rev)) != NULL) { 97 | log_tree_commit(&rev, commit); 98 | printf("-- \ncgit %s\n\n", cgit_version); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ui-patch.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_PATCH_H 2 | #define UI_PATCH_H 3 | 4 | extern void cgit_print_patch(const char *new_rev, const char *old_rev, 5 | const char *prefix); 6 | 7 | #endif /* UI_PATCH_H */ 8 | -------------------------------------------------------------------------------- /ui-plain.c: -------------------------------------------------------------------------------- 1 | /* ui-plain.c: functions for output of plain blobs by path 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-plain.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | struct walk_tree_context { 17 | int match_baselen; 18 | int match; 19 | }; 20 | 21 | static int print_object(const struct object_id *oid, const char *path) 22 | { 23 | enum object_type type; 24 | char *buf, *mimetype; 25 | unsigned long size; 26 | 27 | type = oid_object_info(the_repository, oid, &size); 28 | if (type == OBJ_BAD) { 29 | cgit_print_error_page(404, "Not found", "Not found"); 30 | return 0; 31 | } 32 | 33 | buf = repo_read_object_file(the_repository, oid, &type, &size); 34 | if (!buf) { 35 | cgit_print_error_page(404, "Not found", "Not found"); 36 | return 0; 37 | } 38 | 39 | mimetype = get_mimetype_for_filename(path); 40 | ctx.page.mimetype = mimetype; 41 | 42 | if (!ctx.repo->enable_html_serving) { 43 | html("X-Content-Type-Options: nosniff\n"); 44 | html("Content-Security-Policy: default-src 'none'\n"); 45 | if (mimetype) { 46 | /* Built-in white list allows PDF and everything that isn't text/ and application/ */ 47 | if ((!strncmp(mimetype, "text/", 5) || !strncmp(mimetype, "application/", 12)) && strcmp(mimetype, "application/pdf")) 48 | ctx.page.mimetype = NULL; 49 | } 50 | } 51 | 52 | if (!ctx.page.mimetype) { 53 | if (buffer_is_binary(buf, size)) { 54 | ctx.page.mimetype = "application/octet-stream"; 55 | ctx.page.charset = NULL; 56 | } else { 57 | ctx.page.mimetype = "text/plain"; 58 | } 59 | } 60 | ctx.page.filename = path; 61 | ctx.page.size = size; 62 | ctx.page.etag = oid_to_hex(oid); 63 | cgit_print_http_headers(); 64 | html_raw(buf, size); 65 | free(mimetype); 66 | free(buf); 67 | return 1; 68 | } 69 | 70 | static char *buildpath(const char *base, int baselen, const char *path) 71 | { 72 | if (path[0]) 73 | return fmtalloc("%.*s%s/", baselen, base, path); 74 | else 75 | return fmtalloc("%.*s/", baselen, base); 76 | } 77 | 78 | static void print_dir(const struct object_id *oid, const char *base, 79 | int baselen, const char *path) 80 | { 81 | char *fullpath, *slash; 82 | size_t len; 83 | 84 | fullpath = buildpath(base, baselen, path); 85 | slash = (fullpath[0] == '/' ? "" : "/"); 86 | ctx.page.etag = oid_to_hex(oid); 87 | cgit_print_http_headers(); 88 | htmlf("%s", slash); 89 | html_txt(fullpath); 90 | htmlf("\n\n

%s", slash); 91 | html_txt(fullpath); 92 | html("

\n
    \n"); 93 | len = strlen(fullpath); 94 | if (len > 1) { 95 | fullpath[len - 1] = 0; 96 | slash = strrchr(fullpath, '/'); 97 | if (slash) 98 | *(slash + 1) = 0; 99 | else { 100 | free(fullpath); 101 | fullpath = NULL; 102 | } 103 | html("
  • "); 104 | cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.oid, 105 | fullpath); 106 | html("
  • \n"); 107 | } 108 | free(fullpath); 109 | } 110 | 111 | static void print_dir_entry(const struct object_id *oid, const char *base, 112 | int baselen, const char *path, unsigned mode) 113 | { 114 | char *fullpath; 115 | 116 | fullpath = buildpath(base, baselen, path); 117 | if (!S_ISDIR(mode) && !S_ISGITLINK(mode)) 118 | fullpath[strlen(fullpath) - 1] = 0; 119 | html("
  • "); 120 | if (S_ISGITLINK(mode)) { 121 | cgit_submodule_link(NULL, fullpath, oid_to_hex(oid)); 122 | } else 123 | cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.oid, 124 | fullpath); 125 | html("
  • \n"); 126 | free(fullpath); 127 | } 128 | 129 | static void print_dir_tail(void) 130 | { 131 | html("
\n\n"); 132 | } 133 | 134 | static int walk_tree(const struct object_id *oid, struct strbuf *base, 135 | const char *pathname, unsigned mode, void *cbdata) 136 | { 137 | struct walk_tree_context *walk_tree_ctx = cbdata; 138 | 139 | if (base->len == walk_tree_ctx->match_baselen) { 140 | if (S_ISREG(mode) || S_ISLNK(mode)) { 141 | if (print_object(oid, pathname)) 142 | walk_tree_ctx->match = 1; 143 | } else if (S_ISDIR(mode)) { 144 | print_dir(oid, base->buf, base->len, pathname); 145 | walk_tree_ctx->match = 2; 146 | return READ_TREE_RECURSIVE; 147 | } 148 | } else if (base->len < INT_MAX && (int)base->len > walk_tree_ctx->match_baselen) { 149 | print_dir_entry(oid, base->buf, base->len, pathname, mode); 150 | walk_tree_ctx->match = 2; 151 | } else if (S_ISDIR(mode)) { 152 | return READ_TREE_RECURSIVE; 153 | } 154 | 155 | return 0; 156 | } 157 | 158 | static int basedir_len(const char *path) 159 | { 160 | char *p = strrchr(path, '/'); 161 | if (p) 162 | return p - path + 1; 163 | return 0; 164 | } 165 | 166 | void cgit_print_plain(void) 167 | { 168 | const char *rev = ctx.qry.oid; 169 | struct object_id oid; 170 | struct commit *commit; 171 | struct pathspec_item path_items = { 172 | .match = ctx.qry.path, 173 | .len = ctx.qry.path ? strlen(ctx.qry.path) : 0 174 | }; 175 | struct pathspec paths = { 176 | .nr = 1, 177 | .items = &path_items 178 | }; 179 | struct walk_tree_context walk_tree_ctx = { 180 | .match = 0 181 | }; 182 | 183 | if (!rev) 184 | rev = ctx.qry.head; 185 | 186 | if (repo_get_oid(the_repository, rev, &oid)) { 187 | cgit_print_error_page(404, "Not found", "Not found"); 188 | return; 189 | } 190 | commit = lookup_commit_reference(the_repository, &oid); 191 | if (!commit || repo_parse_commit(the_repository, commit)) { 192 | cgit_print_error_page(404, "Not found", "Not found"); 193 | return; 194 | } 195 | if (!path_items.match) { 196 | path_items.match = ""; 197 | walk_tree_ctx.match_baselen = -1; 198 | print_dir(get_commit_tree_oid(commit), "", 0, ""); 199 | walk_tree_ctx.match = 2; 200 | } 201 | else 202 | walk_tree_ctx.match_baselen = basedir_len(path_items.match); 203 | read_tree(the_repository, repo_get_commit_tree(the_repository, commit), 204 | &paths, walk_tree, &walk_tree_ctx); 205 | if (!walk_tree_ctx.match) 206 | cgit_print_error_page(404, "Not found", "Not found"); 207 | else if (walk_tree_ctx.match == 2) 208 | print_dir_tail(); 209 | } 210 | -------------------------------------------------------------------------------- /ui-plain.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_PLAIN_H 2 | #define UI_PLAIN_H 3 | 4 | extern void cgit_print_plain(void); 5 | 6 | #endif /* UI_PLAIN_H */ 7 | -------------------------------------------------------------------------------- /ui-refs.c: -------------------------------------------------------------------------------- 1 | /* ui-refs.c: browse symbolic refs 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-refs.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | static inline int cmp_age(int age1, int age2) 17 | { 18 | /* age1 and age2 are assumed to be non-negative */ 19 | return age2 - age1; 20 | } 21 | 22 | static int cmp_ref_name(const void *a, const void *b) 23 | { 24 | struct refinfo *r1 = *(struct refinfo **)a; 25 | struct refinfo *r2 = *(struct refinfo **)b; 26 | 27 | return strcmp(r1->refname, r2->refname); 28 | } 29 | 30 | static int cmp_branch_age(const void *a, const void *b) 31 | { 32 | struct refinfo *r1 = *(struct refinfo **)a; 33 | struct refinfo *r2 = *(struct refinfo **)b; 34 | 35 | return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 36 | } 37 | 38 | static int get_ref_age(struct refinfo *ref) 39 | { 40 | if (!ref->object) 41 | return 0; 42 | switch (ref->object->type) { 43 | case OBJ_TAG: 44 | return ref->tag ? ref->tag->tagger_date : 0; 45 | case OBJ_COMMIT: 46 | return ref->commit ? ref->commit->committer_date : 0; 47 | } 48 | return 0; 49 | } 50 | 51 | static int cmp_tag_age(const void *a, const void *b) 52 | { 53 | struct refinfo *r1 = *(struct refinfo **)a; 54 | struct refinfo *r2 = *(struct refinfo **)b; 55 | 56 | return cmp_age(get_ref_age(r1), get_ref_age(r2)); 57 | } 58 | 59 | static int print_branch(struct refinfo *ref) 60 | { 61 | struct commitinfo *info = ref->commit; 62 | char *name = (char *)ref->refname; 63 | 64 | if (!info) 65 | return 1; 66 | html(""); 67 | cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 68 | ctx.qry.showmsg, 0); 69 | html(""); 70 | 71 | if (ref->object->type == OBJ_COMMIT) { 72 | cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL); 73 | html(""); 74 | cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs"); 75 | html_txt(info->author); 76 | cgit_close_filter(ctx.repo->email_filter); 77 | html(""); 78 | cgit_print_age(info->committer_date, info->committer_tz, -1); 79 | } else { 80 | html(""); 81 | cgit_object_link(ref->object); 82 | } 83 | html("\n"); 84 | return 0; 85 | } 86 | 87 | static void print_tag_header(void) 88 | { 89 | html("Tag" 90 | "Download" 91 | "Author" 92 | "Age\n"); 93 | } 94 | 95 | static int print_tag(struct refinfo *ref) 96 | { 97 | struct tag *tag = NULL; 98 | struct taginfo *info = NULL; 99 | char *name = (char *)ref->refname; 100 | struct object *obj = ref->object; 101 | 102 | if (obj->type == OBJ_TAG) { 103 | tag = (struct tag *)obj; 104 | obj = tag->tagged; 105 | info = ref->tag; 106 | if (!info) 107 | return 1; 108 | } 109 | 110 | html(""); 111 | cgit_tag_link(name, NULL, NULL, name); 112 | html(""); 113 | if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT)) 114 | cgit_print_snapshot_links(ctx.repo, name, "  "); 115 | else 116 | cgit_object_link(obj); 117 | html(""); 118 | if (info) { 119 | if (info->tagger) { 120 | cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "refs"); 121 | html_txt(info->tagger); 122 | cgit_close_filter(ctx.repo->email_filter); 123 | } 124 | } else if (ref->object->type == OBJ_COMMIT) { 125 | cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs"); 126 | html_txt(ref->commit->author); 127 | cgit_close_filter(ctx.repo->email_filter); 128 | } 129 | html(""); 130 | if (info) { 131 | if (info->tagger_date > 0) 132 | cgit_print_age(info->tagger_date, info->tagger_tz, -1); 133 | } else if (ref->object->type == OBJ_COMMIT) { 134 | cgit_print_age(ref->commit->commit->date, 0, -1); 135 | } 136 | html("\n"); 137 | 138 | return 0; 139 | } 140 | 141 | static void print_refs_link(const char *path) 142 | { 143 | html(""); 144 | cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 145 | html(""); 146 | } 147 | 148 | void cgit_print_branches(int maxcount) 149 | { 150 | struct reflist list; 151 | int i; 152 | 153 | html("Branch" 154 | "Commit message" 155 | "Author" 156 | "Age\n"); 157 | 158 | list.refs = NULL; 159 | list.alloc = list.count = 0; 160 | refs_for_each_branch_ref(get_main_ref_store(the_repository), 161 | cgit_refs_cb, &list); 162 | if (ctx.repo->enable_remote_branches) 163 | refs_for_each_remote_ref(get_main_ref_store(the_repository), 164 | cgit_refs_cb, &list); 165 | 166 | if (maxcount == 0 || maxcount > list.count) 167 | maxcount = list.count; 168 | 169 | qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 170 | if (ctx.repo->branch_sort == 0) 171 | qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 172 | 173 | for (i = 0; i < maxcount; i++) 174 | print_branch(list.refs[i]); 175 | 176 | if (maxcount < list.count) 177 | print_refs_link("heads"); 178 | 179 | cgit_free_reflist_inner(&list); 180 | } 181 | 182 | void cgit_print_tags(int maxcount) 183 | { 184 | struct reflist list; 185 | int i; 186 | 187 | list.refs = NULL; 188 | list.alloc = list.count = 0; 189 | refs_for_each_tag_ref(get_main_ref_store(the_repository), 190 | cgit_refs_cb, &list); 191 | if (list.count == 0) 192 | return; 193 | qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 194 | if (!maxcount) 195 | maxcount = list.count; 196 | else if (maxcount > list.count) 197 | maxcount = list.count; 198 | print_tag_header(); 199 | for (i = 0; i < maxcount; i++) 200 | print_tag(list.refs[i]); 201 | 202 | if (maxcount < list.count) 203 | print_refs_link("tags"); 204 | 205 | cgit_free_reflist_inner(&list); 206 | } 207 | 208 | void cgit_print_refs(void) 209 | { 210 | cgit_print_layout_start(); 211 | html(""); 212 | 213 | if (ctx.qry.path && starts_with(ctx.qry.path, "heads")) 214 | cgit_print_branches(0); 215 | else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) 216 | cgit_print_tags(0); 217 | else { 218 | cgit_print_branches(0); 219 | html(""); 220 | cgit_print_tags(0); 221 | } 222 | html("
 
"); 223 | cgit_print_layout_end(); 224 | } 225 | -------------------------------------------------------------------------------- /ui-refs.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_REFS_H 2 | #define UI_REFS_H 3 | 4 | extern void cgit_print_branches(int maxcount); 5 | extern void cgit_print_tags(int maxcount); 6 | extern void cgit_print_refs(void); 7 | 8 | #endif /* UI_REFS_H */ 9 | -------------------------------------------------------------------------------- /ui-repolist.c: -------------------------------------------------------------------------------- 1 | /* ui-repolist.c: functions for generating the repolist page 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include "cgit.h" 10 | #include "ui-repolist.h" 11 | #include "html.h" 12 | #include "ui-shared.h" 13 | 14 | static time_t read_agefile(const char *path) 15 | { 16 | time_t result; 17 | size_t size; 18 | char *buf = NULL; 19 | struct strbuf date_buf = STRBUF_INIT; 20 | 21 | if (readfile(path, &buf, &size)) { 22 | free(buf); 23 | return 0; 24 | } 25 | 26 | if (parse_date(buf, &date_buf) == 0) 27 | result = strtoul(date_buf.buf, NULL, 10); 28 | else 29 | result = 0; 30 | free(buf); 31 | strbuf_release(&date_buf); 32 | return result; 33 | } 34 | 35 | static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) 36 | { 37 | struct strbuf path = STRBUF_INIT; 38 | struct stat s; 39 | struct cgit_repo *r = (struct cgit_repo *)repo; 40 | 41 | if (repo->mtime != -1) { 42 | *mtime = repo->mtime; 43 | return 1; 44 | } 45 | strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile); 46 | if (stat(path.buf, &s) == 0) { 47 | *mtime = read_agefile(path.buf); 48 | if (*mtime) { 49 | r->mtime = *mtime; 50 | goto end; 51 | } 52 | } 53 | 54 | strbuf_reset(&path); 55 | strbuf_addf(&path, "%s/refs/heads/%s", repo->path, 56 | repo->defbranch ? repo->defbranch : "master"); 57 | if (stat(path.buf, &s) == 0) { 58 | *mtime = s.st_mtime; 59 | r->mtime = *mtime; 60 | goto end; 61 | } 62 | 63 | strbuf_reset(&path); 64 | strbuf_addf(&path, "%s/%s", repo->path, "packed-refs"); 65 | if (stat(path.buf, &s) == 0) { 66 | *mtime = s.st_mtime; 67 | r->mtime = *mtime; 68 | goto end; 69 | } 70 | 71 | *mtime = 0; 72 | r->mtime = *mtime; 73 | end: 74 | strbuf_release(&path); 75 | return (r->mtime != 0); 76 | } 77 | 78 | static void print_modtime(struct cgit_repo *repo) 79 | { 80 | time_t t; 81 | if (get_repo_modtime(repo, &t)) 82 | cgit_print_age(t, 0, -1); 83 | } 84 | 85 | static int is_match(struct cgit_repo *repo) 86 | { 87 | if (!ctx.qry.search) 88 | return 1; 89 | if (repo->url && strcasestr(repo->url, ctx.qry.search)) 90 | return 1; 91 | if (repo->name && strcasestr(repo->name, ctx.qry.search)) 92 | return 1; 93 | if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) 94 | return 1; 95 | if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) 96 | return 1; 97 | return 0; 98 | } 99 | 100 | static int is_in_url(struct cgit_repo *repo) 101 | { 102 | if (!ctx.qry.url) 103 | return 1; 104 | if (repo->url && starts_with(repo->url, ctx.qry.url)) 105 | return 1; 106 | return 0; 107 | } 108 | 109 | static int is_visible(struct cgit_repo *repo) 110 | { 111 | if (repo->hide || repo->ignore) 112 | return 0; 113 | if (!(is_match(repo) && is_in_url(repo))) 114 | return 0; 115 | return 1; 116 | } 117 | 118 | static int any_repos_visible(void) 119 | { 120 | int i; 121 | 122 | for (i = 0; i < cgit_repolist.count; i++) { 123 | if (is_visible(&cgit_repolist.repos[i])) 124 | return 1; 125 | } 126 | return 0; 127 | } 128 | 129 | static void print_sort_header(const char *title, const char *sort) 130 | { 131 | char *currenturl = cgit_currenturl(); 132 | html("%s", title); 140 | free(currenturl); 141 | } 142 | 143 | static void print_header(void) 144 | { 145 | html(""); 146 | print_sort_header("Name", "name"); 147 | print_sort_header("Description", "desc"); 148 | if (ctx.cfg.enable_index_owner) 149 | print_sort_header("Owner", "owner"); 150 | print_sort_header("Idle", "idle"); 151 | if (ctx.cfg.enable_index_links) 152 | html("Links"); 153 | html("\n"); 154 | } 155 | 156 | 157 | static void print_pager(int items, int pagelen, char *search, char *sort) 158 | { 159 | int i, ofs; 160 | char *class = NULL; 161 | html("
    "); 162 | for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { 163 | class = (ctx.qry.ofs == ofs) ? "current" : NULL; 164 | html("
  • "); 165 | cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1), 166 | class, search, sort, ofs, 0); 167 | html("
  • "); 168 | } 169 | html("
"); 170 | } 171 | 172 | static int cmp(const char *s1, const char *s2) 173 | { 174 | if (s1 && s2) { 175 | if (ctx.cfg.case_sensitive_sort) 176 | return strcmp(s1, s2); 177 | else 178 | return strcasecmp(s1, s2); 179 | } 180 | if (s1 && !s2) 181 | return -1; 182 | if (s2 && !s1) 183 | return 1; 184 | return 0; 185 | } 186 | 187 | static int sort_name(const void *a, const void *b) 188 | { 189 | const struct cgit_repo *r1 = a; 190 | const struct cgit_repo *r2 = b; 191 | 192 | return cmp(r1->name, r2->name); 193 | } 194 | 195 | static int sort_desc(const void *a, const void *b) 196 | { 197 | const struct cgit_repo *r1 = a; 198 | const struct cgit_repo *r2 = b; 199 | 200 | return cmp(r1->desc, r2->desc); 201 | } 202 | 203 | static int sort_owner(const void *a, const void *b) 204 | { 205 | const struct cgit_repo *r1 = a; 206 | const struct cgit_repo *r2 = b; 207 | 208 | return cmp(r1->owner, r2->owner); 209 | } 210 | 211 | static int sort_idle(const void *a, const void *b) 212 | { 213 | const struct cgit_repo *r1 = a; 214 | const struct cgit_repo *r2 = b; 215 | time_t t1, t2; 216 | 217 | t1 = t2 = 0; 218 | get_repo_modtime(r1, &t1); 219 | get_repo_modtime(r2, &t2); 220 | return t2 - t1; 221 | } 222 | 223 | static int sort_section(const void *a, const void *b) 224 | { 225 | const struct cgit_repo *r1 = a; 226 | const struct cgit_repo *r2 = b; 227 | int result; 228 | 229 | result = cmp(r1->section, r2->section); 230 | if (!result) { 231 | if (!strcmp(ctx.cfg.repository_sort, "age")) 232 | result = sort_idle(r1, r2); 233 | if (!result) 234 | result = cmp(r1->name, r2->name); 235 | } 236 | return result; 237 | } 238 | 239 | struct sortcolumn { 240 | const char *name; 241 | int (*fn)(const void *a, const void *b); 242 | }; 243 | 244 | static const struct sortcolumn sortcolumn[] = { 245 | {"section", sort_section}, 246 | {"name", sort_name}, 247 | {"desc", sort_desc}, 248 | {"owner", sort_owner}, 249 | {"idle", sort_idle}, 250 | {NULL, NULL} 251 | }; 252 | 253 | static int sort_repolist(char *field) 254 | { 255 | const struct sortcolumn *column; 256 | 257 | for (column = &sortcolumn[0]; column->name; column++) { 258 | if (strcmp(field, column->name)) 259 | continue; 260 | qsort(cgit_repolist.repos, cgit_repolist.count, 261 | sizeof(struct cgit_repo), column->fn); 262 | return 1; 263 | } 264 | return 0; 265 | } 266 | 267 | 268 | void cgit_print_repolist(void) 269 | { 270 | int i, columns = 3, hits = 0, header = 0; 271 | char *last_section = NULL; 272 | char *section; 273 | char *repourl; 274 | int sorted = 0; 275 | 276 | if (!any_repos_visible()) { 277 | cgit_print_error_page(404, "Not found", "No repositories found"); 278 | return; 279 | } 280 | 281 | if (ctx.cfg.enable_index_links) 282 | ++columns; 283 | if (ctx.cfg.enable_index_owner) 284 | ++columns; 285 | 286 | ctx.page.title = ctx.cfg.root_title; 287 | cgit_print_http_headers(); 288 | cgit_print_docstart(); 289 | cgit_print_pageheader(); 290 | 291 | if (ctx.qry.sort) 292 | sorted = sort_repolist(ctx.qry.sort); 293 | else if (ctx.cfg.section_sort) 294 | sort_repolist("section"); 295 | 296 | html(""); 297 | for (i = 0; i < cgit_repolist.count; i++) { 298 | ctx.repo = &cgit_repolist.repos[i]; 299 | if (!is_visible(ctx.repo)) 300 | continue; 301 | hits++; 302 | if (hits <= ctx.qry.ofs) 303 | continue; 304 | if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 305 | continue; 306 | if (!header++) 307 | print_header(); 308 | section = ctx.repo->section; 309 | if (section && !strcmp(section, "")) 310 | section = NULL; 311 | if (!sorted && 312 | ((last_section == NULL && section != NULL) || 313 | (last_section != NULL && section == NULL) || 314 | (last_section != NULL && section != NULL && 315 | strcmp(section, last_section)))) { 316 | htmlf(""); 320 | last_section = section; 321 | } 322 | htmlf(""); 353 | if (ctx.cfg.enable_index_links) { 354 | html(""); 360 | } 361 | html("\n"); 362 | } 363 | html("
", 317 | columns); 318 | html_txt(section); 319 | html("
", 323 | !sorted && section ? "sublevel-repo" : "toplevel-repo"); 324 | cgit_summary_link(ctx.repo->name, NULL, NULL, NULL); 325 | html(""); 326 | repourl = cgit_repourl(ctx.repo->url); 327 | html_link_open(repourl, NULL, NULL); 328 | free(repourl); 329 | if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0) 330 | html("..."); 331 | html_link_close(); 332 | html(""); 333 | if (ctx.cfg.enable_index_owner) { 334 | if (ctx.repo->owner_filter) { 335 | cgit_open_filter(ctx.repo->owner_filter); 336 | html_txt(ctx.repo->owner); 337 | cgit_close_filter(ctx.repo->owner_filter); 338 | } else { 339 | char *currenturl = cgit_currenturl(); 340 | html(""); 345 | html_txt(ctx.repo->owner); 346 | html(""); 347 | free(currenturl); 348 | } 349 | html(""); 350 | } 351 | print_modtime(ctx.repo); 352 | html(""); 355 | cgit_summary_link("summary", NULL, "button", NULL); 356 | cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 357 | 0, NULL, NULL, ctx.qry.showmsg, 0); 358 | cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 359 | html("
"); 364 | if (hits > ctx.cfg.max_repo_count) 365 | print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort); 366 | cgit_print_docend(); 367 | } 368 | 369 | void cgit_print_site_readme(void) 370 | { 371 | cgit_print_layout_start(); 372 | if (!ctx.cfg.root_readme) 373 | goto done; 374 | cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme); 375 | html_include(ctx.cfg.root_readme); 376 | cgit_close_filter(ctx.cfg.about_filter); 377 | done: 378 | cgit_print_layout_end(); 379 | } 380 | -------------------------------------------------------------------------------- /ui-repolist.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_REPOLIST_H 2 | #define UI_REPOLIST_H 3 | 4 | extern void cgit_print_repolist(void); 5 | extern void cgit_print_site_readme(void); 6 | 7 | #endif /* UI_REPOLIST_H */ 8 | -------------------------------------------------------------------------------- /ui-shared.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_SHARED_H 2 | #define UI_SHARED_H 3 | 4 | extern const char *cgit_httpscheme(void); 5 | extern char *cgit_hosturl(void); 6 | extern const char *cgit_rooturl(void); 7 | extern char *cgit_currenturl(void); 8 | extern char *cgit_currentfullurl(void); 9 | extern const char *cgit_loginurl(void); 10 | extern char *cgit_repourl(const char *reponame); 11 | extern char *cgit_fileurl(const char *reponame, const char *pagename, 12 | const char *filename, const char *query); 13 | extern char *cgit_pageurl(const char *reponame, const char *pagename, 14 | const char *query); 15 | 16 | extern void cgit_add_clone_urls(void (*fn)(const char *)); 17 | 18 | extern void cgit_index_link(const char *name, const char *title, 19 | const char *class, const char *pattern, const char *sort, int ofs, int always_root); 20 | extern void cgit_summary_link(const char *name, const char *title, 21 | const char *class, const char *head); 22 | extern void cgit_tag_link(const char *name, const char *title, 23 | const char *class, const char *tag); 24 | extern void cgit_tree_link(const char *name, const char *title, 25 | const char *class, const char *head, 26 | const char *rev, const char *path); 27 | extern void cgit_plain_link(const char *name, const char *title, 28 | const char *class, const char *head, 29 | const char *rev, const char *path); 30 | extern void cgit_blame_link(const char *name, const char *title, 31 | const char *class, const char *head, 32 | const char *rev, const char *path); 33 | extern void cgit_log_link(const char *name, const char *title, 34 | const char *class, const char *head, const char *rev, 35 | const char *path, int ofs, const char *grep, 36 | const char *pattern, int showmsg, int follow); 37 | extern void cgit_commit_link(const char *name, const char *title, 38 | const char *class, const char *head, 39 | const char *rev, const char *path); 40 | extern void cgit_patch_link(const char *name, const char *title, 41 | const char *class, const char *head, 42 | const char *rev, const char *path); 43 | extern void cgit_refs_link(const char *name, const char *title, 44 | const char *class, const char *head, 45 | const char *rev, const char *path); 46 | extern void cgit_snapshot_link(const char *name, const char *title, 47 | const char *class, const char *head, 48 | const char *rev, const char *archivename); 49 | extern void cgit_diff_link(const char *name, const char *title, 50 | const char *class, const char *head, 51 | const char *new_rev, const char *old_rev, 52 | const char *path); 53 | extern void cgit_stats_link(const char *name, const char *title, 54 | const char *class, const char *head, 55 | const char *path); 56 | extern void cgit_object_link(struct object *obj); 57 | 58 | extern void cgit_submodule_link(const char *class, char *path, 59 | const char *rev); 60 | 61 | extern void cgit_print_layout_start(void); 62 | extern void cgit_print_layout_end(void); 63 | 64 | __attribute__((format (printf,1,2))) 65 | extern void cgit_print_error(const char *fmt, ...); 66 | __attribute__((format (printf,1,0))) 67 | extern void cgit_vprint_error(const char *fmt, va_list ap); 68 | extern const struct date_mode cgit_date_mode(enum date_mode_type type); 69 | extern void cgit_print_age(time_t t, int tz, time_t max_relative); 70 | extern void cgit_print_http_headers(void); 71 | extern void cgit_redirect(const char *url, bool permanent); 72 | extern void cgit_print_docstart(void); 73 | extern void cgit_print_docend(void); 74 | __attribute__((format (printf,3,4))) 75 | extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...); 76 | extern void cgit_print_pageheader(void); 77 | extern void cgit_print_filemode(unsigned short mode); 78 | extern void cgit_compose_snapshot_prefix(struct strbuf *filename, 79 | const char *base, const char *ref); 80 | extern void cgit_print_snapshot_links(const struct cgit_repo *repo, 81 | const char *ref, const char *separator); 82 | extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo); 83 | extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 84 | const char *page); 85 | 86 | extern void cgit_set_title_from_path(const char *path); 87 | #endif /* UI_SHARED_H */ 88 | -------------------------------------------------------------------------------- /ui-snapshot.c: -------------------------------------------------------------------------------- 1 | /* ui-snapshot.c: generate snapshot of a commit 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-snapshot.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | static int write_archive_type(const char *format, const char *hex, const char *prefix) 17 | { 18 | struct strvec argv = STRVEC_INIT; 19 | const char **nargv; 20 | int result; 21 | strvec_push(&argv, "snapshot"); 22 | strvec_push(&argv, format); 23 | if (prefix) { 24 | struct strbuf buf = STRBUF_INIT; 25 | strbuf_addstr(&buf, prefix); 26 | strbuf_addch(&buf, '/'); 27 | strvec_push(&argv, "--prefix"); 28 | strvec_push(&argv, buf.buf); 29 | strbuf_release(&buf); 30 | } 31 | strvec_push(&argv, hex); 32 | /* 33 | * Now we need to copy the pointers to arguments into a new 34 | * structure because write_archive will rearrange its arguments 35 | * which may result in duplicated/missing entries causing leaks 36 | * or double-frees in strvec_clear. 37 | */ 38 | nargv = xmalloc(sizeof(char *) * (argv.nr + 1)); 39 | /* strvec guarantees a trailing NULL entry. */ 40 | memcpy(nargv, argv.v, sizeof(char *) * (argv.nr + 1)); 41 | 42 | result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0); 43 | strvec_clear(&argv); 44 | free(nargv); 45 | return result; 46 | } 47 | 48 | static int write_tar_archive(const char *hex, const char *prefix) 49 | { 50 | return write_archive_type("--format=tar", hex, prefix); 51 | } 52 | 53 | static int write_zip_archive(const char *hex, const char *prefix) 54 | { 55 | return write_archive_type("--format=zip", hex, prefix); 56 | } 57 | 58 | static int write_compressed_tar_archive(const char *hex, 59 | const char *prefix, 60 | char *filter_argv[]) 61 | { 62 | int rv; 63 | struct cgit_exec_filter f; 64 | cgit_exec_filter_init(&f, filter_argv[0], filter_argv); 65 | 66 | cgit_open_filter(&f.base); 67 | rv = write_tar_archive(hex, prefix); 68 | cgit_close_filter(&f.base); 69 | return rv; 70 | } 71 | 72 | static 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 | 78 | static 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 | 84 | static int write_tar_lzip_archive(const char *hex, const char *prefix) 85 | { 86 | char *argv[] = { "lzip", NULL }; 87 | return write_compressed_tar_archive(hex, prefix, argv); 88 | } 89 | 90 | static int write_tar_xz_archive(const char *hex, const char *prefix) 91 | { 92 | char *argv[] = { "xz", NULL }; 93 | return write_compressed_tar_archive(hex, prefix, argv); 94 | } 95 | 96 | static int write_tar_zstd_archive(const char *hex, const char *prefix) 97 | { 98 | char *argv[] = { "zstd", "-T0", NULL }; 99 | return write_compressed_tar_archive(hex, prefix, argv); 100 | } 101 | 102 | const struct cgit_snapshot_format cgit_snapshot_formats[] = { 103 | /* .tar must remain the 0 index */ 104 | { ".tar", "application/x-tar", write_tar_archive }, 105 | { ".tar.gz", "application/x-gzip", write_tar_gzip_archive }, 106 | { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive }, 107 | { ".tar.lz", "application/x-lzip", write_tar_lzip_archive }, 108 | { ".tar.xz", "application/x-xz", write_tar_xz_archive }, 109 | { ".tar.zst", "application/x-zstd", write_tar_zstd_archive }, 110 | { ".zip", "application/x-zip", write_zip_archive }, 111 | { NULL } 112 | }; 113 | 114 | static struct notes_tree snapshot_sig_notes[ARRAY_SIZE(cgit_snapshot_formats)]; 115 | 116 | const struct object_id *cgit_snapshot_get_sig(const char *ref, 117 | const struct cgit_snapshot_format *f) 118 | { 119 | struct notes_tree *tree; 120 | struct object_id oid; 121 | 122 | if (repo_get_oid(the_repository, ref, &oid)) 123 | return NULL; 124 | 125 | tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]]; 126 | if (!tree->initialized) { 127 | struct strbuf notes_ref = STRBUF_INIT; 128 | 129 | strbuf_addf(¬es_ref, "refs/notes/signatures/%s", 130 | f->suffix + 1); 131 | 132 | init_notes(tree, notes_ref.buf, combine_notes_ignore, 0); 133 | strbuf_release(¬es_ref); 134 | } 135 | 136 | return get_note(tree, &oid); 137 | } 138 | 139 | static const struct cgit_snapshot_format *get_format(const char *filename) 140 | { 141 | const struct cgit_snapshot_format *fmt; 142 | 143 | for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { 144 | if (ends_with(filename, fmt->suffix)) 145 | return fmt; 146 | } 147 | return NULL; 148 | } 149 | 150 | const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f) 151 | { 152 | return BIT(f - &cgit_snapshot_formats[0]); 153 | } 154 | 155 | static int make_snapshot(const struct cgit_snapshot_format *format, 156 | const char *hex, const char *prefix, 157 | const char *filename) 158 | { 159 | struct object_id oid; 160 | 161 | if (repo_get_oid(the_repository, hex, &oid)) { 162 | cgit_print_error_page(404, "Not found", 163 | "Bad object id: %s", hex); 164 | return 1; 165 | } 166 | if (!lookup_commit_reference(the_repository, &oid)) { 167 | cgit_print_error_page(400, "Bad request", 168 | "Not a commit reference: %s", hex); 169 | return 1; 170 | } 171 | ctx.page.etag = oid_to_hex(&oid); 172 | ctx.page.mimetype = xstrdup(format->mimetype); 173 | ctx.page.filename = xstrdup(filename); 174 | cgit_print_http_headers(); 175 | init_archivers(); 176 | format->write_func(hex, prefix); 177 | return 0; 178 | } 179 | 180 | static int write_sig(const struct cgit_snapshot_format *format, 181 | const char *hex, const char *archive, 182 | const char *filename) 183 | { 184 | const struct object_id *note = cgit_snapshot_get_sig(hex, format); 185 | enum object_type type; 186 | unsigned long size; 187 | char *buf; 188 | 189 | if (!note) { 190 | cgit_print_error_page(404, "Not found", 191 | "No signature for %s", archive); 192 | return 0; 193 | } 194 | 195 | buf = repo_read_object_file(the_repository, note, &type, &size); 196 | if (!buf) { 197 | cgit_print_error_page(404, "Not found", "Not found"); 198 | return 0; 199 | } 200 | 201 | html("X-Content-Type-Options: nosniff\n"); 202 | html("Content-Security-Policy: default-src 'none'\n"); 203 | ctx.page.etag = oid_to_hex(note); 204 | ctx.page.mimetype = xstrdup("application/pgp-signature"); 205 | ctx.page.filename = xstrdup(filename); 206 | cgit_print_http_headers(); 207 | 208 | html_raw(buf, size); 209 | free(buf); 210 | return 0; 211 | } 212 | 213 | /* Try to guess the requested revision from the requested snapshot name. 214 | * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become 215 | * "cgit-0.7.2". If this is a valid commit object name we've got a winner. 216 | * Otherwise, if the snapshot name has a prefix matching the result from 217 | * repo_basename(), we strip the basename and any following '-' and '_' 218 | * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once 219 | * more. If this still isn't a valid commit object name, we check if pre- 220 | * pending a 'v' or a 'V' to the remaining snapshot name ("0.7.2" -> 221 | * "v0.7.2") gives us something valid. 222 | */ 223 | static const char *get_ref_from_filename(const struct cgit_repo *repo, 224 | const char *filename, 225 | const struct cgit_snapshot_format *format) 226 | { 227 | const char *reponame; 228 | struct object_id oid; 229 | struct strbuf snapshot = STRBUF_INIT; 230 | int result = 1; 231 | 232 | strbuf_addstr(&snapshot, filename); 233 | strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix)); 234 | 235 | if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) 236 | goto out; 237 | 238 | reponame = cgit_snapshot_prefix(repo); 239 | if (starts_with(snapshot.buf, reponame)) { 240 | const char *new_start = snapshot.buf; 241 | new_start += strlen(reponame); 242 | while (new_start && (*new_start == '-' || *new_start == '_')) 243 | new_start++; 244 | strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0); 245 | } 246 | 247 | if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) 248 | goto out; 249 | 250 | strbuf_insert(&snapshot, 0, "v", 1); 251 | if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) 252 | goto out; 253 | 254 | strbuf_splice(&snapshot, 0, 1, "V", 1); 255 | if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0) 256 | goto out; 257 | 258 | result = 0; 259 | strbuf_release(&snapshot); 260 | 261 | out: 262 | return result ? strbuf_detach(&snapshot, NULL) : NULL; 263 | } 264 | 265 | void cgit_print_snapshot(const char *head, const char *hex, 266 | const char *filename, int dwim) 267 | { 268 | const struct cgit_snapshot_format* f; 269 | const char *sig_filename = NULL; 270 | char *adj_filename = NULL; 271 | char *prefix = NULL; 272 | 273 | if (!filename) { 274 | cgit_print_error_page(400, "Bad request", 275 | "No snapshot name specified"); 276 | return; 277 | } 278 | 279 | if (ends_with(filename, ".asc")) { 280 | sig_filename = filename; 281 | 282 | /* Strip ".asc" from filename for common format processing */ 283 | adj_filename = xstrdup(filename); 284 | adj_filename[strlen(adj_filename) - 4] = '\0'; 285 | filename = adj_filename; 286 | } 287 | 288 | f = get_format(filename); 289 | if (!f || (!sig_filename && !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) { 290 | cgit_print_error_page(400, "Bad request", 291 | "Unsupported snapshot format: %s", filename); 292 | return; 293 | } 294 | 295 | if (!hex && dwim) { 296 | hex = get_ref_from_filename(ctx.repo, filename, f); 297 | if (hex == NULL) { 298 | cgit_print_error_page(404, "Not found", "Not found"); 299 | return; 300 | } 301 | prefix = xstrdup(filename); 302 | prefix[strlen(filename) - strlen(f->suffix)] = '\0'; 303 | } 304 | 305 | if (!hex) 306 | hex = head; 307 | 308 | if (!prefix) 309 | prefix = xstrdup(cgit_snapshot_prefix(ctx.repo)); 310 | 311 | if (sig_filename) 312 | write_sig(f, hex, filename, sig_filename); 313 | else 314 | make_snapshot(f, hex, prefix, filename); 315 | 316 | free(prefix); 317 | free(adj_filename); 318 | } 319 | -------------------------------------------------------------------------------- /ui-snapshot.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_SNAPSHOT_H 2 | #define UI_SNAPSHOT_H 3 | 4 | extern void cgit_print_snapshot(const char *head, const char *hex, 5 | const char *filename, int dwim); 6 | 7 | #endif /* UI_SNAPSHOT_H */ 8 | -------------------------------------------------------------------------------- /ui-ssdiff.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_SSDIFF_H 2 | #define UI_SSDIFF_H 3 | 4 | /* 5 | * ssdiff line limits 6 | */ 7 | #ifndef MAX_SSDIFF_M 8 | #define MAX_SSDIFF_M 128 9 | #endif 10 | 11 | #ifndef MAX_SSDIFF_N 12 | #define MAX_SSDIFF_N 128 13 | #endif 14 | #define MAX_SSDIFF_SIZE ((MAX_SSDIFF_M) * (MAX_SSDIFF_N)) 15 | 16 | extern void cgit_ssdiff_print_deferred_lines(void); 17 | 18 | extern void cgit_ssdiff_line_cb(char *line, int len); 19 | 20 | extern void cgit_ssdiff_header_begin(void); 21 | extern void cgit_ssdiff_header_end(void); 22 | 23 | extern void cgit_ssdiff_footer(void); 24 | 25 | #endif /* UI_SSDIFF_H */ 26 | -------------------------------------------------------------------------------- /ui-stats.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_STATS_H 2 | #define UI_STATS_H 3 | 4 | #include "cgit.h" 5 | 6 | struct cgit_period { 7 | const char code; 8 | const char *name; 9 | int max_periods; 10 | int count; 11 | 12 | /* Convert a tm value to the first day in the period */ 13 | void (*trunc)(struct tm *tm); 14 | 15 | /* Update tm value to start of next/previous period */ 16 | void (*dec)(struct tm *tm); 17 | void (*inc)(struct tm *tm); 18 | 19 | /* Pretty-print a tm value */ 20 | char *(*pretty)(struct tm *tm); 21 | }; 22 | 23 | extern int cgit_find_stats_period(const char *expr, const struct cgit_period **period); 24 | extern const char *cgit_find_stats_periodname(int idx); 25 | 26 | extern void cgit_show_stats(void); 27 | 28 | #endif /* UI_STATS_H */ 29 | -------------------------------------------------------------------------------- /ui-summary.c: -------------------------------------------------------------------------------- 1 | /* ui-summary.c: functions for generating repo summary page 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #include "cgit.h" 10 | #include "ui-summary.h" 11 | #include "html.h" 12 | #include "ui-blob.h" 13 | #include "ui-log.h" 14 | #include "ui-plain.h" 15 | #include "ui-refs.h" 16 | #include "ui-shared.h" 17 | 18 | static int urls; 19 | 20 | static void print_url(const char *url) 21 | { 22 | int columns = 3; 23 | 24 | if (ctx.repo->enable_log_filecount) 25 | columns++; 26 | if (ctx.repo->enable_log_linecount) 27 | columns++; 28 | 29 | if (urls++ == 0) { 30 | htmlf(" ", columns); 31 | htmlf("Clone\n", columns); 32 | } 33 | 34 | htmlf(""); 39 | html_txt(url); 40 | html("\n"); 41 | } 42 | 43 | void cgit_print_summary(void) 44 | { 45 | int columns = 3; 46 | 47 | if (ctx.repo->enable_log_filecount) 48 | columns++; 49 | if (ctx.repo->enable_log_linecount) 50 | columns++; 51 | 52 | cgit_print_layout_start(); 53 | html(""); 54 | cgit_print_branches(ctx.cfg.summary_branches); 55 | htmlf("", columns); 56 | cgit_print_tags(ctx.cfg.summary_tags); 57 | if (ctx.cfg.summary_log > 0) { 58 | htmlf("", columns); 59 | cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 60 | NULL, NULL, 0, 0, 0); 61 | } 62 | urls = 0; 63 | cgit_add_clone_urls(print_url); 64 | html("
 
 
"); 65 | cgit_print_layout_end(); 66 | } 67 | 68 | /* The caller must free the return value. */ 69 | static char* append_readme_path(const char *filename, const char *ref, const char *path) 70 | { 71 | char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL; 72 | /* If a subpath is specified for the about page, make it relative 73 | * to the directory containing the configured readme. */ 74 | 75 | file = xstrdup(filename); 76 | base_dir = dirname(file); 77 | if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) { 78 | if (!ref) { 79 | free(file); 80 | return NULL; 81 | } 82 | full_path = xstrdup(path); 83 | } else 84 | full_path = fmtalloc("%s/%s", base_dir, path); 85 | 86 | if (!ref) { 87 | resolved_base = realpath(base_dir, NULL); 88 | resolved_full = realpath(full_path, NULL); 89 | if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) { 90 | free(full_path); 91 | full_path = NULL; 92 | } 93 | } 94 | 95 | free(file); 96 | free(resolved_base); 97 | free(resolved_full); 98 | 99 | return full_path; 100 | } 101 | 102 | void cgit_print_repo_readme(const char *path) 103 | { 104 | char *filename, *ref, *mimetype; 105 | int free_filename = 0; 106 | 107 | mimetype = get_mimetype_for_filename(path); 108 | if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) { 109 | ctx.page.mimetype = mimetype; 110 | ctx.page.charset = NULL; 111 | cgit_print_plain(); 112 | free(mimetype); 113 | return; 114 | } 115 | free(mimetype); 116 | 117 | cgit_print_layout_start(); 118 | if (ctx.repo->readme.nr == 0) 119 | goto done; 120 | 121 | filename = ctx.repo->readme.items[0].string; 122 | ref = ctx.repo->readme.items[0].util; 123 | 124 | if (path) { 125 | free_filename = 1; 126 | filename = append_readme_path(filename, ref, path); 127 | if (!filename) 128 | goto done; 129 | } 130 | 131 | /* Print the calculated readme, either from the git repo or from the 132 | * filesystem, while applying the about-filter. 133 | */ 134 | html("
"); 135 | cgit_open_filter(ctx.repo->about_filter, filename); 136 | if (ref) 137 | cgit_print_file(filename, ref, 1); 138 | else 139 | html_include(filename); 140 | cgit_close_filter(ctx.repo->about_filter); 141 | 142 | html("
"); 143 | if (free_filename) 144 | free(filename); 145 | 146 | done: 147 | cgit_print_layout_end(); 148 | } 149 | -------------------------------------------------------------------------------- /ui-summary.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_SUMMARY_H 2 | #define UI_SUMMARY_H 3 | 4 | extern void cgit_print_summary(void); 5 | extern void cgit_print_repo_readme(const char *path); 6 | 7 | #endif /* UI_SUMMARY_H */ 8 | -------------------------------------------------------------------------------- /ui-tag.c: -------------------------------------------------------------------------------- 1 | /* ui-tag.c: display a tag 2 | * 3 | * Copyright (C) 2006-2014 cgit Development Team 4 | * 5 | * Licensed under GNU General Public License v2 6 | * (see COPYING for full license text) 7 | */ 8 | 9 | #define USE_THE_REPOSITORY_VARIABLE 10 | 11 | #include "cgit.h" 12 | #include "ui-tag.h" 13 | #include "html.h" 14 | #include "ui-shared.h" 15 | 16 | static void print_tag_content(char *buf) 17 | { 18 | char *p; 19 | 20 | if (!buf) 21 | return; 22 | 23 | html("
"); 24 | p = strchr(buf, '\n'); 25 | if (p) 26 | *p = '\0'; 27 | html_txt(buf); 28 | html("
"); 29 | if (p) { 30 | html("
"); 31 | html_txt(++p); 32 | html("
"); 33 | } 34 | } 35 | 36 | static void print_download_links(char *revname) 37 | { 38 | html("download"); 39 | cgit_print_snapshot_links(ctx.repo, revname, "
"); 40 | html(""); 41 | } 42 | 43 | void cgit_print_tag(char *revname) 44 | { 45 | struct strbuf fullref = STRBUF_INIT; 46 | struct object_id oid; 47 | struct object *obj; 48 | 49 | if (!revname) 50 | revname = ctx.qry.head; 51 | 52 | strbuf_addf(&fullref, "refs/tags/%s", revname); 53 | if (repo_get_oid(the_repository, fullref.buf, &oid)) { 54 | cgit_print_error_page(404, "Not found", 55 | "Bad tag reference: %s", revname); 56 | goto cleanup; 57 | } 58 | obj = parse_object(the_repository, &oid); 59 | if (!obj) { 60 | cgit_print_error_page(500, "Internal server error", 61 | "Bad object id: %s", oid_to_hex(&oid)); 62 | goto cleanup; 63 | } 64 | if (obj->type == OBJ_TAG) { 65 | struct tag *tag; 66 | struct taginfo *info; 67 | 68 | tag = lookup_tag(the_repository, &oid); 69 | if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 70 | cgit_print_error_page(500, "Internal server error", 71 | "Bad tag object: %s", revname); 72 | goto cleanup; 73 | } 74 | cgit_print_layout_start(); 75 | html("\n"); 76 | html("\n", oid_to_hex(&oid)); 79 | if (info->tagger_date > 0) { 80 | html("\n"); 84 | } 85 | if (info->tagger) { 86 | html("\n"); 95 | } 96 | html("\n"); 99 | if (ctx.repo->snapshots) 100 | print_download_links(revname); 101 | html("
tag name"); 77 | html_txt(revname); 78 | htmlf(" (%s)
tag date"); 81 | html_txt(show_date(info->tagger_date, info->tagger_tz, 82 | cgit_date_mode(DATE_ISO8601))); 83 | html("
tagged by"); 87 | cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "tag"); 88 | html_txt(info->tagger); 89 | if (info->tagger_email && !ctx.cfg.noplainemail) { 90 | html(" "); 91 | html_txt(info->tagger_email); 92 | } 93 | cgit_close_filter(ctx.repo->email_filter); 94 | html("
tagged object"); 97 | cgit_object_link(tag->tagged); 98 | html("
\n"); 102 | print_tag_content(info->msg); 103 | cgit_print_layout_end(); 104 | cgit_free_taginfo(info); 105 | } else { 106 | cgit_print_layout_start(); 107 | html("\n"); 108 | html("\n"); 111 | html("\n"); 114 | if (ctx.repo->snapshots) 115 | print_download_links(revname); 116 | html("
tag name"); 109 | html_txt(revname); 110 | html("
tagged object"); 112 | cgit_object_link(obj); 113 | html("
\n"); 117 | cgit_print_layout_end(); 118 | } 119 | 120 | cleanup: 121 | strbuf_release(&fullref); 122 | } 123 | -------------------------------------------------------------------------------- /ui-tag.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_TAG_H 2 | #define UI_TAG_H 3 | 4 | extern void cgit_print_tag(char *revname); 5 | 6 | #endif /* UI_TAG_H */ 7 | -------------------------------------------------------------------------------- /ui-tree.h: -------------------------------------------------------------------------------- 1 | #ifndef UI_TREE_H 2 | #define UI_TREE_H 3 | 4 | extern void cgit_print_tree(const char *rev, char *path); 5 | 6 | #endif /* UI_TREE_H */ 7 | --------------------------------------------------------------------------------