Squashed 'third_party/cgit/' content from commit 8fc0c81
git-subtree-dir: third_party/cgit git-subtree-split: 8fc0c81bbbed21ee30e8a48b2ab1066a029b7b32
This commit is contained in:
commit
723dc8fbcb
102 changed files with 15632 additions and 0 deletions
27
filters/about-formatting.sh
Executable file
27
filters/about-formatting.sh
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This may be used with the about-filter or repo.about-filter setting in cgitrc.
|
||||
# It passes formatting of about pages to differing programs, depending on the usage.
|
||||
|
||||
# Markdown support requires python and markdown-python.
|
||||
# RestructuredText support requires python and docutils.
|
||||
# Man page support requires groff.
|
||||
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
|
||||
cd "$(dirname $0)/html-converters/"
|
||||
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
||||
*.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;;
|
||||
*.rst) exec ./rst2html; ;;
|
||||
*.[1-9]) exec ./man2html; ;;
|
||||
*.htm|*.html) exec cat; ;;
|
||||
*.txt|*) exec ./txt2html; ;;
|
||||
esac
|
||||
28
filters/commit-links.sh
Executable file
28
filters/commit-links.sh
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
# This script can be used to generate links in commit messages.
|
||||
#
|
||||
# To use this script, refer to this file with either the commit-filter or the
|
||||
# repo.commit-filter options in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
|
||||
regex=''
|
||||
|
||||
# This expression generates links to commits referenced by their SHA1.
|
||||
regex=$regex'
|
||||
s|\b([0-9a-fA-F]{7,40})\b|<a href="./?id=\1">\1</a>|g'
|
||||
|
||||
# This expression generates links to a fictional bugtracker.
|
||||
regex=$regex'
|
||||
s|#([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
|
||||
|
||||
sed -re "$regex"
|
||||
35
filters/email-gravatar.lua
Normal file
35
filters/email-gravatar.lua
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
-- It adds gravatar icons to author names. It is designed to be used with the lua:
|
||||
-- prefix in filters. It is much faster than the corresponding python script.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
--
|
||||
|
||||
local digest = require("openssl.digest")
|
||||
|
||||
function md5_hex(input)
|
||||
local b = digest.new("md5"):final(input)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
function filter_open(email, page)
|
||||
buffer = ""
|
||||
md5 = md5_hex(email:sub(2, -2):lower())
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer)
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
||||
|
||||
|
||||
39
filters/email-gravatar.py
Executable file
39
filters/email-gravatar.py
Executable file
|
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
|
||||
# script is very slow, in comparison.
|
||||
#
|
||||
# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
# It receives an email address on argv[1] and text on stdin. It prints
|
||||
# to stdout that text prepended by a gravatar at 10pt.
|
||||
|
||||
import sys
|
||||
import hashlib
|
||||
import codecs
|
||||
|
||||
email = sys.argv[1].lower().strip()
|
||||
if email[0] == '<':
|
||||
email = email[1:]
|
||||
if email[-1] == '>':
|
||||
email = email[0:-1]
|
||||
|
||||
page = sys.argv[2]
|
||||
|
||||
sys.stdin = codecs.getreader("utf-8")(sys.stdin.detach())
|
||||
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
|
||||
|
||||
md5 = hashlib.md5(email.encode()).hexdigest()
|
||||
text = sys.stdin.read().strip()
|
||||
|
||||
print("<img src='//www.gravatar.com/avatar/" + md5 + "?s=13&d=retro' width='13' height='13' alt='Gravatar' /> " + text)
|
||||
36
filters/email-libravatar.lua
Normal file
36
filters/email-libravatar.lua
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
-- It adds libravatar icons to author names. It is designed to be used with the lua:
|
||||
-- prefix in filters.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
--
|
||||
|
||||
local digest = require("openssl.digest")
|
||||
|
||||
function md5_hex(input)
|
||||
local b = digest.new("md5"):final(input)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
function filter_open(email, page)
|
||||
buffer = ""
|
||||
md5 = md5_hex(email:sub(2, -2):lower())
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/"
|
||||
html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer)
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
||||
|
||||
|
||||
359
filters/file-authentication.lua
Normal file
359
filters/file-authentication.lua
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
-- This script may be used with the auth-filter.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- username1:hash1
|
||||
-- username2:hash2
|
||||
-- username3:hash3
|
||||
-- ...
|
||||
-- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`.
|
||||
-- This file should not be world-readable.
|
||||
local users_filename = "/etc/cgit-auth/users"
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- groupname1:username1,username2,username3,...
|
||||
-- ...
|
||||
local groups_filename = "/etc/cgit-auth/groups"
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- reponame1:groupname1,groupname2,groupname3,...
|
||||
-- ...
|
||||
local repos_filename = "/etc/cgit-auth/repos"
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should not be world-readable.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Looks up a hash for a given user.
|
||||
function lookup_hash(user)
|
||||
local line
|
||||
for line in io.lines(users_filename) do
|
||||
local u, h = string.match(line, "(.-):(.+)")
|
||||
if u:lower() == user:lower() then
|
||||
return h
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Looks up users for a given repo.
|
||||
function lookup_users(repo)
|
||||
local users = nil
|
||||
local groups = nil
|
||||
local line, group, user
|
||||
for line in io.lines(repos_filename) do
|
||||
local r, g = string.match(line, "(.-):(.+)")
|
||||
if r == repo then
|
||||
groups = { }
|
||||
for group in string.gmatch(g, "([^,]+)") do
|
||||
groups[group:lower()] = true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
if groups == nil then
|
||||
return nil
|
||||
end
|
||||
for line in io.lines(groups_filename) do
|
||||
local g, u = string.match(line, "(.-):(.+)")
|
||||
if groups[g:lower()] then
|
||||
if users == nil then
|
||||
users = { }
|
||||
end
|
||||
for user in string.gmatch(u, "([^,]+)") do
|
||||
users[user:lower()] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return users
|
||||
end
|
||||
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local hash = lookup_hash(post["username"])
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
local username = secure_value("username", post["username"], os.time() + 604800)
|
||||
set_cookie("cgitauth", username)
|
||||
end
|
||||
|
||||
html("\n")
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Returns 1 if the cookie is valid and 0 if it is not.
|
||||
function authenticate_cookie()
|
||||
accepted_users = lookup_users(cgit["repo"])
|
||||
if accepted_users == nil then
|
||||
-- We return as valid if the repo is not protected.
|
||||
return 1
|
||||
end
|
||||
|
||||
local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
|
||||
if username == nil or not accepted_users[username:lower()] 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("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
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
|
||||
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, 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"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions 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 url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
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
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
||||
360
filters/gentoo-ldap-authentication.lua
Normal file
360
filters/gentoo-ldap-authentication.lua
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- lualdap >= 1.2
|
||||
-- <https://git.zx2c4.com/lualdap/about/>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local lualdap = require("lualdap")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
--
|
||||
--
|
||||
-- Configure these variables for your settings.
|
||||
--
|
||||
--
|
||||
|
||||
-- A list of password protected repositories, with which gentooAccess
|
||||
-- group is allowed to access each one.
|
||||
local protected_repos = {
|
||||
glouglou = "infra",
|
||||
portage = "dev"
|
||||
}
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should be guarded.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
local groups = gentoo_ldap_user_groups(post["username"], post["password"])
|
||||
if groups == nil then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
|
||||
end
|
||||
|
||||
html("\n")
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Returns 1 if the cookie is valid and 0 if it is not.
|
||||
function authenticate_cookie()
|
||||
local required_group = protected_repos[cgit["repo"]]
|
||||
if required_group == nil then
|
||||
-- We return as valid if the repo is not protected.
|
||||
return 1
|
||||
end
|
||||
|
||||
local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
|
||||
if user_groups == nil or user_groups == "" then
|
||||
return 0
|
||||
end
|
||||
for group in string.gmatch(user_groups, "[^,]+") do
|
||||
if group == required_group then
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Prints the html for the login form.
|
||||
function body()
|
||||
html("<h2>Gentoo LDAP Authentication Required</h2>")
|
||||
html("<form method='post' action='")
|
||||
html_attr(cgit["login"])
|
||||
html("'>")
|
||||
html("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
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
|
||||
|
||||
--
|
||||
--
|
||||
-- Gentoo LDAP support.
|
||||
--
|
||||
--
|
||||
|
||||
function gentoo_ldap_user_groups(username, password)
|
||||
-- Ensure the user is alphanumeric
|
||||
if username == nil or username:match("%W") then
|
||||
return nil
|
||||
end
|
||||
|
||||
local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"
|
||||
|
||||
local ldap, err = lualdap.open_simple {
|
||||
uri = "ldap://ldap1.gentoo.org",
|
||||
who = who,
|
||||
password = password,
|
||||
starttls = true,
|
||||
certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
|
||||
keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
|
||||
cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
|
||||
}
|
||||
if ldap == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local group_suffix = ".group"
|
||||
local group_suffix_len = group_suffix:len()
|
||||
local groups = {}
|
||||
for dn, attribs in ldap:search { base = who, scope = "subtree" } do
|
||||
local access = attribs["gentooAccess"]
|
||||
if dn == who and access ~= nil then
|
||||
for i, v in ipairs(access) do
|
||||
local vlen = v:len()
|
||||
if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
|
||||
table.insert(groups, v:sub(1, vlen - group_suffix_len))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ldap:close()
|
||||
|
||||
return groups
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, 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"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions 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 url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
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 string.match(cookies, ";" .. name .. "=(.-);")
|
||||
end
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
||||
4
filters/html-converters/man2html
Executable file
4
filters/html-converters/man2html
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
echo "<div style=\"font-family: monospace\">"
|
||||
groff -mandoc -T html -P -r -P -l | egrep -v '(<html>|<head>|<meta|<title>|</title>|</head>|<body>|</body>|</html>|<!DOCTYPE|"http://www.w3.org)'
|
||||
echo "</div>"
|
||||
307
filters/html-converters/md2html
Executable file
307
filters/html-converters/md2html
Executable file
|
|
@ -0,0 +1,307 @@
|
|||
#!/usr/bin/env python3
|
||||
import markdown
|
||||
import sys
|
||||
import io
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from markdown.extensions.toc import TocExtension
|
||||
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stdout.write('''
|
||||
<style>
|
||||
.markdown-body {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
overflow: hidden;
|
||||
}
|
||||
.markdown-body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.markdown-body>*:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.markdown-body a.absent {
|
||||
color: #c00;
|
||||
}
|
||||
.markdown-body a.anchor {
|
||||
display: block;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
||||
margin: 20px 0 10px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: text;
|
||||
position: relative;
|
||||
}
|
||||
.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link {
|
||||
display: none;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
padding-left: 0;
|
||||
margin-left: -22px;
|
||||
top: 15%;
|
||||
}
|
||||
.markdown-body h1:hover a.anchor .mini-icon-link, .markdown-body h2:hover a.anchor .mini-icon-link, .markdown-body h3:hover a.anchor .mini-icon-link, .markdown-body h4:hover a.anchor .mini-icon-link, .markdown-body h5:hover a.anchor .mini-icon-link, .markdown-body h6:hover a.anchor .mini-icon-link {
|
||||
display: inline-block;
|
||||
}
|
||||
div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink {
|
||||
color: black;
|
||||
}
|
||||
.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
.markdown-body h1 {
|
||||
font-size: 28px;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.markdown-body h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.markdown-body h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.markdown-body h6 {
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
}
|
||||
.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre {
|
||||
margin: 15px 0;
|
||||
}
|
||||
.markdown-body hr {
|
||||
background: transparent url("/dirty-shade.png") repeat-x 0 0;
|
||||
border: 0 none;
|
||||
color: #ccc;
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.markdown-body li p.first {
|
||||
display: inline-block;
|
||||
}
|
||||
.markdown-body ul, .markdown-body ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
.markdown-body ul.no-list, .markdown-body ol.no-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.markdown-body dl {
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body dl dt {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 15px 0 5px;
|
||||
}
|
||||
.markdown-body dl dt:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body dl dt>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body dl dt>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body dl dd {
|
||||
margin: 0 0 15px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.markdown-body dl dd>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body dl dd>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body blockquote {
|
||||
border-left: 4px solid #DDD;
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
}
|
||||
.markdown-body blockquote>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body blockquote>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
.markdown-body table th, .markdown-body table td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 13px;
|
||||
}
|
||||
.markdown-body table tr {
|
||||
border-top: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
}
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.markdown-body span.frame {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.markdown-body span.frame>span {
|
||||
border: 1px solid #ddd;
|
||||
display: block;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
padding: 7px;
|
||||
width: auto;
|
||||
}
|
||||
.markdown-body span.frame span img {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
.markdown-body span.frame span span {
|
||||
clear: both;
|
||||
color: #333;
|
||||
display: block;
|
||||
padding: 5px 0 0;
|
||||
}
|
||||
.markdown-body span.align-center {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
}
|
||||
.markdown-body span.align-center>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
.markdown-body span.align-center span img {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.markdown-body span.align-right {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
}
|
||||
.markdown-body span.align-right>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body span.align-right span img {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body span.float-left {
|
||||
display: block;
|
||||
margin-right: 13px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
}
|
||||
.markdown-body span.float-left span {
|
||||
margin: 13px 0 0;
|
||||
}
|
||||
.markdown-body span.float-right {
|
||||
display: block;
|
||||
margin-left: 13px;
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
}
|
||||
.markdown-body span.float-right>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body code, .markdown-body tt {
|
||||
margin: 0 2px;
|
||||
padding: 0px 5px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-body code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.markdown-body pre>code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
.markdown-body .highlight pre, .markdown-body pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-body pre code, .markdown-body pre tt {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
''')
|
||||
sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight'))
|
||||
sys.stdout.write('''
|
||||
</style>
|
||||
''')
|
||||
sys.stdout.write("<div class='markdown-body'>")
|
||||
sys.stdout.flush()
|
||||
# Note: you may want to run this through bleach for sanitization
|
||||
markdown.markdownFromFile(
|
||||
output_format="html5",
|
||||
extensions=[
|
||||
"markdown.extensions.fenced_code",
|
||||
"markdown.extensions.codehilite",
|
||||
"markdown.extensions.tables",
|
||||
TocExtension(anchorlink=True)],
|
||||
extension_configs={
|
||||
"markdown.extensions.codehilite":{"css_class":"highlight"}})
|
||||
sys.stdout.write("</div>")
|
||||
2
filters/html-converters/rst2html
Executable file
2
filters/html-converters/rst2html
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s")
|
||||
4
filters/html-converters/txt2html
Executable file
4
filters/html-converters/txt2html
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
echo "<pre>"
|
||||
sed "s|&|\\&|g;s|'|\\'|g;s|\"|\\"|g;s|<|\\<|g;s|>|\\>|g"
|
||||
echo "</pre>"
|
||||
17
filters/owner-example.lua
Normal file
17
filters/owner-example.lua
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
-- This script is an example of an owner-filter. It replaces the
|
||||
-- usual query link with one to a fictional homepage. This script may
|
||||
-- be used with the owner-filter or repo.owner-filter settings in
|
||||
-- cgitrc with the `lua:` prefix.
|
||||
|
||||
function filter_open()
|
||||
buffer = ""
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer))
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
||||
314
filters/simple-authentication.lua
Normal file
314
filters/simple-authentication.lua
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
--
|
||||
--
|
||||
-- Configure these variables for your settings.
|
||||
--
|
||||
--
|
||||
|
||||
-- A list of password protected repositories along with the users who can access them.
|
||||
local protected_repos = {
|
||||
glouglou = { laurent = true, jason = true },
|
||||
qt = { jason = true, bob = true }
|
||||
}
|
||||
|
||||
-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
|
||||
local users = {
|
||||
jason = "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1",
|
||||
laurent = "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.",
|
||||
bob = "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC"
|
||||
}
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should be guarded.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local hash = users[post["username"]]
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
local username = secure_value("username", post["username"], os.time() + 604800)
|
||||
set_cookie("cgitauth", username)
|
||||
end
|
||||
|
||||
html("\n")
|
||||
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_value("username", get_cookie(http["cookie"], "cgitauth"))
|
||||
if username == nil or not accepted_users[username:lower()] 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("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
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
|
||||
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, 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"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions 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 url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
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
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
||||
55
filters/syntax-highlighting.py
Executable file
55
filters/syntax-highlighting.py
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# This script uses Pygments and Python3. You must have both installed
|
||||
# for this to work.
|
||||
#
|
||||
# http://pygments.org/
|
||||
# http://python.org/
|
||||
#
|
||||
# It may be used with the source-filter or repo.source-filter settings
|
||||
# in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the
|
||||
# configuration of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
|
||||
|
||||
import sys
|
||||
import io
|
||||
from pygments import highlight
|
||||
from pygments.util import ClassNotFound
|
||||
from pygments.lexers import TextLexer
|
||||
from pygments.lexers import guess_lexer
|
||||
from pygments.lexers import guess_lexer_for_filename
|
||||
from pygments.formatters import HtmlFormatter
|
||||
|
||||
|
||||
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
data = sys.stdin.read()
|
||||
filename = sys.argv[1]
|
||||
formatter = HtmlFormatter(style='pastie', nobackground=True)
|
||||
|
||||
try:
|
||||
lexer = guess_lexer_for_filename(filename, data)
|
||||
except ClassNotFound:
|
||||
# check if there is any shebang
|
||||
if data[0:2] == '#!':
|
||||
lexer = guess_lexer(data)
|
||||
else:
|
||||
lexer = TextLexer()
|
||||
except TypeError:
|
||||
lexer = TextLexer()
|
||||
|
||||
# highlight! :-)
|
||||
# printout pygments' css definitions as well
|
||||
sys.stdout.write('<style>')
|
||||
sys.stdout.write(formatter.get_style_defs('.highlight'))
|
||||
sys.stdout.write('</style>')
|
||||
sys.stdout.write(highlight(data, lexer, formatter, outfile=None))
|
||||
121
filters/syntax-highlighting.sh
Executable file
121
filters/syntax-highlighting.sh
Executable file
|
|
@ -0,0 +1,121 @@
|
|||
#!/bin/sh
|
||||
# This script can be used to implement syntax highlighting in the cgit
|
||||
# tree-view by referring to this file with the source-filter or repo.source-
|
||||
# filter options in cgitrc.
|
||||
#
|
||||
# This script requires a shell supporting the ${var##pattern} syntax.
|
||||
# It is supported by at least dash and bash, however busybox environments
|
||||
# might have to use an external call to sed instead.
|
||||
#
|
||||
# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
|
||||
# highlighting, so you'll probably want something like the following included
|
||||
# in your css file:
|
||||
#
|
||||
# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
|
||||
#
|
||||
# table.blob .num { color:#2928ff; }
|
||||
# table.blob .esc { color:#ff00ff; }
|
||||
# table.blob .str { color:#ff0000; }
|
||||
# table.blob .dstr { color:#818100; }
|
||||
# table.blob .slc { color:#838183; font-style:italic; }
|
||||
# table.blob .com { color:#838183; font-style:italic; }
|
||||
# table.blob .dir { color:#008200; }
|
||||
# table.blob .sym { color:#000000; }
|
||||
# table.blob .kwa { color:#000000; font-weight:bold; }
|
||||
# table.blob .kwb { color:#830000; }
|
||||
# table.blob .kwc { color:#000000; font-weight:bold; }
|
||||
# table.blob .kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#ffffff; }
|
||||
# pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
|
||||
# .hl.num { color:#2928ff; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#ff0000; }
|
||||
# .hl.dstr { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.dir { color:#008200; }
|
||||
# .hl.sym { color:#000000; }
|
||||
# .hl.line { color:#555555; }
|
||||
# .hl.mark { background-color:#ffffbb;}
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#830000; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#e0eaee; }
|
||||
# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
|
||||
# .hl.num { color:#b07e00; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#bf0303; }
|
||||
# .hl.pps { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.ppc { color:#008200; }
|
||||
# .hl.opt { color:#000000; }
|
||||
# .hl.lin { color:#555555; }
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#0057ae; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#e0eaee; }
|
||||
# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
|
||||
# .hl.num { color:#b07e00; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#bf0303; }
|
||||
# .hl.pps { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.ppc { color:#008200; }
|
||||
# .hl.opt { color:#000000; }
|
||||
# .hl.ipl { color:#0057ae; }
|
||||
# .hl.lin { color:#555555; }
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#0057ae; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
|
||||
# store filename and extension in local vars
|
||||
BASENAME="$1"
|
||||
EXTENSION="${BASENAME##*.}"
|
||||
|
||||
[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
|
||||
[ -z "${EXTENSION}" ] && EXTENSION=txt
|
||||
|
||||
# map Makefile and Makefile.* to .mk
|
||||
[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
|
||||
|
||||
# highlight versions 2 and 3 have different commandline options. Specifically,
|
||||
# the -X option that is used for version 2 is replaced by the -O xhtml option
|
||||
# for version 3.
|
||||
#
|
||||
# Version 2 can be found (for example) on EPEL 5, while version 3 can be
|
||||
# found (for example) on EPEL 6.
|
||||
#
|
||||
# This is for version 2
|
||||
exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
|
||||
|
||||
# This is for version 3
|
||||
#exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null
|
||||
Loading…
Add table
Add a link
Reference in a new issue