auth: add basic authentication filter framework
This leverages the new lua support. See filters/simple-authentication.lua for explaination of how this works. There is also additional documentation in cgitrc.5.txt. Though this is a cookie-based approach, cgit's caching mechanism is preserved for authenticated pages. Very plugable and extendable depending on user needs. The sample script uses an HMAC-SHA1 based cookie to store the currently logged in user, with an expiration date. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
这个提交包含在:
		
							
								
								
									
										96
									
								
								cgit.c
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								cgit.c
									
									
									
									
									
								
							| @@ -192,6 +192,8 @@ static void config_cb(const char *name, const char *value) | ||||
| 		ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT); | ||||
| 	else if (!strcmp(name, "email-filter")) | ||||
| 		ctx.cfg.email_filter = cgit_new_filter(value, EMAIL); | ||||
| 	else if (!strcmp(name, "auth-filter")) | ||||
| 		ctx.cfg.auth_filter = cgit_new_filter(value, AUTH); | ||||
| 	else if (!strcmp(name, "embedded")) | ||||
| 		ctx.cfg.embedded = atoi(value); | ||||
| 	else if (!strcmp(name, "max-atom-items")) | ||||
| @@ -378,6 +380,10 @@ static void prepare_context(struct cgit_context *ctx) | ||||
| 	ctx->env.script_name = getenv("SCRIPT_NAME"); | ||||
| 	ctx->env.server_name = getenv("SERVER_NAME"); | ||||
| 	ctx->env.server_port = getenv("SERVER_PORT"); | ||||
| 	ctx->env.http_cookie = getenv("HTTP_COOKIE"); | ||||
| 	ctx->env.http_referer = getenv("HTTP_REFERER"); | ||||
| 	ctx->env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0; | ||||
| 	ctx->env.authenticated = 0; | ||||
| 	ctx->page.mimetype = "text/html"; | ||||
| 	ctx->page.charset = PAGE_ENCODING; | ||||
| 	ctx->page.filename = NULL; | ||||
| @@ -593,11 +599,92 @@ static int prepare_repo_cmd(struct cgit_context *ctx) | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| static inline void open_auth_filter(struct cgit_context *ctx, const char *function) | ||||
| { | ||||
| 	cgit_open_filter(ctx->cfg.auth_filter, function, | ||||
| 		ctx->env.http_cookie ? ctx->env.http_cookie : "", | ||||
| 		ctx->env.request_method ? ctx->env.request_method : "", | ||||
| 		ctx->env.query_string ? ctx->env.query_string : "", | ||||
| 		ctx->env.http_referer ? ctx->env.http_referer : "", | ||||
| 		ctx->env.path_info ? ctx->env.path_info : "", | ||||
| 		ctx->env.http_host ? ctx->env.http_host : "", | ||||
| 		ctx->env.https ? ctx->env.https : "", | ||||
| 		ctx->qry.repo ? ctx->qry.repo : "", | ||||
| 		ctx->qry.page ? ctx->qry.page : "", | ||||
| 		ctx->qry.url ? ctx->qry.url : ""); | ||||
| } | ||||
|  | ||||
| #define MAX_AUTHENTICATION_POST_BYTES 4096 | ||||
| static inline void authenticate_post(struct cgit_context *ctx) | ||||
| { | ||||
| 	if (ctx->env.http_referer && strlen(ctx->env.http_referer) > 0) { | ||||
| 		html("Status: 302 Redirect\n"); | ||||
| 		html("Cache-Control: no-cache, no-store\n"); | ||||
| 		htmlf("Location: %s\n", ctx->env.http_referer); | ||||
| 	} else { | ||||
| 		html("Status: 501 Missing Referer\n"); | ||||
| 		html("Cache-Control: no-cache, no-store\n\n"); | ||||
| 		exit(0); | ||||
| 	} | ||||
|  | ||||
| 	open_auth_filter(ctx, "authenticate-post"); | ||||
| 	char buffer[MAX_AUTHENTICATION_POST_BYTES]; | ||||
| 	int len; | ||||
| 	len = ctx->env.content_length; | ||||
| 	if (len > MAX_AUTHENTICATION_POST_BYTES) | ||||
| 		len = MAX_AUTHENTICATION_POST_BYTES; | ||||
| 	if (read(STDIN_FILENO, buffer, len) < 0) | ||||
| 		die_errno("Could not read POST from stdin"); | ||||
| 	if (write(STDOUT_FILENO, buffer, len) < 0) | ||||
| 		die_errno("Could not write POST to stdout"); | ||||
| 	/* The filter may now spit out a Set-Cookie: ... */ | ||||
| 	cgit_close_filter(ctx->cfg.auth_filter); | ||||
|  | ||||
| 	html("\n"); | ||||
| 	exit(0); | ||||
| } | ||||
|  | ||||
| static inline void authenticate_cookie(struct cgit_context *ctx) | ||||
| { | ||||
| 	/* If we don't have an auth_filter, consider all cookies valid, and thus return early. */ | ||||
| 	if (!ctx->cfg.auth_filter) { | ||||
| 		ctx->env.authenticated = 1; | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* If we're having something POST'd to /login, we're authenticating POST, | ||||
| 	 * instead of the cookie, so call authenticate_post and bail out early. | ||||
| 	 * This pattern here should match /?p=login with POST. */ | ||||
| 	if (ctx->env.request_method && ctx->qry.page && !ctx->repo && \ | ||||
| 	    !strcmp(ctx->env.request_method, "POST") && !strcmp(ctx->qry.page, "login")) { | ||||
| 		authenticate_post(ctx); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	/* If we've made it this far, we're authenticating the cookie for real, so do that. */ | ||||
| 	open_auth_filter(ctx, "authenticate-cookie"); | ||||
| 	ctx->env.authenticated = cgit_close_filter(ctx->cfg.auth_filter); | ||||
| } | ||||
|  | ||||
| static void process_request(void *cbdata) | ||||
| { | ||||
| 	struct cgit_context *ctx = cbdata; | ||||
| 	struct cgit_cmd *cmd; | ||||
|  | ||||
| 	/* If we're not yet authenticated, no matter what page we're on, | ||||
| 	 * display the authentication body from the auth_filter. This should | ||||
| 	 * never be cached. */ | ||||
| 	if (!ctx->env.authenticated) { | ||||
| 		ctx->page.title = "Authentication Required"; | ||||
| 		cgit_print_http_headers(ctx); | ||||
| 		cgit_print_docstart(ctx); | ||||
| 		cgit_print_pageheader(ctx); | ||||
| 		open_auth_filter(ctx, "body"); | ||||
| 		cgit_close_filter(ctx->cfg.auth_filter); | ||||
| 		cgit_print_docend(); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	cmd = cgit_get_cmd(ctx); | ||||
| 	if (!cmd) { | ||||
| 		ctx->page.title = "cgit error"; | ||||
| @@ -911,6 +998,7 @@ int main(int argc, const char **argv) | ||||
| 	int err, ttl; | ||||
|  | ||||
| 	cgit_init_filters(); | ||||
| 	atexit(cgit_cleanup_filters); | ||||
|  | ||||
| 	prepare_context(&ctx); | ||||
| 	cgit_repolist.length = 0; | ||||
| @@ -948,18 +1036,22 @@ int main(int argc, const char **argv) | ||||
| 		cgit_parse_url(ctx.qry.url); | ||||
| 	} | ||||
|  | ||||
| 	/* Before we go any further, we set ctx.env.authenticated by checking to see | ||||
| 	 * if the supplied cookie is valid. All cookies are valid if there is no | ||||
| 	 * auth_filter. If there is an auth_filter, the filter decides. */ | ||||
| 	authenticate_cookie(&ctx); | ||||
|  | ||||
| 	ttl = calc_ttl(); | ||||
| 	if (ttl < 0) | ||||
| 		ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */ | ||||
| 	else | ||||
| 		ctx.page.expires += ttl * 60; | ||||
| 	if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) | ||||
| 	if (!ctx.env.authenticated || (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))) | ||||
| 		ctx.cfg.nocache = 1; | ||||
| 	if (ctx.cfg.nocache) | ||||
| 		ctx.cfg.cache_size = 0; | ||||
| 	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, | ||||
| 			    ctx.qry.raw, ttl, process_request, &ctx); | ||||
| 	cgit_cleanup_filters(); | ||||
| 	if (err) | ||||
| 		cgit_print_error("Error processing page: %s (%d)", | ||||
| 				 strerror(err), err); | ||||
|   | ||||
							
								
								
									
										7
									
								
								cgit.h
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								cgit.h
									
									
									
									
									
								
							| @@ -53,7 +53,7 @@ typedef void (*filepair_fn)(struct diff_filepair *pair); | ||||
| typedef void (*linediff_fn)(char *line, int len); | ||||
|  | ||||
| typedef enum { | ||||
| 	ABOUT, COMMIT, SOURCE, EMAIL | ||||
| 	ABOUT, COMMIT, SOURCE, EMAIL, AUTH | ||||
| } filter_type; | ||||
|  | ||||
| struct cgit_filter { | ||||
| @@ -252,6 +252,7 @@ struct cgit_config { | ||||
| 	struct cgit_filter *commit_filter; | ||||
| 	struct cgit_filter *source_filter; | ||||
| 	struct cgit_filter *email_filter; | ||||
| 	struct cgit_filter *auth_filter; | ||||
| }; | ||||
|  | ||||
| struct cgit_page { | ||||
| @@ -278,6 +279,10 @@ struct cgit_environment { | ||||
| 	const char *script_name; | ||||
| 	const char *server_name; | ||||
| 	const char *server_port; | ||||
| 	const char *http_cookie; | ||||
| 	const char *http_referer; | ||||
| 	unsigned int content_length; | ||||
| 	int authenticated; | ||||
| }; | ||||
|  | ||||
| struct cgit_context { | ||||
|   | ||||
							
								
								
									
										36
									
								
								cgitrc.5.txt
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								cgitrc.5.txt
									
									
									
									
									
								
							| @@ -42,6 +42,13 @@ agefile:: | ||||
| 	hh:mm:ss". You may want to generate this file from a post-receive | ||||
| 	hook. Default value: "info/web/last-modified". | ||||
|  | ||||
| auth-filter:: | ||||
| 	Specifies a command that will be invoked for authenticating repository | ||||
| 	access. Receives quite a few arguments, and data on both stdin and | ||||
| 	stdout for authentication processing. Details follow later in this | ||||
| 	document. If no auth-filter is specified, no authentication is | ||||
| 	performed. Default value: none. See also: "FILTER API". | ||||
|  | ||||
| branch-sort:: | ||||
| 	Flag which, when set to "age", enables date ordering in the branch ref | ||||
| 	list, and when set to "name" enables ordering by branch name. Default | ||||
| @@ -605,6 +612,8 @@ specification with the relevant string; available values are: | ||||
| 		URL escapes for a path and writes 'str' to the webpage. | ||||
| 	'html_url_arg(str)':: | ||||
| 		URL escapes for an argument and writes 'str' to the webpage. | ||||
| 	'html_include(file)':: | ||||
| 		Includes 'file' in webpage. | ||||
|  | ||||
|  | ||||
| Parameters are provided to filters as follows. | ||||
| @@ -635,7 +644,32 @@ source filter:: | ||||
| 	file that is to be filtered is available on standard input and the | ||||
| 	filtered contents is expected on standard output. | ||||
|  | ||||
| Also, all filters are handed the following environment variables: | ||||
| auth filter:: | ||||
| 	The authentication filter receives 11 parameters: | ||||
| 	  - filter action, explained below, which specifies which action the | ||||
| 	    filter is called for | ||||
| 	  - http cookie | ||||
| 	  - http method | ||||
| 	  - http referer | ||||
| 	  - http path | ||||
| 	  - http https flag | ||||
| 	  - cgit repo | ||||
| 	  - cgit page | ||||
| 	  - cgit url | ||||
| 	When the filter action is "body", this filter must write to output the | ||||
| 	HTML for displaying the login form, which POSTs to "/?p=login". When | ||||
| 	the filter action is "authenticate-cookie", this filter must validate | ||||
| 	the http cookie and return a 0 if it is invalid or 1 if it is invalid, | ||||
| 	in the exit code / close function. If the filter action is | ||||
| 	"authenticate-post", this filter receives POST'd parameters on | ||||
| 	standard input, and should write to output one or more "Set-Cookie" | ||||
| 	HTTP headers, each followed by a newline. | ||||
|  | ||||
| 	Please see `filters/simple-authentication.lua` for a clear example | ||||
| 	script that may be modified. | ||||
|  | ||||
|  | ||||
| All filters are handed the following environment variables: | ||||
|  | ||||
| - CGIT_REPO_URL (from repo.url) | ||||
| - CGIT_REPO_NAME (from repo.name) | ||||
|   | ||||
							
								
								
									
										11
									
								
								filter.c
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								filter.c
									
									
									
									
									
								
							| @@ -244,6 +244,11 @@ static int html_url_arg_lua_filter(lua_State *lua_state) | ||||
| 	return hook_lua_filter(lua_state, html_url_arg); | ||||
| } | ||||
|  | ||||
| static int html_include_lua_filter(lua_State *lua_state) | ||||
| { | ||||
| 	return hook_lua_filter(lua_state, (void (*)(const char *))html_include); | ||||
| } | ||||
|  | ||||
| static void cleanup_lua_filter(struct cgit_filter *base) | ||||
| { | ||||
| 	struct lua_filter *filter = (struct lua_filter *)base; | ||||
| @@ -279,6 +284,8 @@ static int init_lua_filter(struct lua_filter *filter) | ||||
| 	lua_setglobal(filter->lua_state, "html_url_path"); | ||||
| 	lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter); | ||||
| 	lua_setglobal(filter->lua_state, "html_url_arg"); | ||||
| 	lua_pushcfunction(filter->lua_state, html_include_lua_filter); | ||||
| 	lua_setglobal(filter->lua_state, "html_include"); | ||||
|  | ||||
| 	if (luaL_dofile(filter->lua_state, filter->script_file)) { | ||||
| 		error_lua_filter(filter); | ||||
| @@ -409,6 +416,10 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) | ||||
| 		colon = NULL; | ||||
|  | ||||
| 	switch (filtertype) { | ||||
| 		case AUTH: | ||||
| 			argument_count = 11; | ||||
| 			break; | ||||
|  | ||||
| 		case EMAIL: | ||||
| 			argument_count = 2; | ||||
| 			break; | ||||
|   | ||||
							
								
								
									
										225
									
								
								filters/simple-authentication.lua
									
									
									
									
									
										普通文件
									
								
							
							
						
						
									
										225
									
								
								filters/simple-authentication.lua
									
									
									
									
									
										普通文件
									
								
							| @@ -0,0 +1,225 @@ | ||||
| -- This script may be used with the auth-filter. Be sure to configure it as you wish. | ||||
| -- | ||||
| -- Requirements: | ||||
| -- 	luacrypto >= 0.3 | ||||
| -- 	<http://mkottman.github.io/luacrypto/> | ||||
| -- | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- | ||||
| -- Configure these variables for your settings. | ||||
| -- | ||||
| -- | ||||
|  | ||||
| local protected_repos = { | ||||
| 	glouglou	= { laurent = true, jason = true }, | ||||
| 	qt		= { jason = true, bob = true } | ||||
| } | ||||
|  | ||||
| local users = { | ||||
| 	jason		= "secretpassword", | ||||
| 	laurent		= "s3cr3t", | ||||
| 	bob		= "ilikelua" | ||||
| } | ||||
|  | ||||
| local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" | ||||
|  | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- | ||||
| -- Authentication functions follow below. Swap these out if you want different authentication semantics. | ||||
| -- | ||||
| -- | ||||
|  | ||||
| -- Sets HTTP cookie headers based on post | ||||
| function authenticate_post() | ||||
| 	local password = users[post["username"]] | ||||
| 	-- TODO: Implement time invariant string comparison function to mitigate against timing attack. | ||||
| 	if password == nil or password ~= post["password"] then | ||||
| 		construct_cookie("", "cgitauth") | ||||
| 	else | ||||
| 		construct_cookie(post["username"], "cgitauth") | ||||
| 	end | ||||
| 	return 0 | ||||
| end | ||||
|  | ||||
|  | ||||
| -- Returns 1 if the cookie is valid and 0 if it is not. | ||||
| function authenticate_cookie() | ||||
| 	accepted_users = protected_repos[cgit["repo"]] | ||||
| 	if accepted_users == nil then | ||||
| 		-- We return as valid if the repo is not protected. | ||||
| 		return 1 | ||||
| 	end | ||||
|  | ||||
| 	local username = validate_cookie(get_cookie(http["cookie"], "cgitauth")) | ||||
| 	if username == nil or not accepted_users[username] then | ||||
| 		return 0 | ||||
| 	else | ||||
| 		return 1 | ||||
| 	end | ||||
| end | ||||
|  | ||||
| -- Prints the html for the login form. | ||||
| function body() | ||||
| 	html("<h2>Authentication Required</h2>") | ||||
| 	html("<form method='post' action='") | ||||
| 	html_attr(cgit["login"]) | ||||
| 	html("'>") | ||||
| 	html("<table>") | ||||
| 	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") | ||||
| 	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") | ||||
| 	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>") | ||||
| 	html("</table></form>") | ||||
|  | ||||
| 	return 0 | ||||
| end | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- | ||||
| -- Cookie construction and validation helpers. | ||||
| -- | ||||
| -- | ||||
|  | ||||
| local crypto = require("crypto") | ||||
|  | ||||
| -- Returns username of cookie if cookie is valid. Otherwise returns nil. | ||||
| function validate_cookie(cookie) | ||||
| 	local i = 0 | ||||
| 	local username = "" | ||||
| 	local expiration = 0 | ||||
| 	local salt = "" | ||||
| 	local hmac = "" | ||||
|  | ||||
| 	if cookie:len() < 3 or cookie:sub(1, 1) == "|" then | ||||
| 		return nil | ||||
| 	end | ||||
|  | ||||
| 	for component in string.gmatch(cookie, "[^|]+") do | ||||
| 		if i == 0 then | ||||
| 			username = component | ||||
| 		elseif i == 1 then | ||||
| 			expiration = tonumber(component) | ||||
| 			if expiration == nil then | ||||
| 				expiration = 0 | ||||
| 			end | ||||
| 		elseif i == 2 then | ||||
| 			salt = component | ||||
| 		elseif i == 3 then | ||||
| 			hmac = component | ||||
| 		else | ||||
| 			break | ||||
| 		end | ||||
| 		i = i + 1 | ||||
| 	end | ||||
|  | ||||
| 	if hmac == nil or hmac:len() == 0 then | ||||
| 		return nil | ||||
| 	end | ||||
|  | ||||
| 	-- TODO: implement time invariant comparison to prevent against timing attack. | ||||
| 	if hmac ~= crypto.hmac.digest("sha1", username .. "|" .. tostring(expiration) .. "|" .. salt, secret) then | ||||
| 		return nil | ||||
| 	end | ||||
|  | ||||
| 	if expiration <= os.time() then | ||||
| 		return nil | ||||
| 	end | ||||
|  | ||||
| 	return username:lower() | ||||
| end | ||||
|  | ||||
| function construct_cookie(username, cookie) | ||||
| 	local authstr = "" | ||||
| 	if username:len() > 0 then | ||||
| 		-- One week expiration time | ||||
| 		local expiration = os.time() + 604800 | ||||
| 		local salt = crypto.hex(crypto.rand.bytes(16)) | ||||
|  | ||||
| 		authstr = username .. "|" .. tostring(expiration) .. "|" .. salt | ||||
| 		authstr = authstr .. "|" .. crypto.hmac.digest("sha1", authstr, secret) | ||||
| 	end | ||||
|  | ||||
| 	html("Set-Cookie: " .. cookie .. "=" .. authstr .. "; HttpOnly") | ||||
| 	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then | ||||
| 		html("; secure") | ||||
| 	end | ||||
| 	html("\n") | ||||
| end | ||||
|  | ||||
| -- | ||||
| -- | ||||
| -- Wrapper around filter API follows below, exposing the http table, the cgit table, and the post table to the above functions. | ||||
| -- | ||||
| -- | ||||
|  | ||||
| local actions = {} | ||||
| actions["authenticate-post"] = authenticate_post | ||||
| actions["authenticate-cookie"] = authenticate_cookie | ||||
| actions["body"] = body | ||||
|  | ||||
| function filter_open(...) | ||||
| 	action = actions[select(1, ...)] | ||||
|  | ||||
| 	http = {} | ||||
| 	http["cookie"] = select(2, ...) | ||||
| 	http["method"] = select(3, ...) | ||||
| 	http["query"] = select(4, ...) | ||||
| 	http["referer"] = select(5, ...) | ||||
| 	http["path"] = select(6, ...) | ||||
| 	http["host"] = select(7, ...) | ||||
| 	http["https"] = select(8, ...) | ||||
|  | ||||
| 	cgit = {} | ||||
| 	cgit["repo"] = select(9, ...) | ||||
| 	cgit["page"] = select(10, ...) | ||||
| 	cgit["url"] = select(11, ...) | ||||
|  | ||||
| 	cgit["login"] = "" | ||||
| 	for _ in cgit["url"]:gfind("/") do | ||||
| 		cgit["login"] = cgit["login"] .. "../" | ||||
| 	end | ||||
| 	cgit["login"] = cgit["login"] .. "?p=login" | ||||
|  | ||||
| end | ||||
|  | ||||
| function filter_close() | ||||
| 	return action() | ||||
| end | ||||
|  | ||||
| function filter_write(str) | ||||
| 	post = parse_qs(str) | ||||
| end | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- | ||||
| -- Utility functions follow below, based on keplerproject/wsapi. | ||||
| -- | ||||
| -- | ||||
|  | ||||
| function url_decode(str) | ||||
| 	if not str then | ||||
| 		return "" | ||||
| 	end | ||||
| 	str = string.gsub(str, "+", " ") | ||||
| 	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) | ||||
| 	str = string.gsub(str, "\r\n", "\n") | ||||
| 	return str | ||||
| end | ||||
|  | ||||
| function parse_qs(qs) | ||||
| 	local tab = {} | ||||
| 	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do | ||||
| 		tab[url_decode(key)] = url_decode(val) | ||||
| 	end | ||||
| 	return tab | ||||
| end | ||||
|  | ||||
| function get_cookie(cookies, name) | ||||
| 	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") | ||||
| 	return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) | ||||
| end | ||||
							
								
								
									
										28
									
								
								ui-shared.c
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								ui-shared.c
									
									
									
									
									
								
							| @@ -641,6 +641,8 @@ void cgit_print_http_headers(struct cgit_context *ctx) | ||||
| 	if (ctx->page.filename) | ||||
| 		htmlf("Content-Disposition: inline; filename=\"%s\"\n", | ||||
| 		      ctx->page.filename); | ||||
| 	if (!ctx->env.authenticated) | ||||
| 		html("Cache-Control: no-cache, no-store\n"); | ||||
| 	htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); | ||||
| 	htmlf("Expires: %s\n", http_date(ctx->page.expires)); | ||||
| 	if (ctx->page.etag) | ||||
| @@ -814,14 +816,16 @@ static void print_header(struct cgit_context *ctx) | ||||
| 		cgit_index_link("index", NULL, NULL, NULL, NULL, 0); | ||||
| 		html(" : "); | ||||
| 		cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); | ||||
| 		html("</td><td class='form'>"); | ||||
| 		html("<form method='get' action=''>\n"); | ||||
| 		cgit_add_hidden_formfields(0, 1, ctx->qry.page); | ||||
| 		html("<select name='h' onchange='this.form.submit();'>\n"); | ||||
| 		for_each_branch_ref(print_branch_option, ctx->qry.head); | ||||
| 		html("</select> "); | ||||
| 		html("<input type='submit' name='' value='switch'/>"); | ||||
| 		html("</form>"); | ||||
| 		if (ctx->env.authenticated) { | ||||
| 			html("</td><td class='form'>"); | ||||
| 			html("<form method='get' action=''>\n"); | ||||
| 			cgit_add_hidden_formfields(0, 1, ctx->qry.page); | ||||
| 			html("<select name='h' onchange='this.form.submit();'>\n"); | ||||
| 			for_each_branch_ref(print_branch_option, ctx->qry.head); | ||||
| 			html("</select> "); | ||||
| 			html("<input type='submit' name='' value='switch'/>"); | ||||
| 			html("</form>"); | ||||
| 		} | ||||
| 	} else | ||||
| 		html_txt(ctx->cfg.root_title); | ||||
| 	html("</td></tr>\n"); | ||||
| @@ -843,11 +847,11 @@ static void print_header(struct cgit_context *ctx) | ||||
| void cgit_print_pageheader(struct cgit_context *ctx) | ||||
| { | ||||
| 	html("<div id='cgit'>"); | ||||
| 	if (!ctx->cfg.noheader) | ||||
| 	if (!ctx->env.authenticated || !ctx->cfg.noheader) | ||||
| 		print_header(ctx); | ||||
|  | ||||
| 	html("<table class='tabs'><tr><td>\n"); | ||||
| 	if (ctx->repo) { | ||||
| 	if (ctx->env.authenticated && ctx->repo) { | ||||
| 		cgit_summary_link("summary", NULL, hc(ctx, "summary"), | ||||
| 				  ctx->qry.head); | ||||
| 		cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, | ||||
| @@ -886,7 +890,7 @@ void cgit_print_pageheader(struct cgit_context *ctx) | ||||
| 		html("'/>\n"); | ||||
| 		html("<input type='submit' value='search'/>\n"); | ||||
| 		html("</form>\n"); | ||||
| 	} else { | ||||
| 	} else if (ctx->env.authenticated) { | ||||
| 		site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, NULL, 0); | ||||
| 		if (ctx->cfg.root_readme) | ||||
| 			site_link("about", "about", NULL, hc(ctx, "about"), | ||||
| @@ -902,7 +906,7 @@ void cgit_print_pageheader(struct cgit_context *ctx) | ||||
| 		html("</form>"); | ||||
| 	} | ||||
| 	html("</td></tr></table>\n"); | ||||
| 	if (ctx->qry.vpath) { | ||||
| 	if (ctx->env.authenticated && ctx->qry.vpath) { | ||||
| 		html("<div class='path'>"); | ||||
| 		html("path: "); | ||||
| 		cgit_print_path_crumbs(ctx, ctx->qry.vpath); | ||||
|   | ||||
		在新工单中引用
	
	屏蔽一个用户
	 Jason A. Donenfeld
					Jason A. Donenfeld