LiquidProxy Lua Edition
at master 216 lines 5.4 kB view raw
1local ch = require "coro-http" 2local fs = require "fs" 3---@diagnostic disable-next-line: undefined-field 4local btoa = require "base64".decode 5 6local ports = Config.ports.http 7local mod_secure = Config.secure.mod.http 8local mod = Config.mod.http 9 10local webui, wus, whtest 11local authpls = "Please authenticate :)" 12local authplsl = tostring(authpls:len()) 13if mod.webui then 14 webui, wus = (table.unpack or unpack)(require "app.http.webui") 15 16 local wuih = mod.webui.hosts 17 function whtest(url) 18 if wuih then 19 if table.has(wuih, url) then return true end 20 if url:sub(1, 4) == "www." then 21 if table.has(wuih, url:sub(5)) then return true end 22 end 23 end 24 return false 25 end 26end 27 28HTTPCatchAlls = {} 29HTTPMatches = {} 30local hmfn = {} 31 32if fs.existsSync "scripts" then 33 for _, file in pairs(fs.readdirSync "scripts") do 34 if file:find("%.lua$") then 35 local func, rules = require("scripts."..file:match("(.+)%.lua")) 36 if func then table.insert(HTTPCatchAlls, func) end 37 for host, func in pairs(rules or {}) do 38 if hmfn[host] then 39 l:error("%s conflicts with rule in %s and %s", host, hmfn[host], file) 40 os.exit(1) 41 else 42 HTTPMatches[host] = func 43 hmfn[host] = file 44 end 45 end 46 end 47 end 48end 49 50-- 0.3, 1.0, 1.1, 1.2, 1.3 51function Ver2Num(ver) 52 if ver:sub(1,4) == "TLSv" then 53 return tonumber(ver:sub(5)) 54 else -- SSL 55 return tonumber("0."..ver:sub(5)) 56 end 57end 58local maxver = Ver2Num((Config.secure.tls.max)) 59 60local function haw(req, socket) 61 if socket.ssl then 62 if Ver2Num(req.socket.ssl:get("version")) <= maxver then 63 return true 64 end 65 end 66 if mod_secure.password then 67 local a = req["Proxy-Authorization"] or req["Authorization"] 68 if a then 69 if a:sub(1, 6) == "Basic " then 70 local u, p = btoa(a:sub(7)):match("^([^:]*):?(.+)$") 71 if u == "" then 72 if mod_secure.require_username then 73 l:debug "Auth fail: No username but server requires it" return false 74 end 75 elseif u ~= mod_secure.username then 76 if Config.secure.username_whitelist[u] then 77 return true 78 else 79 l:debug "Auth fail: Wrong and not whitelisted username" return false 80 end 81 end 82 if p == mod_secure.password then 83 return true 84 else 85 l:debug "Auth fail: Wrong password" return false 86 end 87 end 88 else 89 l:debug "Auth fail: No authorization header" return 90 end 91 end 92 93 l:debug "Auth fail: Unspecified" return false 94end 95function HTTPAuth(req, socket) 96 local ip = socket:getpeername().ip 97 if AllowedIPs[ip] then return true end 98 if haw(req, socket) then 99 RemoveIP(ip) return true 100 else 101 AddIP(ip) return false 102 end 103end 104 105local plainproxy = require "app.http.plain" 106local connectproxy = require "app.http.connect" 107 108local authb = "Please authenticate :)\r\n" 109local auth_webui = { 110 code = 401, 111 {"WWW-Authenticate", mod.webui.realm and "Basic realm=\""..mod.webui.realm.."\"" or "Basic"}, 112 {"Content-Length", authb:len()} 113} 114local auth_proxy = { 115 code = 407, 116 {"Proxy-Authenticate", "Basic"}, 117 {"Content-Lenghth", authb:len()} 118} 119 120local nob = Config.mod.http.forbidden_response 121local noh = {code = 200} 122if nob then 123 nob = nob.."r\n" 124 noh = { 125 code = 200, 126 {"Content-Type", "text/plain"}, 127 {"Content-Length", nob:len()} 128 } 129end 130 131local issb = "Internal Server Error\r\n" 132local issh = { 133 code = 500, 134 {"Content-Length", issb:len()} 135} 136 137local head_metatable = { 138 __index = function(t, k) 139 local ct = rawget(t, "_cache") 140 if not ct then 141 ct = {} 142 rawset(t, "_cache", ct) 143 end 144 local c = ct[k] 145 if c then return c end 146 for _, t in pairs(t) do 147 if type(t) == "table" and t[1] == k then 148 return t[2] 149 end 150 end 151 end 152} 153 154---@return table headers 155---@return string? body 156local function onReq(req, body, socket) 157 setmetatable(req, head_metatable) 158 local ip = socket:getpeername().ip 159 if BannedIPs[ip] then 160 return noh, nob 161 end 162 local suc, a, b = xpcall(function() 163 if req.method == "CONNECT" then 164 if wus and whtest(req.path:match("^([^:]+)")) then 165 return wus(req) 166 end 167 return connectproxy(req, socket) 168 else 169 if req.path:sub(1, 7) == "http://" then -- This can never be HTTPS 170 if not HTTPAuth(req, socket) then 171 return auth_proxy, authb 172 end 173 if webui and whtest(req.path:sub(8):match("^(.-)/")) then 174 if mod_secure.webui_authenticate then 175 if not HTTPAuth(req, socket) then 176 return auth_webui, authb 177 end 178 end 179 return webui(req) 180 end 181 return plainproxy(req, body) 182 else 183 if req.path:sub(1, 1) ~= "/" or not (webui and mod.webui.proxyless) then 184 return noh, nob 185 else 186 if mod_secure.webui_authenticate then 187 if not HTTPAuth(req, socket) then 188 return auth_webui, authb 189 end 190 end 191 return webui(req) 192 end 193 end 194 end 195 end, function(err) 196 return debug.traceback(err, 1) 197 end) 198 if not suc then 199 l:error(a) 200 return issh, issb 201 end 202 ---@diagnostic disable-next-line: return-type-mismatch 203 return a, b 204end 205 206if ports.plain then 207 ch.createServer("0.0.0.0", ports.plain, onReq) 208 LogStarted("HTTP", "plain", ports.plain) 209end 210 211if ports.secure then 212 --TODO: remove diag 213---@diagnostic disable-next-line: redundant-parameter 214 ch.createServer("0.0.0.0", ports.secure, onReq, {key = Key, cert = Cert, server = true}) 215 LogStarted("HTTP", "secure", ports.secure) 216end