summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2011-03-05 13:01:59 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2011-03-05 13:01:59 (UTC)
commit1b09cbd303d889ec2636127584d57b7f1b70c25e (patch) (unidiff)
tree2b1188f946451d06af4d9120cbc3ba34de716f21
parent979c460e7f71d153ae79da67b8b21c3412f0fe02 (diff)
parent9e849950dc7c1f2fb6ffa62ab65bd30f35717d13 (diff)
downloadcgit-1b09cbd303d889ec2636127584d57b7f1b70c25e.zip
cgit-1b09cbd303d889ec2636127584d57b7f1b70c25e.tar.gz
cgit-1b09cbd303d889ec2636127584d57b7f1b70c25e.tar.bz2
Merge branch 'stable'
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile4
-rw-r--r--cgit.c5
-rw-r--r--html.c2
-rw-r--r--ui-shared.c2
4 files changed, 7 insertions, 6 deletions
diff --git a/Makefile b/Makefile
index a988751..14b4df4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,244 +1,244 @@
1CGIT_VERSION = v0.8.3.4 1CGIT_VERSION = v0.8.3.5
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7prefix = /usr 7prefix = /usr
8libdir = $(prefix)/lib 8libdir = $(prefix)/lib
9filterdir = $(libdir)/cgit/filters 9filterdir = $(libdir)/cgit/filters
10docdir = $(prefix)/share/doc/cgit 10docdir = $(prefix)/share/doc/cgit
11htmldir = $(docdir) 11htmldir = $(docdir)
12pdfdir = $(docdir) 12pdfdir = $(docdir)
13mandir = $(prefix)/share/man 13mandir = $(prefix)/share/man
14SHA1_HEADER = <openssl/sha.h> 14SHA1_HEADER = <openssl/sha.h>
15GIT_VER = 1.7.4 15GIT_VER = 1.7.4
16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
17INSTALL = install 17INSTALL = install
18MAN5_TXT = $(wildcard *.5.txt) 18MAN5_TXT = $(wildcard *.5.txt)
19MAN_TXT = $(MAN5_TXT) 19MAN_TXT = $(MAN5_TXT)
20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT)) 20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) 21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT)) 22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT))
23 23
24# Define NO_STRCASESTR if you don't have strcasestr. 24# Define NO_STRCASESTR if you don't have strcasestr.
25# 25#
26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1 26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
27# implementation (slower). 27# implementation (slower).
28# 28#
29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
30# 30#
31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) 31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
32# do not support the 'size specifiers' introduced by C99, namely ll, hh, 32# do not support the 'size specifiers' introduced by C99, namely ll, hh,
33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). 33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
34# some C compilers supported these specifiers prior to C99 as an extension. 34# some C compilers supported these specifiers prior to C99 as an extension.
35# 35#
36 36
37#-include config.mak 37#-include config.mak
38 38
39# 39#
40# Platform specific tweaks 40# Platform specific tweaks
41# 41#
42 42
43uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 43uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
44uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 44uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
45uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 45uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
46 46
47ifeq ($(uname_O),Cygwin) 47ifeq ($(uname_O),Cygwin)
48 NO_STRCASESTR = YesPlease 48 NO_STRCASESTR = YesPlease
49 NEEDS_LIBICONV = YesPlease 49 NEEDS_LIBICONV = YesPlease
50endif 50endif
51 51
52# 52#
53# Let the user override the above settings. 53# Let the user override the above settings.
54# 54#
55-include cgit.conf 55-include cgit.conf
56 56
57# 57#
58# Define a way to invoke make in subdirs quietly, shamelessly ripped 58# Define a way to invoke make in subdirs quietly, shamelessly ripped
59# from git.git 59# from git.git
60# 60#
61QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 61QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
62QUIET_SUBDIR1 = 62QUIET_SUBDIR1 =
63 63
64ifneq ($(findstring $(MAKEFLAGS),w),w) 64ifneq ($(findstring $(MAKEFLAGS),w),w)
65PRINT_DIR = --no-print-directory 65PRINT_DIR = --no-print-directory
66else # "make -w" 66else # "make -w"
67NO_SUBDIR = : 67NO_SUBDIR = :
68endif 68endif
69 69
70ifndef V 70ifndef V
71 QUIET_CC = @echo ' ' CC $@; 71 QUIET_CC = @echo ' ' CC $@;
72 QUIET_MM = @echo ' ' MM $@; 72 QUIET_MM = @echo ' ' MM $@;
73 QUIET_SUBDIR0 = +@subdir= 73 QUIET_SUBDIR0 = +@subdir=
74 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 74 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
75 $(MAKE) $(PRINT_DIR) -C $$subdir 75 $(MAKE) $(PRINT_DIR) -C $$subdir
76endif 76endif
77 77
78# 78#
79# Define a pattern rule for automatic dependency building 79# Define a pattern rule for automatic dependency building
80# 80#
81%.d: %.c 81%.d: %.c
82 $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 82 $(QUIET_MM)$(CC) $(CFLAGS) -MM -MP $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
83 83
84# 84#
85# Define a pattern rule for silent object building 85# Define a pattern rule for silent object building
86# 86#
87%.o: %.c 87%.o: %.c
88 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 88 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
89 89
90 90
91EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread 91EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
92OBJECTS = 92OBJECTS =
93OBJECTS += cache.o 93OBJECTS += cache.o
94OBJECTS += cgit.o 94OBJECTS += cgit.o
95OBJECTS += cmd.o 95OBJECTS += cmd.o
96OBJECTS += configfile.o 96OBJECTS += configfile.o
97OBJECTS += html.o 97OBJECTS += html.o
98OBJECTS += parsing.o 98OBJECTS += parsing.o
99OBJECTS += scan-tree.o 99OBJECTS += scan-tree.o
100OBJECTS += shared.o 100OBJECTS += shared.o
101OBJECTS += ui-atom.o 101OBJECTS += ui-atom.o
102OBJECTS += ui-blob.o 102OBJECTS += ui-blob.o
103OBJECTS += ui-clone.o 103OBJECTS += ui-clone.o
104OBJECTS += ui-commit.o 104OBJECTS += ui-commit.o
105OBJECTS += ui-diff.o 105OBJECTS += ui-diff.o
106OBJECTS += ui-log.o 106OBJECTS += ui-log.o
107OBJECTS += ui-patch.o 107OBJECTS += ui-patch.o
108OBJECTS += ui-plain.o 108OBJECTS += ui-plain.o
109OBJECTS += ui-refs.o 109OBJECTS += ui-refs.o
110OBJECTS += ui-repolist.o 110OBJECTS += ui-repolist.o
111OBJECTS += ui-shared.o 111OBJECTS += ui-shared.o
112OBJECTS += ui-snapshot.o 112OBJECTS += ui-snapshot.o
113OBJECTS += ui-ssdiff.o 113OBJECTS += ui-ssdiff.o
114OBJECTS += ui-stats.o 114OBJECTS += ui-stats.o
115OBJECTS += ui-summary.o 115OBJECTS += ui-summary.o
116OBJECTS += ui-tag.o 116OBJECTS += ui-tag.o
117OBJECTS += ui-tree.o 117OBJECTS += ui-tree.o
118OBJECTS += vector.o 118OBJECTS += vector.o
119 119
120ifdef NEEDS_LIBICONV 120ifdef NEEDS_LIBICONV
121 EXTLIBS += -liconv 121 EXTLIBS += -liconv
122endif 122endif
123 123
124 124
125.PHONY: all libgit test install uninstall clean force-version get-git \ 125.PHONY: all libgit test install uninstall clean force-version get-git \
126 doc clean-doc install-doc install-man install-html install-pdf \ 126 doc clean-doc install-doc install-man install-html install-pdf \
127 uninstall-doc uninstall-man uninstall-html uninstall-pdf 127 uninstall-doc uninstall-man uninstall-html uninstall-pdf
128 128
129all: cgit 129all: cgit
130 130
131VERSION: force-version 131VERSION: force-version
132 @./gen-version.sh "$(CGIT_VERSION)" 132 @./gen-version.sh "$(CGIT_VERSION)"
133-include VERSION 133-include VERSION
134 134
135 135
136CFLAGS += -g -Wall -Igit 136CFLAGS += -g -Wall -Igit
137CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 137CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
138CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 138CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
139CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 139CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
140CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 140CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
141CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 141CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
142 142
143GIT_OPTIONS = prefix=/usr 143GIT_OPTIONS = prefix=/usr
144 144
145ifdef NO_ICONV 145ifdef NO_ICONV
146 CFLAGS += -DNO_ICONV 146 CFLAGS += -DNO_ICONV
147endif 147endif
148ifdef NO_STRCASESTR 148ifdef NO_STRCASESTR
149 CFLAGS += -DNO_STRCASESTR 149 CFLAGS += -DNO_STRCASESTR
150endif 150endif
151ifdef NO_C99_FORMAT 151ifdef NO_C99_FORMAT
152 CFLAGS += -DNO_C99_FORMAT 152 CFLAGS += -DNO_C99_FORMAT
153endif 153endif
154ifdef NO_OPENSSL 154ifdef NO_OPENSSL
155 CFLAGS += -DNO_OPENSSL 155 CFLAGS += -DNO_OPENSSL
156 GIT_OPTIONS += NO_OPENSSL=1 156 GIT_OPTIONS += NO_OPENSSL=1
157else 157else
158 EXTLIBS += -lcrypto 158 EXTLIBS += -lcrypto
159endif 159endif
160 160
161cgit: $(OBJECTS) libgit 161cgit: $(OBJECTS) libgit
162 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 162 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
163 163
164cgit.o: VERSION 164cgit.o: VERSION
165 165
166ifneq "$(MAKECMDGOALS)" "clean" 166ifneq "$(MAKECMDGOALS)" "clean"
167 -include $(OBJECTS:.o=.d) 167 -include $(OBJECTS:.o=.d)
168endif 168endif
169 169
170libgit: 170libgit:
171 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a 171 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
172 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a 172 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
173 173
174test: all 174test: all
175 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 175 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
176 176
177install: all 177install: all
178 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 178 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
179 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 179 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
180 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 180 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
181 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 181 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
182 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 182 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
183 $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir) 183 $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
184 $(INSTALL) -m 0755 filters/* $(DESTDIR)$(filterdir) 184 $(INSTALL) -m 0755 filters/* $(DESTDIR)$(filterdir)
185 185
186install-doc: install-man install-html install-pdf 186install-doc: install-man install-html install-pdf
187 187
188install-man: doc-man 188install-man: doc-man
189 $(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5 189 $(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
190 $(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5 190 $(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
191 191
192install-html: doc-html 192install-html: doc-html
193 $(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir) 193 $(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
194 $(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir) 194 $(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
195 195
196install-pdf: doc-pdf 196install-pdf: doc-pdf
197 $(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir) 197 $(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
198 $(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir) 198 $(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
199 199
200uninstall: 200uninstall:
201 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 201 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
202 rm -f $(CGIT_DATA_PATH)/cgit.css 202 rm -f $(CGIT_DATA_PATH)/cgit.css
203 rm -f $(CGIT_DATA_PATH)/cgit.png 203 rm -f $(CGIT_DATA_PATH)/cgit.png
204 204
205uninstall-doc: uninstall-man uninstall-html uninstall-pdf 205uninstall-doc: uninstall-man uninstall-html uninstall-pdf
206 206
207uninstall-man: 207uninstall-man:
208 @for i in $(DOC_MAN5); do \ 208 @for i in $(DOC_MAN5); do \
209 rm -fv $(DESTDIR)$(mandir)/man5/$$i; \ 209 rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
210 done 210 done
211 211
212uninstall-html: 212uninstall-html:
213 @for i in $(DOC_HTML); do \ 213 @for i in $(DOC_HTML); do \
214 rm -fv $(DESTDIR)$(htmldir)/$$i; \ 214 rm -fv $(DESTDIR)$(htmldir)/$$i; \
215 done 215 done
216 216
217uninstall-pdf: 217uninstall-pdf:
218 @for i in $(DOC_PDF); do \ 218 @for i in $(DOC_PDF); do \
219 rm -fv $(DESTDIR)$(pdfdir)/$$i; \ 219 rm -fv $(DESTDIR)$(pdfdir)/$$i; \
220 done 220 done
221 221
222doc: doc-man doc-html doc-pdf 222doc: doc-man doc-html doc-pdf
223doc-man: doc-man5 223doc-man: doc-man5
224doc-man5: $(DOC_MAN5) 224doc-man5: $(DOC_MAN5)
225doc-html: $(DOC_HTML) 225doc-html: $(DOC_HTML)
226doc-pdf: $(DOC_PDF) 226doc-pdf: $(DOC_PDF)
227 227
228%.5 : %.5.txt 228%.5 : %.5.txt
229 a2x -f manpage $< 229 a2x -f manpage $<
230 230
231$(DOC_HTML): %.html : %.txt 231$(DOC_HTML): %.html : %.txt
232 a2x -f xhtml --stylesheet=cgit-doc.css $< 232 a2x -f xhtml --stylesheet=cgit-doc.css $<
233 233
234$(DOC_PDF): %.pdf : %.txt 234$(DOC_PDF): %.pdf : %.txt
235 a2x -f pdf cgitrc.5.txt 235 a2x -f pdf cgitrc.5.txt
236 236
237clean: clean-doc 237clean: clean-doc
238 rm -f cgit VERSION *.o *.d 238 rm -f cgit VERSION *.o *.d
239 239
240clean-doc: 240clean-doc:
241 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo 241 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
242 242
243get-git: 243get-git:
244 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 244 curl $(GIT_URL) | tar -xjf - && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index 916feb4..f4dd6ef 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,791 +1,792 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "cache.h" 11#include "cache.h"
12#include "cmd.h" 12#include "cmd.h"
13#include "configfile.h" 13#include "configfile.h"
14#include "html.h" 14#include "html.h"
15#include "ui-shared.h" 15#include "ui-shared.h"
16#include "ui-stats.h" 16#include "ui-stats.h"
17#include "scan-tree.h" 17#include "scan-tree.h"
18 18
19const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
20 20
21void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
22{ 22{
23 struct string_list_item *item; 23 struct string_list_item *item;
24 24
25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
26 item->util = xstrdup(value); 26 item->util = xstrdup(value);
27} 27}
28 28
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 32
33 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
34 return NULL; 34 return NULL;
35 35
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph")) 60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value); 61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
62 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
64 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
66 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
67 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
68 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
69 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
70 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
71 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
72 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
73 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
74 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
75 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
76 else if (!strcmp(name, "readme") && value != NULL) 76 else if (!strcmp(name, "readme") && value != NULL)
77 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
78 else if (!strcmp(name, "logo") && value != NULL) 78 else if (!strcmp(name, "logo") && value != NULL)
79 repo->logo = xstrdup(value); 79 repo->logo = xstrdup(value);
80 else if (!strcmp(name, "logo-link") && value != NULL) 80 else if (!strcmp(name, "logo-link") && value != NULL)
81 repo->logo_link = xstrdup(value); 81 repo->logo_link = xstrdup(value);
82 else if (ctx.cfg.enable_filter_overrides) { 82 else if (ctx.cfg.enable_filter_overrides) {
83 if (!strcmp(name, "about-filter")) 83 if (!strcmp(name, "about-filter"))
84 repo->about_filter = new_filter(value, 0); 84 repo->about_filter = new_filter(value, 0);
85 else if (!strcmp(name, "commit-filter")) 85 else if (!strcmp(name, "commit-filter"))
86 repo->commit_filter = new_filter(value, 0); 86 repo->commit_filter = new_filter(value, 0);
87 else if (!strcmp(name, "source-filter")) 87 else if (!strcmp(name, "source-filter"))
88 repo->source_filter = new_filter(value, 1); 88 repo->source_filter = new_filter(value, 1);
89 } 89 }
90} 90}
91 91
92void config_cb(const char *name, const char *value) 92void config_cb(const char *name, const char *value)
93{ 93{
94 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 94 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
95 ctx.cfg.section = xstrdup(value); 95 ctx.cfg.section = xstrdup(value);
96 else if (!strcmp(name, "repo.url")) 96 else if (!strcmp(name, "repo.url"))
97 ctx.repo = cgit_add_repo(value); 97 ctx.repo = cgit_add_repo(value);
98 else if (ctx.repo && !strcmp(name, "repo.path")) 98 else if (ctx.repo && !strcmp(name, "repo.path"))
99 ctx.repo->path = trim_end(value, '/'); 99 ctx.repo->path = trim_end(value, '/');
100 else if (ctx.repo && !prefixcmp(name, "repo.")) 100 else if (ctx.repo && !prefixcmp(name, "repo."))
101 repo_config(ctx.repo, name + 5, value); 101 repo_config(ctx.repo, name + 5, value);
102 else if (!strcmp(name, "readme")) 102 else if (!strcmp(name, "readme"))
103 ctx.cfg.readme = xstrdup(value); 103 ctx.cfg.readme = xstrdup(value);
104 else if (!strcmp(name, "root-title")) 104 else if (!strcmp(name, "root-title"))
105 ctx.cfg.root_title = xstrdup(value); 105 ctx.cfg.root_title = xstrdup(value);
106 else if (!strcmp(name, "root-desc")) 106 else if (!strcmp(name, "root-desc"))
107 ctx.cfg.root_desc = xstrdup(value); 107 ctx.cfg.root_desc = xstrdup(value);
108 else if (!strcmp(name, "root-readme")) 108 else if (!strcmp(name, "root-readme"))
109 ctx.cfg.root_readme = xstrdup(value); 109 ctx.cfg.root_readme = xstrdup(value);
110 else if (!strcmp(name, "css")) 110 else if (!strcmp(name, "css"))
111 ctx.cfg.css = xstrdup(value); 111 ctx.cfg.css = xstrdup(value);
112 else if (!strcmp(name, "favicon")) 112 else if (!strcmp(name, "favicon"))
113 ctx.cfg.favicon = xstrdup(value); 113 ctx.cfg.favicon = xstrdup(value);
114 else if (!strcmp(name, "footer")) 114 else if (!strcmp(name, "footer"))
115 ctx.cfg.footer = xstrdup(value); 115 ctx.cfg.footer = xstrdup(value);
116 else if (!strcmp(name, "head-include")) 116 else if (!strcmp(name, "head-include"))
117 ctx.cfg.head_include = xstrdup(value); 117 ctx.cfg.head_include = xstrdup(value);
118 else if (!strcmp(name, "header")) 118 else if (!strcmp(name, "header"))
119 ctx.cfg.header = xstrdup(value); 119 ctx.cfg.header = xstrdup(value);
120 else if (!strcmp(name, "logo")) 120 else if (!strcmp(name, "logo"))
121 ctx.cfg.logo = xstrdup(value); 121 ctx.cfg.logo = xstrdup(value);
122 else if (!strcmp(name, "index-header")) 122 else if (!strcmp(name, "index-header"))
123 ctx.cfg.index_header = xstrdup(value); 123 ctx.cfg.index_header = xstrdup(value);
124 else if (!strcmp(name, "index-info")) 124 else if (!strcmp(name, "index-info"))
125 ctx.cfg.index_info = xstrdup(value); 125 ctx.cfg.index_info = xstrdup(value);
126 else if (!strcmp(name, "logo-link")) 126 else if (!strcmp(name, "logo-link"))
127 ctx.cfg.logo_link = xstrdup(value); 127 ctx.cfg.logo_link = xstrdup(value);
128 else if (!strcmp(name, "module-link")) 128 else if (!strcmp(name, "module-link"))
129 ctx.cfg.module_link = xstrdup(value); 129 ctx.cfg.module_link = xstrdup(value);
130 else if (!strcmp(name, "strict-export")) 130 else if (!strcmp(name, "strict-export"))
131 ctx.cfg.strict_export = xstrdup(value); 131 ctx.cfg.strict_export = xstrdup(value);
132 else if (!strcmp(name, "virtual-root")) { 132 else if (!strcmp(name, "virtual-root")) {
133 ctx.cfg.virtual_root = trim_end(value, '/'); 133 ctx.cfg.virtual_root = trim_end(value, '/');
134 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 134 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
135 ctx.cfg.virtual_root = ""; 135 ctx.cfg.virtual_root = "";
136 } else if (!strcmp(name, "nocache")) 136 } else if (!strcmp(name, "nocache"))
137 ctx.cfg.nocache = atoi(value); 137 ctx.cfg.nocache = atoi(value);
138 else if (!strcmp(name, "noplainemail")) 138 else if (!strcmp(name, "noplainemail"))
139 ctx.cfg.noplainemail = atoi(value); 139 ctx.cfg.noplainemail = atoi(value);
140 else if (!strcmp(name, "noheader")) 140 else if (!strcmp(name, "noheader"))
141 ctx.cfg.noheader = atoi(value); 141 ctx.cfg.noheader = atoi(value);
142 else if (!strcmp(name, "snapshots")) 142 else if (!strcmp(name, "snapshots"))
143 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 143 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
144 else if (!strcmp(name, "enable-filter-overrides")) 144 else if (!strcmp(name, "enable-filter-overrides"))
145 ctx.cfg.enable_filter_overrides = atoi(value); 145 ctx.cfg.enable_filter_overrides = atoi(value);
146 else if (!strcmp(name, "enable-gitweb-owner")) 146 else if (!strcmp(name, "enable-gitweb-owner"))
147 ctx.cfg.enable_gitweb_owner = atoi(value); 147 ctx.cfg.enable_gitweb_owner = atoi(value);
148 else if (!strcmp(name, "enable-index-links")) 148 else if (!strcmp(name, "enable-index-links"))
149 ctx.cfg.enable_index_links = atoi(value); 149 ctx.cfg.enable_index_links = atoi(value);
150 else if (!strcmp(name, "enable-commit-graph")) 150 else if (!strcmp(name, "enable-commit-graph"))
151 ctx.cfg.enable_commit_graph = atoi(value); 151 ctx.cfg.enable_commit_graph = atoi(value);
152 else if (!strcmp(name, "enable-log-filecount")) 152 else if (!strcmp(name, "enable-log-filecount"))
153 ctx.cfg.enable_log_filecount = atoi(value); 153 ctx.cfg.enable_log_filecount = atoi(value);
154 else if (!strcmp(name, "enable-log-linecount")) 154 else if (!strcmp(name, "enable-log-linecount"))
155 ctx.cfg.enable_log_linecount = atoi(value); 155 ctx.cfg.enable_log_linecount = atoi(value);
156 else if (!strcmp(name, "enable-remote-branches")) 156 else if (!strcmp(name, "enable-remote-branches"))
157 ctx.cfg.enable_remote_branches = atoi(value); 157 ctx.cfg.enable_remote_branches = atoi(value);
158 else if (!strcmp(name, "enable-subject-links")) 158 else if (!strcmp(name, "enable-subject-links"))
159 ctx.cfg.enable_subject_links = atoi(value); 159 ctx.cfg.enable_subject_links = atoi(value);
160 else if (!strcmp(name, "enable-tree-linenumbers")) 160 else if (!strcmp(name, "enable-tree-linenumbers"))
161 ctx.cfg.enable_tree_linenumbers = atoi(value); 161 ctx.cfg.enable_tree_linenumbers = atoi(value);
162 else if (!strcmp(name, "max-stats")) 162 else if (!strcmp(name, "max-stats"))
163 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 163 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
164 else if (!strcmp(name, "cache-size")) 164 else if (!strcmp(name, "cache-size"))
165 ctx.cfg.cache_size = atoi(value); 165 ctx.cfg.cache_size = atoi(value);
166 else if (!strcmp(name, "cache-root")) 166 else if (!strcmp(name, "cache-root"))
167 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 167 ctx.cfg.cache_root = xstrdup(expand_macros(value));
168 else if (!strcmp(name, "cache-root-ttl")) 168 else if (!strcmp(name, "cache-root-ttl"))
169 ctx.cfg.cache_root_ttl = atoi(value); 169 ctx.cfg.cache_root_ttl = atoi(value);
170 else if (!strcmp(name, "cache-repo-ttl")) 170 else if (!strcmp(name, "cache-repo-ttl"))
171 ctx.cfg.cache_repo_ttl = atoi(value); 171 ctx.cfg.cache_repo_ttl = atoi(value);
172 else if (!strcmp(name, "cache-scanrc-ttl")) 172 else if (!strcmp(name, "cache-scanrc-ttl"))
173 ctx.cfg.cache_scanrc_ttl = atoi(value); 173 ctx.cfg.cache_scanrc_ttl = atoi(value);
174 else if (!strcmp(name, "cache-static-ttl")) 174 else if (!strcmp(name, "cache-static-ttl"))
175 ctx.cfg.cache_static_ttl = atoi(value); 175 ctx.cfg.cache_static_ttl = atoi(value);
176 else if (!strcmp(name, "cache-dynamic-ttl")) 176 else if (!strcmp(name, "cache-dynamic-ttl"))
177 ctx.cfg.cache_dynamic_ttl = atoi(value); 177 ctx.cfg.cache_dynamic_ttl = atoi(value);
178 else if (!strcmp(name, "about-filter")) 178 else if (!strcmp(name, "about-filter"))
179 ctx.cfg.about_filter = new_filter(value, 0); 179 ctx.cfg.about_filter = new_filter(value, 0);
180 else if (!strcmp(name, "commit-filter")) 180 else if (!strcmp(name, "commit-filter"))
181 ctx.cfg.commit_filter = new_filter(value, 0); 181 ctx.cfg.commit_filter = new_filter(value, 0);
182 else if (!strcmp(name, "embedded")) 182 else if (!strcmp(name, "embedded"))
183 ctx.cfg.embedded = atoi(value); 183 ctx.cfg.embedded = atoi(value);
184 else if (!strcmp(name, "max-atom-items")) 184 else if (!strcmp(name, "max-atom-items"))
185 ctx.cfg.max_atom_items = atoi(value); 185 ctx.cfg.max_atom_items = atoi(value);
186 else if (!strcmp(name, "max-message-length")) 186 else if (!strcmp(name, "max-message-length"))
187 ctx.cfg.max_msg_len = atoi(value); 187 ctx.cfg.max_msg_len = atoi(value);
188 else if (!strcmp(name, "max-repodesc-length")) 188 else if (!strcmp(name, "max-repodesc-length"))
189 ctx.cfg.max_repodesc_len = atoi(value); 189 ctx.cfg.max_repodesc_len = atoi(value);
190 else if (!strcmp(name, "max-blob-size")) 190 else if (!strcmp(name, "max-blob-size"))
191 ctx.cfg.max_blob_size = atoi(value); 191 ctx.cfg.max_blob_size = atoi(value);
192 else if (!strcmp(name, "max-repo-count")) 192 else if (!strcmp(name, "max-repo-count"))
193 ctx.cfg.max_repo_count = atoi(value); 193 ctx.cfg.max_repo_count = atoi(value);
194 else if (!strcmp(name, "max-commit-count")) 194 else if (!strcmp(name, "max-commit-count"))
195 ctx.cfg.max_commit_count = atoi(value); 195 ctx.cfg.max_commit_count = atoi(value);
196 else if (!strcmp(name, "project-list")) 196 else if (!strcmp(name, "project-list"))
197 ctx.cfg.project_list = xstrdup(expand_macros(value)); 197 ctx.cfg.project_list = xstrdup(expand_macros(value));
198 else if (!strcmp(name, "scan-path")) 198 else if (!strcmp(name, "scan-path"))
199 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 199 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
200 process_cached_repolist(expand_macros(value)); 200 process_cached_repolist(expand_macros(value));
201 else if (ctx.cfg.project_list) 201 else if (ctx.cfg.project_list)
202 scan_projects(expand_macros(value), 202 scan_projects(expand_macros(value),
203 ctx.cfg.project_list, repo_config); 203 ctx.cfg.project_list, repo_config);
204 else 204 else
205 scan_tree(expand_macros(value), repo_config); 205 scan_tree(expand_macros(value), repo_config);
206 else if (!strcmp(name, "scan-hidden-path")) 206 else if (!strcmp(name, "scan-hidden-path"))
207 ctx.cfg.scan_hidden_path = atoi(value); 207 ctx.cfg.scan_hidden_path = atoi(value);
208 else if (!strcmp(name, "section-from-path")) 208 else if (!strcmp(name, "section-from-path"))
209 ctx.cfg.section_from_path = atoi(value); 209 ctx.cfg.section_from_path = atoi(value);
210 else if (!strcmp(name, "source-filter")) 210 else if (!strcmp(name, "source-filter"))
211 ctx.cfg.source_filter = new_filter(value, 1); 211 ctx.cfg.source_filter = new_filter(value, 1);
212 else if (!strcmp(name, "summary-log")) 212 else if (!strcmp(name, "summary-log"))
213 ctx.cfg.summary_log = atoi(value); 213 ctx.cfg.summary_log = atoi(value);
214 else if (!strcmp(name, "summary-branches")) 214 else if (!strcmp(name, "summary-branches"))
215 ctx.cfg.summary_branches = atoi(value); 215 ctx.cfg.summary_branches = atoi(value);
216 else if (!strcmp(name, "summary-tags")) 216 else if (!strcmp(name, "summary-tags"))
217 ctx.cfg.summary_tags = atoi(value); 217 ctx.cfg.summary_tags = atoi(value);
218 else if (!strcmp(name, "side-by-side-diffs")) 218 else if (!strcmp(name, "side-by-side-diffs"))
219 ctx.cfg.ssdiff = atoi(value); 219 ctx.cfg.ssdiff = atoi(value);
220 else if (!strcmp(name, "agefile")) 220 else if (!strcmp(name, "agefile"))
221 ctx.cfg.agefile = xstrdup(value); 221 ctx.cfg.agefile = xstrdup(value);
222 else if (!strcmp(name, "renamelimit")) 222 else if (!strcmp(name, "renamelimit"))
223 ctx.cfg.renamelimit = atoi(value); 223 ctx.cfg.renamelimit = atoi(value);
224 else if (!strcmp(name, "remove-suffix")) 224 else if (!strcmp(name, "remove-suffix"))
225 ctx.cfg.remove_suffix = atoi(value); 225 ctx.cfg.remove_suffix = atoi(value);
226 else if (!strcmp(name, "robots")) 226 else if (!strcmp(name, "robots"))
227 ctx.cfg.robots = xstrdup(value); 227 ctx.cfg.robots = xstrdup(value);
228 else if (!strcmp(name, "clone-prefix")) 228 else if (!strcmp(name, "clone-prefix"))
229 ctx.cfg.clone_prefix = xstrdup(value); 229 ctx.cfg.clone_prefix = xstrdup(value);
230 else if (!strcmp(name, "local-time")) 230 else if (!strcmp(name, "local-time"))
231 ctx.cfg.local_time = atoi(value); 231 ctx.cfg.local_time = atoi(value);
232 else if (!prefixcmp(name, "mimetype.")) 232 else if (!prefixcmp(name, "mimetype."))
233 add_mimetype(name + 9, value); 233 add_mimetype(name + 9, value);
234 else if (!strcmp(name, "include")) 234 else if (!strcmp(name, "include"))
235 parse_configfile(expand_macros(value), config_cb); 235 parse_configfile(expand_macros(value), config_cb);
236} 236}
237 237
238static void querystring_cb(const char *name, const char *value) 238static void querystring_cb(const char *name, const char *value)
239{ 239{
240 if (!value) 240 if (!value)
241 value = ""; 241 value = "";
242 242
243 if (!strcmp(name,"r")) { 243 if (!strcmp(name,"r")) {
244 ctx.qry.repo = xstrdup(value); 244 ctx.qry.repo = xstrdup(value);
245 ctx.repo = cgit_get_repoinfo(value); 245 ctx.repo = cgit_get_repoinfo(value);
246 } else if (!strcmp(name, "p")) { 246 } else if (!strcmp(name, "p")) {
247 ctx.qry.page = xstrdup(value); 247 ctx.qry.page = xstrdup(value);
248 } else if (!strcmp(name, "url")) { 248 } else if (!strcmp(name, "url")) {
249 if (*value == '/') 249 if (*value == '/')
250 value++; 250 value++;
251 ctx.qry.url = xstrdup(value); 251 ctx.qry.url = xstrdup(value);
252 cgit_parse_url(value); 252 cgit_parse_url(value);
253 } else if (!strcmp(name, "qt")) { 253 } else if (!strcmp(name, "qt")) {
254 ctx.qry.grep = xstrdup(value); 254 ctx.qry.grep = xstrdup(value);
255 } else if (!strcmp(name, "q")) { 255 } else if (!strcmp(name, "q")) {
256 ctx.qry.search = xstrdup(value); 256 ctx.qry.search = xstrdup(value);
257 } else if (!strcmp(name, "h")) { 257 } else if (!strcmp(name, "h")) {
258 ctx.qry.head = xstrdup(value); 258 ctx.qry.head = xstrdup(value);
259 ctx.qry.has_symref = 1; 259 ctx.qry.has_symref = 1;
260 } else if (!strcmp(name, "id")) { 260 } else if (!strcmp(name, "id")) {
261 ctx.qry.sha1 = xstrdup(value); 261 ctx.qry.sha1 = xstrdup(value);
262 ctx.qry.has_sha1 = 1; 262 ctx.qry.has_sha1 = 1;
263 } else if (!strcmp(name, "id2")) { 263 } else if (!strcmp(name, "id2")) {
264 ctx.qry.sha2 = xstrdup(value); 264 ctx.qry.sha2 = xstrdup(value);
265 ctx.qry.has_sha1 = 1; 265 ctx.qry.has_sha1 = 1;
266 } else if (!strcmp(name, "ofs")) { 266 } else if (!strcmp(name, "ofs")) {
267 ctx.qry.ofs = atoi(value); 267 ctx.qry.ofs = atoi(value);
268 } else if (!strcmp(name, "path")) { 268 } else if (!strcmp(name, "path")) {
269 ctx.qry.path = trim_end(value, '/'); 269 ctx.qry.path = trim_end(value, '/');
270 } else if (!strcmp(name, "name")) { 270 } else if (!strcmp(name, "name")) {
271 ctx.qry.name = xstrdup(value); 271 ctx.qry.name = xstrdup(value);
272 } else if (!strcmp(name, "mimetype")) { 272 } else if (!strcmp(name, "mimetype")) {
273 ctx.qry.mimetype = xstrdup(value); 273 ctx.qry.mimetype = xstrdup(value);
274 } else if (!strcmp(name, "s")){ 274 } else if (!strcmp(name, "s")){
275 ctx.qry.sort = xstrdup(value); 275 ctx.qry.sort = xstrdup(value);
276 } else if (!strcmp(name, "showmsg")) { 276 } else if (!strcmp(name, "showmsg")) {
277 ctx.qry.showmsg = atoi(value); 277 ctx.qry.showmsg = atoi(value);
278 } else if (!strcmp(name, "period")) { 278 } else if (!strcmp(name, "period")) {
279 ctx.qry.period = xstrdup(value); 279 ctx.qry.period = xstrdup(value);
280 } else if (!strcmp(name, "ss")) { 280 } else if (!strcmp(name, "ss")) {
281 ctx.qry.ssdiff = atoi(value); 281 ctx.qry.ssdiff = atoi(value);
282 } else if (!strcmp(name, "all")) { 282 } else if (!strcmp(name, "all")) {
283 ctx.qry.show_all = atoi(value); 283 ctx.qry.show_all = atoi(value);
284 } else if (!strcmp(name, "context")) { 284 } else if (!strcmp(name, "context")) {
285 ctx.qry.context = atoi(value); 285 ctx.qry.context = atoi(value);
286 } else if (!strcmp(name, "ignorews")) { 286 } else if (!strcmp(name, "ignorews")) {
287 ctx.qry.ignorews = atoi(value); 287 ctx.qry.ignorews = atoi(value);
288 } 288 }
289} 289}
290 290
291char *xstrdupn(const char *str) 291char *xstrdupn(const char *str)
292{ 292{
293 return (str ? xstrdup(str) : NULL); 293 return (str ? xstrdup(str) : NULL);
294} 294}
295 295
296static void prepare_context(struct cgit_context *ctx) 296static void prepare_context(struct cgit_context *ctx)
297{ 297{
298 memset(ctx, 0, sizeof(*ctx)); 298 memset(ctx, 0, sizeof(*ctx));
299 ctx->cfg.agefile = "info/web/last-modified"; 299 ctx->cfg.agefile = "info/web/last-modified";
300 ctx->cfg.nocache = 0; 300 ctx->cfg.nocache = 0;
301 ctx->cfg.cache_size = 0; 301 ctx->cfg.cache_size = 0;
302 ctx->cfg.cache_dynamic_ttl = 5; 302 ctx->cfg.cache_dynamic_ttl = 5;
303 ctx->cfg.cache_max_create_time = 5; 303 ctx->cfg.cache_max_create_time = 5;
304 ctx->cfg.cache_repo_ttl = 5; 304 ctx->cfg.cache_repo_ttl = 5;
305 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 305 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
306 ctx->cfg.cache_root_ttl = 5; 306 ctx->cfg.cache_root_ttl = 5;
307 ctx->cfg.cache_scanrc_ttl = 15; 307 ctx->cfg.cache_scanrc_ttl = 15;
308 ctx->cfg.cache_static_ttl = -1; 308 ctx->cfg.cache_static_ttl = -1;
309 ctx->cfg.css = "/cgit.css"; 309 ctx->cfg.css = "/cgit.css";
310 ctx->cfg.logo = "/cgit.png"; 310 ctx->cfg.logo = "/cgit.png";
311 ctx->cfg.local_time = 0; 311 ctx->cfg.local_time = 0;
312 ctx->cfg.enable_gitweb_owner = 1; 312 ctx->cfg.enable_gitweb_owner = 1;
313 ctx->cfg.enable_tree_linenumbers = 1; 313 ctx->cfg.enable_tree_linenumbers = 1;
314 ctx->cfg.max_repo_count = 50; 314 ctx->cfg.max_repo_count = 50;
315 ctx->cfg.max_commit_count = 50; 315 ctx->cfg.max_commit_count = 50;
316 ctx->cfg.max_lock_attempts = 5; 316 ctx->cfg.max_lock_attempts = 5;
317 ctx->cfg.max_msg_len = 80; 317 ctx->cfg.max_msg_len = 80;
318 ctx->cfg.max_repodesc_len = 80; 318 ctx->cfg.max_repodesc_len = 80;
319 ctx->cfg.max_blob_size = 0; 319 ctx->cfg.max_blob_size = 0;
320 ctx->cfg.max_stats = 0; 320 ctx->cfg.max_stats = 0;
321 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 321 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
322 ctx->cfg.project_list = NULL; 322 ctx->cfg.project_list = NULL;
323 ctx->cfg.renamelimit = -1; 323 ctx->cfg.renamelimit = -1;
324 ctx->cfg.remove_suffix = 0; 324 ctx->cfg.remove_suffix = 0;
325 ctx->cfg.robots = "index, nofollow"; 325 ctx->cfg.robots = "index, nofollow";
326 ctx->cfg.root_title = "Git repository browser"; 326 ctx->cfg.root_title = "Git repository browser";
327 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 327 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
328 ctx->cfg.scan_hidden_path = 0; 328 ctx->cfg.scan_hidden_path = 0;
329 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 329 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
330 ctx->cfg.section = ""; 330 ctx->cfg.section = "";
331 ctx->cfg.summary_branches = 10; 331 ctx->cfg.summary_branches = 10;
332 ctx->cfg.summary_log = 10; 332 ctx->cfg.summary_log = 10;
333 ctx->cfg.summary_tags = 10; 333 ctx->cfg.summary_tags = 10;
334 ctx->cfg.max_atom_items = 10; 334 ctx->cfg.max_atom_items = 10;
335 ctx->cfg.ssdiff = 0; 335 ctx->cfg.ssdiff = 0;
336 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 336 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
337 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 337 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
338 ctx->env.https = xstrdupn(getenv("HTTPS")); 338 ctx->env.https = xstrdupn(getenv("HTTPS"));
339 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 339 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
340 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 340 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
341 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 341 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
342 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 342 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
343 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 343 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
344 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 344 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
345 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 345 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
346 ctx->page.mimetype = "text/html"; 346 ctx->page.mimetype = "text/html";
347 ctx->page.charset = PAGE_ENCODING; 347 ctx->page.charset = PAGE_ENCODING;
348 ctx->page.filename = NULL; 348 ctx->page.filename = NULL;
349 ctx->page.size = 0; 349 ctx->page.size = 0;
350 ctx->page.modified = time(NULL); 350 ctx->page.modified = time(NULL);
351 ctx->page.expires = ctx->page.modified; 351 ctx->page.expires = ctx->page.modified;
352 ctx->page.etag = NULL; 352 ctx->page.etag = NULL;
353 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 353 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
354 if (ctx->env.script_name) 354 if (ctx->env.script_name)
355 ctx->cfg.script_name = ctx->env.script_name; 355 ctx->cfg.script_name = ctx->env.script_name;
356 if (ctx->env.query_string) 356 if (ctx->env.query_string)
357 ctx->qry.raw = ctx->env.query_string; 357 ctx->qry.raw = ctx->env.query_string;
358 if (!ctx->env.cgit_config) 358 if (!ctx->env.cgit_config)
359 ctx->env.cgit_config = CGIT_CONFIG; 359 ctx->env.cgit_config = CGIT_CONFIG;
360} 360}
361 361
362struct refmatch { 362struct refmatch {
363 char *req_ref; 363 char *req_ref;
364 char *first_ref; 364 char *first_ref;
365 int match; 365 int match;
366}; 366};
367 367
368int find_current_ref(const char *refname, const unsigned char *sha1, 368int find_current_ref(const char *refname, const unsigned char *sha1,
369 int flags, void *cb_data) 369 int flags, void *cb_data)
370{ 370{
371 struct refmatch *info; 371 struct refmatch *info;
372 372
373 info = (struct refmatch *)cb_data; 373 info = (struct refmatch *)cb_data;
374 if (!strcmp(refname, info->req_ref)) 374 if (!strcmp(refname, info->req_ref))
375 info->match = 1; 375 info->match = 1;
376 if (!info->first_ref) 376 if (!info->first_ref)
377 info->first_ref = xstrdup(refname); 377 info->first_ref = xstrdup(refname);
378 return info->match; 378 return info->match;
379} 379}
380 380
381char *find_default_branch(struct cgit_repo *repo) 381char *find_default_branch(struct cgit_repo *repo)
382{ 382{
383 struct refmatch info; 383 struct refmatch info;
384 char *ref; 384 char *ref;
385 385
386 info.req_ref = repo->defbranch; 386 info.req_ref = repo->defbranch;
387 info.first_ref = NULL; 387 info.first_ref = NULL;
388 info.match = 0; 388 info.match = 0;
389 for_each_branch_ref(find_current_ref, &info); 389 for_each_branch_ref(find_current_ref, &info);
390 if (info.match) 390 if (info.match)
391 ref = info.req_ref; 391 ref = info.req_ref;
392 else 392 else
393 ref = info.first_ref; 393 ref = info.first_ref;
394 if (ref) 394 if (ref)
395 ref = xstrdup(ref); 395 ref = xstrdup(ref);
396 return ref; 396 return ref;
397} 397}
398 398
399static int prepare_repo_cmd(struct cgit_context *ctx) 399static int prepare_repo_cmd(struct cgit_context *ctx)
400{ 400{
401 char *tmp; 401 char *tmp;
402 unsigned char sha1[20]; 402 unsigned char sha1[20];
403 int nongit = 0; 403 int nongit = 0;
404 404
405 setenv("GIT_DIR", ctx->repo->path, 1); 405 setenv("GIT_DIR", ctx->repo->path, 1);
406 setup_git_directory_gently(&nongit); 406 setup_git_directory_gently(&nongit);
407 if (nongit) { 407 if (nongit) {
408 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 408 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
409 "config error"); 409 "config error");
410 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 410 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
411 ctx->repo = NULL; 411 ctx->repo = NULL;
412 cgit_print_http_headers(ctx); 412 cgit_print_http_headers(ctx);
413 cgit_print_docstart(ctx); 413 cgit_print_docstart(ctx);
414 cgit_print_pageheader(ctx); 414 cgit_print_pageheader(ctx);
415 cgit_print_error(tmp); 415 cgit_print_error(tmp);
416 cgit_print_docend(); 416 cgit_print_docend();
417 return 1; 417 return 1;
418 } 418 }
419 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 419 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
420 420
421 if (!ctx->qry.head) { 421 if (!ctx->qry.head) {
422 ctx->qry.nohead = 1; 422 ctx->qry.nohead = 1;
423 ctx->qry.head = find_default_branch(ctx->repo); 423 ctx->qry.head = find_default_branch(ctx->repo);
424 ctx->repo->defbranch = ctx->qry.head; 424 ctx->repo->defbranch = ctx->qry.head;
425 } 425 }
426 426
427 if (!ctx->qry.head) { 427 if (!ctx->qry.head) {
428 cgit_print_http_headers(ctx); 428 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 429 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
431 cgit_print_error("Repository seems to be empty"); 431 cgit_print_error("Repository seems to be empty");
432 cgit_print_docend(); 432 cgit_print_docend();
433 return 1; 433 return 1;
434 } 434 }
435 435
436 if (get_sha1(ctx->qry.head, sha1)) { 436 if (get_sha1(ctx->qry.head, sha1)) {
437 tmp = xstrdup(ctx->qry.head); 437 tmp = xstrdup(ctx->qry.head);
438 ctx->qry.head = ctx->repo->defbranch; 438 ctx->qry.head = ctx->repo->defbranch;
439 ctx->page.status = 404; 439 ctx->page.status = 404;
440 ctx->page.statusmsg = "not found"; 440 ctx->page.statusmsg = "not found";
441 cgit_print_http_headers(ctx); 441 cgit_print_http_headers(ctx);
442 cgit_print_docstart(ctx); 442 cgit_print_docstart(ctx);
443 cgit_print_pageheader(ctx); 443 cgit_print_pageheader(ctx);
444 cgit_print_error(fmt("Invalid branch: %s", tmp)); 444 cgit_print_error(fmt("Invalid branch: %s", tmp));
445 cgit_print_docend(); 445 cgit_print_docend();
446 return 1; 446 return 1;
447 } 447 }
448 return 0; 448 return 0;
449} 449}
450 450
451static void process_request(void *cbdata) 451static void process_request(void *cbdata)
452{ 452{
453 struct cgit_context *ctx = cbdata; 453 struct cgit_context *ctx = cbdata;
454 struct cgit_cmd *cmd; 454 struct cgit_cmd *cmd;
455 455
456 cmd = cgit_get_cmd(ctx); 456 cmd = cgit_get_cmd(ctx);
457 if (!cmd) { 457 if (!cmd) {
458 ctx->page.title = "cgit error"; 458 ctx->page.title = "cgit error";
459 cgit_print_http_headers(ctx); 459 cgit_print_http_headers(ctx);
460 cgit_print_docstart(ctx); 460 cgit_print_docstart(ctx);
461 cgit_print_pageheader(ctx); 461 cgit_print_pageheader(ctx);
462 cgit_print_error("Invalid request"); 462 cgit_print_error("Invalid request");
463 cgit_print_docend(); 463 cgit_print_docend();
464 return; 464 return;
465 } 465 }
466 466
467 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual" 467 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
468 * in-project path limit to be made available at ctx->qry.vpath. 468 * in-project path limit to be made available at ctx->qry.vpath.
469 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). 469 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
470 */ 470 */
471 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 471 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
472 472
473 if (cmd->want_repo && !ctx->repo) { 473 if (cmd->want_repo && !ctx->repo) {
474 cgit_print_http_headers(ctx); 474 cgit_print_http_headers(ctx);
475 cgit_print_docstart(ctx); 475 cgit_print_docstart(ctx);
476 cgit_print_pageheader(ctx); 476 cgit_print_pageheader(ctx);
477 cgit_print_error(fmt("No repository selected")); 477 cgit_print_error(fmt("No repository selected"));
478 cgit_print_docend(); 478 cgit_print_docend();
479 return; 479 return;
480 } 480 }
481 481
482 if (ctx->repo && prepare_repo_cmd(ctx)) 482 if (ctx->repo && prepare_repo_cmd(ctx))
483 return; 483 return;
484 484
485 if (cmd->want_layout) { 485 if (cmd->want_layout) {
486 cgit_print_http_headers(ctx); 486 cgit_print_http_headers(ctx);
487 cgit_print_docstart(ctx); 487 cgit_print_docstart(ctx);
488 cgit_print_pageheader(ctx); 488 cgit_print_pageheader(ctx);
489 } 489 }
490 490
491 cmd->fn(ctx); 491 cmd->fn(ctx);
492 492
493 if (cmd->want_layout) 493 if (cmd->want_layout)
494 cgit_print_docend(); 494 cgit_print_docend();
495} 495}
496 496
497int cmp_repos(const void *a, const void *b) 497int cmp_repos(const void *a, const void *b)
498{ 498{
499 const struct cgit_repo *ra = a, *rb = b; 499 const struct cgit_repo *ra = a, *rb = b;
500 return strcmp(ra->url, rb->url); 500 return strcmp(ra->url, rb->url);
501} 501}
502 502
503char *build_snapshot_setting(int bitmap) 503char *build_snapshot_setting(int bitmap)
504{ 504{
505 const struct cgit_snapshot_format *f; 505 const struct cgit_snapshot_format *f;
506 char *result = xstrdup(""); 506 char *result = xstrdup("");
507 char *tmp; 507 char *tmp;
508 int len; 508 int len;
509 509
510 for (f = cgit_snapshot_formats; f->suffix; f++) { 510 for (f = cgit_snapshot_formats; f->suffix; f++) {
511 if (f->bit & bitmap) { 511 if (f->bit & bitmap) {
512 tmp = result; 512 tmp = result;
513 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 513 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
514 free(tmp); 514 free(tmp);
515 } 515 }
516 } 516 }
517 len = strlen(result); 517 len = strlen(result);
518 if (len) 518 if (len)
519 result[len - 1] = '\0'; 519 result[len - 1] = '\0';
520 return result; 520 return result;
521} 521}
522 522
523char *get_first_line(char *txt) 523char *get_first_line(char *txt)
524{ 524{
525 char *t = xstrdup(txt); 525 char *t = xstrdup(txt);
526 char *p = strchr(t, '\n'); 526 char *p = strchr(t, '\n');
527 if (p) 527 if (p)
528 *p = '\0'; 528 *p = '\0';
529 return t; 529 return t;
530} 530}
531 531
532void print_repo(FILE *f, struct cgit_repo *repo) 532void print_repo(FILE *f, struct cgit_repo *repo)
533{ 533{
534 fprintf(f, "repo.url=%s\n", repo->url); 534 fprintf(f, "repo.url=%s\n", repo->url);
535 fprintf(f, "repo.name=%s\n", repo->name); 535 fprintf(f, "repo.name=%s\n", repo->name);
536 fprintf(f, "repo.path=%s\n", repo->path); 536 fprintf(f, "repo.path=%s\n", repo->path);
537 if (repo->owner) 537 if (repo->owner)
538 fprintf(f, "repo.owner=%s\n", repo->owner); 538 fprintf(f, "repo.owner=%s\n", repo->owner);
539 if (repo->desc) { 539 if (repo->desc) {
540 char *tmp = get_first_line(repo->desc); 540 char *tmp = get_first_line(repo->desc);
541 fprintf(f, "repo.desc=%s\n", tmp); 541 fprintf(f, "repo.desc=%s\n", tmp);
542 free(tmp); 542 free(tmp);
543 } 543 }
544 if (repo->readme) 544 if (repo->readme)
545 fprintf(f, "repo.readme=%s\n", repo->readme); 545 fprintf(f, "repo.readme=%s\n", repo->readme);
546 if (repo->defbranch) 546 if (repo->defbranch)
547 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 547 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
548 if (repo->module_link) 548 if (repo->module_link)
549 fprintf(f, "repo.module-link=%s\n", repo->module_link); 549 fprintf(f, "repo.module-link=%s\n", repo->module_link);
550 if (repo->section) 550 if (repo->section)
551 fprintf(f, "repo.section=%s\n", repo->section); 551 fprintf(f, "repo.section=%s\n", repo->section);
552 if (repo->clone_url) 552 if (repo->clone_url)
553 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 553 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
554 fprintf(f, "repo.enable-commit-graph=%d\n", 554 fprintf(f, "repo.enable-commit-graph=%d\n",
555 repo->enable_commit_graph); 555 repo->enable_commit_graph);
556 fprintf(f, "repo.enable-log-filecount=%d\n", 556 fprintf(f, "repo.enable-log-filecount=%d\n",
557 repo->enable_log_filecount); 557 repo->enable_log_filecount);
558 fprintf(f, "repo.enable-log-linecount=%d\n", 558 fprintf(f, "repo.enable-log-linecount=%d\n",
559 repo->enable_log_linecount); 559 repo->enable_log_linecount);
560 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 560 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
561 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 561 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
562 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 562 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
563 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 563 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
564 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 564 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
565 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 565 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
566 if (repo->snapshots != ctx.cfg.snapshots) { 566 if (repo->snapshots != ctx.cfg.snapshots) {
567 char *tmp = build_snapshot_setting(repo->snapshots); 567 char *tmp = build_snapshot_setting(repo->snapshots);
568 fprintf(f, "repo.snapshots=%s\n", tmp); 568 fprintf(f, "repo.snapshots=%s\n", tmp);
569 free(tmp); 569 free(tmp);
570 } 570 }
571 if (repo->max_stats != ctx.cfg.max_stats) 571 if (repo->max_stats != ctx.cfg.max_stats)
572 fprintf(f, "repo.max-stats=%s\n", 572 fprintf(f, "repo.max-stats=%s\n",
573 cgit_find_stats_periodname(repo->max_stats)); 573 cgit_find_stats_periodname(repo->max_stats));
574 fprintf(f, "\n"); 574 fprintf(f, "\n");
575} 575}
576 576
577void print_repolist(FILE *f, struct cgit_repolist *list, int start) 577void print_repolist(FILE *f, struct cgit_repolist *list, int start)
578{ 578{
579 int i; 579 int i;
580 580
581 for(i = start; i < list->count; i++) 581 for(i = start; i < list->count; i++)
582 print_repo(f, &list->repos[i]); 582 print_repo(f, &list->repos[i]);
583} 583}
584 584
585/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 585/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
586 * and return 0 on success. 586 * and return 0 on success.
587 */ 587 */
588static int generate_cached_repolist(const char *path, const char *cached_rc) 588static int generate_cached_repolist(const char *path, const char *cached_rc)
589{ 589{
590 char *locked_rc; 590 char *locked_rc;
591 int idx; 591 int idx;
592 FILE *f; 592 FILE *f;
593 593
594 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 594 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
595 f = fopen(locked_rc, "wx"); 595 f = fopen(locked_rc, "wx");
596 if (!f) { 596 if (!f) {
597 /* Inform about the error unless the lockfile already existed, 597 /* Inform about the error unless the lockfile already existed,
598 * since that only means we've got concurrent requests. 598 * since that only means we've got concurrent requests.
599 */ 599 */
600 if (errno != EEXIST) 600 if (errno != EEXIST)
601 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 601 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
602 locked_rc, strerror(errno), errno); 602 locked_rc, strerror(errno), errno);
603 return errno; 603 return errno;
604 } 604 }
605 idx = cgit_repolist.count; 605 idx = cgit_repolist.count;
606 if (ctx.cfg.project_list) 606 if (ctx.cfg.project_list)
607 scan_projects(path, ctx.cfg.project_list, repo_config); 607 scan_projects(path, ctx.cfg.project_list, repo_config);
608 else 608 else
609 scan_tree(path, repo_config); 609 scan_tree(path, repo_config);
610 print_repolist(f, &cgit_repolist, idx); 610 print_repolist(f, &cgit_repolist, idx);
611 if (rename(locked_rc, cached_rc)) 611 if (rename(locked_rc, cached_rc))
612 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 612 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
613 locked_rc, cached_rc, strerror(errno), errno); 613 locked_rc, cached_rc, strerror(errno), errno);
614 fclose(f); 614 fclose(f);
615 return 0; 615 return 0;
616} 616}
617 617
618static void process_cached_repolist(const char *path) 618static void process_cached_repolist(const char *path)
619{ 619{
620 struct stat st; 620 struct stat st;
621 char *cached_rc; 621 char *cached_rc;
622 time_t age; 622 time_t age;
623 unsigned long hash; 623 unsigned long hash;
624 624
625 hash = hash_str(path); 625 hash = hash_str(path);
626 if (ctx.cfg.project_list) 626 if (ctx.cfg.project_list)
627 hash += hash_str(ctx.cfg.project_list); 627 hash += hash_str(ctx.cfg.project_list);
628 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash)); 628 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
629 629
630 if (stat(cached_rc, &st)) { 630 if (stat(cached_rc, &st)) {
631 /* Nothing is cached, we need to scan without forking. And 631 /* Nothing is cached, we need to scan without forking. And
632 * if we fail to generate a cached repolist, we need to 632 * if we fail to generate a cached repolist, we need to
633 * invoke scan_tree manually. 633 * invoke scan_tree manually.
634 */ 634 */
635 if (generate_cached_repolist(path, cached_rc)) { 635 if (generate_cached_repolist(path, cached_rc)) {
636 if (ctx.cfg.project_list) 636 if (ctx.cfg.project_list)
637 scan_projects(path, ctx.cfg.project_list, 637 scan_projects(path, ctx.cfg.project_list,
638 repo_config); 638 repo_config);
639 else 639 else
640 scan_tree(path, repo_config); 640 scan_tree(path, repo_config);
641 } 641 }
642 return; 642 return;
643 } 643 }
644 644
645 parse_configfile(cached_rc, config_cb); 645 parse_configfile(cached_rc, config_cb);
646 646
647 /* If the cached configfile hasn't expired, lets exit now */ 647 /* If the cached configfile hasn't expired, lets exit now */
648 age = time(NULL) - st.st_mtime; 648 age = time(NULL) - st.st_mtime;
649 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 649 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
650 return; 650 return;
651 651
652 /* The cached repolist has been parsed, but it was old. So lets 652 /* The cached repolist has been parsed, but it was old. So lets
653 * rescan the specified path and generate a new cached repolist 653 * rescan the specified path and generate a new cached repolist
654 * in a child-process to avoid latency for the current request. 654 * in a child-process to avoid latency for the current request.
655 */ 655 */
656 if (fork()) 656 if (fork())
657 return; 657 return;
658 658
659 exit(generate_cached_repolist(path, cached_rc)); 659 exit(generate_cached_repolist(path, cached_rc));
660} 660}
661 661
662static void cgit_parse_args(int argc, const char **argv) 662static void cgit_parse_args(int argc, const char **argv)
663{ 663{
664 int i; 664 int i;
665 int scan = 0; 665 int scan = 0;
666 666
667 for (i = 1; i < argc; i++) { 667 for (i = 1; i < argc; i++) {
668 if (!strncmp(argv[i], "--cache=", 8)) { 668 if (!strncmp(argv[i], "--cache=", 8)) {
669 ctx.cfg.cache_root = xstrdup(argv[i]+8); 669 ctx.cfg.cache_root = xstrdup(argv[i]+8);
670 } 670 }
671 if (!strcmp(argv[i], "--nocache")) { 671 if (!strcmp(argv[i], "--nocache")) {
672 ctx.cfg.nocache = 1; 672 ctx.cfg.nocache = 1;
673 } 673 }
674 if (!strcmp(argv[i], "--nohttp")) { 674 if (!strcmp(argv[i], "--nohttp")) {
675 ctx.env.no_http = "1"; 675 ctx.env.no_http = "1";
676 } 676 }
677 if (!strncmp(argv[i], "--query=", 8)) { 677 if (!strncmp(argv[i], "--query=", 8)) {
678 ctx.qry.raw = xstrdup(argv[i]+8); 678 ctx.qry.raw = xstrdup(argv[i]+8);
679 } 679 }
680 if (!strncmp(argv[i], "--repo=", 7)) { 680 if (!strncmp(argv[i], "--repo=", 7)) {
681 ctx.qry.repo = xstrdup(argv[i]+7); 681 ctx.qry.repo = xstrdup(argv[i]+7);
682 } 682 }
683 if (!strncmp(argv[i], "--page=", 7)) { 683 if (!strncmp(argv[i], "--page=", 7)) {
684 ctx.qry.page = xstrdup(argv[i]+7); 684 ctx.qry.page = xstrdup(argv[i]+7);
685 } 685 }
686 if (!strncmp(argv[i], "--head=", 7)) { 686 if (!strncmp(argv[i], "--head=", 7)) {
687 ctx.qry.head = xstrdup(argv[i]+7); 687 ctx.qry.head = xstrdup(argv[i]+7);
688 ctx.qry.has_symref = 1; 688 ctx.qry.has_symref = 1;
689 } 689 }
690 if (!strncmp(argv[i], "--sha1=", 7)) { 690 if (!strncmp(argv[i], "--sha1=", 7)) {
691 ctx.qry.sha1 = xstrdup(argv[i]+7); 691 ctx.qry.sha1 = xstrdup(argv[i]+7);
692 ctx.qry.has_sha1 = 1; 692 ctx.qry.has_sha1 = 1;
693 } 693 }
694 if (!strncmp(argv[i], "--ofs=", 6)) { 694 if (!strncmp(argv[i], "--ofs=", 6)) {
695 ctx.qry.ofs = atoi(argv[i]+6); 695 ctx.qry.ofs = atoi(argv[i]+6);
696 } 696 }
697 if (!strncmp(argv[i], "--scan-tree=", 12) || 697 if (!strncmp(argv[i], "--scan-tree=", 12) ||
698 !strncmp(argv[i], "--scan-path=", 12)) { 698 !strncmp(argv[i], "--scan-path=", 12)) {
699 /* HACK: the global snapshot bitmask defines the 699 /* HACK: the global snapshot bitmask defines the
700 * set of allowed snapshot formats, but the config 700 * set of allowed snapshot formats, but the config
701 * file hasn't been parsed yet so the mask is 701 * file hasn't been parsed yet so the mask is
702 * currently 0. By setting all bits high before 702 * currently 0. By setting all bits high before
703 * scanning we make sure that any in-repo cgitrc 703 * scanning we make sure that any in-repo cgitrc
704 * snapshot setting is respected by scan_tree(). 704 * snapshot setting is respected by scan_tree().
705 * BTW: we assume that there'll never be more than 705 * BTW: we assume that there'll never be more than
706 * 255 different snapshot formats supported by cgit... 706 * 255 different snapshot formats supported by cgit...
707 */ 707 */
708 ctx.cfg.snapshots = 0xFF; 708 ctx.cfg.snapshots = 0xFF;
709 scan++; 709 scan++;
710 scan_tree(argv[i] + 12, repo_config); 710 scan_tree(argv[i] + 12, repo_config);
711 } 711 }
712 } 712 }
713 if (scan) { 713 if (scan) {
714 qsort(cgit_repolist.repos, cgit_repolist.count, 714 qsort(cgit_repolist.repos, cgit_repolist.count,
715 sizeof(struct cgit_repo), cmp_repos); 715 sizeof(struct cgit_repo), cmp_repos);
716 print_repolist(stdout, &cgit_repolist, 0); 716 print_repolist(stdout, &cgit_repolist, 0);
717 exit(0); 717 exit(0);
718 } 718 }
719} 719}
720 720
721static int calc_ttl() 721static int calc_ttl()
722{ 722{
723 if (!ctx.repo) 723 if (!ctx.repo)
724 return ctx.cfg.cache_root_ttl; 724 return ctx.cfg.cache_root_ttl;
725 725
726 if (!ctx.qry.page) 726 if (!ctx.qry.page)
727 return ctx.cfg.cache_repo_ttl; 727 return ctx.cfg.cache_repo_ttl;
728 728
729 if (ctx.qry.has_symref) 729 if (ctx.qry.has_symref)
730 return ctx.cfg.cache_dynamic_ttl; 730 return ctx.cfg.cache_dynamic_ttl;
731 731
732 if (ctx.qry.has_sha1) 732 if (ctx.qry.has_sha1)
733 return ctx.cfg.cache_static_ttl; 733 return ctx.cfg.cache_static_ttl;
734 734
735 return ctx.cfg.cache_repo_ttl; 735 return ctx.cfg.cache_repo_ttl;
736} 736}
737 737
738int main(int argc, const char **argv) 738int main(int argc, const char **argv)
739{ 739{
740 const char *path; 740 const char *path;
741 char *qry; 741 char *qry;
742 int err, ttl; 742 int err, ttl;
743 743
744 prepare_context(&ctx); 744 prepare_context(&ctx);
745 cgit_repolist.length = 0; 745 cgit_repolist.length = 0;
746 cgit_repolist.count = 0; 746 cgit_repolist.count = 0;
747 cgit_repolist.repos = NULL; 747 cgit_repolist.repos = NULL;
748 748
749 cgit_parse_args(argc, argv); 749 cgit_parse_args(argc, argv);
750 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); 750 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
751 ctx.repo = NULL; 751 ctx.repo = NULL;
752 http_parse_querystring(ctx.qry.raw, querystring_cb); 752 http_parse_querystring(ctx.qry.raw, querystring_cb);
753 753
754 /* If virtual-root isn't specified in cgitrc, lets pretend 754 /* If virtual-root isn't specified in cgitrc, lets pretend
755 * that virtual-root equals SCRIPT_NAME. 755 * that virtual-root equals SCRIPT_NAME, minus any possibly
756 * trailing slashes.
756 */ 757 */
757 if (!ctx.cfg.virtual_root) 758 if (!ctx.cfg.virtual_root)
758 ctx.cfg.virtual_root = ctx.cfg.script_name; 759 ctx.cfg.virtual_root = trim_end(ctx.cfg.script_name, '/');
759 760
760 /* If no url parameter is specified on the querystring, lets 761 /* If no url parameter is specified on the querystring, lets
761 * use PATH_INFO as url. This allows cgit to work with virtual 762 * use PATH_INFO as url. This allows cgit to work with virtual
762 * urls without the need for rewriterules in the webserver (as 763 * urls without the need for rewriterules in the webserver (as
763 * long as PATH_INFO is included in the cache lookup key). 764 * long as PATH_INFO is included in the cache lookup key).
764 */ 765 */
765 path = ctx.env.path_info; 766 path = ctx.env.path_info;
766 if (!ctx.qry.url && path) { 767 if (!ctx.qry.url && path) {
767 if (path[0] == '/') 768 if (path[0] == '/')
768 path++; 769 path++;
769 ctx.qry.url = xstrdup(path); 770 ctx.qry.url = xstrdup(path);
770 if (ctx.qry.raw) { 771 if (ctx.qry.raw) {
771 qry = ctx.qry.raw; 772 qry = ctx.qry.raw;
772 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 773 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
773 free(qry); 774 free(qry);
774 } else 775 } else
775 ctx.qry.raw = xstrdup(ctx.qry.url); 776 ctx.qry.raw = xstrdup(ctx.qry.url);
776 cgit_parse_url(ctx.qry.url); 777 cgit_parse_url(ctx.qry.url);
777 } 778 }
778 779
779 ttl = calc_ttl(); 780 ttl = calc_ttl();
780 ctx.page.expires += ttl*60; 781 ctx.page.expires += ttl*60;
781 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 782 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
782 ctx.cfg.nocache = 1; 783 ctx.cfg.nocache = 1;
783 if (ctx.cfg.nocache) 784 if (ctx.cfg.nocache)
784 ctx.cfg.cache_size = 0; 785 ctx.cfg.cache_size = 0;
785 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 786 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
786 ctx.qry.raw, ttl, process_request, &ctx); 787 ctx.qry.raw, ttl, process_request, &ctx);
787 if (err) 788 if (err)
788 cgit_print_error(fmt("Error processing page: %s (%d)", 789 cgit_print_error(fmt("Error processing page: %s (%d)",
789 strerror(err), err)); 790 strerror(err), err));
790 return err; 791 return err;
791} 792}
diff --git a/html.c b/html.c
index a1fe87d..a60bc13 100644
--- a/html.c
+++ b/html.c
@@ -1,320 +1,320 @@
1/* html.c: helper functions for html output 1/* html.c: helper functions for html output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */ 16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
17static const char* url_escape_table[256] = { 17static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", 18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", 19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", 20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
21 "%1e", "%1f", "+", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0, 21 "%1e", "%1f", "+", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0,
22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d", 22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d",
23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0, 24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0,
25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b", 25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b",
26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85", 26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", 27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", 28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3", 29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", 30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", 31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1", 32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb", 33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", 34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", 35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9", 36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3", 37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", 38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff" 39 "%fe", "%ff"
40}; 40};
41 41
42int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
43 43
44char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
45{ 45{
46 static char buf[8][1024]; 46 static char buf[8][1024];
47 static int bufidx; 47 static int bufidx;
48 int len; 48 int len;
49 va_list args; 49 va_list args;
50 50
51 bufidx++; 51 bufidx++;
52 bufidx &= 7; 52 bufidx &= 7;
53 53
54 va_start(args, format); 54 va_start(args, format);
55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
56 va_end(args); 56 va_end(args);
57 if (len>sizeof(buf[bufidx])) { 57 if (len>sizeof(buf[bufidx])) {
58 fprintf(stderr, "[html.c] string truncated: %s\n", format); 58 fprintf(stderr, "[html.c] string truncated: %s\n", format);
59 exit(1); 59 exit(1);
60 } 60 }
61 return buf[bufidx]; 61 return buf[bufidx];
62} 62}
63 63
64void html_raw(const char *data, size_t size) 64void html_raw(const char *data, size_t size)
65{ 65{
66 write(htmlfd, data, size); 66 write(htmlfd, data, size);
67} 67}
68 68
69void html(const char *txt) 69void html(const char *txt)
70{ 70{
71 write(htmlfd, txt, strlen(txt)); 71 write(htmlfd, txt, strlen(txt));
72} 72}
73 73
74void htmlf(const char *format, ...) 74void htmlf(const char *format, ...)
75{ 75{
76 static char buf[65536]; 76 static char buf[65536];
77 va_list args; 77 va_list args;
78 78
79 va_start(args, format); 79 va_start(args, format);
80 vsnprintf(buf, sizeof(buf), format, args); 80 vsnprintf(buf, sizeof(buf), format, args);
81 va_end(args); 81 va_end(args);
82 html(buf); 82 html(buf);
83} 83}
84 84
85void html_status(int code, const char *msg, int more_headers) 85void html_status(int code, const char *msg, int more_headers)
86{ 86{
87 htmlf("Status: %d %s\n", code, msg); 87 htmlf("Status: %d %s\n", code, msg);
88 if (!more_headers) 88 if (!more_headers)
89 html("\n"); 89 html("\n");
90} 90}
91 91
92void html_txt(const char *txt) 92void html_txt(const char *txt)
93{ 93{
94 const char *t = txt; 94 const char *t = txt;
95 while(t && *t){ 95 while(t && *t){
96 int c = *t; 96 int c = *t;
97 if (c=='<' || c=='>' || c=='&') { 97 if (c=='<' || c=='>' || c=='&') {
98 html_raw(txt, t - txt); 98 html_raw(txt, t - txt);
99 if (c=='>') 99 if (c=='>')
100 html("&gt;"); 100 html("&gt;");
101 else if (c=='<') 101 else if (c=='<')
102 html("&lt;"); 102 html("&lt;");
103 else if (c=='&') 103 else if (c=='&')
104 html("&amp;"); 104 html("&amp;");
105 txt = t+1; 105 txt = t+1;
106 } 106 }
107 t++; 107 t++;
108 } 108 }
109 if (t!=txt) 109 if (t!=txt)
110 html(txt); 110 html(txt);
111} 111}
112 112
113void html_ntxt(int len, const char *txt) 113void html_ntxt(int len, const char *txt)
114{ 114{
115 const char *t = txt; 115 const char *t = txt;
116 while(t && *t && len--){ 116 while(t && *t && len--){
117 int c = *t; 117 int c = *t;
118 if (c=='<' || c=='>' || c=='&') { 118 if (c=='<' || c=='>' || c=='&') {
119 html_raw(txt, t - txt); 119 html_raw(txt, t - txt);
120 if (c=='>') 120 if (c=='>')
121 html("&gt;"); 121 html("&gt;");
122 else if (c=='<') 122 else if (c=='<')
123 html("&lt;"); 123 html("&lt;");
124 else if (c=='&') 124 else if (c=='&')
125 html("&amp;"); 125 html("&amp;");
126 txt = t+1; 126 txt = t+1;
127 } 127 }
128 t++; 128 t++;
129 } 129 }
130 if (t!=txt) 130 if (t!=txt)
131 html_raw(txt, t - txt); 131 html_raw(txt, t - txt);
132 if (len<0) 132 if (len<0)
133 html("..."); 133 html("...");
134} 134}
135 135
136void html_attr(const char *txt) 136void html_attr(const char *txt)
137{ 137{
138 const char *t = txt; 138 const char *t = txt;
139 while(t && *t){ 139 while(t && *t){
140 int c = *t; 140 int c = *t;
141 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
142 html_raw(txt, t - txt); 142 html_raw(txt, t - txt);
143 if (c=='>') 143 if (c=='>')
144 html("&gt;"); 144 html("&gt;");
145 else if (c=='<') 145 else if (c=='<')
146 html("&lt;"); 146 html("&lt;");
147 else if (c=='\'') 147 else if (c=='\'')
148 html("&#x27;"); 148 html("&#x27;");
149 else if (c=='"') 149 else if (c=='"')
150 html("&quot;"); 150 html("&quot;");
151 txt = t+1; 151 txt = t+1;
152 } 152 }
153 t++; 153 t++;
154 } 154 }
155 if (t!=txt) 155 if (t!=txt)
156 html(txt); 156 html(txt);
157} 157}
158 158
159void html_url_path(const char *txt) 159void html_url_path(const char *txt)
160{ 160{
161 const char *t = txt; 161 const char *t = txt;
162 while(t && *t){ 162 while(t && *t){
163 int c = *t; 163 int c = *t;
164 const char *e = url_escape_table[c]; 164 const char *e = url_escape_table[c];
165 if (e && c!='+' && c!='&' && c!='+') { 165 if (e && c!='+' && c!='&' && c!='+') {
166 html_raw(txt, t - txt); 166 html_raw(txt, t - txt);
167 html_raw(e, 3); 167 html_raw(e, 3);
168 txt = t+1; 168 txt = t+1;
169 } 169 }
170 t++; 170 t++;
171 } 171 }
172 if (t!=txt) 172 if (t!=txt)
173 html(txt); 173 html(txt);
174} 174}
175 175
176void html_url_arg(const char *txt) 176void html_url_arg(const char *txt)
177{ 177{
178 const char *t = txt; 178 const char *t = txt;
179 while(t && *t){ 179 while(t && *t){
180 int c = *t; 180 int c = *t;
181 const char *e = url_escape_table[c]; 181 const char *e = url_escape_table[c];
182 if (e) { 182 if (e) {
183 html_raw(txt, t - txt); 183 html_raw(txt, t - txt);
184 html_raw(e, strlen(e)); 184 html_raw(e, strlen(e));
185 txt = t+1; 185 txt = t+1;
186 } 186 }
187 t++; 187 t++;
188 } 188 }
189 if (t!=txt) 189 if (t!=txt)
190 html(txt); 190 html(txt);
191} 191}
192 192
193void html_hidden(const char *name, const char *value) 193void html_hidden(const char *name, const char *value)
194{ 194{
195 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
196 html_attr(name); 196 html_attr(name);
197 html("' value='"); 197 html("' value='");
198 html_attr(value); 198 html_attr(value);
199 html("'/>"); 199 html("'/>");
200} 200}
201 201
202void html_option(const char *value, const char *text, const char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
203{ 203{
204 html("<option value='"); 204 html("<option value='");
205 html_attr(value); 205 html_attr(value);
206 html("'"); 206 html("'");
207 if (selected_value && !strcmp(selected_value, value)) 207 if (selected_value && !strcmp(selected_value, value))
208 html(" selected='selected'"); 208 html(" selected='selected'");
209 html(">"); 209 html(">");
210 html_txt(text); 210 html_txt(text);
211 html("</option>\n"); 211 html("</option>\n");
212} 212}
213 213
214void html_link_open(const char *url, const char *title, const char *class) 214void html_link_open(const char *url, const char *title, const char *class)
215{ 215{
216 html("<a href='"); 216 html("<a href='");
217 html_attr(url); 217 html_attr(url);
218 if (title) { 218 if (title) {
219 html("' title='"); 219 html("' title='");
220 html_attr(title); 220 html_attr(title);
221 } 221 }
222 if (class) { 222 if (class) {
223 html("' class='"); 223 html("' class='");
224 html_attr(class); 224 html_attr(class);
225 } 225 }
226 html("'>"); 226 html("'>");
227} 227}
228 228
229void html_link_close(void) 229void html_link_close(void)
230{ 230{
231 html("</a>"); 231 html("</a>");
232} 232}
233 233
234void html_fileperm(unsigned short mode) 234void html_fileperm(unsigned short mode)
235{ 235{
236 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), 236 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
237 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); 237 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
238} 238}
239 239
240int html_include(const char *filename) 240int html_include(const char *filename)
241{ 241{
242 FILE *f; 242 FILE *f;
243 char buf[4096]; 243 char buf[4096];
244 size_t len; 244 size_t len;
245 245
246 if (!(f = fopen(filename, "r"))) { 246 if (!(f = fopen(filename, "r"))) {
247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
248 filename, strerror(errno), errno); 248 filename, strerror(errno), errno);
249 return -1; 249 return -1;
250 } 250 }
251 while((len = fread(buf, 1, 4096, f)) > 0) 251 while((len = fread(buf, 1, 4096, f)) > 0)
252 html_raw(buf, len); 252 html_raw(buf, len);
253 fclose(f); 253 fclose(f);
254 return 0; 254 return 0;
255} 255}
256 256
257int hextoint(char c) 257int hextoint(char c)
258{ 258{
259 if (c >= 'a' && c <= 'f') 259 if (c >= 'a' && c <= 'f')
260 return 10 + c - 'a'; 260 return 10 + c - 'a';
261 else if (c >= 'A' && c <= 'F') 261 else if (c >= 'A' && c <= 'F')
262 return 10 + c - 'A'; 262 return 10 + c - 'A';
263 else if (c >= '0' && c <= '9') 263 else if (c >= '0' && c <= '9')
264 return c - '0'; 264 return c - '0';
265 else 265 else
266 return -1; 266 return -1;
267} 267}
268 268
269char *convert_query_hexchar(char *txt) 269char *convert_query_hexchar(char *txt)
270{ 270{
271 int d1, d2, n; 271 int d1, d2, n;
272 n = strlen(txt); 272 n = strlen(txt);
273 if (n < 3) { 273 if (n < 3) {
274 *txt = '\0'; 274 *txt = '\0';
275 return txt-1; 275 return txt-1;
276 } 276 }
277 d1 = hextoint(*(txt+1)); 277 d1 = hextoint(*(txt+1));
278 d2 = hextoint(*(txt+2)); 278 d2 = hextoint(*(txt+2));
279 if (d1<0 || d2<0) { 279 if (d1<0 || d2<0) {
280 memmove(txt, txt+3, n-3); 280 memmove(txt, txt+3, n-2);
281 return txt-1; 281 return txt-1;
282 } else { 282 } else {
283 *txt = d1 * 16 + d2; 283 *txt = d1 * 16 + d2;
284 memmove(txt+1, txt+3, n-2); 284 memmove(txt+1, txt+3, n-2);
285 return txt; 285 return txt;
286 } 286 }
287} 287}
288 288
289int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value)) 289int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
290{ 290{
291 char *t, *txt, *value = NULL, c; 291 char *t, *txt, *value = NULL, c;
292 292
293 if (!txt_) 293 if (!txt_)
294 return 0; 294 return 0;
295 295
296 t = txt = strdup(txt_); 296 t = txt = strdup(txt_);
297 if (t == NULL) { 297 if (t == NULL) {
298 printf("Out of memory\n"); 298 printf("Out of memory\n");
299 exit(1); 299 exit(1);
300 } 300 }
301 while((c=*t) != '\0') { 301 while((c=*t) != '\0') {
302 if (c=='=') { 302 if (c=='=') {
303 *t = '\0'; 303 *t = '\0';
304 value = t+1; 304 value = t+1;
305 } else if (c=='+') { 305 } else if (c=='+') {
306 *t = ' '; 306 *t = ' ';
307 } else if (c=='%') { 307 } else if (c=='%') {
308 t = convert_query_hexchar(t); 308 t = convert_query_hexchar(t);
309 } else if (c=='&') { 309 } else if (c=='&') {
310 *t = '\0'; 310 *t = '\0';
311 (*fn)(txt, value); 311 (*fn)(txt, value);
312 txt = t+1; 312 txt = t+1;
313 value = NULL; 313 value = NULL;
314 } 314 }
315 t++; 315 t++;
316 } 316 }
317 if (t!=txt) 317 if (t!=txt)
318 (*fn)(txt, value); 318 (*fn)(txt, value);
319 return 0; 319 return 0;
320} 320}
diff --git a/ui-shared.c b/ui-shared.c
index 7efae7a..5aa9119 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,919 +1,919 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(const char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
55 55
56char *cgit_rooturl() 56char *cgit_rooturl()
57{ 57{
58 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
59 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
60 else 60 else
61 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
62} 62}
63 63
64char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
65{ 65{
66 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
68 } else { 68 } else {
69 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
70 } 70 }
71} 71}
72 72
73char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
74 const char *filename, const char *query) 74 const char *filename, const char *query)
75{ 75{
76 char *tmp; 76 char *tmp;
77 char *delim; 77 char *delim;
78 78
79 if (ctx.cfg.virtual_root) { 79 if (ctx.cfg.virtual_root) {
80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
81 pagename, (filename ? filename:"")); 81 pagename, (filename ? filename:""));
82 delim = "?"; 82 delim = "?";
83 } else { 83 } else {
84 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 84 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
85 (filename ? filename : "")); 85 (filename ? filename : ""));
86 delim = "&"; 86 delim = "&";
87 } 87 }
88 if (query) 88 if (query)
89 tmp = fmt("%s%s%s", tmp, delim, query); 89 tmp = fmt("%s%s%s", tmp, delim, query);
90 return tmp; 90 return tmp;
91} 91}
92 92
93char *cgit_pageurl(const char *reponame, const char *pagename, 93char *cgit_pageurl(const char *reponame, const char *pagename,
94 const char *query) 94 const char *query)
95{ 95{
96 return cgit_fileurl(reponame,pagename,0,query); 96 return cgit_fileurl(reponame,pagename,0,query);
97} 97}
98 98
99const char *cgit_repobasename(const char *reponame) 99const char *cgit_repobasename(const char *reponame)
100{ 100{
101 /* I assume we don't need to store more than one repo basename */ 101 /* I assume we don't need to store more than one repo basename */
102 static char rvbuf[1024]; 102 static char rvbuf[1024];
103 int p; 103 int p;
104 const char *rv; 104 const char *rv;
105 strncpy(rvbuf,reponame,sizeof(rvbuf)); 105 strncpy(rvbuf,reponame,sizeof(rvbuf));
106 if(rvbuf[sizeof(rvbuf)-1]) 106 if(rvbuf[sizeof(rvbuf)-1])
107 die("cgit_repobasename: truncated repository name '%s'", reponame); 107 die("cgit_repobasename: truncated repository name '%s'", reponame);
108 p = strlen(rvbuf)-1; 108 p = strlen(rvbuf)-1;
109 /* strip trailing slashes */ 109 /* strip trailing slashes */
110 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 110 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
111 /* strip trailing .git */ 111 /* strip trailing .git */
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(const char *page, const char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(const char *page, const char *name, const char *title, 163static void site_link(const char *page, const char *name, const char *title,
164 const char *class, const char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(const char *name, const char *title, const char *class, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 const char *pattern, int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(const char *title, const char *class, const char *page, 190static char *repolink(const char *title, const char *class, const char *page,
191 const char *head, const char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
216 html("/"); 216 html("/");
217 if (path) 217 if (path)
218 html_url_path(path); 218 html_url_path(path);
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(const char *page, const char *name, const char *title, 243static void reporevlink(const char *page, const char *name, const char *title,
244 const char *class, const char *head, const char *rev, 244 const char *class, const char *head, const char *rev,
245 const char *path) 245 const char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(const char *name, const char *title, const char *class, 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head) 261 const char *head)
262{ 262{
263 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
264} 264}
265 265
266void cgit_tag_link(const char *name, const char *title, const char *class, 266void cgit_tag_link(const char *name, const char *title, const char *class,
267 const char *head, const char *rev) 267 const char *head, const char *rev)
268{ 268{
269 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
270} 270}
271 271
272void cgit_tree_link(const char *name, const char *title, const char *class, 272void cgit_tree_link(const char *name, const char *title, const char *class,
273 const char *head, const char *rev, const char *path) 273 const char *head, const char *rev, const char *path)
274{ 274{
275 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
276} 276}
277 277
278void cgit_plain_link(const char *name, const char *title, const char *class, 278void cgit_plain_link(const char *name, const char *title, const char *class,
279 const char *head, const char *rev, const char *path) 279 const char *head, const char *rev, const char *path)
280{ 280{
281 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
282} 282}
283 283
284void cgit_log_link(const char *name, const char *title, const char *class, 284void cgit_log_link(const char *name, const char *title, const char *class,
285 const char *head, const char *rev, const char *path, 285 const char *head, const char *rev, const char *path,
286 int ofs, const char *grep, const char *pattern, int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
287{ 287{
288 char *delim; 288 char *delim;
289 289
290 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
291 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
292 html(delim); 292 html(delim);
293 html("id="); 293 html("id=");
294 html_url_arg(rev); 294 html_url_arg(rev);
295 delim = "&"; 295 delim = "&";
296 } 296 }
297 if (grep && pattern) { 297 if (grep && pattern) {
298 html(delim); 298 html(delim);
299 html("qt="); 299 html("qt=");
300 html_url_arg(grep); 300 html_url_arg(grep);
301 delim = "&"; 301 delim = "&";
302 html(delim); 302 html(delim);
303 html("q="); 303 html("q=");
304 html_url_arg(pattern); 304 html_url_arg(pattern);
305 } 305 }
306 if (ofs > 0) { 306 if (ofs > 0) {
307 html(delim); 307 html(delim);
308 html("ofs="); 308 html("ofs=");
309 htmlf("%d", ofs); 309 htmlf("%d", ofs);
310 delim = "&"; 310 delim = "&";
311 } 311 }
312 if (showmsg) { 312 if (showmsg) {
313 html(delim); 313 html(delim);
314 html("showmsg=1"); 314 html("showmsg=1");
315 } 315 }
316 html("'>"); 316 html("'>");
317 html_txt(name); 317 html_txt(name);
318 html("</a>"); 318 html("</a>");
319} 319}
320 320
321void cgit_commit_link(char *name, const char *title, const char *class, 321void cgit_commit_link(char *name, const char *title, const char *class,
322 const char *head, const char *rev, const char *path, 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff) 323 int toggle_ssdiff)
324{ 324{
325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
326 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
327 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
328 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
329 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
330 } 330 }
331 331
332 char *delim; 332 char *delim;
333 333
334 delim = repolink(title, class, "commit", head, path); 334 delim = repolink(title, class, "commit", head, path);
335 if (rev && strcmp(rev, ctx.qry.head)) { 335 if (rev && strcmp(rev, ctx.qry.head)) {
336 html(delim); 336 html(delim);
337 html("id="); 337 html("id=");
338 html_url_arg(rev); 338 html_url_arg(rev);
339 delim = "&amp;"; 339 delim = "&amp;";
340 } 340 }
341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
342 html(delim); 342 html(delim);
343 html("ss=1"); 343 html("ss=1");
344 delim = "&amp;"; 344 delim = "&amp;";
345 } 345 }
346 if (ctx.qry.context > 0 && ctx.qry.context != 3) { 346 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
347 html(delim); 347 html(delim);
348 html("context="); 348 html("context=");
349 htmlf("%d", ctx.qry.context); 349 htmlf("%d", ctx.qry.context);
350 delim = "&amp;"; 350 delim = "&amp;";
351 } 351 }
352 if (ctx.qry.ignorews) { 352 if (ctx.qry.ignorews) {
353 html(delim); 353 html(delim);
354 html("ignorews=1"); 354 html("ignorews=1");
355 delim = "&amp;"; 355 delim = "&amp;";
356 } 356 }
357 html("'>"); 357 html("'>");
358 html_txt(name); 358 html_txt(name);
359 html("</a>"); 359 html("</a>");
360} 360}
361 361
362void cgit_refs_link(const char *name, const char *title, const char *class, 362void cgit_refs_link(const char *name, const char *title, const char *class,
363 const char *head, const char *rev, const char *path) 363 const char *head, const char *rev, const char *path)
364{ 364{
365 reporevlink("refs", name, title, class, head, rev, path); 365 reporevlink("refs", name, title, class, head, rev, path);
366} 366}
367 367
368void cgit_snapshot_link(const char *name, const char *title, const char *class, 368void cgit_snapshot_link(const char *name, const char *title, const char *class,
369 const char *head, const char *rev, 369 const char *head, const char *rev,
370 const char *archivename) 370 const char *archivename)
371{ 371{
372 reporevlink("snapshot", name, title, class, head, rev, archivename); 372 reporevlink("snapshot", name, title, class, head, rev, archivename);
373} 373}
374 374
375void cgit_diff_link(const char *name, const char *title, const char *class, 375void cgit_diff_link(const char *name, const char *title, const char *class,
376 const char *head, const char *new_rev, const char *old_rev, 376 const char *head, const char *new_rev, const char *old_rev,
377 const char *path, int toggle_ssdiff) 377 const char *path, int toggle_ssdiff)
378{ 378{
379 char *delim; 379 char *delim;
380 380
381 delim = repolink(title, class, "diff", head, path); 381 delim = repolink(title, class, "diff", head, path);
382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 382 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
383 html(delim); 383 html(delim);
384 html("id="); 384 html("id=");
385 html_url_arg(new_rev); 385 html_url_arg(new_rev);
386 delim = "&amp;"; 386 delim = "&amp;";
387 } 387 }
388 if (old_rev) { 388 if (old_rev) {
389 html(delim); 389 html(delim);
390 html("id2="); 390 html("id2=");
391 html_url_arg(old_rev); 391 html_url_arg(old_rev);
392 delim = "&amp;"; 392 delim = "&amp;";
393 } 393 }
394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 394 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
395 html(delim); 395 html(delim);
396 html("ss=1"); 396 html("ss=1");
397 delim = "&amp;"; 397 delim = "&amp;";
398 } 398 }
399 if (ctx.qry.context > 0 && ctx.qry.context != 3) { 399 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
400 html(delim); 400 html(delim);
401 html("context="); 401 html("context=");
402 htmlf("%d", ctx.qry.context); 402 htmlf("%d", ctx.qry.context);
403 delim = "&amp;"; 403 delim = "&amp;";
404 } 404 }
405 if (ctx.qry.ignorews) { 405 if (ctx.qry.ignorews) {
406 html(delim); 406 html(delim);
407 html("ignorews=1"); 407 html("ignorews=1");
408 delim = "&amp;"; 408 delim = "&amp;";
409 } 409 }
410 html("'>"); 410 html("'>");
411 html_txt(name); 411 html_txt(name);
412 html("</a>"); 412 html("</a>");
413} 413}
414 414
415void cgit_patch_link(const char *name, const char *title, const char *class, 415void cgit_patch_link(const char *name, const char *title, const char *class,
416 const char *head, const char *rev, const char *path) 416 const char *head, const char *rev, const char *path)
417{ 417{
418 reporevlink("patch", name, title, class, head, rev, path); 418 reporevlink("patch", name, title, class, head, rev, path);
419} 419}
420 420
421void cgit_stats_link(const char *name, const char *title, const char *class, 421void cgit_stats_link(const char *name, const char *title, const char *class,
422 const char *head, const char *path) 422 const char *head, const char *path)
423{ 423{
424 reporevlink("stats", name, title, class, head, NULL, path); 424 reporevlink("stats", name, title, class, head, NULL, path);
425} 425}
426 426
427void cgit_self_link(char *name, const char *title, const char *class, 427void cgit_self_link(char *name, const char *title, const char *class,
428 struct cgit_context *ctx) 428 struct cgit_context *ctx)
429{ 429{
430 if (!strcmp(ctx->qry.page, "repolist")) 430 if (!strcmp(ctx->qry.page, "repolist"))
431 return cgit_index_link(name, title, class, ctx->qry.search, 431 return cgit_index_link(name, title, class, ctx->qry.search,
432 ctx->qry.ofs); 432 ctx->qry.ofs);
433 else if (!strcmp(ctx->qry.page, "summary")) 433 else if (!strcmp(ctx->qry.page, "summary"))
434 return cgit_summary_link(name, title, class, ctx->qry.head); 434 return cgit_summary_link(name, title, class, ctx->qry.head);
435 else if (!strcmp(ctx->qry.page, "tag")) 435 else if (!strcmp(ctx->qry.page, "tag"))
436 return cgit_tag_link(name, title, class, ctx->qry.head, 436 return cgit_tag_link(name, title, class, ctx->qry.head,
437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL); 437 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
438 else if (!strcmp(ctx->qry.page, "tree")) 438 else if (!strcmp(ctx->qry.page, "tree"))
439 return cgit_tree_link(name, title, class, ctx->qry.head, 439 return cgit_tree_link(name, title, class, ctx->qry.head,
440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 440 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
441 ctx->qry.path); 441 ctx->qry.path);
442 else if (!strcmp(ctx->qry.page, "plain")) 442 else if (!strcmp(ctx->qry.page, "plain"))
443 return cgit_plain_link(name, title, class, ctx->qry.head, 443 return cgit_plain_link(name, title, class, ctx->qry.head,
444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
445 ctx->qry.path); 445 ctx->qry.path);
446 else if (!strcmp(ctx->qry.page, "log")) 446 else if (!strcmp(ctx->qry.page, "log"))
447 return cgit_log_link(name, title, class, ctx->qry.head, 447 return cgit_log_link(name, title, class, ctx->qry.head,
448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
449 ctx->qry.path, ctx->qry.ofs, 449 ctx->qry.path, ctx->qry.ofs,
450 ctx->qry.grep, ctx->qry.search, 450 ctx->qry.grep, ctx->qry.search,
451 ctx->qry.showmsg); 451 ctx->qry.showmsg);
452 else if (!strcmp(ctx->qry.page, "commit")) 452 else if (!strcmp(ctx->qry.page, "commit"))
453 return cgit_commit_link(name, title, class, ctx->qry.head, 453 return cgit_commit_link(name, title, class, ctx->qry.head,
454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 454 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
455 ctx->qry.path, 0); 455 ctx->qry.path, 0);
456 else if (!strcmp(ctx->qry.page, "patch")) 456 else if (!strcmp(ctx->qry.page, "patch"))
457 return cgit_patch_link(name, title, class, ctx->qry.head, 457 return cgit_patch_link(name, title, class, ctx->qry.head,
458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 458 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
459 ctx->qry.path); 459 ctx->qry.path);
460 else if (!strcmp(ctx->qry.page, "refs")) 460 else if (!strcmp(ctx->qry.page, "refs"))
461 return cgit_refs_link(name, title, class, ctx->qry.head, 461 return cgit_refs_link(name, title, class, ctx->qry.head,
462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 462 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
463 ctx->qry.path); 463 ctx->qry.path);
464 else if (!strcmp(ctx->qry.page, "snapshot")) 464 else if (!strcmp(ctx->qry.page, "snapshot"))
465 return cgit_snapshot_link(name, title, class, ctx->qry.head, 465 return cgit_snapshot_link(name, title, class, ctx->qry.head,
466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, 466 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
467 ctx->qry.path); 467 ctx->qry.path);
468 else if (!strcmp(ctx->qry.page, "diff")) 468 else if (!strcmp(ctx->qry.page, "diff"))
469 return cgit_diff_link(name, title, class, ctx->qry.head, 469 return cgit_diff_link(name, title, class, ctx->qry.head,
470 ctx->qry.sha1, ctx->qry.sha2, 470 ctx->qry.sha1, ctx->qry.sha2,
471 ctx->qry.path, 0); 471 ctx->qry.path, 0);
472 else if (!strcmp(ctx->qry.page, "stats")) 472 else if (!strcmp(ctx->qry.page, "stats"))
473 return cgit_stats_link(name, title, class, ctx->qry.head, 473 return cgit_stats_link(name, title, class, ctx->qry.head,
474 ctx->qry.path); 474 ctx->qry.path);
475 475
476 /* Don't known how to make link for this page */ 476 /* Don't known how to make link for this page */
477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path); 477 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
478 html("><!-- cgit_self_link() doesn't know how to make link for page '"); 478 html("><!-- cgit_self_link() doesn't know how to make link for page '");
479 html_txt(ctx->qry.page); 479 html_txt(ctx->qry.page);
480 html("' -->"); 480 html("' -->");
481 html_txt(name); 481 html_txt(name);
482 html("</a>"); 482 html("</a>");
483} 483}
484 484
485void cgit_object_link(struct object *obj) 485void cgit_object_link(struct object *obj)
486{ 486{
487 char *page, *shortrev, *fullrev, *name; 487 char *page, *shortrev, *fullrev, *name;
488 488
489 fullrev = sha1_to_hex(obj->sha1); 489 fullrev = sha1_to_hex(obj->sha1);
490 shortrev = xstrdup(fullrev); 490 shortrev = xstrdup(fullrev);
491 shortrev[10] = '\0'; 491 shortrev[10] = '\0';
492 if (obj->type == OBJ_COMMIT) { 492 if (obj->type == OBJ_COMMIT) {
493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 493 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
494 ctx.qry.head, fullrev, NULL, 0); 494 ctx.qry.head, fullrev, NULL, 0);
495 return; 495 return;
496 } else if (obj->type == OBJ_TREE) 496 } else if (obj->type == OBJ_TREE)
497 page = "tree"; 497 page = "tree";
498 else if (obj->type == OBJ_TAG) 498 else if (obj->type == OBJ_TAG)
499 page = "tag"; 499 page = "tag";
500 else 500 else
501 page = "blob"; 501 page = "blob";
502 name = fmt("%s %s...", typename(obj->type), shortrev); 502 name = fmt("%s %s...", typename(obj->type), shortrev);
503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 503 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
504} 504}
505 505
506void cgit_print_date(time_t secs, const char *format, int local_time) 506void cgit_print_date(time_t secs, const char *format, int local_time)
507{ 507{
508 char buf[64]; 508 char buf[64];
509 struct tm *time; 509 struct tm *time;
510 510
511 if (!secs) 511 if (!secs)
512 return; 512 return;
513 if(local_time) 513 if(local_time)
514 time = localtime(&secs); 514 time = localtime(&secs);
515 else 515 else
516 time = gmtime(&secs); 516 time = gmtime(&secs);
517 strftime(buf, sizeof(buf)-1, format, time); 517 strftime(buf, sizeof(buf)-1, format, time);
518 html_txt(buf); 518 html_txt(buf);
519} 519}
520 520
521void cgit_print_age(time_t t, time_t max_relative, const char *format) 521void cgit_print_age(time_t t, time_t max_relative, const char *format)
522{ 522{
523 time_t now, secs; 523 time_t now, secs;
524 524
525 if (!t) 525 if (!t)
526 return; 526 return;
527 time(&now); 527 time(&now);
528 secs = now - t; 528 secs = now - t;
529 529
530 if (secs > max_relative && max_relative >= 0) { 530 if (secs > max_relative && max_relative >= 0) {
531 cgit_print_date(t, format, ctx.cfg.local_time); 531 cgit_print_date(t, format, ctx.cfg.local_time);
532 return; 532 return;
533 } 533 }
534 534
535 if (secs < TM_HOUR * 2) { 535 if (secs < TM_HOUR * 2) {
536 htmlf("<span class='age-mins'>%.0f min.</span>", 536 htmlf("<span class='age-mins'>%.0f min.</span>",
537 secs * 1.0 / TM_MIN); 537 secs * 1.0 / TM_MIN);
538 return; 538 return;
539 } 539 }
540 if (secs < TM_DAY * 2) { 540 if (secs < TM_DAY * 2) {
541 htmlf("<span class='age-hours'>%.0f hours</span>", 541 htmlf("<span class='age-hours'>%.0f hours</span>",
542 secs * 1.0 / TM_HOUR); 542 secs * 1.0 / TM_HOUR);
543 return; 543 return;
544 } 544 }
545 if (secs < TM_WEEK * 2) { 545 if (secs < TM_WEEK * 2) {
546 htmlf("<span class='age-days'>%.0f days</span>", 546 htmlf("<span class='age-days'>%.0f days</span>",
547 secs * 1.0 / TM_DAY); 547 secs * 1.0 / TM_DAY);
548 return; 548 return;
549 } 549 }
550 if (secs < TM_MONTH * 2) { 550 if (secs < TM_MONTH * 2) {
551 htmlf("<span class='age-weeks'>%.0f weeks</span>", 551 htmlf("<span class='age-weeks'>%.0f weeks</span>",
552 secs * 1.0 / TM_WEEK); 552 secs * 1.0 / TM_WEEK);
553 return; 553 return;
554 } 554 }
555 if (secs < TM_YEAR * 2) { 555 if (secs < TM_YEAR * 2) {
556 htmlf("<span class='age-months'>%.0f months</span>", 556 htmlf("<span class='age-months'>%.0f months</span>",
557 secs * 1.0 / TM_MONTH); 557 secs * 1.0 / TM_MONTH);
558 return; 558 return;
559 } 559 }
560 htmlf("<span class='age-years'>%.0f years</span>", 560 htmlf("<span class='age-years'>%.0f years</span>",
561 secs * 1.0 / TM_YEAR); 561 secs * 1.0 / TM_YEAR);
562} 562}
563 563
564void cgit_print_http_headers(struct cgit_context *ctx) 564void cgit_print_http_headers(struct cgit_context *ctx)
565{ 565{
566 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 566 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
567 return; 567 return;
568 568
569 if (ctx->page.status) 569 if (ctx->page.status)
570 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 570 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
571 if (ctx->page.mimetype && ctx->page.charset) 571 if (ctx->page.mimetype && ctx->page.charset)
572 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 572 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
573 ctx->page.charset); 573 ctx->page.charset);
574 else if (ctx->page.mimetype) 574 else if (ctx->page.mimetype)
575 htmlf("Content-Type: %s\n", ctx->page.mimetype); 575 htmlf("Content-Type: %s\n", ctx->page.mimetype);
576 if (ctx->page.size) 576 if (ctx->page.size)
577 htmlf("Content-Length: %ld\n", ctx->page.size); 577 htmlf("Content-Length: %zd\n", ctx->page.size);
578 if (ctx->page.filename) 578 if (ctx->page.filename)
579 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 579 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
580 ctx->page.filename); 580 ctx->page.filename);
581 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 581 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
582 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 582 htmlf("Expires: %s\n", http_date(ctx->page.expires));
583 if (ctx->page.etag) 583 if (ctx->page.etag)
584 htmlf("ETag: \"%s\"\n", ctx->page.etag); 584 htmlf("ETag: \"%s\"\n", ctx->page.etag);
585 html("\n"); 585 html("\n");
586 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 586 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
587 exit(0); 587 exit(0);
588} 588}
589 589
590void cgit_print_docstart(struct cgit_context *ctx) 590void cgit_print_docstart(struct cgit_context *ctx)
591{ 591{
592 if (ctx->cfg.embedded) { 592 if (ctx->cfg.embedded) {
593 if (ctx->cfg.header) 593 if (ctx->cfg.header)
594 html_include(ctx->cfg.header); 594 html_include(ctx->cfg.header);
595 return; 595 return;
596 } 596 }
597 597
598 char *host = cgit_hosturl(); 598 char *host = cgit_hosturl();
599 html(cgit_doctype); 599 html(cgit_doctype);
600 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 600 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
601 html("<head>\n"); 601 html("<head>\n");
602 html("<title>"); 602 html("<title>");
603 html_txt(ctx->page.title); 603 html_txt(ctx->page.title);
604 html("</title>\n"); 604 html("</title>\n");
605 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 605 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
606 if (ctx->cfg.robots && *ctx->cfg.robots) 606 if (ctx->cfg.robots && *ctx->cfg.robots)
607 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 607 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
608 html("<link rel='stylesheet' type='text/css' href='"); 608 html("<link rel='stylesheet' type='text/css' href='");
609 html_attr(ctx->cfg.css); 609 html_attr(ctx->cfg.css);
610 html("'/>\n"); 610 html("'/>\n");
611 if (ctx->cfg.favicon) { 611 if (ctx->cfg.favicon) {
612 html("<link rel='shortcut icon' href='"); 612 html("<link rel='shortcut icon' href='");
613 html_attr(ctx->cfg.favicon); 613 html_attr(ctx->cfg.favicon);
614 html("'/>\n"); 614 html("'/>\n");
615 } 615 }
616 if (host && ctx->repo) { 616 if (host && ctx->repo) {
617 html("<link rel='alternate' title='Atom feed' href='"); 617 html("<link rel='alternate' title='Atom feed' href='");
618 html(cgit_httpscheme()); 618 html(cgit_httpscheme());
619 html_attr(cgit_hosturl()); 619 html_attr(cgit_hosturl());
620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath, 620 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
621 fmt("h=%s", ctx->qry.head))); 621 fmt("h=%s", ctx->qry.head)));
622 html("' type='application/atom+xml'/>\n"); 622 html("' type='application/atom+xml'/>\n");
623 } 623 }
624 if (ctx->cfg.head_include) 624 if (ctx->cfg.head_include)
625 html_include(ctx->cfg.head_include); 625 html_include(ctx->cfg.head_include);
626 html("</head>\n"); 626 html("</head>\n");
627 html("<body>\n"); 627 html("<body>\n");
628 if (ctx->cfg.header) 628 if (ctx->cfg.header)
629 html_include(ctx->cfg.header); 629 html_include(ctx->cfg.header);
630} 630}
631 631
632void cgit_print_docend() 632void cgit_print_docend()
633{ 633{
634 html("</div> <!-- class=content -->\n"); 634 html("</div> <!-- class=content -->\n");
635 if (ctx.cfg.embedded) { 635 if (ctx.cfg.embedded) {
636 html("</div> <!-- id=cgit -->\n"); 636 html("</div> <!-- id=cgit -->\n");
637 if (ctx.cfg.footer) 637 if (ctx.cfg.footer)
638 html_include(ctx.cfg.footer); 638 html_include(ctx.cfg.footer);
639 return; 639 return;
640 } 640 }
641 if (ctx.cfg.footer) 641 if (ctx.cfg.footer)
642 html_include(ctx.cfg.footer); 642 html_include(ctx.cfg.footer);
643 else { 643 else {
644 htmlf("<div class='footer'>generated by cgit %s at ", 644 htmlf("<div class='footer'>generated by cgit %s at ",
645 cgit_version); 645 cgit_version);
646 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 646 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
647 html("</div>\n"); 647 html("</div>\n");
648 } 648 }
649 html("</div> <!-- id=cgit -->\n"); 649 html("</div> <!-- id=cgit -->\n");
650 html("</body>\n</html>\n"); 650 html("</body>\n</html>\n");
651} 651}
652 652
653int print_branch_option(const char *refname, const unsigned char *sha1, 653int print_branch_option(const char *refname, const unsigned char *sha1,
654 int flags, void *cb_data) 654 int flags, void *cb_data)
655{ 655{
656 char *name = (char *)refname; 656 char *name = (char *)refname;
657 html_option(name, name, ctx.qry.head); 657 html_option(name, name, ctx.qry.head);
658 return 0; 658 return 0;
659} 659}
660 660
661int print_archive_ref(const char *refname, const unsigned char *sha1, 661int print_archive_ref(const char *refname, const unsigned char *sha1,
662 int flags, void *cb_data) 662 int flags, void *cb_data)
663{ 663{
664 struct tag *tag; 664 struct tag *tag;
665 struct taginfo *info; 665 struct taginfo *info;
666 struct object *obj; 666 struct object *obj;
667 char buf[256], *url; 667 char buf[256], *url;
668 unsigned char fileid[20]; 668 unsigned char fileid[20];
669 int *header = (int *)cb_data; 669 int *header = (int *)cb_data;
670 670
671 if (prefixcmp(refname, "refs/archives")) 671 if (prefixcmp(refname, "refs/archives"))
672 return 0; 672 return 0;
673 strncpy(buf, refname+14, sizeof(buf)); 673 strncpy(buf, refname+14, sizeof(buf));
674 obj = parse_object(sha1); 674 obj = parse_object(sha1);
675 if (!obj) 675 if (!obj)
676 return 1; 676 return 1;
677 if (obj->type == OBJ_TAG) { 677 if (obj->type == OBJ_TAG) {
678 tag = lookup_tag(sha1); 678 tag = lookup_tag(sha1);
679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
680 return 0; 680 return 0;
681 hashcpy(fileid, tag->tagged->sha1); 681 hashcpy(fileid, tag->tagged->sha1);
682 } else if (obj->type != OBJ_BLOB) { 682 } else if (obj->type != OBJ_BLOB) {
683 return 0; 683 return 0;
684 } else { 684 } else {
685 hashcpy(fileid, sha1); 685 hashcpy(fileid, sha1);
686 } 686 }
687 if (!*header) { 687 if (!*header) {
688 html("<h1>download</h1>\n"); 688 html("<h1>download</h1>\n");
689 *header = 1; 689 *header = 1;
690 } 690 }
691 url = cgit_pageurl(ctx.qry.repo, "blob", 691 url = cgit_pageurl(ctx.qry.repo, "blob",
692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
693 buf)); 693 buf));
694 html_link_open(url, NULL, "menu"); 694 html_link_open(url, NULL, "menu");
695 html_txt(strlpart(buf, 20)); 695 html_txt(strlpart(buf, 20));
696 html_link_close(); 696 html_link_close();
697 return 0; 697 return 0;
698} 698}
699 699
700void cgit_add_hidden_formfields(int incl_head, int incl_search, 700void cgit_add_hidden_formfields(int incl_head, int incl_search,
701 const char *page) 701 const char *page)
702{ 702{
703 char *url; 703 char *url;
704 704
705 if (!ctx.cfg.virtual_root) { 705 if (!ctx.cfg.virtual_root) {
706 url = fmt("%s/%s", ctx.qry.repo, page); 706 url = fmt("%s/%s", ctx.qry.repo, page);
707 if (ctx.qry.vpath) 707 if (ctx.qry.vpath)
708 url = fmt("%s/%s", url, ctx.qry.vpath); 708 url = fmt("%s/%s", url, ctx.qry.vpath);
709 html_hidden("url", url); 709 html_hidden("url", url);
710 } 710 }
711 711
712 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 712 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
713 strcmp(ctx.qry.head, ctx.repo->defbranch)) 713 strcmp(ctx.qry.head, ctx.repo->defbranch))
714 html_hidden("h", ctx.qry.head); 714 html_hidden("h", ctx.qry.head);
715 715
716 if (ctx.qry.sha1) 716 if (ctx.qry.sha1)
717 html_hidden("id", ctx.qry.sha1); 717 html_hidden("id", ctx.qry.sha1);
718 if (ctx.qry.sha2) 718 if (ctx.qry.sha2)
719 html_hidden("id2", ctx.qry.sha2); 719 html_hidden("id2", ctx.qry.sha2);
720 if (ctx.qry.showmsg) 720 if (ctx.qry.showmsg)
721 html_hidden("showmsg", "1"); 721 html_hidden("showmsg", "1");
722 722
723 if (incl_search) { 723 if (incl_search) {
724 if (ctx.qry.grep) 724 if (ctx.qry.grep)
725 html_hidden("qt", ctx.qry.grep); 725 html_hidden("qt", ctx.qry.grep);
726 if (ctx.qry.search) 726 if (ctx.qry.search)
727 html_hidden("q", ctx.qry.search); 727 html_hidden("q", ctx.qry.search);
728 } 728 }
729} 729}
730 730
731static const char *hc(struct cgit_context *ctx, const char *page) 731static const char *hc(struct cgit_context *ctx, const char *page)
732{ 732{
733 return strcmp(ctx->qry.page, page) ? NULL : "active"; 733 return strcmp(ctx->qry.page, page) ? NULL : "active";
734} 734}
735 735
736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path) 736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
737{ 737{
738 char *old_path = ctx->qry.path; 738 char *old_path = ctx->qry.path;
739 char *p = path, *q, *end = path + strlen(path); 739 char *p = path, *q, *end = path + strlen(path);
740 740
741 ctx->qry.path = NULL; 741 ctx->qry.path = NULL;
742 cgit_self_link("root", NULL, NULL, ctx); 742 cgit_self_link("root", NULL, NULL, ctx);
743 ctx->qry.path = p = path; 743 ctx->qry.path = p = path;
744 while (p < end) { 744 while (p < end) {
745 if (!(q = strchr(p, '/'))) 745 if (!(q = strchr(p, '/')))
746 q = end; 746 q = end;
747 *q = '\0'; 747 *q = '\0';
748 html_txt("/"); 748 html_txt("/");
749 cgit_self_link(p, NULL, NULL, ctx); 749 cgit_self_link(p, NULL, NULL, ctx);
750 if (q < end) 750 if (q < end)
751 *q = '/'; 751 *q = '/';
752 p = q + 1; 752 p = q + 1;
753 } 753 }
754 ctx->qry.path = old_path; 754 ctx->qry.path = old_path;
755} 755}
756 756
757static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
758{ 758{
759 char *logo = NULL, *logo_link = NULL; 759 char *logo = NULL, *logo_link = NULL;
760 760
761 html("<table id='header'>\n"); 761 html("<table id='header'>\n");
762 html("<tr>\n"); 762 html("<tr>\n");
763 763
764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo) 764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
765 logo = ctx->repo->logo; 765 logo = ctx->repo->logo;
766 else 766 else
767 logo = ctx->cfg.logo; 767 logo = ctx->cfg.logo;
768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link) 768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
769 logo_link = ctx->repo->logo_link; 769 logo_link = ctx->repo->logo_link;
770 else 770 else
771 logo_link = ctx->cfg.logo_link; 771 logo_link = ctx->cfg.logo_link;
772 if (logo && *logo) { 772 if (logo && *logo) {
773 html("<td class='logo' rowspan='2'><a href='"); 773 html("<td class='logo' rowspan='2'><a href='");
774 if (logo_link && *logo_link) 774 if (logo_link && *logo_link)
775 html_attr(logo_link); 775 html_attr(logo_link);
776 else 776 else
777 html_attr(cgit_rooturl()); 777 html_attr(cgit_rooturl());
778 html("'><img src='"); 778 html("'><img src='");
779 html_attr(logo); 779 html_attr(logo);
780 html("' alt='cgit logo'/></a></td>\n"); 780 html("' alt='cgit logo'/></a></td>\n");
781 } 781 }
782 782
783 html("<td class='main'>"); 783 html("<td class='main'>");
784 if (ctx->repo) { 784 if (ctx->repo) {
785 cgit_index_link("index", NULL, NULL, NULL, 0); 785 cgit_index_link("index", NULL, NULL, NULL, 0);
786 html(" : "); 786 html(" : ");
787 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 787 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
788 html("</td><td class='form'>"); 788 html("</td><td class='form'>");
789 html("<form method='get' action=''>\n"); 789 html("<form method='get' action=''>\n");
790 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 790 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
791 html("<select name='h' onchange='this.form.submit();'>\n"); 791 html("<select name='h' onchange='this.form.submit();'>\n");
792 for_each_branch_ref(print_branch_option, ctx->qry.head); 792 for_each_branch_ref(print_branch_option, ctx->qry.head);
793 html("</select> "); 793 html("</select> ");
794 html("<input type='submit' name='' value='switch'/>"); 794 html("<input type='submit' name='' value='switch'/>");
795 html("</form>"); 795 html("</form>");
796 } else 796 } else
797 html_txt(ctx->cfg.root_title); 797 html_txt(ctx->cfg.root_title);
798 html("</td></tr>\n"); 798 html("</td></tr>\n");
799 799
800 html("<tr><td class='sub'>"); 800 html("<tr><td class='sub'>");
801 if (ctx->repo) { 801 if (ctx->repo) {
802 html_txt(ctx->repo->desc); 802 html_txt(ctx->repo->desc);
803 html("</td><td class='sub right'>"); 803 html("</td><td class='sub right'>");
804 html_txt(ctx->repo->owner); 804 html_txt(ctx->repo->owner);
805 } else { 805 } else {
806 if (ctx->cfg.root_desc) 806 if (ctx->cfg.root_desc)
807 html_txt(ctx->cfg.root_desc); 807 html_txt(ctx->cfg.root_desc);
808 else if (ctx->cfg.index_info) 808 else if (ctx->cfg.index_info)
809 html_include(ctx->cfg.index_info); 809 html_include(ctx->cfg.index_info);
810 } 810 }
811 html("</td></tr></table>\n"); 811 html("</td></tr></table>\n");
812} 812}
813 813
814void cgit_print_pageheader(struct cgit_context *ctx) 814void cgit_print_pageheader(struct cgit_context *ctx)
815{ 815{
816 html("<div id='cgit'>"); 816 html("<div id='cgit'>");
817 if (!ctx->cfg.noheader) 817 if (!ctx->cfg.noheader)
818 print_header(ctx); 818 print_header(ctx);
819 819
820 html("<table class='tabs'><tr><td>\n"); 820 html("<table class='tabs'><tr><td>\n");
821 if (ctx->repo) { 821 if (ctx->repo) {
822 cgit_summary_link("summary", NULL, hc(ctx, "summary"), 822 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
823 ctx->qry.head); 823 ctx->qry.head);
824 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, 824 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
825 ctx->qry.sha1, NULL); 825 ctx->qry.sha1, NULL);
826 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, 826 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
827 NULL, ctx->qry.vpath, 0, NULL, NULL, 827 NULL, ctx->qry.vpath, 0, NULL, NULL,
828 ctx->qry.showmsg); 828 ctx->qry.showmsg);
829 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, 829 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
830 ctx->qry.sha1, ctx->qry.vpath); 830 ctx->qry.sha1, ctx->qry.vpath);
831 cgit_commit_link("commit", NULL, hc(ctx, "commit"), 831 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
832 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); 832 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
833 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, 833 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
834 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); 834 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
835 if (ctx->repo->max_stats) 835 if (ctx->repo->max_stats)
836 cgit_stats_link("stats", NULL, hc(ctx, "stats"), 836 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
837 ctx->qry.head, ctx->qry.vpath); 837 ctx->qry.head, ctx->qry.vpath);
838 if (ctx->repo->readme) 838 if (ctx->repo->readme)
839 reporevlink("about", "about", NULL, 839 reporevlink("about", "about", NULL,
840 hc(ctx, "about"), ctx->qry.head, NULL, 840 hc(ctx, "about"), ctx->qry.head, NULL,
841 NULL); 841 NULL);
842 html("</td><td class='form'>"); 842 html("</td><td class='form'>");
843 html("<form class='right' method='get' action='"); 843 html("<form class='right' method='get' action='");
844 if (ctx->cfg.virtual_root) 844 if (ctx->cfg.virtual_root)
845 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 845 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
846 ctx->qry.vpath, NULL)); 846 ctx->qry.vpath, NULL));
847 html("'>\n"); 847 html("'>\n");
848 cgit_add_hidden_formfields(1, 0, "log"); 848 cgit_add_hidden_formfields(1, 0, "log");
849 html("<select name='qt'>\n"); 849 html("<select name='qt'>\n");
850 html_option("grep", "log msg", ctx->qry.grep); 850 html_option("grep", "log msg", ctx->qry.grep);
851 html_option("author", "author", ctx->qry.grep); 851 html_option("author", "author", ctx->qry.grep);
852 html_option("committer", "committer", ctx->qry.grep); 852 html_option("committer", "committer", ctx->qry.grep);
853 html_option("range", "range", ctx->qry.grep); 853 html_option("range", "range", ctx->qry.grep);
854 html("</select>\n"); 854 html("</select>\n");
855 html("<input class='txt' type='text' size='10' name='q' value='"); 855 html("<input class='txt' type='text' size='10' name='q' value='");
856 html_attr(ctx->qry.search); 856 html_attr(ctx->qry.search);
857 html("'/>\n"); 857 html("'/>\n");
858 html("<input type='submit' value='search'/>\n"); 858 html("<input type='submit' value='search'/>\n");
859 html("</form>\n"); 859 html("</form>\n");
860 } else { 860 } else {
861 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0); 861 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
862 if (ctx->cfg.root_readme) 862 if (ctx->cfg.root_readme)
863 site_link("about", "about", NULL, hc(ctx, "about"), 863 site_link("about", "about", NULL, hc(ctx, "about"),
864 NULL, 0); 864 NULL, 0);
865 html("</td><td class='form'>"); 865 html("</td><td class='form'>");
866 html("<form method='get' action='"); 866 html("<form method='get' action='");
867 html_attr(cgit_rooturl()); 867 html_attr(cgit_rooturl());
868 html("'>\n"); 868 html("'>\n");
869 html("<input type='text' name='q' size='10' value='"); 869 html("<input type='text' name='q' size='10' value='");
870 html_attr(ctx->qry.search); 870 html_attr(ctx->qry.search);
871 html("'/>\n"); 871 html("'/>\n");
872 html("<input type='submit' value='search'/>\n"); 872 html("<input type='submit' value='search'/>\n");
873 html("</form>"); 873 html("</form>");
874 } 874 }
875 html("</td></tr></table>\n"); 875 html("</td></tr></table>\n");
876 if (ctx->qry.vpath) { 876 if (ctx->qry.vpath) {
877 html("<div class='path'>"); 877 html("<div class='path'>");
878 html("path: "); 878 html("path: ");
879 cgit_print_path_crumbs(ctx, ctx->qry.vpath); 879 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
880 html("</div>"); 880 html("</div>");
881 } 881 }
882 html("<div class='content'>"); 882 html("<div class='content'>");
883} 883}
884 884
885void cgit_print_filemode(unsigned short mode) 885void cgit_print_filemode(unsigned short mode)
886{ 886{
887 if (S_ISDIR(mode)) 887 if (S_ISDIR(mode))
888 html("d"); 888 html("d");
889 else if (S_ISLNK(mode)) 889 else if (S_ISLNK(mode))
890 html("l"); 890 html("l");
891 else if (S_ISGITLINK(mode)) 891 else if (S_ISGITLINK(mode))
892 html("m"); 892 html("m");
893 else 893 else
894 html("-"); 894 html("-");
895 html_fileperm(mode >> 6); 895 html_fileperm(mode >> 6);
896 html_fileperm(mode >> 3); 896 html_fileperm(mode >> 3);
897 html_fileperm(mode); 897 html_fileperm(mode);
898} 898}
899 899
900void cgit_print_snapshot_links(const char *repo, const char *head, 900void cgit_print_snapshot_links(const char *repo, const char *head,
901 const char *hex, int snapshots) 901 const char *hex, int snapshots)
902{ 902{
903 const struct cgit_snapshot_format* f; 903 const struct cgit_snapshot_format* f;
904 char *prefix; 904 char *prefix;
905 char *filename; 905 char *filename;
906 unsigned char sha1[20]; 906 unsigned char sha1[20];
907 907
908 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && 908 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
909 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) 909 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
910 hex++; 910 hex++;
911 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); 911 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
912 for (f = cgit_snapshot_formats; f->suffix; f++) { 912 for (f = cgit_snapshot_formats; f->suffix; f++) {
913 if (!(snapshots & f->bit)) 913 if (!(snapshots & f->bit))
914 continue; 914 continue;
915 filename = fmt("%s%s", prefix, f->suffix); 915 filename = fmt("%s%s", prefix, f->suffix);
916 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 916 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
917 html("<br/>"); 917 html("<br/>");
918 } 918 }
919} 919}