summaryrefslogtreecommitdiffabout
path: root/ui-shared.c
authorLars Hjemli <hjemli@gmail.com>2009-01-11 20:23:04 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-01-11 20:23:04 (UTC)
commiteb14609dc46461728a065c0a243b338fc32fd762 (patch) (unidiff)
treef00563342db8859f46ac8141fdaa5d4e17eb940e /ui-shared.c
parent720b6ece90900df9f836a45d8e7f1cd56f62400a (diff)
downloadcgit-eb14609dc46461728a065c0a243b338fc32fd762.zip
cgit-eb14609dc46461728a065c0a243b338fc32fd762.tar.gz
cgit-eb14609dc46461728a065c0a243b338fc32fd762.tar.bz2
Avoid SEGFAULT on invalid requests
When an unknown page is requested, either on the querystring or via PATH_INFO, we end up with a null-referencing cgit_cmd. This null- pointer is then used as argument to the hc() function (which decides what tab to render as 'active'), but this function failed to check if a valid cmd was specified and a SEGFAULT would occur. This patch fixes the issue by introducing a 'fallback-cmd' which specifies what tab to render as 'active' when no valid cmd is requested. While at it, we now also keep track of the active repository even if an invalid cmd was requested since we want to show the error message about the invalid request in the correct context. Noticed-by: Robin Redeker <elmex@ta-sa.org> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (limited to 'ui-shared.c') (more/less context) (ignore whitespace changes)
-rw-r--r--ui-shared.c7
1 files changed, 6 insertions, 1 deletions
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..76cd00d 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,714 +1,719 @@
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", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "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(char *msg) 30void cgit_print_error(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_hosturl() 37char *cgit_hosturl()
38{ 38{
39 char *host, *port; 39 char *host, *port;
40 40
41 host = getenv("HTTP_HOST"); 41 host = getenv("HTTP_HOST");
42 if (host) { 42 if (host) {
43 host = xstrdup(host); 43 host = xstrdup(host);
44 } else { 44 } else {
45 host = getenv("SERVER_NAME"); 45 host = getenv("SERVER_NAME");
46 if (!host) 46 if (!host)
47 return NULL; 47 return NULL;
48 port = getenv("SERVER_PORT"); 48 port = getenv("SERVER_PORT");
49 if (port && atoi(port) != 80) 49 if (port && atoi(port) != 80)
50 host = xstrdup(fmt("%s:%d", host, atoi(port))); 50 host = xstrdup(fmt("%s:%d", host, atoi(port)));
51 else 51 else
52 host = xstrdup(host); 52 host = xstrdup(host);
53 } 53 }
54 return host; 54 return host;
55} 55}
56 56
57char *cgit_rooturl() 57char *cgit_rooturl()
58{ 58{
59 if (ctx.cfg.virtual_root) 59 if (ctx.cfg.virtual_root)
60 return fmt("%s/", ctx.cfg.virtual_root); 60 return fmt("%s/", ctx.cfg.virtual_root);
61 else 61 else
62 return ctx.cfg.script_name; 62 return ctx.cfg.script_name;
63} 63}
64 64
65char *cgit_repourl(const char *reponame) 65char *cgit_repourl(const char *reponame)
66{ 66{
67 if (ctx.cfg.virtual_root) { 67 if (ctx.cfg.virtual_root) {
68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
69 } else { 69 } else {
70 return fmt("?r=%s", reponame); 70 return fmt("?r=%s", reponame);
71 } 71 }
72} 72}
73 73
74char *cgit_fileurl(const char *reponame, const char *pagename, 74char *cgit_fileurl(const char *reponame, const char *pagename,
75 const char *filename, const char *query) 75 const char *filename, const char *query)
76{ 76{
77 char *tmp; 77 char *tmp;
78 char *delim; 78 char *delim;
79 79
80 if (ctx.cfg.virtual_root) { 80 if (ctx.cfg.virtual_root) {
81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
82 pagename, (filename ? filename:"")); 82 pagename, (filename ? filename:""));
83 delim = "?"; 83 delim = "?";
84 } else { 84 } else {
85 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 85 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
86 (filename ? filename : "")); 86 (filename ? filename : ""));
87 delim = "&"; 87 delim = "&";
88 } 88 }
89 if (query) 89 if (query)
90 tmp = fmt("%s%s%s", tmp, delim, query); 90 tmp = fmt("%s%s%s", tmp, delim, query);
91 return tmp; 91 return tmp;
92} 92}
93 93
94char *cgit_pageurl(const char *reponame, const char *pagename, 94char *cgit_pageurl(const char *reponame, const char *pagename,
95 const char *query) 95 const char *query)
96{ 96{
97 return cgit_fileurl(reponame,pagename,0,query); 97 return cgit_fileurl(reponame,pagename,0,query);
98} 98}
99 99
100const char *cgit_repobasename(const char *reponame) 100const char *cgit_repobasename(const char *reponame)
101{ 101{
102 /* I assume we don't need to store more than one repo basename */ 102 /* I assume we don't need to store more than one repo basename */
103 static char rvbuf[1024]; 103 static char rvbuf[1024];
104 int p; 104 int p;
105 const char *rv; 105 const char *rv;
106 strncpy(rvbuf,reponame,sizeof(rvbuf)); 106 strncpy(rvbuf,reponame,sizeof(rvbuf));
107 if(rvbuf[sizeof(rvbuf)-1]) 107 if(rvbuf[sizeof(rvbuf)-1])
108 die("cgit_repobasename: truncated repository name '%s'", reponame); 108 die("cgit_repobasename: truncated repository name '%s'", reponame);
109 p = strlen(rvbuf)-1; 109 p = strlen(rvbuf)-1;
110 /* strip trailing slashes */ 110 /* strip trailing slashes */
111 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 111 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
112 /* strip trailing .git */ 112 /* strip trailing .git */
113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
114 p -= 3; rvbuf[p--] = 0; 114 p -= 3; rvbuf[p--] = 0;
115 } 115 }
116 /* strip more trailing slashes if any */ 116 /* strip more trailing slashes if any */
117 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 117 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
118 /* find last slash in the remaining string */ 118 /* find last slash in the remaining string */
119 rv = strrchr(rvbuf,'/'); 119 rv = strrchr(rvbuf,'/');
120 if(rv) 120 if(rv)
121 return ++rv; 121 return ++rv;
122 return rvbuf; 122 return rvbuf;
123} 123}
124 124
125char *cgit_currurl() 125char *cgit_currurl()
126{ 126{
127 if (!ctx.cfg.virtual_root) 127 if (!ctx.cfg.virtual_root)
128 return ctx.cfg.script_name; 128 return ctx.cfg.script_name;
129 else if (ctx.qry.page) 129 else if (ctx.qry.page)
130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
131 else if (ctx.qry.repo) 131 else if (ctx.qry.repo)
132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
133 else 133 else
134 return fmt("%s/", ctx.cfg.virtual_root); 134 return fmt("%s/", ctx.cfg.virtual_root);
135} 135}
136 136
137static void site_url(char *page, char *search, int ofs) 137static void site_url(char *page, char *search, int ofs)
138{ 138{
139 char *delim = "?"; 139 char *delim = "?";
140 140
141 if (ctx.cfg.virtual_root) { 141 if (ctx.cfg.virtual_root) {
142 html_attr(ctx.cfg.virtual_root); 142 html_attr(ctx.cfg.virtual_root);
143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
144 html("/"); 144 html("/");
145 } else 145 } else
146 html(ctx.cfg.script_name); 146 html(ctx.cfg.script_name);
147 147
148 if (page) { 148 if (page) {
149 htmlf("?p=%s", page); 149 htmlf("?p=%s", page);
150 delim = "&"; 150 delim = "&";
151 } 151 }
152 if (search) { 152 if (search) {
153 html(delim); 153 html(delim);
154 html("q="); 154 html("q=");
155 html_attr(search); 155 html_attr(search);
156 delim = "&"; 156 delim = "&";
157 } 157 }
158 if (ofs) { 158 if (ofs) {
159 html(delim); 159 html(delim);
160 htmlf("ofs=%d", ofs); 160 htmlf("ofs=%d", ofs);
161 } 161 }
162} 162}
163 163
164static void site_link(char *page, char *name, char *title, char *class, 164static void site_link(char *page, char *name, char *title, char *class,
165 char *search, int ofs) 165 char *search, int ofs)
166{ 166{
167 html("<a"); 167 html("<a");
168 if (title) { 168 if (title) {
169 html(" title='"); 169 html(" title='");
170 html_attr(title); 170 html_attr(title);
171 html("'"); 171 html("'");
172 } 172 }
173 if (class) { 173 if (class) {
174 html(" class='"); 174 html(" class='");
175 html_attr(class); 175 html_attr(class);
176 html("'"); 176 html("'");
177 } 177 }
178 html(" href='"); 178 html(" href='");
179 site_url(page, search, ofs); 179 site_url(page, search, ofs);
180 html("'>"); 180 html("'>");
181 html_txt(name); 181 html_txt(name);
182 html("</a>"); 182 html("</a>");
183} 183}
184 184
185void cgit_index_link(char *name, char *title, char *class, char *pattern, 185void cgit_index_link(char *name, char *title, char *class, char *pattern,
186 int ofs) 186 int ofs)
187{ 187{
188 site_link(NULL, name, title, class, pattern, ofs); 188 site_link(NULL, name, title, class, pattern, ofs);
189} 189}
190 190
191static char *repolink(char *title, char *class, char *page, char *head, 191static char *repolink(char *title, char *class, char *page, char *head,
192 char *path) 192 char *path)
193{ 193{
194 char *delim = "?"; 194 char *delim = "?";
195 195
196 html("<a"); 196 html("<a");
197 if (title) { 197 if (title) {
198 html(" title='"); 198 html(" title='");
199 html_attr(title); 199 html_attr(title);
200 html("'"); 200 html("'");
201 } 201 }
202 if (class) { 202 if (class) {
203 html(" class='"); 203 html(" class='");
204 html_attr(class); 204 html_attr(class);
205 html("'"); 205 html("'");
206 } 206 }
207 html(" href='"); 207 html(" href='");
208 if (ctx.cfg.virtual_root) { 208 if (ctx.cfg.virtual_root) {
209 html_url_path(ctx.cfg.virtual_root); 209 html_url_path(ctx.cfg.virtual_root);
210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
211 html("/"); 211 html("/");
212 html_url_path(ctx.repo->url); 212 html_url_path(ctx.repo->url);
213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
214 html("/"); 214 html("/");
215 if (page) { 215 if (page) {
216 html_url_path(page); 216 html_url_path(page);
217 html("/"); 217 html("/");
218 if (path) 218 if (path)
219 html_url_path(path); 219 html_url_path(path);
220 } 220 }
221 } else { 221 } else {
222 html(ctx.cfg.script_name); 222 html(ctx.cfg.script_name);
223 html("?url="); 223 html("?url=");
224 html_url_arg(ctx.repo->url); 224 html_url_arg(ctx.repo->url);
225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
226 html("/"); 226 html("/");
227 if (page) { 227 if (page) {
228 html_url_arg(page); 228 html_url_arg(page);
229 html("/"); 229 html("/");
230 if (path) 230 if (path)
231 html_url_arg(path); 231 html_url_arg(path);
232 } 232 }
233 delim = "&amp;"; 233 delim = "&amp;";
234 } 234 }
235 if (head && strcmp(head, ctx.repo->defbranch)) { 235 if (head && strcmp(head, ctx.repo->defbranch)) {
236 html(delim); 236 html(delim);
237 html("h="); 237 html("h=");
238 html_url_arg(head); 238 html_url_arg(head);
239 delim = "&amp;"; 239 delim = "&amp;";
240 } 240 }
241 return fmt("%s", delim); 241 return fmt("%s", delim);
242} 242}
243 243
244static void reporevlink(char *page, char *name, char *title, char *class, 244static void reporevlink(char *page, char *name, char *title, char *class,
245 char *head, char *rev, char *path) 245 char *head, char *rev, 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 && strcmp(rev, ctx.qry.head)) { 250 if (rev && 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(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern) 284 char *rev, char *path, int ofs, char *grep, char *pattern)
285{ 285{
286 char *delim; 286 char *delim;
287 287
288 delim = repolink(title, class, "log", head, path); 288 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 289 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 290 html(delim);
291 html("id="); 291 html("id=");
292 html_url_arg(rev); 292 html_url_arg(rev);
293 delim = "&"; 293 delim = "&";
294 } 294 }
295 if (grep && pattern) { 295 if (grep && pattern) {
296 html(delim); 296 html(delim);
297 html("qt="); 297 html("qt=");
298 html_url_arg(grep); 298 html_url_arg(grep);
299 delim = "&"; 299 delim = "&";
300 html(delim); 300 html(delim);
301 html("q="); 301 html("q=");
302 html_url_arg(pattern); 302 html_url_arg(pattern);
303 } 303 }
304 if (ofs > 0) { 304 if (ofs > 0) {
305 html(delim); 305 html(delim);
306 html("ofs="); 306 html("ofs=");
307 htmlf("%d", ofs); 307 htmlf("%d", ofs);
308 } 308 }
309 html("'>"); 309 html("'>");
310 html_txt(name); 310 html_txt(name);
311 html("</a>"); 311 html("</a>");
312} 312}
313 313
314void cgit_commit_link(char *name, char *title, char *class, char *head, 314void cgit_commit_link(char *name, char *title, char *class, char *head,
315 char *rev) 315 char *rev)
316{ 316{
317 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 317 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
318 name[ctx.cfg.max_msg_len] = '\0'; 318 name[ctx.cfg.max_msg_len] = '\0';
319 name[ctx.cfg.max_msg_len - 1] = '.'; 319 name[ctx.cfg.max_msg_len - 1] = '.';
320 name[ctx.cfg.max_msg_len - 2] = '.'; 320 name[ctx.cfg.max_msg_len - 2] = '.';
321 name[ctx.cfg.max_msg_len - 3] = '.'; 321 name[ctx.cfg.max_msg_len - 3] = '.';
322 } 322 }
323 reporevlink("commit", name, title, class, head, rev, NULL); 323 reporevlink("commit", name, title, class, head, rev, NULL);
324} 324}
325 325
326void cgit_refs_link(char *name, char *title, char *class, char *head, 326void cgit_refs_link(char *name, char *title, char *class, char *head,
327 char *rev, char *path) 327 char *rev, char *path)
328{ 328{
329 reporevlink("refs", name, title, class, head, rev, path); 329 reporevlink("refs", name, title, class, head, rev, path);
330} 330}
331 331
332void cgit_snapshot_link(char *name, char *title, char *class, char *head, 332void cgit_snapshot_link(char *name, char *title, char *class, char *head,
333 char *rev, char *archivename) 333 char *rev, char *archivename)
334{ 334{
335 reporevlink("snapshot", name, title, class, head, rev, archivename); 335 reporevlink("snapshot", name, title, class, head, rev, archivename);
336} 336}
337 337
338void cgit_diff_link(char *name, char *title, char *class, char *head, 338void cgit_diff_link(char *name, char *title, char *class, char *head,
339 char *new_rev, char *old_rev, char *path) 339 char *new_rev, char *old_rev, char *path)
340{ 340{
341 char *delim; 341 char *delim;
342 342
343 delim = repolink(title, class, "diff", head, path); 343 delim = repolink(title, class, "diff", head, path);
344 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 344 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
345 html(delim); 345 html(delim);
346 html("id="); 346 html("id=");
347 html_url_arg(new_rev); 347 html_url_arg(new_rev);
348 delim = "&amp;"; 348 delim = "&amp;";
349 } 349 }
350 if (old_rev) { 350 if (old_rev) {
351 html(delim); 351 html(delim);
352 html("id2="); 352 html("id2=");
353 html_url_arg(old_rev); 353 html_url_arg(old_rev);
354 } 354 }
355 html("'>"); 355 html("'>");
356 html_txt(name); 356 html_txt(name);
357 html("</a>"); 357 html("</a>");
358} 358}
359 359
360void cgit_patch_link(char *name, char *title, char *class, char *head, 360void cgit_patch_link(char *name, char *title, char *class, char *head,
361 char *rev) 361 char *rev)
362{ 362{
363 reporevlink("patch", name, title, class, head, rev, NULL); 363 reporevlink("patch", name, title, class, head, rev, NULL);
364} 364}
365 365
366void cgit_object_link(struct object *obj) 366void cgit_object_link(struct object *obj)
367{ 367{
368 char *page, *rev, *name; 368 char *page, *rev, *name;
369 369
370 if (obj->type == OBJ_COMMIT) { 370 if (obj->type == OBJ_COMMIT) {
371 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 371 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
372 ctx.qry.head, sha1_to_hex(obj->sha1)); 372 ctx.qry.head, sha1_to_hex(obj->sha1));
373 return; 373 return;
374 } else if (obj->type == OBJ_TREE) 374 } else if (obj->type == OBJ_TREE)
375 page = "tree"; 375 page = "tree";
376 else if (obj->type == OBJ_TAG) 376 else if (obj->type == OBJ_TAG)
377 page = "tag"; 377 page = "tag";
378 else 378 else
379 page = "blob"; 379 page = "blob";
380 rev = sha1_to_hex(obj->sha1); 380 rev = sha1_to_hex(obj->sha1);
381 name = fmt("%s %s", typename(obj->type), rev); 381 name = fmt("%s %s", typename(obj->type), rev);
382 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL); 382 reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
383} 383}
384 384
385void cgit_print_date(time_t secs, char *format, int local_time) 385void cgit_print_date(time_t secs, char *format, int local_time)
386{ 386{
387 char buf[64]; 387 char buf[64];
388 struct tm *time; 388 struct tm *time;
389 389
390 if (!secs) 390 if (!secs)
391 return; 391 return;
392 if(local_time) 392 if(local_time)
393 time = localtime(&secs); 393 time = localtime(&secs);
394 else 394 else
395 time = gmtime(&secs); 395 time = gmtime(&secs);
396 strftime(buf, sizeof(buf)-1, format, time); 396 strftime(buf, sizeof(buf)-1, format, time);
397 html_txt(buf); 397 html_txt(buf);
398} 398}
399 399
400void cgit_print_age(time_t t, time_t max_relative, char *format) 400void cgit_print_age(time_t t, time_t max_relative, char *format)
401{ 401{
402 time_t now, secs; 402 time_t now, secs;
403 403
404 if (!t) 404 if (!t)
405 return; 405 return;
406 time(&now); 406 time(&now);
407 secs = now - t; 407 secs = now - t;
408 408
409 if (secs > max_relative && max_relative >= 0) { 409 if (secs > max_relative && max_relative >= 0) {
410 cgit_print_date(t, format, ctx.cfg.local_time); 410 cgit_print_date(t, format, ctx.cfg.local_time);
411 return; 411 return;
412 } 412 }
413 413
414 if (secs < TM_HOUR * 2) { 414 if (secs < TM_HOUR * 2) {
415 htmlf("<span class='age-mins'>%.0f min.</span>", 415 htmlf("<span class='age-mins'>%.0f min.</span>",
416 secs * 1.0 / TM_MIN); 416 secs * 1.0 / TM_MIN);
417 return; 417 return;
418 } 418 }
419 if (secs < TM_DAY * 2) { 419 if (secs < TM_DAY * 2) {
420 htmlf("<span class='age-hours'>%.0f hours</span>", 420 htmlf("<span class='age-hours'>%.0f hours</span>",
421 secs * 1.0 / TM_HOUR); 421 secs * 1.0 / TM_HOUR);
422 return; 422 return;
423 } 423 }
424 if (secs < TM_WEEK * 2) { 424 if (secs < TM_WEEK * 2) {
425 htmlf("<span class='age-days'>%.0f days</span>", 425 htmlf("<span class='age-days'>%.0f days</span>",
426 secs * 1.0 / TM_DAY); 426 secs * 1.0 / TM_DAY);
427 return; 427 return;
428 } 428 }
429 if (secs < TM_MONTH * 2) { 429 if (secs < TM_MONTH * 2) {
430 htmlf("<span class='age-weeks'>%.0f weeks</span>", 430 htmlf("<span class='age-weeks'>%.0f weeks</span>",
431 secs * 1.0 / TM_WEEK); 431 secs * 1.0 / TM_WEEK);
432 return; 432 return;
433 } 433 }
434 if (secs < TM_YEAR * 2) { 434 if (secs < TM_YEAR * 2) {
435 htmlf("<span class='age-months'>%.0f months</span>", 435 htmlf("<span class='age-months'>%.0f months</span>",
436 secs * 1.0 / TM_MONTH); 436 secs * 1.0 / TM_MONTH);
437 return; 437 return;
438 } 438 }
439 htmlf("<span class='age-years'>%.0f years</span>", 439 htmlf("<span class='age-years'>%.0f years</span>",
440 secs * 1.0 / TM_YEAR); 440 secs * 1.0 / TM_YEAR);
441} 441}
442 442
443void cgit_print_http_headers(struct cgit_context *ctx) 443void cgit_print_http_headers(struct cgit_context *ctx)
444{ 444{
445 if (ctx->page.mimetype && ctx->page.charset) 445 if (ctx->page.mimetype && ctx->page.charset)
446 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 446 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
447 ctx->page.charset); 447 ctx->page.charset);
448 else if (ctx->page.mimetype) 448 else if (ctx->page.mimetype)
449 htmlf("Content-Type: %s\n", ctx->page.mimetype); 449 htmlf("Content-Type: %s\n", ctx->page.mimetype);
450 if (ctx->page.size) 450 if (ctx->page.size)
451 htmlf("Content-Length: %ld\n", ctx->page.size); 451 htmlf("Content-Length: %ld\n", ctx->page.size);
452 if (ctx->page.filename) 452 if (ctx->page.filename)
453 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 453 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
454 ctx->page.filename); 454 ctx->page.filename);
455 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 455 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
456 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 456 htmlf("Expires: %s\n", http_date(ctx->page.expires));
457 html("\n"); 457 html("\n");
458} 458}
459 459
460void cgit_print_docstart(struct cgit_context *ctx) 460void cgit_print_docstart(struct cgit_context *ctx)
461{ 461{
462 char *host = cgit_hosturl(); 462 char *host = cgit_hosturl();
463 html(cgit_doctype); 463 html(cgit_doctype);
464 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 464 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
465 html("<head>\n"); 465 html("<head>\n");
466 html("<title>"); 466 html("<title>");
467 html_txt(ctx->page.title); 467 html_txt(ctx->page.title);
468 html("</title>\n"); 468 html("</title>\n");
469 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 469 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
470 if (ctx->cfg.robots && *ctx->cfg.robots) 470 if (ctx->cfg.robots && *ctx->cfg.robots)
471 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 471 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
472 html("<link rel='stylesheet' type='text/css' href='"); 472 html("<link rel='stylesheet' type='text/css' href='");
473 html_attr(ctx->cfg.css); 473 html_attr(ctx->cfg.css);
474 html("'/>\n"); 474 html("'/>\n");
475 if (ctx->cfg.favicon) { 475 if (ctx->cfg.favicon) {
476 html("<link rel='shortcut icon' href='"); 476 html("<link rel='shortcut icon' href='");
477 html_attr(ctx->cfg.favicon); 477 html_attr(ctx->cfg.favicon);
478 html("'/>\n"); 478 html("'/>\n");
479 } 479 }
480 if (host && ctx->repo) { 480 if (host && ctx->repo) {
481 html("<link rel='alternate' title='Atom feed' href='http://"); 481 html("<link rel='alternate' title='Atom feed' href='http://");
482 html_attr(cgit_hosturl()); 482 html_attr(cgit_hosturl());
483 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 483 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
484 fmt("h=%s", ctx->qry.head))); 484 fmt("h=%s", ctx->qry.head)));
485 html("' type='application/atom+xml'/>"); 485 html("' type='application/atom+xml'/>");
486 } 486 }
487 html("</head>\n"); 487 html("</head>\n");
488 html("<body>\n"); 488 html("<body>\n");
489} 489}
490 490
491void cgit_print_docend() 491void cgit_print_docend()
492{ 492{
493 html("</div>"); 493 html("</div>");
494 if (ctx.cfg.footer) 494 if (ctx.cfg.footer)
495 html_include(ctx.cfg.footer); 495 html_include(ctx.cfg.footer);
496 else { 496 else {
497 htmlf("<div class='footer'>generated by cgit %s at ", 497 htmlf("<div class='footer'>generated by cgit %s at ",
498 cgit_version); 498 cgit_version);
499 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 499 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
500 html("</div>\n"); 500 html("</div>\n");
501 } 501 }
502 html("</body>\n</html>\n"); 502 html("</body>\n</html>\n");
503} 503}
504 504
505int print_branch_option(const char *refname, const unsigned char *sha1, 505int print_branch_option(const char *refname, const unsigned char *sha1,
506 int flags, void *cb_data) 506 int flags, void *cb_data)
507{ 507{
508 char *name = (char *)refname; 508 char *name = (char *)refname;
509 html_option(name, name, ctx.qry.head); 509 html_option(name, name, ctx.qry.head);
510 return 0; 510 return 0;
511} 511}
512 512
513int print_archive_ref(const char *refname, const unsigned char *sha1, 513int print_archive_ref(const char *refname, const unsigned char *sha1,
514 int flags, void *cb_data) 514 int flags, void *cb_data)
515{ 515{
516 struct tag *tag; 516 struct tag *tag;
517 struct taginfo *info; 517 struct taginfo *info;
518 struct object *obj; 518 struct object *obj;
519 char buf[256], *url; 519 char buf[256], *url;
520 unsigned char fileid[20]; 520 unsigned char fileid[20];
521 int *header = (int *)cb_data; 521 int *header = (int *)cb_data;
522 522
523 if (prefixcmp(refname, "refs/archives")) 523 if (prefixcmp(refname, "refs/archives"))
524 return 0; 524 return 0;
525 strncpy(buf, refname+14, sizeof(buf)); 525 strncpy(buf, refname+14, sizeof(buf));
526 obj = parse_object(sha1); 526 obj = parse_object(sha1);
527 if (!obj) 527 if (!obj)
528 return 1; 528 return 1;
529 if (obj->type == OBJ_TAG) { 529 if (obj->type == OBJ_TAG) {
530 tag = lookup_tag(sha1); 530 tag = lookup_tag(sha1);
531 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 531 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
532 return 0; 532 return 0;
533 hashcpy(fileid, tag->tagged->sha1); 533 hashcpy(fileid, tag->tagged->sha1);
534 } else if (obj->type != OBJ_BLOB) { 534 } else if (obj->type != OBJ_BLOB) {
535 return 0; 535 return 0;
536 } else { 536 } else {
537 hashcpy(fileid, sha1); 537 hashcpy(fileid, sha1);
538 } 538 }
539 if (!*header) { 539 if (!*header) {
540 html("<h1>download</h1>\n"); 540 html("<h1>download</h1>\n");
541 *header = 1; 541 *header = 1;
542 } 542 }
543 url = cgit_pageurl(ctx.qry.repo, "blob", 543 url = cgit_pageurl(ctx.qry.repo, "blob",
544 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 544 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
545 buf)); 545 buf));
546 html_link_open(url, NULL, "menu"); 546 html_link_open(url, NULL, "menu");
547 html_txt(strlpart(buf, 20)); 547 html_txt(strlpart(buf, 20));
548 html_link_close(); 548 html_link_close();
549 return 0; 549 return 0;
550} 550}
551 551
552void add_hidden_formfields(int incl_head, int incl_search, char *page) 552void add_hidden_formfields(int incl_head, int incl_search, char *page)
553{ 553{
554 char *url; 554 char *url;
555 555
556 if (!ctx.cfg.virtual_root) { 556 if (!ctx.cfg.virtual_root) {
557 url = fmt("%s/%s", ctx.qry.repo, page); 557 url = fmt("%s/%s", ctx.qry.repo, page);
558 if (ctx.qry.path) 558 if (ctx.qry.path)
559 url = fmt("%s/%s", url, ctx.qry.path); 559 url = fmt("%s/%s", url, ctx.qry.path);
560 html_hidden("url", url); 560 html_hidden("url", url);
561 } 561 }
562 562
563 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 563 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
564 strcmp(ctx.qry.head, ctx.repo->defbranch)) 564 strcmp(ctx.qry.head, ctx.repo->defbranch))
565 html_hidden("h", ctx.qry.head); 565 html_hidden("h", ctx.qry.head);
566 566
567 if (ctx.qry.sha1) 567 if (ctx.qry.sha1)
568 html_hidden("id", ctx.qry.sha1); 568 html_hidden("id", ctx.qry.sha1);
569 if (ctx.qry.sha2) 569 if (ctx.qry.sha2)
570 html_hidden("id2", ctx.qry.sha2); 570 html_hidden("id2", ctx.qry.sha2);
571 571
572 if (incl_search) { 572 if (incl_search) {
573 if (ctx.qry.grep) 573 if (ctx.qry.grep)
574 html_hidden("qt", ctx.qry.grep); 574 html_hidden("qt", ctx.qry.grep);
575 if (ctx.qry.search) 575 if (ctx.qry.search)
576 html_hidden("q", ctx.qry.search); 576 html_hidden("q", ctx.qry.search);
577 } 577 }
578} 578}
579 579
580const char *fallback_cmd = "repolist";
581
580char *hc(struct cgit_cmd *cmd, const char *page) 582char *hc(struct cgit_cmd *cmd, const char *page)
581{ 583{
582 return (strcmp(cmd->name, page) ? NULL : "active"); 584 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
583} 585}
584 586
585void cgit_print_pageheader(struct cgit_context *ctx) 587void cgit_print_pageheader(struct cgit_context *ctx)
586{ 588{
587 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 589 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
588 590
591 if (!cmd && ctx->repo)
592 fallback_cmd = "summary";
593
589 html("<table id='header'>\n"); 594 html("<table id='header'>\n");
590 html("<tr>\n"); 595 html("<tr>\n");
591 html("<td class='logo' rowspan='2'><a href='"); 596 html("<td class='logo' rowspan='2'><a href='");
592 if (ctx->cfg.logo_link) 597 if (ctx->cfg.logo_link)
593 html_attr(ctx->cfg.logo_link); 598 html_attr(ctx->cfg.logo_link);
594 else 599 else
595 html_attr(cgit_rooturl()); 600 html_attr(cgit_rooturl());
596 html("'><img src='"); 601 html("'><img src='");
597 html_attr(ctx->cfg.logo); 602 html_attr(ctx->cfg.logo);
598 html("' alt='cgit logo'/></a></td>\n"); 603 html("' alt='cgit logo'/></a></td>\n");
599 604
600 html("<td class='main'>"); 605 html("<td class='main'>");
601 if (ctx->repo) { 606 if (ctx->repo) {
602 cgit_index_link("index", NULL, NULL, NULL, 0); 607 cgit_index_link("index", NULL, NULL, NULL, 0);
603 html(" : "); 608 html(" : ");
604 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 609 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
605 html("</td><td class='form'>"); 610 html("</td><td class='form'>");
606 html("<form method='get' action=''>\n"); 611 html("<form method='get' action=''>\n");
607 add_hidden_formfields(0, 1, ctx->qry.page); 612 add_hidden_formfields(0, 1, ctx->qry.page);
608 html("<select name='h' onchange='this.form.submit();'>\n"); 613 html("<select name='h' onchange='this.form.submit();'>\n");
609 for_each_branch_ref(print_branch_option, ctx->qry.head); 614 for_each_branch_ref(print_branch_option, ctx->qry.head);
610 html("</select> "); 615 html("</select> ");
611 html("<input type='submit' name='' value='switch'/>"); 616 html("<input type='submit' name='' value='switch'/>");
612 html("</form>"); 617 html("</form>");
613 } else 618 } else
614 html_txt(ctx->cfg.root_title); 619 html_txt(ctx->cfg.root_title);
615 html("</td></tr>\n"); 620 html("</td></tr>\n");
616 621
617 html("<tr><td class='sub'>"); 622 html("<tr><td class='sub'>");
618 if (ctx->repo) { 623 if (ctx->repo) {
619 html_txt(ctx->repo->desc); 624 html_txt(ctx->repo->desc);
620 html("</td><td class='sub right'>"); 625 html("</td><td class='sub right'>");
621 html_txt(ctx->repo->owner); 626 html_txt(ctx->repo->owner);
622 } else { 627 } else {
623 if (ctx->cfg.root_desc) 628 if (ctx->cfg.root_desc)
624 html_txt(ctx->cfg.root_desc); 629 html_txt(ctx->cfg.root_desc);
625 else if (ctx->cfg.index_info) 630 else if (ctx->cfg.index_info)
626 html_include(ctx->cfg.index_info); 631 html_include(ctx->cfg.index_info);
627 } 632 }
628 html("</td></tr></table>\n"); 633 html("</td></tr></table>\n");
629 634
630 html("<table class='tabs'><tr><td>\n"); 635 html("<table class='tabs'><tr><td>\n");
631 if (ctx->repo) { 636 if (ctx->repo) {
632 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 637 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
633 ctx->qry.head); 638 ctx->qry.head);
634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 639 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
635 ctx->qry.sha1, NULL); 640 ctx->qry.sha1, NULL);
636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 641 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
637 NULL, NULL, 0, NULL, NULL); 642 NULL, NULL, 0, NULL, NULL);
638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 643 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
639 ctx->qry.sha1, NULL); 644 ctx->qry.sha1, NULL);
640 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 645 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
641 ctx->qry.head, ctx->qry.sha1); 646 ctx->qry.head, ctx->qry.sha1);
642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 647 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
643 ctx->qry.sha1, ctx->qry.sha2, NULL); 648 ctx->qry.sha1, ctx->qry.sha2, NULL);
644 if (ctx->repo->readme) 649 if (ctx->repo->readme)
645 reporevlink("about", "about", NULL, 650 reporevlink("about", "about", NULL,
646 hc(cmd, "about"), ctx->qry.head, NULL, 651 hc(cmd, "about"), ctx->qry.head, NULL,
647 NULL); 652 NULL);
648 html("</td><td class='form'>"); 653 html("</td><td class='form'>");
649 html("<form class='right' method='get' action='"); 654 html("<form class='right' method='get' action='");
650 if (ctx->cfg.virtual_root) 655 if (ctx->cfg.virtual_root)
651 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 656 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
652 ctx->qry.path, NULL)); 657 ctx->qry.path, NULL));
653 html("'>\n"); 658 html("'>\n");
654 add_hidden_formfields(1, 0, "log"); 659 add_hidden_formfields(1, 0, "log");
655 html("<select name='qt'>\n"); 660 html("<select name='qt'>\n");
656 html_option("grep", "log msg", ctx->qry.grep); 661 html_option("grep", "log msg", ctx->qry.grep);
657 html_option("author", "author", ctx->qry.grep); 662 html_option("author", "author", ctx->qry.grep);
658 html_option("committer", "committer", ctx->qry.grep); 663 html_option("committer", "committer", ctx->qry.grep);
659 html("</select>\n"); 664 html("</select>\n");
660 html("<input class='txt' type='text' size='10' name='q' value='"); 665 html("<input class='txt' type='text' size='10' name='q' value='");
661 html_attr(ctx->qry.search); 666 html_attr(ctx->qry.search);
662 html("'/>\n"); 667 html("'/>\n");
663 html("<input type='submit' value='search'/>\n"); 668 html("<input type='submit' value='search'/>\n");
664 html("</form>\n"); 669 html("</form>\n");
665 } else { 670 } else {
666 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 671 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
667 if (ctx->cfg.root_readme) 672 if (ctx->cfg.root_readme)
668 site_link("about", "about", NULL, hc(cmd, "about"), 673 site_link("about", "about", NULL, hc(cmd, "about"),
669 NULL, 0); 674 NULL, 0);
670 html("</td><td class='form'>"); 675 html("</td><td class='form'>");
671 html("<form method='get' action='"); 676 html("<form method='get' action='");
672 html_attr(cgit_rooturl()); 677 html_attr(cgit_rooturl());
673 html("'>\n"); 678 html("'>\n");
674 html("<input type='text' name='q' size='10' value='"); 679 html("<input type='text' name='q' size='10' value='");
675 html_attr(ctx->qry.search); 680 html_attr(ctx->qry.search);
676 html("'/>\n"); 681 html("'/>\n");
677 html("<input type='submit' value='search'/>\n"); 682 html("<input type='submit' value='search'/>\n");
678 html("</form>"); 683 html("</form>");
679 } 684 }
680 html("</td></tr></table>\n"); 685 html("</td></tr></table>\n");
681 html("<div class='content'>"); 686 html("<div class='content'>");
682} 687}
683 688
684void cgit_print_filemode(unsigned short mode) 689void cgit_print_filemode(unsigned short mode)
685{ 690{
686 if (S_ISDIR(mode)) 691 if (S_ISDIR(mode))
687 html("d"); 692 html("d");
688 else if (S_ISLNK(mode)) 693 else if (S_ISLNK(mode))
689 html("l"); 694 html("l");
690 else if (S_ISGITLINK(mode)) 695 else if (S_ISGITLINK(mode))
691 html("m"); 696 html("m");
692 else 697 else
693 html("-"); 698 html("-");
694 html_fileperm(mode >> 6); 699 html_fileperm(mode >> 6);
695 html_fileperm(mode >> 3); 700 html_fileperm(mode >> 3);
696 html_fileperm(mode); 701 html_fileperm(mode);
697} 702}
698 703
699void cgit_print_snapshot_links(const char *repo, const char *head, 704void cgit_print_snapshot_links(const char *repo, const char *head,
700 const char *hex, int snapshots) 705 const char *hex, int snapshots)
701{ 706{
702 const struct cgit_snapshot_format* f; 707 const struct cgit_snapshot_format* f;
703 char *filename; 708 char *filename;
704 709
705 for (f = cgit_snapshot_formats; f->suffix; f++) { 710 for (f = cgit_snapshot_formats; f->suffix; f++) {
706 if (!(snapshots & f->bit)) 711 if (!(snapshots & f->bit))
707 continue; 712 continue;
708 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 713 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
709 f->suffix); 714 f->suffix);
710 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 715 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
711 (char *)hex, filename); 716 (char *)hex, filename);
712 html("<br/>"); 717 html("<br/>");
713 } 718 }
714} 719}