-- 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 through 5.5 and LuaJIT, where 5.5 needs luaossl 20260910 or -- later, since earlier builds fail to load there. -- -- 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, while a screen of double density is -- offered an image twice the size. 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 local function avatar_url(size) return base_url .. avatar_hash .. "?s=" .. size .. "&d=" .. default_image 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