-- cgit email-filter that puts a Gravatar icon next to an author name. Enable -- it with the email-filter or repo.email-filter setting and the lua: prefix, -- so it runs in cgit's embedded interpreter with no per-request process. The -- companion email-libravatar.lua is the same filter pointed at Libravatar -- instead. -- -- email-filter=lua:/path/to/email-gravatar.lua -- -- Runs on Lua 5.1, 5.2, 5.3, 5.4 and LuaJIT. Lua 5.5 is not supported, because -- luaossl has no 5.5 build. -- -- The one dependency is luaossl, the OpenSSL binding that provides -- openssl.digest, from . The reliable -- cross-platform install is LuaRocks, matched to the Lua version cgit is built -- against, and it needs the OpenSSL development headers present. Without it -- the filter draws no icons and leaves the names as they are. -- -- # Debian and Ubuntu -- sudo apt install luarocks libssl-dev -- sudo luarocks --lua-version 5.1 install luaossl -- -- # Fedora -- sudo dnf install luarocks openssl-devel -- sudo luarocks --lua-version 5.1 install luaossl -- -- # macOS with Homebrew -- brew install luarocks openssl -- luarocks install luaossl OPENSSL_DIR="$(brew --prefix openssl)" -- -- Every page view sends the visitor's IP address and a hash of each -- committer's email to a third-party service, so leave this filter off if -- that is not acceptable for your instance. Addresses are hashed with -- SHA-256, the form Gravatar documents. local has_digest, digest = pcall(require, "openssl.digest") -- The size is in pixels and serves both as the image asked of the service -- and as the width and height attributes. The default image is what -- Gravatar draws for an address it has never seen. The endpoint is https so -- the icon is not blocked as mixed content on an https page. local avatar_size = 13 local default_image = "retro" local base_url = "https://gravatar.com/avatar/" -- One name reaches this filter as an open, writes and a close, so what the -- open works out is kept here for the close. local buffer = "" local avatar_hash = nil local function hash_hex(input) local raw = digest.new("sha256"):final(input) local hex = "" for i = 1, #raw do hex = hex .. string.format("%.2x", string.byte(raw, i)) end return hex end -- Lua patterns backtrack, so "^%s*(.-)%s*$" would cost the square of a long -- run of spaces inside the address. Finding each end directly stays linear. local function trim(text) local first = text:find("%S") if first == nil then return "" end local last = #text while text:find("^%s", last) do last = last - 1 end return text:sub(first, last) end -- cgit can hand over the address still wrapped in angle brackets, and the -- service hashes the trimmed lowercase form, so an address that skipped this -- would hash to something the service has never heard of. A missing or empty -- address becomes nil, which is how the caller knows to draw no icon. local function normalize_email(email) if email == nil then return nil end -- The first "<" and the last ">" are found directly, because "<(.*)>" -- backtracks to the end of the address from every "<" in it. local open = email:find("<", 1, true) local close = email:find(">[^>]*$") if open and close and close > open then email = email:sub(open + 1, close - 1) end email = trim(email):lower() if email == "" then return nil end return email end function filter_open(email, page) buffer = "" local address = normalize_email(email) if address == nil or not has_digest then avatar_hash = nil else avatar_hash = hash_hex(address) end end function filter_write(text) buffer = buffer .. text end -- A log page can carry one icon per commit, so the attributes let the -- browser defer the fetches for icons still off screen and keep decoding -- off the critical path. The icon only decorates the name beside it, so its -- alt text is empty and a screen reader goes straight to the name. function filter_close() if avatar_hash == nil then html(buffer) else html(" " .. buffer) end return 0 end