#!/usr/bin/env lua -- *********************************************************************** -- -- Returns an SVG image of an Urbit ID. -- Copyright 2020 by Sean Conner. -- -- This program is free software: you can redistribute it and/or modify it -- under the terms of the GNU General Public License as published by the -- Free Software Foundation, either version 3 of the License, or (at your -- option) any later version. -- -- This program is distributed in the hope that it will be useful, but -- WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General -- Public License for more details. -- -- You should have received a copy of the GNU General Public License along -- with this program. If not, see . -- -- Comments, questions and criticisms can be sent to: sean@conman.org -- -- *********************************************************************** -- luacheck: ignore 611 631 -- -- NOTE: This is a CGI script that will work under HTTP, GEMINI and GOPHER, -- provided that $SERVER_PROTOCOL, $REQUEST_METHOD and $QUERY_STRING -- are supported by the server. -- -- Lua 5.1 or higher. local sigil = { dal = "", rym = "", bal = "", tob = "", sep = "", hoc = "", fet = "", lan = "", hap = "", leb = "", lab = "", lib = "", dux = "", feb = "", lig = "", tom = "", tal = "", sal = "", ral = "", dys = "", lux = "", son = "", nal = "", mal = "", mud = "", der = "", rux = "", tep = "", hal = "", doz = "", fal = "", siv = "", syd = "", mex = "", lyt = "", tyd = "", wyd = "", bar = "", mid = "", sit = "", tex = "", mep = "", dyt = "", wex = "", fog = "", bid = "", nep = "", hec = "", fit = "", mec = "", bep = "", mel = "", bit = "", dun = "", bex = "", fun = "", ryt = "", nid = "", fex = "", syt = "", hex = "", dif = "", dib = "", tid = "", han = "", met = "", bel = "", mig = "", mip = "", mit = "", moc = "", mol = "", mop = "", ryc = "", pur = "", tyc = "", ris = "", sur = "", rag = "", sid = "", tem = "", tev = "", ter = "", rev = "", sup = "", wer = "", lun = "", mun = "", mev = "", lev = "", ler = "", mer = "", ner = "", mut = "", per = "", nev = "", dem = "", luc = "", tan = "", san = "", rus = "", dex = "", tus = "", sef = "", fus = "", tuc = "", sog = "", mir = "", fan = "", wal = "", lus = "", mus = "", mog = "", pen = "", wyt = "", mes = "", nes = "", fil = "", def = "", fep = "", bes = "", dar = "", rum = "", sum = "", fes = "", dep = "", hes = "", wan = "", nam = "", pec = "", fen = "", rec = "", den = "", lec = "", fed = "", nec = "", ped = "", byt = "", red = "", ben = "", led = "", tec = "", ned = "", med = "", ban = "", ton = "", dan = "", bec = "", div = "", fam = "", set = "", wit = "", mon = "", lon = "", lep = "", som = "", sen = "", dec = "", wid = "", fyl = "", lom = "", dev = "", pan = "", hut = "", sap = "", tap = "", bud = "", lut = "", lud = "", ram = "", map = "", let = "", sec = "", lap = "", pun = "", dut = "", tud = "", bet = "", dat = "", det = "", los = "", dap = "", rud = "", heb = "", dur = "", nut = "", fip = "", deb = "", sut = "", rut = "", bur = "", del = "", bic = "", fel = "", mat = "", dos = "", byn = "", fos = "", lor = "", hos = "", nib = "", fyn = "", dig = "", dyn = "", rib = "", sib = "", dav = "", fin = "", lat = "", rys = "", hul = "", pas = "", lug = "", hin = "", sar = "", tar = "", mar = "", nar = "", dul = "", par = "", byl = "", ful = "", fer = "", lar = "", lop = "", don = "", lav = "", bon = "", ric = "", nys = "", tic = "", sop = "", top = "", fas = "", wis = "", lys = "", rav = "", wyc = "", nyl = "", mic = "", mis = "", wed = "", nel = "", ryg = "", ted = "", sed = "", das = "", lis = "", dop = "", wic = "", fop = "", net = "", hop = "", bis = "", nex = "", dis = "", lac = "", mac = "", nac = "", fad = "", pac = "", had = "", rac = "", ['nil'] = "", tac = "", las = "", mas = "", wac = "", deg = "", has = "", bat = "", rad = "", nor = "", tas = "", rov = "", mor = "", hat = "", sor = "", bac = "", lid = "", dac = "", wor = "", tor = "", hil = "", hac = "", sov = "", noc = "", meg = "", ril = "", weg = "", pil = "", mug = "", teg = "", seg = "", rop = "", ron = "", tug = "", sug = "", het = "", nop = "", ryn = "", dir = "", til = "", sil = "", bil = "", dyr = "", tyl = "", syl = "", ryl = "", rit = "", pyl = "", byr = "", peg = "", lyr = "", reg = "", nyr = "", leg = "", nov = "", dil = "", wyl = "", tyn = "", syn = "", lad = "", num = "", nup = "", nus = "", web = "", nux = "", nyd = "", reb = "", seb = "", teb = "", meb = "", wyn = "", fig = "", nyt = "", pin = "", nom = "", rin = "", pad = "", lin = "", mul = "", sub = "", min = "", pal = "", win = "", pat = "", pub = "", tin = "", dyl = "", pem = "", myr = "", sol = "", rol = "", pes = "", tol = "", wol = "", mot = "", nod = "", pet = "", ['for'] = "", pol = "", dor = "", hod = "", dov = "", nol = "", mod = "", lod = "", bot = "", tad = "", pex = "", dol = "", pic = "", fol = "", hol = "", tod = "", pit = "", zod = "", nem = "", pod = "", fot = "", bol = "", dot = "", pon = "", pos = "", nav = "", fod = "", bor = "", ran = "", rap = "", ref = "", mil = "", nub = "", rem = "", rep = "", res = "", rex = "", rid = "", sav = "", neb = "", myl = "", ryx = "", syx = "", roc = "", bus = "", nyx = "", dus = "", pyx = "", ruc = "", duc = "", sat = "", lyx = "", fyr = "", fir = "", rup = "", ryd = "", ber = "", rip = "", bin = "", hep = "", sel = "", rel = "", loc = "", pel = "", wel = "", soc = "", poc = "", tel = "", din = "", nul = "", toc = "", tip = "", fur = "", rul = "", syp = "", typ = "", sam = "", ryp = "", lur = "", tul = "", lyn = "", ros = "", nat = "", myn = "", mos = "", nos = "", sig = "", rig = "", syr = "", tyr = "", sun = "", run = "", tog = "", tun = "", mag = "", lag = "", tyv = "", bos = "", tux = "", sic = "", nis = "", lex = "", fid = "", hid = "", lup = "", len = "", lyd = "", lit = "", sim = "", tim = "", riv = "", ren = "", pid = "", ten = "", wen = "", pag = "", liv = "", tir = "", doc = "", tag = "", tab = "", fon = "", hav = "", des = "", wep = "", hob = "", ser = "", sev = "", sip = "", wat = "", hus = "", wes = "", sem = "", tes = "", wyx = "", nim = "", dab = "", mur = "", fab = "", nym = "", hab = "", sud = "", ret = "", sul = "", sym = "", wet = "", nap = "", har = "", put = "", tam = "", rab = "", sab = "", _pre = { "doz","mar","bin","wan","sam","lit","sig","hid","fid","lis","sog","dir","wac","sab","wis","sib", "rig","sol","dop","mod","fog","lid","hop","dar","dor","lor","hod","fol","rin","tog","sil","mir", "hol","pas","lac","rov","liv","dal","sat","lib","tab","han","tic","pid","tor","bol","fos","dot", "los","dil","for","pil","ram","tir","win","tad","bic","dif","roc","wid","bis","das","mid","lop", "ril","nar","dap","mol","san","loc","nov","sit","nid","tip","sic","rop","wit","nat","pan","min", "rit","pod","mot","tam","tol","sav","pos","nap","nop","som","fin","fon","ban","mor","wor","sip", "ron","nor","bot","wic","soc","wat","dol","mag","pic","dav","bid","bal","tim","tas","mal","lig", "siv","tag","pad","sal","div","dac","tan","sid","fab","tar","mon","ran","nis","wol","mis","pal", "las","dis","map","rab","tob","rol","lat","lon","nod","nav","fig","nom","nib","pag","sop","ral", "bil","had","doc","rid","moc","pac","rav","rip","fal","tod","til","tin","hap","mic","fan","pat", "tac","lab","mog","sim","son","pin","lom","ric","tap","fir","has","bos","bat","poc","hac","tid", "hav","sap","lin","dib","hos","dab","bit","bar","rac","par","lod","dos","bor","toc","hil","mac", "tom","dig","fil","fas","mit","hob","har","mig","hin","rad","mas","hal","rag","lag","fad","top", "mop","hab","nil","nos","mil","fop","fam","dat","nol","din","hat","nac","ris","fot","rib","hoc", "nim","lar","fit","wal","rap","sar","nal","mos","lan","don","dan","lad","dov","riv","bac","pol", "lap","tal","pit","nam","bon","ros","ton","fod","pon","sov","noc","sor","lav","mat","mip","fip", }, _suf = { "zod","nec","bud","wes","sev","per","sut","let","ful","pen","syt","dur","wep","ser","wyl","sun", "ryp","syx","dyr","nup","heb","peg","lup","dep","dys","put","lug","hec","ryt","tyv","syd","nex", "lun","mep","lut","sep","pes","del","sul","ped","tem","led","tul","met","wen","byn","hex","feb", "pyl","dul","het","mev","rut","tyl","wyd","tep","bes","dex","sef","wyc","bur","der","nep","pur", "rys","reb","den","nut","sub","pet","rul","syn","reg","tyd","sup","sem","wyn","rec","meg","net", "sec","mul","nym","tev","web","sum","mut","nyx","rex","teb","fus","hep","ben","mus","wyx","sym", "sel","ruc","dec","wex","syr","wet","dyl","myn","mes","det","bet","bel","tux","tug","myr","pel", "syp","ter","meb","set","dut","deg","tex","sur","fel","tud","nux","rux","ren","wyt","nub","med", "lyt","dus","neb","rum","tyn","seg","lyx","pun","res","red","fun","rev","ref","mec","ted","rus", "bex","leb","dux","ryn","num","pyx","ryg","ryx","fep","tyr","tus","tyc","leg","nem","fer","mer", "ten","lus","nus","syl","tec","mex","pub","rym","tuc","fyl","lep","deb","ber","mug","hut","tun", "byl","sud","pem","dev","lur","def","bus","bep","run","mel","pex","dyt","byt","typ","lev","myl", "wed","duc","fur","fex","nul","luc","len","ner","lex","rup","ned","lec","ryd","lyd","fen","wel", "nyd","hus","rel","rud","nes","hes","fet","des","ret","dun","ler","nyr","seb","hul","ryl","lud", "rem","lys","fyn","wer","ryc","sug","nys","nyl","lyn","dyn","dem","lux","fed","sed","bec","mun", "lyr","tes","mud","nyt","byr","sen","weg","fyr","mur","tel","rep","teg","pec","nel","nev","fes", }, } -- *********************************************************************** local explanation = [[ THE SIGIL This CGI script expects up to three parameters, only one which is required. The four parameters are: id This is either an Urbit identifier, such as ~lapzod~namtel or eight hexadecimal digits: F000F3F9 This is a REQUIRED parameter. fg The foreground color, as defined by CSS. For example, 'red'. White is the default color. bg The background color, as defined by CSS. For example, 'orange'. Black is the default color. The output is an SVG file of the resulting sigil for your use. ]] -- *********************************************************************** local svg = [[ %s %s %s %s ]] -- *********************************************************************** local function query(q) local function tochar(c) return string.char(tonumber(c,16)) end local res = {} for name,value in q:gmatch("([^=]+)=([^&]*)&?") do -- breakout name/value pairs name = name:gsub("%%(%x%x)",tochar) -- do unescaping value = value:gsub("%%(%x%x)",tochar) res[name] = value end return res end -- *********************************************************************** local function getid(id) local function name() local a,b,c,d = id:match("~(%l%l%l)(%l%l%l)~?(%l%l%l)(%l%l%l)$") if a and b and c and d then return sigil[a],sigil[b],sigil[c],sigil[d] end end local function value() local a,b,c,d = id:match("^(%x%x)(%x%x)(%x%x)(%x%x)") if a and b and c and d then a = tonumber(a,16) + 1 b = tonumber(b,16) + 1 c = tonumber(c,16) + 1 d = tonumber(d,16) + 1 return sigil[sigil._pre[a]], sigil[sigil._suf[b]], sigil[sigil._pre[c]], sigil[sigil._suf[d]] end end if id then if id:match "^~" then return name() elseif id:match "^%x" then return value() end end end -- *********************************************************************** local function process() local q = os.getenv("QUERY_STRING") if q then q = query(q) local a,b,c,d = getid(q.id) if a and b and c and d then return string.format(svg,a,b,c,d):gsub("@([FB]G)",{ FG = q.fg or 'white', BG = q.bg or 'black' }) end end end -- *********************************************************************** local function HTTP() local method = os.getenv("REQUEST_METHOD") if not method then io.stdout:write( "Status: 400\r\n", "Content-Type: text/plain\r\n", "\r\n", "Bad Request\r\n" ) end if method ~= "GET" then io.stdout:write( "Status: 405\r\n", "Content-Type: text/plain\r\n", "\r\n", "Only GET allowed\r\n" ) end local doc = process() if doc then io.stdout:write( "Content-Type: image/svg+xml\r\n", string.format("Content-Length: %d\r\n",#doc), "\r\n", doc ) else io.stdout:write( "Content-Type: text/plain\r\n", string.format("Content-Length: %d\r\n",#explanation), "\r\n", explanation ) end end -- *********************************************************************** local function GEMINI() local doc = process() if doc then io.stdout:write( "Content-Type: image/svg+xml\r\n", string.format("Content-Length: %d\r\n",#doc), "\r\n", doc ) else io.stdout:write( "Content-Type: text/plain\r\n", string.format("Content-Length: %d\r\n",#explanation), "\r\n", explanation ) end end -- *********************************************************************** local function GOPHER() local doc = process() io.stdout:write(doc or explanation) end -- *********************************************************************** local protocol = os.getenv("SERVER_PROTOCOL") if not protocol then os.exit(1) elseif protocol:match "^HTTP/" then HTTP() elseif protocol == "GEMINI" then GEMINI() elseif protocol == "GOPHER" then GOPHER() else os.exit(1) end