local sql = require("lsqlite3") local config = require("config") local types = require("types") local util = {} --see https://perishablepress.com/stop-using-unsafe-characters-in-urls/ --no underscore because we use that for our operative pages local url_characters = [[abcdefghijklmnopqrstuvwxyz]].. [[ABCDEFGHIJKLMNOPQRSTUVWXYZ]].. [[0123456789]] local url_characters_legacy = url_characters .. [[$-+!*'(),]] local url_characters_rev = {} for i = 1,string.len(url_characters) do url_characters_rev[string.sub(url_characters,i,i)] = i end local url_characters_rev_legacy = {} for i = 1,string.len(url_characters_legacy) do url_characters_rev_legacy[string.sub(url_characters_legacy,i,i)] = i end --[[ Encode a number to a shorter HTML-safe url path ]] function util.encode_id(number) local result = {} local charlen = string.len(url_characters) repeat local pos = (number % charlen) + 1 number = math.floor(number / charlen) table.insert(result,string.sub(url_characters,pos,pos)) until number == 0 return table.concat(result) end --[[ Legacy code, try to encode with invalid characters in the url first ]] local new_encode = util.encode_id function util.encode_id(number) if number >= config.legacy_url_cutoff then return new_encode(number) else local result = {} local charlen = string.len(url_characters_legacy) repeat local pos = (number % charlen) + 1 number = math.floor(number / charlen) table.insert(result,string.sub(url_characters_legacy,pos,pos)) until number == 0 return table.concat(result) end end --[[ Given a short HTML-safe url path, convert it to a storyid ]] function util.decode_id(s) local res, id = pcall(function() local n = 0 local charlen = string.len(url_characters) for i = 1,string.len(s) do local char = string.sub(s,i,i) local pos = url_characters_rev[char] - 1 n = n + (pos*math.pow(charlen,i-1)) end return n end) if res then return id else return false,"Failed to decode id:" .. s end end --[[ Legacy code, try to decode with invalid characters in the url first ]] local new_decode = util.decode_id function util.decode_id(s) local res, id = pcall(function() local n = 0 local charlen = string.len(url_characters_legacy) for i = 1,string.len(s) do local char = string.sub(s,i,i) local pos = url_characters_rev_legacy[char] - 1 n = n + (pos * math.pow(charlen,i-1)) end return n end) if res then if id > config.legacy_url_cutoff then return new_decode(s) else return id end else return false,"Failed to decode id:" .. s end end --arbitary data to hex encoded string function util.encode_unlisted(str) assert(type(str) == "string","Tried to encode something not a string:" .. type(Str)) local safe = {} for i = 1,#str do local byte = str:byte(i) table.insert(safe,string.format("%02x",byte)) end return table.concat(safe) end --hex encoded string to arbitrary data function util.decode_unlisted(str) print("str was:",str) local output = {} for byte in str:gmatch("%x%x") do table.insert(output, string.char(tonumber(byte,16))) end return table.concat(output) end --[[ Parses a semicolon seperated string into it's parts: 1. seperates by semicolon 2. trims whitespace 3. lowercases 4. capitalizes the first letter. Returns an array of zero or more strings. There is no blank tag, parsing "one;two;;three" will yield {"one","two","three"} ]] function util.parse_tags(str) local tags = {} for tag in string.gmatch(str,"([^;]+)") do assert(tag, "Found a nil or false tag in:" .. str) local tag_trimmed = string.match(tag,"%s*(.*)%s*") local tag_lower = string.lower(tag_trimmed) local tag_capitalized = string.gsub(tag_lower,"^.",string.upper) assert(tag_capitalized, "After processing tag:" .. tag .. " it was falsey.") if string.len(tag_capitalized) > 0 then table.insert(tags, tag_capitalized) end end return tags end if config.debugging then function util.checktypes(...) local args = {...} if #args == 1 then args = table.unpack(args) end assert( #args % 3 == 0, "Arguments to checktypes() must be triplets of " .. ", , " ) for i = 1,#args,3 do local var, ltype, veri_f = args[i+0], args[i+1], args[i+2] assert( type(var) == ltype, string.format( "Expected argument %d (%q) to be type %s, but was %s", i/3 ) ) if veri_f then assert(veri_f(var)) end end end else function util.checktypes(...) end end local function decodeentity(capture) return string.char(tonumber(capture,16)) --Decode base 16 and conver to character end function util.decodeentities(str) return string.gsub(str,"%%(%x%x)",decodeentity) end return util