summaryrefslogtreecommitdiffabout
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
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 (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c1
-rw-r--r--ui-shared.c7
2 files changed, 6 insertions, 2 deletions
diff --git a/cgit.c b/cgit.c
index c82587b..6e5215e 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,472 +1,471 @@
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 * 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 "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "scan-tree.h" 15#include "scan-tree.h"
16 16
17const char *cgit_version = CGIT_VERSION; 17const char *cgit_version = CGIT_VERSION;
18 18
19void config_cb(const char *name, const char *value) 19void config_cb(const char *name, const char *value)
20{ 20{
21 if (!strcmp(name, "root-title")) 21 if (!strcmp(name, "root-title"))
22 ctx.cfg.root_title = xstrdup(value); 22 ctx.cfg.root_title = xstrdup(value);
23 else if (!strcmp(name, "root-desc")) 23 else if (!strcmp(name, "root-desc"))
24 ctx.cfg.root_desc = xstrdup(value); 24 ctx.cfg.root_desc = xstrdup(value);
25 else if (!strcmp(name, "root-readme")) 25 else if (!strcmp(name, "root-readme"))
26 ctx.cfg.root_readme = xstrdup(value); 26 ctx.cfg.root_readme = xstrdup(value);
27 else if (!strcmp(name, "css")) 27 else if (!strcmp(name, "css"))
28 ctx.cfg.css = xstrdup(value); 28 ctx.cfg.css = xstrdup(value);
29 else if (!strcmp(name, "favicon")) 29 else if (!strcmp(name, "favicon"))
30 ctx.cfg.favicon = xstrdup(value); 30 ctx.cfg.favicon = xstrdup(value);
31 else if (!strcmp(name, "footer")) 31 else if (!strcmp(name, "footer"))
32 ctx.cfg.footer = xstrdup(value); 32 ctx.cfg.footer = xstrdup(value);
33 else if (!strcmp(name, "logo")) 33 else if (!strcmp(name, "logo"))
34 ctx.cfg.logo = xstrdup(value); 34 ctx.cfg.logo = xstrdup(value);
35 else if (!strcmp(name, "index-header")) 35 else if (!strcmp(name, "index-header"))
36 ctx.cfg.index_header = xstrdup(value); 36 ctx.cfg.index_header = xstrdup(value);
37 else if (!strcmp(name, "index-info")) 37 else if (!strcmp(name, "index-info"))
38 ctx.cfg.index_info = xstrdup(value); 38 ctx.cfg.index_info = xstrdup(value);
39 else if (!strcmp(name, "logo-link")) 39 else if (!strcmp(name, "logo-link"))
40 ctx.cfg.logo_link = xstrdup(value); 40 ctx.cfg.logo_link = xstrdup(value);
41 else if (!strcmp(name, "module-link")) 41 else if (!strcmp(name, "module-link"))
42 ctx.cfg.module_link = xstrdup(value); 42 ctx.cfg.module_link = xstrdup(value);
43 else if (!strcmp(name, "virtual-root")) { 43 else if (!strcmp(name, "virtual-root")) {
44 ctx.cfg.virtual_root = trim_end(value, '/'); 44 ctx.cfg.virtual_root = trim_end(value, '/');
45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
46 ctx.cfg.virtual_root = ""; 46 ctx.cfg.virtual_root = "";
47 } else if (!strcmp(name, "nocache")) 47 } else if (!strcmp(name, "nocache"))
48 ctx.cfg.nocache = atoi(value); 48 ctx.cfg.nocache = atoi(value);
49 else if (!strcmp(name, "snapshots")) 49 else if (!strcmp(name, "snapshots"))
50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
51 else if (!strcmp(name, "enable-index-links")) 51 else if (!strcmp(name, "enable-index-links"))
52 ctx.cfg.enable_index_links = atoi(value); 52 ctx.cfg.enable_index_links = atoi(value);
53 else if (!strcmp(name, "enable-log-filecount")) 53 else if (!strcmp(name, "enable-log-filecount"))
54 ctx.cfg.enable_log_filecount = atoi(value); 54 ctx.cfg.enable_log_filecount = atoi(value);
55 else if (!strcmp(name, "enable-log-linecount")) 55 else if (!strcmp(name, "enable-log-linecount"))
56 ctx.cfg.enable_log_linecount = atoi(value); 56 ctx.cfg.enable_log_linecount = atoi(value);
57 else if (!strcmp(name, "cache-size")) 57 else if (!strcmp(name, "cache-size"))
58 ctx.cfg.cache_size = atoi(value); 58 ctx.cfg.cache_size = atoi(value);
59 else if (!strcmp(name, "cache-root")) 59 else if (!strcmp(name, "cache-root"))
60 ctx.cfg.cache_root = xstrdup(value); 60 ctx.cfg.cache_root = xstrdup(value);
61 else if (!strcmp(name, "cache-root-ttl")) 61 else if (!strcmp(name, "cache-root-ttl"))
62 ctx.cfg.cache_root_ttl = atoi(value); 62 ctx.cfg.cache_root_ttl = atoi(value);
63 else if (!strcmp(name, "cache-repo-ttl")) 63 else if (!strcmp(name, "cache-repo-ttl"))
64 ctx.cfg.cache_repo_ttl = atoi(value); 64 ctx.cfg.cache_repo_ttl = atoi(value);
65 else if (!strcmp(name, "cache-static-ttl")) 65 else if (!strcmp(name, "cache-static-ttl"))
66 ctx.cfg.cache_static_ttl = atoi(value); 66 ctx.cfg.cache_static_ttl = atoi(value);
67 else if (!strcmp(name, "cache-dynamic-ttl")) 67 else if (!strcmp(name, "cache-dynamic-ttl"))
68 ctx.cfg.cache_dynamic_ttl = atoi(value); 68 ctx.cfg.cache_dynamic_ttl = atoi(value);
69 else if (!strcmp(name, "max-message-length")) 69 else if (!strcmp(name, "max-message-length"))
70 ctx.cfg.max_msg_len = atoi(value); 70 ctx.cfg.max_msg_len = atoi(value);
71 else if (!strcmp(name, "max-repodesc-length")) 71 else if (!strcmp(name, "max-repodesc-length"))
72 ctx.cfg.max_repodesc_len = atoi(value); 72 ctx.cfg.max_repodesc_len = atoi(value);
73 else if (!strcmp(name, "max-repo-count")) 73 else if (!strcmp(name, "max-repo-count"))
74 ctx.cfg.max_repo_count = atoi(value); 74 ctx.cfg.max_repo_count = atoi(value);
75 else if (!strcmp(name, "max-commit-count")) 75 else if (!strcmp(name, "max-commit-count"))
76 ctx.cfg.max_commit_count = atoi(value); 76 ctx.cfg.max_commit_count = atoi(value);
77 else if (!strcmp(name, "summary-log")) 77 else if (!strcmp(name, "summary-log"))
78 ctx.cfg.summary_log = atoi(value); 78 ctx.cfg.summary_log = atoi(value);
79 else if (!strcmp(name, "summary-branches")) 79 else if (!strcmp(name, "summary-branches"))
80 ctx.cfg.summary_branches = atoi(value); 80 ctx.cfg.summary_branches = atoi(value);
81 else if (!strcmp(name, "summary-tags")) 81 else if (!strcmp(name, "summary-tags"))
82 ctx.cfg.summary_tags = atoi(value); 82 ctx.cfg.summary_tags = atoi(value);
83 else if (!strcmp(name, "agefile")) 83 else if (!strcmp(name, "agefile"))
84 ctx.cfg.agefile = xstrdup(value); 84 ctx.cfg.agefile = xstrdup(value);
85 else if (!strcmp(name, "renamelimit")) 85 else if (!strcmp(name, "renamelimit"))
86 ctx.cfg.renamelimit = atoi(value); 86 ctx.cfg.renamelimit = atoi(value);
87 else if (!strcmp(name, "robots")) 87 else if (!strcmp(name, "robots"))
88 ctx.cfg.robots = xstrdup(value); 88 ctx.cfg.robots = xstrdup(value);
89 else if (!strcmp(name, "clone-prefix")) 89 else if (!strcmp(name, "clone-prefix"))
90 ctx.cfg.clone_prefix = xstrdup(value); 90 ctx.cfg.clone_prefix = xstrdup(value);
91 else if (!strcmp(name, "local-time")) 91 else if (!strcmp(name, "local-time"))
92 ctx.cfg.local_time = atoi(value); 92 ctx.cfg.local_time = atoi(value);
93 else if (!strcmp(name, "repo.group")) 93 else if (!strcmp(name, "repo.group"))
94 ctx.cfg.repo_group = xstrdup(value); 94 ctx.cfg.repo_group = xstrdup(value);
95 else if (!strcmp(name, "repo.url")) 95 else if (!strcmp(name, "repo.url"))
96 ctx.repo = cgit_add_repo(value); 96 ctx.repo = cgit_add_repo(value);
97 else if (!strcmp(name, "repo.name")) 97 else if (!strcmp(name, "repo.name"))
98 ctx.repo->name = xstrdup(value); 98 ctx.repo->name = xstrdup(value);
99 else if (ctx.repo && !strcmp(name, "repo.path")) 99 else if (ctx.repo && !strcmp(name, "repo.path"))
100 ctx.repo->path = trim_end(value, '/'); 100 ctx.repo->path = trim_end(value, '/');
101 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 101 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
102 ctx.repo->clone_url = xstrdup(value); 102 ctx.repo->clone_url = xstrdup(value);
103 else if (ctx.repo && !strcmp(name, "repo.desc")) 103 else if (ctx.repo && !strcmp(name, "repo.desc"))
104 ctx.repo->desc = xstrdup(value); 104 ctx.repo->desc = xstrdup(value);
105 else if (ctx.repo && !strcmp(name, "repo.owner")) 105 else if (ctx.repo && !strcmp(name, "repo.owner"))
106 ctx.repo->owner = xstrdup(value); 106 ctx.repo->owner = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 107 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
108 ctx.repo->defbranch = xstrdup(value); 108 ctx.repo->defbranch = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 109 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
115 else if (ctx.repo && !strcmp(name, "repo.module-link")) 115 else if (ctx.repo && !strcmp(name, "repo.module-link"))
116 ctx.repo->module_link= xstrdup(value); 116 ctx.repo->module_link= xstrdup(value);
117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
118 if (*value == '/') 118 if (*value == '/')
119 ctx.repo->readme = xstrdup(value); 119 ctx.repo->readme = xstrdup(value);
120 else 120 else
121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
122 } else if (!strcmp(name, "include")) 122 } else if (!strcmp(name, "include"))
123 parse_configfile(value, config_cb); 123 parse_configfile(value, config_cb);
124} 124}
125 125
126static void querystring_cb(const char *name, const char *value) 126static void querystring_cb(const char *name, const char *value)
127{ 127{
128 if (!strcmp(name,"r")) { 128 if (!strcmp(name,"r")) {
129 ctx.qry.repo = xstrdup(value); 129 ctx.qry.repo = xstrdup(value);
130 ctx.repo = cgit_get_repoinfo(value); 130 ctx.repo = cgit_get_repoinfo(value);
131 } else if (!strcmp(name, "p")) { 131 } else if (!strcmp(name, "p")) {
132 ctx.qry.page = xstrdup(value); 132 ctx.qry.page = xstrdup(value);
133 } else if (!strcmp(name, "url")) { 133 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 134 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 135 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 136 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 137 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 138 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 139 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 140 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 141 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 142 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 143 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 144 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 145 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 146 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 147 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 148 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 149 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 150 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 151 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 152 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 153 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 154 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 155 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 156 ctx.qry.mimetype = xstrdup(value);
157 } 157 }
158} 158}
159 159
160static void prepare_context(struct cgit_context *ctx) 160static void prepare_context(struct cgit_context *ctx)
161{ 161{
162 memset(ctx, 0, sizeof(ctx)); 162 memset(ctx, 0, sizeof(ctx));
163 ctx->cfg.agefile = "info/web/last-modified"; 163 ctx->cfg.agefile = "info/web/last-modified";
164 ctx->cfg.nocache = 0; 164 ctx->cfg.nocache = 0;
165 ctx->cfg.cache_size = 0; 165 ctx->cfg.cache_size = 0;
166 ctx->cfg.cache_dynamic_ttl = 5; 166 ctx->cfg.cache_dynamic_ttl = 5;
167 ctx->cfg.cache_max_create_time = 5; 167 ctx->cfg.cache_max_create_time = 5;
168 ctx->cfg.cache_repo_ttl = 5; 168 ctx->cfg.cache_repo_ttl = 5;
169 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 169 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
170 ctx->cfg.cache_root_ttl = 5; 170 ctx->cfg.cache_root_ttl = 5;
171 ctx->cfg.cache_static_ttl = -1; 171 ctx->cfg.cache_static_ttl = -1;
172 ctx->cfg.css = "/cgit.css"; 172 ctx->cfg.css = "/cgit.css";
173 ctx->cfg.logo = "/git-logo.png"; 173 ctx->cfg.logo = "/git-logo.png";
174 ctx->cfg.local_time = 0; 174 ctx->cfg.local_time = 0;
175 ctx->cfg.max_repo_count = 50; 175 ctx->cfg.max_repo_count = 50;
176 ctx->cfg.max_commit_count = 50; 176 ctx->cfg.max_commit_count = 50;
177 ctx->cfg.max_lock_attempts = 5; 177 ctx->cfg.max_lock_attempts = 5;
178 ctx->cfg.max_msg_len = 80; 178 ctx->cfg.max_msg_len = 80;
179 ctx->cfg.max_repodesc_len = 80; 179 ctx->cfg.max_repodesc_len = 80;
180 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 180 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
181 ctx->cfg.renamelimit = -1; 181 ctx->cfg.renamelimit = -1;
182 ctx->cfg.robots = "index, nofollow"; 182 ctx->cfg.robots = "index, nofollow";
183 ctx->cfg.root_title = "Git repository browser"; 183 ctx->cfg.root_title = "Git repository browser";
184 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 184 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
185 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 185 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
186 ctx->cfg.summary_branches = 10; 186 ctx->cfg.summary_branches = 10;
187 ctx->cfg.summary_log = 10; 187 ctx->cfg.summary_log = 10;
188 ctx->cfg.summary_tags = 10; 188 ctx->cfg.summary_tags = 10;
189 ctx->page.mimetype = "text/html"; 189 ctx->page.mimetype = "text/html";
190 ctx->page.charset = PAGE_ENCODING; 190 ctx->page.charset = PAGE_ENCODING;
191 ctx->page.filename = NULL; 191 ctx->page.filename = NULL;
192 ctx->page.size = 0; 192 ctx->page.size = 0;
193 ctx->page.modified = time(NULL); 193 ctx->page.modified = time(NULL);
194 ctx->page.expires = ctx->page.modified; 194 ctx->page.expires = ctx->page.modified;
195} 195}
196 196
197struct refmatch { 197struct refmatch {
198 char *req_ref; 198 char *req_ref;
199 char *first_ref; 199 char *first_ref;
200 int match; 200 int match;
201}; 201};
202 202
203int find_current_ref(const char *refname, const unsigned char *sha1, 203int find_current_ref(const char *refname, const unsigned char *sha1,
204 int flags, void *cb_data) 204 int flags, void *cb_data)
205{ 205{
206 struct refmatch *info; 206 struct refmatch *info;
207 207
208 info = (struct refmatch *)cb_data; 208 info = (struct refmatch *)cb_data;
209 if (!strcmp(refname, info->req_ref)) 209 if (!strcmp(refname, info->req_ref))
210 info->match = 1; 210 info->match = 1;
211 if (!info->first_ref) 211 if (!info->first_ref)
212 info->first_ref = xstrdup(refname); 212 info->first_ref = xstrdup(refname);
213 return info->match; 213 return info->match;
214} 214}
215 215
216char *find_default_branch(struct cgit_repo *repo) 216char *find_default_branch(struct cgit_repo *repo)
217{ 217{
218 struct refmatch info; 218 struct refmatch info;
219 char *ref; 219 char *ref;
220 220
221 info.req_ref = repo->defbranch; 221 info.req_ref = repo->defbranch;
222 info.first_ref = NULL; 222 info.first_ref = NULL;
223 info.match = 0; 223 info.match = 0;
224 for_each_branch_ref(find_current_ref, &info); 224 for_each_branch_ref(find_current_ref, &info);
225 if (info.match) 225 if (info.match)
226 ref = info.req_ref; 226 ref = info.req_ref;
227 else 227 else
228 ref = info.first_ref; 228 ref = info.first_ref;
229 if (ref) 229 if (ref)
230 ref = xstrdup(ref); 230 ref = xstrdup(ref);
231 return ref; 231 return ref;
232} 232}
233 233
234static int prepare_repo_cmd(struct cgit_context *ctx) 234static int prepare_repo_cmd(struct cgit_context *ctx)
235{ 235{
236 char *tmp; 236 char *tmp;
237 unsigned char sha1[20]; 237 unsigned char sha1[20];
238 int nongit = 0; 238 int nongit = 0;
239 239
240 setenv("GIT_DIR", ctx->repo->path, 1); 240 setenv("GIT_DIR", ctx->repo->path, 1);
241 setup_git_directory_gently(&nongit); 241 setup_git_directory_gently(&nongit);
242 if (nongit) { 242 if (nongit) {
243 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 243 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
244 "config error"); 244 "config error");
245 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 245 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
246 ctx->repo = NULL; 246 ctx->repo = NULL;
247 cgit_print_http_headers(ctx); 247 cgit_print_http_headers(ctx);
248 cgit_print_docstart(ctx); 248 cgit_print_docstart(ctx);
249 cgit_print_pageheader(ctx); 249 cgit_print_pageheader(ctx);
250 cgit_print_error(tmp); 250 cgit_print_error(tmp);
251 cgit_print_docend(); 251 cgit_print_docend();
252 return 1; 252 return 1;
253 } 253 }
254 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 254 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
255 255
256 if (!ctx->qry.head) { 256 if (!ctx->qry.head) {
257 ctx->qry.nohead = 1; 257 ctx->qry.nohead = 1;
258 ctx->qry.head = find_default_branch(ctx->repo); 258 ctx->qry.head = find_default_branch(ctx->repo);
259 ctx->repo->defbranch = ctx->qry.head; 259 ctx->repo->defbranch = ctx->qry.head;
260 } 260 }
261 261
262 if (!ctx->qry.head) { 262 if (!ctx->qry.head) {
263 cgit_print_http_headers(ctx); 263 cgit_print_http_headers(ctx);
264 cgit_print_docstart(ctx); 264 cgit_print_docstart(ctx);
265 cgit_print_pageheader(ctx); 265 cgit_print_pageheader(ctx);
266 cgit_print_error("Repository seems to be empty"); 266 cgit_print_error("Repository seems to be empty");
267 cgit_print_docend(); 267 cgit_print_docend();
268 return 1; 268 return 1;
269 } 269 }
270 270
271 if (get_sha1(ctx->qry.head, sha1)) { 271 if (get_sha1(ctx->qry.head, sha1)) {
272 tmp = xstrdup(ctx->qry.head); 272 tmp = xstrdup(ctx->qry.head);
273 ctx->qry.head = ctx->repo->defbranch; 273 ctx->qry.head = ctx->repo->defbranch;
274 cgit_print_http_headers(ctx); 274 cgit_print_http_headers(ctx);
275 cgit_print_docstart(ctx); 275 cgit_print_docstart(ctx);
276 cgit_print_pageheader(ctx); 276 cgit_print_pageheader(ctx);
277 cgit_print_error(fmt("Invalid branch: %s", tmp)); 277 cgit_print_error(fmt("Invalid branch: %s", tmp));
278 cgit_print_docend(); 278 cgit_print_docend();
279 return 1; 279 return 1;
280 } 280 }
281 return 0; 281 return 0;
282} 282}
283 283
284static void process_request(void *cbdata) 284static void process_request(void *cbdata)
285{ 285{
286 struct cgit_context *ctx = cbdata; 286 struct cgit_context *ctx = cbdata;
287 struct cgit_cmd *cmd; 287 struct cgit_cmd *cmd;
288 288
289 cmd = cgit_get_cmd(ctx); 289 cmd = cgit_get_cmd(ctx);
290 if (!cmd) { 290 if (!cmd) {
291 ctx->page.title = "cgit error"; 291 ctx->page.title = "cgit error";
292 ctx->repo = NULL;
293 cgit_print_http_headers(ctx); 292 cgit_print_http_headers(ctx);
294 cgit_print_docstart(ctx); 293 cgit_print_docstart(ctx);
295 cgit_print_pageheader(ctx); 294 cgit_print_pageheader(ctx);
296 cgit_print_error("Invalid request"); 295 cgit_print_error("Invalid request");
297 cgit_print_docend(); 296 cgit_print_docend();
298 return; 297 return;
299 } 298 }
300 299
301 if (cmd->want_repo && !ctx->repo) { 300 if (cmd->want_repo && !ctx->repo) {
302 cgit_print_http_headers(ctx); 301 cgit_print_http_headers(ctx);
303 cgit_print_docstart(ctx); 302 cgit_print_docstart(ctx);
304 cgit_print_pageheader(ctx); 303 cgit_print_pageheader(ctx);
305 cgit_print_error(fmt("No repository selected")); 304 cgit_print_error(fmt("No repository selected"));
306 cgit_print_docend(); 305 cgit_print_docend();
307 return; 306 return;
308 } 307 }
309 308
310 if (ctx->repo && prepare_repo_cmd(ctx)) 309 if (ctx->repo && prepare_repo_cmd(ctx))
311 return; 310 return;
312 311
313 if (cmd->want_layout) { 312 if (cmd->want_layout) {
314 cgit_print_http_headers(ctx); 313 cgit_print_http_headers(ctx);
315 cgit_print_docstart(ctx); 314 cgit_print_docstart(ctx);
316 cgit_print_pageheader(ctx); 315 cgit_print_pageheader(ctx);
317 } 316 }
318 317
319 cmd->fn(ctx); 318 cmd->fn(ctx);
320 319
321 if (cmd->want_layout) 320 if (cmd->want_layout)
322 cgit_print_docend(); 321 cgit_print_docend();
323} 322}
324 323
325int cmp_repos(const void *a, const void *b) 324int cmp_repos(const void *a, const void *b)
326{ 325{
327 const struct cgit_repo *ra = a, *rb = b; 326 const struct cgit_repo *ra = a, *rb = b;
328 return strcmp(ra->url, rb->url); 327 return strcmp(ra->url, rb->url);
329} 328}
330 329
331void print_repo(struct cgit_repo *repo) 330void print_repo(struct cgit_repo *repo)
332{ 331{
333 printf("repo.url=%s\n", repo->url); 332 printf("repo.url=%s\n", repo->url);
334 printf("repo.name=%s\n", repo->name); 333 printf("repo.name=%s\n", repo->name);
335 printf("repo.path=%s\n", repo->path); 334 printf("repo.path=%s\n", repo->path);
336 if (repo->owner) 335 if (repo->owner)
337 printf("repo.owner=%s\n", repo->owner); 336 printf("repo.owner=%s\n", repo->owner);
338 if (repo->desc) 337 if (repo->desc)
339 printf("repo.desc=%s\n", repo->desc); 338 printf("repo.desc=%s\n", repo->desc);
340 if (repo->readme) 339 if (repo->readme)
341 printf("repo.readme=%s\n", repo->readme); 340 printf("repo.readme=%s\n", repo->readme);
342 printf("\n"); 341 printf("\n");
343} 342}
344 343
345void print_repolist(struct cgit_repolist *list) 344void print_repolist(struct cgit_repolist *list)
346{ 345{
347 int i; 346 int i;
348 347
349 for(i = 0; i < list->count; i++) 348 for(i = 0; i < list->count; i++)
350 print_repo(&list->repos[i]); 349 print_repo(&list->repos[i]);
351} 350}
352 351
353 352
354static void cgit_parse_args(int argc, const char **argv) 353static void cgit_parse_args(int argc, const char **argv)
355{ 354{
356 int i; 355 int i;
357 int scan = 0; 356 int scan = 0;
358 357
359 for (i = 1; i < argc; i++) { 358 for (i = 1; i < argc; i++) {
360 if (!strncmp(argv[i], "--cache=", 8)) { 359 if (!strncmp(argv[i], "--cache=", 8)) {
361 ctx.cfg.cache_root = xstrdup(argv[i]+8); 360 ctx.cfg.cache_root = xstrdup(argv[i]+8);
362 } 361 }
363 if (!strcmp(argv[i], "--nocache")) { 362 if (!strcmp(argv[i], "--nocache")) {
364 ctx.cfg.nocache = 1; 363 ctx.cfg.nocache = 1;
365 } 364 }
366 if (!strncmp(argv[i], "--query=", 8)) { 365 if (!strncmp(argv[i], "--query=", 8)) {
367 ctx.qry.raw = xstrdup(argv[i]+8); 366 ctx.qry.raw = xstrdup(argv[i]+8);
368 } 367 }
369 if (!strncmp(argv[i], "--repo=", 7)) { 368 if (!strncmp(argv[i], "--repo=", 7)) {
370 ctx.qry.repo = xstrdup(argv[i]+7); 369 ctx.qry.repo = xstrdup(argv[i]+7);
371 } 370 }
372 if (!strncmp(argv[i], "--page=", 7)) { 371 if (!strncmp(argv[i], "--page=", 7)) {
373 ctx.qry.page = xstrdup(argv[i]+7); 372 ctx.qry.page = xstrdup(argv[i]+7);
374 } 373 }
375 if (!strncmp(argv[i], "--head=", 7)) { 374 if (!strncmp(argv[i], "--head=", 7)) {
376 ctx.qry.head = xstrdup(argv[i]+7); 375 ctx.qry.head = xstrdup(argv[i]+7);
377 ctx.qry.has_symref = 1; 376 ctx.qry.has_symref = 1;
378 } 377 }
379 if (!strncmp(argv[i], "--sha1=", 7)) { 378 if (!strncmp(argv[i], "--sha1=", 7)) {
380 ctx.qry.sha1 = xstrdup(argv[i]+7); 379 ctx.qry.sha1 = xstrdup(argv[i]+7);
381 ctx.qry.has_sha1 = 1; 380 ctx.qry.has_sha1 = 1;
382 } 381 }
383 if (!strncmp(argv[i], "--ofs=", 6)) { 382 if (!strncmp(argv[i], "--ofs=", 6)) {
384 ctx.qry.ofs = atoi(argv[i]+6); 383 ctx.qry.ofs = atoi(argv[i]+6);
385 } 384 }
386 if (!strncmp(argv[i], "--scan-tree=", 12)) { 385 if (!strncmp(argv[i], "--scan-tree=", 12)) {
387 scan++; 386 scan++;
388 scan_tree(argv[i] + 12); 387 scan_tree(argv[i] + 12);
389 } 388 }
390 } 389 }
391 if (scan) { 390 if (scan) {
392 qsort(cgit_repolist.repos, cgit_repolist.count, 391 qsort(cgit_repolist.repos, cgit_repolist.count,
393 sizeof(struct cgit_repo), cmp_repos); 392 sizeof(struct cgit_repo), cmp_repos);
394 print_repolist(&cgit_repolist); 393 print_repolist(&cgit_repolist);
395 exit(0); 394 exit(0);
396 } 395 }
397} 396}
398 397
399static int calc_ttl() 398static int calc_ttl()
400{ 399{
401 if (!ctx.repo) 400 if (!ctx.repo)
402 return ctx.cfg.cache_root_ttl; 401 return ctx.cfg.cache_root_ttl;
403 402
404 if (!ctx.qry.page) 403 if (!ctx.qry.page)
405 return ctx.cfg.cache_repo_ttl; 404 return ctx.cfg.cache_repo_ttl;
406 405
407 if (ctx.qry.has_symref) 406 if (ctx.qry.has_symref)
408 return ctx.cfg.cache_dynamic_ttl; 407 return ctx.cfg.cache_dynamic_ttl;
409 408
410 if (ctx.qry.has_sha1) 409 if (ctx.qry.has_sha1)
411 return ctx.cfg.cache_static_ttl; 410 return ctx.cfg.cache_static_ttl;
412 411
413 return ctx.cfg.cache_repo_ttl; 412 return ctx.cfg.cache_repo_ttl;
414} 413}
415 414
416int main(int argc, const char **argv) 415int main(int argc, const char **argv)
417{ 416{
418 const char *cgit_config_env = getenv("CGIT_CONFIG"); 417 const char *cgit_config_env = getenv("CGIT_CONFIG");
419 const char *path; 418 const char *path;
420 char *qry; 419 char *qry;
421 int err, ttl; 420 int err, ttl;
422 421
423 prepare_context(&ctx); 422 prepare_context(&ctx);
424 cgit_repolist.length = 0; 423 cgit_repolist.length = 0;
425 cgit_repolist.count = 0; 424 cgit_repolist.count = 0;
426 cgit_repolist.repos = NULL; 425 cgit_repolist.repos = NULL;
427 426
428 if (getenv("SCRIPT_NAME")) 427 if (getenv("SCRIPT_NAME"))
429 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 428 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
430 if (getenv("QUERY_STRING")) 429 if (getenv("QUERY_STRING"))
431 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 430 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
432 cgit_parse_args(argc, argv); 431 cgit_parse_args(argc, argv);
433 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 432 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
434 config_cb); 433 config_cb);
435 ctx.repo = NULL; 434 ctx.repo = NULL;
436 http_parse_querystring(ctx.qry.raw, querystring_cb); 435 http_parse_querystring(ctx.qry.raw, querystring_cb);
437 436
438 /* If virtual-root isn't specified in cgitrc and no url 437 /* If virtual-root isn't specified in cgitrc and no url
439 * parameter is specified on the querystring, lets pretend 438 * parameter is specified on the querystring, lets pretend
440 * that virtualroot equals SCRIPT_NAME and use PATH_INFO as 439 * that virtualroot equals SCRIPT_NAME and use PATH_INFO as
441 * url. This allows cgit to work with virtual urls without 440 * url. This allows cgit to work with virtual urls without
442 * the need for rewriterules in the webserver (as long as 441 * the need for rewriterules in the webserver (as long as
443 * PATH_INFO is included in the cache lookup key). 442 * PATH_INFO is included in the cache lookup key).
444 */ 443 */
445 if (!ctx.cfg.virtual_root && !ctx.qry.url) { 444 if (!ctx.cfg.virtual_root && !ctx.qry.url) {
446 ctx.cfg.virtual_root = ctx.cfg.script_name; 445 ctx.cfg.virtual_root = ctx.cfg.script_name;
447 path = getenv("PATH_INFO"); 446 path = getenv("PATH_INFO");
448 if (path) { 447 if (path) {
449 if (path[0] == '/') 448 if (path[0] == '/')
450 path++; 449 path++;
451 ctx.qry.url = xstrdup(path); 450 ctx.qry.url = xstrdup(path);
452 if (ctx.qry.raw) { 451 if (ctx.qry.raw) {
453 qry = ctx.qry.raw; 452 qry = ctx.qry.raw;
454 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 453 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
455 free(qry); 454 free(qry);
456 } else 455 } else
457 ctx.qry.raw = ctx.qry.url; 456 ctx.qry.raw = ctx.qry.url;
458 cgit_parse_url(ctx.qry.url); 457 cgit_parse_url(ctx.qry.url);
459 } 458 }
460 } 459 }
461 460
462 ttl = calc_ttl(); 461 ttl = calc_ttl();
463 ctx.page.expires += ttl*60; 462 ctx.page.expires += ttl*60;
464 if (ctx.cfg.nocache) 463 if (ctx.cfg.nocache)
465 ctx.cfg.cache_size = 0; 464 ctx.cfg.cache_size = 0;
466 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 465 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
467 ctx.qry.raw, ttl, process_request, &ctx); 466 ctx.qry.raw, ttl, process_request, &ctx);
468 if (err) 467 if (err)
469 cgit_print_error(fmt("Error processing page: %s (%d)", 468 cgit_print_error(fmt("Error processing page: %s (%d)",
470 strerror(err), err)); 469 strerror(err), err));
471 return err; 470 return err;
472} 471}
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}