LiquidProxy Lua Edition
at master 219 lines 5.4 kB view raw
1-- by thenumbernine: github.com/thenumbernine/lua-url/blob/master/unfacebook.lua 2-- slightly, slightly modified 3 4-- because socket.url is another library 5-- meh 6local class = require 'ext.class' 7local table = require 'ext.table' 8--local assert = require 'ext.assert' 9local string = require 'ext.string' 10 11 12local defaultescapechars = "!#$&'()*+,/:;=?@[]%" 13local escapecharsets = {} 14local function escape(s, escapechars) 15 escapechars = escapechars or defaultescapechars 16 local escapecharset = escapecharsets[escapechars] 17 if not escapecharset then 18 escapecharset = string.split(escapechars):mapi(function(ch) return true, ch end):setmetatable(nil) 19 escapecharsets[escapechars] = escapecharset 20 end 21 return (s:gsub('.', function(ch) 22 if escapecharset[ch] then 23 return ('%%%02X'):format(ch:byte()) 24 end 25 return ch -- necessary? 26 end)) 27end 28 29local function unescape(s) 30 return (s:gsub('%%%x%x', function(s) 31 local n = tonumber(s:sub(2), 16) 32 if not n then return s end 33 local ch = string.char(n) 34 if not ch then return s end 35 return ch 36 end)) 37end 38--[[ 39't' handles *BOTH* pairs k=v and ipairs {k,v} 40first it processes ipairs 41then it processes all pairs that are non-integer keys 42this way you can pass it either pairs for quick Lua handling 43or ipairs if you care about the order 44--]] 45local escapekeychars = "!#$&'()*+,:;=?@%" -- key doesn't need /[] escaped ... 46local function argsToStr(t) 47 t = table(t):setmetatable(nil) 48 local s = table() 49 local sep 50 for i=1,#t do 51 local kv = t[i] 52 -- TODO still, what to do for key without value? 53 local k,v = tostring(kv[1]), tostring(kv[2]) 54 if sep then s:insert(sep) end 55 sep = '&' 56 s:insert(escape(k, escapekeychars)) 57 s:insert'=' 58 s:insert(escape(v)) 59 -- clear as you go 60 t[i] = nil 61 t[k] = nil 62 end 63 -- process whats left of ipairs 64 for k,v in pairs(t) do 65 if sep then s:insert(sep) end 66 sep = '&' 67 s:insert(escape(tostring(k), escapekeychars)) 68 s:insert'=' 69 s:insert(escape(tostring(v))) 70 end 71 return s:concat() 72end 73 74local URL = class() 75 76URL.escape = escape 77URL.unescape = unescape 78URL.argsToStr = argsToStr 79 80local function parseArgs(kvstr) 81 -- also return in-order list 82 local kvs = table() 83 for _,kv in ipairs(string.split(kvstr, '&')) do 84 local k,v = kv:match'^([^=]+)=(.*)$' 85 86 -- what if there's no "=" ? then what? 87 if not k then k,v = kv, '' end 88 89 k = unescape(k) or k 90 v = unescape(v) or v 91 92 kvs:insert{k, v} 93 kvs[k] = v 94 end 95 return kvs 96end 97 98URL.parseArgs = function(self,kvstr) 99 if not kvstr then return parseArgs(self) else return parseArgs(kvstr) end 100end 101 102 103--[[ 104args as a string parses the fields. 105args as a table copies the fields. 106 107fields are: 108 scheme 109 host 110 user 111 pass 112 port 113 path 114 params 115 query 116 fragment 117 118 userinfo 119--]] 120function URL:init(args) 121 if type(args) == 'string' then 122 local url = args 123 124 -- <scheme>://<username>:<password>@<host>:<port>/<path>;<parameters>?<query>#<fragment> 125 -- [<scheme>://][<username>[:<password>]@]<host>[:<port>][/<path>][;<parameters>][?<query>][#<fragment>] 126 127 -- parse scheme 128 local scheme, rest = url:match'^([^:]+)://(.*)$' 129 rest = rest or url 130 self.scheme = scheme 131 132 -- parse authority vs path+query+params+fragment 133 local authority, pathqueryparamsfragment = rest:match'^([^/;?#]+)[/;?#](.*)$' -- expect host to end at /;?# 134 authority = authority or rest 135 136 local userinfo, hostandport = authority:match'^([^@]+)@(.*)$' 137 if userinfo then 138 local user, pass = userinfo:match'^([^:]*):(.*)$' 139 user = user or userinfo 140 self.user = user 141 self.pass = pass 142 else 143 hostandport = authority 144 end 145 146 local host, port = hostandport:match'^([^:]+):(.*)$' 147 host = host or hostandport 148 self.host = host 149 self.port = port 150 151 if pathqueryparamsfragment then 152 -- /path;params?query#fragment 153 -- what if you get ;abc/def? then it's still the params 154 local prevrest = pathqueryparamsfragment 155 rest, self.fragment = prevrest:match'^([^#]+)#(.*)$' 156 prevrest = rest or prevrest 157 rest, self.query = prevrest:match'^([^?]+)?(.*)$' 158 prevrest = rest or prevrest 159 rest, self.params = prevrest:match'^([^;]+);(.*)$' 160 prevrest = rest or prevrest 161 self.path = prevrest 162 163 if self.query then self.query = parseArgs(self.query) end 164 if self.params then self.params = parseArgs(self.params) end 165 elseif args:sub(1,1) == "?" then 166 self.query = parseArgs(args) 167 self.params = nil 168 end 169 170 elseif type(args) == 'table' then 171 for k,v in pairs(args) do self[k] = v end 172 elseif type(args) == 'nil' then 173 else 174 error("idk how to build a URL from this") 175 end 176end 177 178URL.__concat = string.concat 179 180-- tostring or another function? 181function URL:__tostring() 182 local s = table() 183 if self.scheme then 184 s:insert(self.scheme) 185 s:insert'://' 186 end 187 if self.user then 188 s:insert(self.user) 189 if self.pass then 190 s:insert':' 191 s:insert(self.pass) 192 end 193 s:insert'@' 194 end 195 s:insert(self.host) 196-- TODO escape path? except /'s ? 197 if self.path then 198 s:insert'/' 199 s:insert(self.path) 200 end 201 if self.params then 202 s:insert';' 203 s:insert(argsToStr(self.params)) 204 end 205 if self.query then 206 s:insert'?' 207 s:insert(argsToStr(self.query)) 208 end 209 if self.fragment then 210 s:insert'#' 211 s:insert(self.fragment) 212 end 213 return s:concat() 214end 215 216-- shorthand 217URL.tostring = URL.__tostring 218 219return URL