Project

General

Profile

ModMagnetExamples » History » Revision 6

Revision 5 (gstrauss, 2021-08-26 05:50) → Revision 6/29 (gstrauss, 2021-08-26 20:17)

{{>toc}} 

 h3. lua examples of lighttpd modules 

 mod_magnet with lua scripts can allow for customized alternatives to many lighttpd modules. 
 The following are simple examples to demonstrate, but are not full reimplementations. 

 (examples below use mod_magnet API since lighttpd 1.4.60) 


 h4. lua mod_access 

 <pre> 
 -- reject access to paths ending in ~ or .inc 
 local path = lighty.r.req_attr["uri.path"] 
 if (string.match(path, "~$") or string.match(path, "%.inc$")) then 
   return 403 
 end 
 </pre> 


 h4. lua mod_alias 

 <pre> 
 local req_attr = lighty.r.req_attr 
 local path = req_attr["physical.path"] 
 if (not path) then return 0 end 
 local plen = path:len() 
 local basedir = req_attr["physical.basedir"] 
 local blen = basedir:len() 
 if (string.match(basedir, "/$")) then blen = blen - 1 end 
 if (0 == plen or plen < blen) then return 0 end 
 local url_fspath = string.sub(path, blen+1, -1) 

 -- remap /basedir/cgi-bin/ to alternative filesystem path 
 if (string.match(url_fspath, "^/cgi-bin/")) then 
   -- prefix match and replacement should both end in slash ('/') or both not 
   local match_len = 9 -- length of "/cgi-bin/" 
   req_attr["physical.basedir"] = "/var/www/servers/www.example.org/cgi-bin/" 
   req_attr["physical.path"] = "/var/www/servers/www.example.org/cgi-bin/" 
                            .. string.sub(url_fspath, match_len+1, -1) 
 end 
 </pre> 


 h4. lua mod_auth 

 <pre> 
 -- There are many many many different ways to implement mod_auth. 
 -- This is a simplistic example of HTTP Basic auth. 
 -- Arbitrarily complex auth could be implemented in lua. 

 local realm = "secure" 
 local r = lighty.r 

 function check_user_pass(user, pass) 
   -- (trivially) hard-code valid username and password (and realm) 
   -- (This should be replaced by something more appropriate for your system) 
   -- (could be table or could be read from an external file or database or ...) 
   -- (lighttpd mod_auth is much more advanced) 
   if (realm == "secure" and user == "admin" and pass == "supersecretpass") then 
     return true 
   else 
     return false 
   end 
 end 

 function unauthorized() 
   r.resp_header["WWW-Authenticate"] = 
     'Basic realm="' .. realm .. '", charset="UTF-8"' 
   return 401    -- 401 Unauthorized 
 end 

 local authorization = r.req_header["Authorization"] 
 if (not authorization) then return unauthorized() end 

 local b64auth = string.match(authorization, "^Basic%s+([%w+/=]+)") 
 if (not b64auth) then return unauthorized() end 

 -- base64 decode into username:password string 
 local auth = lighty.c.b64dec(b64auth) 
 if (not auth) then return unauthorized() end 
 local user, pass = string.match(auth, "^([^:]+):(.*)$") 
 if (not user) then return unauthorized() end 

 -- check authentication 
 if (not check_user_pass(user, pass)) return unauthorized() end 

 -- check authorization 
 -- (could see if authenticated user is authorized to access requested URI) 

 -- authenticated and (optionally) authorized 
 r.req_env["REMOTE_USER"] = user 
 r.req_env["AUTH_TYPE"] = "Basic" 
 </pre> 


 h4. lua mod_dirlisting 

 <pre> 
 local r = lighty.r 
 local path = r.req_attr["physical.path"] 
 -- check that path ends in '/' 
 if (not path or not string.match(path, "/$")) then 
   return 0 
 end 
 -- check that path exists and is a directory 
 local st = lighty.c.stat(path) 
 if (not st or not st.is_dir) then 
   return 0 
 end 
 -- list filenames in directory 
 for name in lighty.c.readdir(path) do r.resp_body:add({name, "\n"}) end 
 r.resp_header["Content-Type"] = "text/plain" 
 return 200 
 </pre> 


 h4. lua mod_evhost 

 <pre> 
 local server_root = "/var/www/servers/" 
 local req_attr = lighty.r.req_attr 

 local host = req_attr["uri.authority"] 
 if (not host) then return 0 end 
 local shard = string.match(host, "(%w)[^.]*%.[^.%]]+$") 
 if (not shard) then return 0 end 

 local docroot = server_root .. shard .. "/" .. host 
 local st = lighty.c.stat(docroot) 
 if (not st or not st.is_dir) then return 0 end 

 req_attr["physical.doc_root"] = docroot 
 req_attr["physical.basedir"] = docroot 
 req_attr["physical.path"] = docroot .. req_attr["physical.rel-path"] 
 </pre> 


 h4. lua mod_expire 

 <pre> 
 local path = lighty.r.req_attr["physical.path"] 
 if (not path) then 
   return 0 
 end 
 local suffix = string.match(path, "(%.[^./]*)$") 
 if (suffix == ".html") then 
   lighty.r.resp_header["Cache-Control"] = "max-age=" .. (lighty.c.time()+300) 
   return 0 
 end 
 if (suffix == ".css" or suffix == ".js") then 
   lighty.r.resp_header["Cache-Control"] = "max-age=" .. (lighty.c.time()+3600) 
   return 0 
 end 
 if (suffix == ".jpg") then 
   lighty.r.resp_header["Cache-Control"] = "max-age=" .. (lighty.c.time()+86400) 
   return 0 
 end 
 </pre> 


 h4. lua mod_extforward 

 <pre> 
 local r = lighty.r 
 local req_header = r.req_header 
 local xffor = req_header["X-Forwarded-For"] 
 if (xffor) then 
   local req_attr = r.req_attr 
   req_attr["request.remote-addr"] = xffor 
   local xfport = req_header["X-Forwarded-Port"] 
   if (xfport) then req_attr["request.remote-port"] = xfport end 
   local xfproto = req_header["X-Forwarded-Proto"] 
   if (xfproto) then req_attr["uri.scheme"] = xfproto end 
 end 
 </pre> 


 h4. lua mod_flv_streaming 

 <pre> 
 $HTTP["url"] =~ "\.flv$" { 
   magnet.attract-physical-path-to = ("/path/to/flv-streaming.lua") 
 } 
 </pre> 
 <pre> 
 -- lua implementation of lighttpd mod_flv_streaming 
 -- 
 -- Aside: it is baffling why FLV streaming is not simply an HTTP Range request 
 -- 
 -- Potential (trivial) enhancements (exercise left to the reader): 
 -- - could call lighty.stat(lighty.env["physical.path"]) 
 --     - check that file exists 
 --     - check start/end offsets 

 local r = lighty.r 

 -- check that target file ends in ".flv" 
 -- (comment out; already checked in lighttpd.conf condition in sample conf) 
 --if (".flv" ~= string.sub(r.req_attr["physical.path"], -4)) then 
 --    return 0 
 --end 

 -- split the query-string and look for start and end offsets (0-based) 
 local qs = lighty.c.urldec_query(r.req_attr["query-string"]) 
 if (qs["start"] == nil) then 
   return 0 
 end 
 local start = tonumber(qs["start"]) 
 local len = qs["end"] ~= nil and (tonumber(qs["end"]) + 1 - start) or -1 
 if (start <= 0) then    -- let other modules handle request (e.g. mod_staticfile) 
   return 0 
 end 

 -- send FLV response 
 r.resp_header["Content-Type"] = "video/x-flv" 
 r.resp_body:set({ 
   "FLV\1\1\0\0\0\9\0\0\0\9",    -- FLV header 
   { filename = r.req_attr["physical.path"], offset = start, length = len } 
 }) 
 return 200 
 </pre> 


 h4. lua mod_indexfile 

 <pre> 
 local req_attr = lighty.r.req_attr 
 local path = req_attr["physical.path"] 
 -- check that path ends in '/' 
 if (not path or not string.match(path, "/$")) then 
   return 0 
 end 
 -- check for index.php then index.html 
 local indexfiles = { "index.php", "index.html" } 
 for _, file in ipairs(indexfiles) do 
   if (lighty.c.stat(path .. file) then 
     req_attr["physical.path"] = path .. file 
     req_attr["uri.path"] = req_attr["uri.path"] .. file 
     return 0 -- let mod_staticfile or other module handle the file 
   end 
 done 
 </pre> 


 h4. lua mod_redirect 

 <pre> 
 -- redirect http to https 
 if (lighty.r.req_attr["uri.scheme"] == "http") then 
   local r = lighty.r 
   r.resp_header["Location"] = "https://" 
                            .. r.req_attr["uri.authority"] 
                            .. r.req_attr["request.uri"] 
   return 302 
 end 
 </pre> 


 h4. lua mod_rewrite 

 <pre> 
 -- redirect if file does not exist (not file, not directory, not anything else) 
 -- (This script must be called from magnet.attract-physical-path-to hook) 
 if (not lighty.c.stat(lighty.r.req_attr["physical.path"])) then 
   local req_attr = lighty.r.req_attr 
   local query = req_attr["uri.query"] 
   req_attr["request.uri"] = "/index.php?path=" 
                          .. req_attr["uri.path-raw"] 
                          .. (query and ("&" .. query) or "") 
   return lighty.REQUEST_RESTART 
 end 
 </pre> 


 h4. lua mod_secdownload 

 <pre> 
 -- extract tokens from url-path 
 local req_attr = lighty.r.req_attr 
 local mac, protected_path, tsstr, rel_path = 
   string.match(req_attr["uri.path"], "^/download/([%w_-]+)(/(%x+)(/.*))$") 
 if (not mac) then return 0 end 
 local ts = tonumber(tsstr, 16) 
 if (not ts) then return 0 end 

 local li = lighty.c 

 -- validate timestamp 
 local timeout = 600 -- (10 mins) validity window 
 local t = li.time() 
 if (((t > ts) and (t - ts) or (ts - t)) > timeout) then 
   return 410     -- 410 Gone; URI will never be valid again 
 end 

 -- validate MAC 
 local secret = "12345-bad-secret"    -- you MUST customize this 
 local hash = li.b64urlenc(li.hmac("sha256", secret, protected_path)) 
 if (not li.digest_eq(hash, mac)) then 
   return 403     -- 403 Forbidden 
 end 

 -- remap to alternative filesystem path 
 local doc_root = "/var/www/servers/www.example.org/download" 
 req_attr["physical.doc_root"] = docroot 
 req_attr["physical.basedir"] = docroot 
 req_attr["physical.path"] = docroot .. rel_path 
 req_attr["physical.rel-path"] = rel_path 
 -- let mod_staticfile or other module handle the file 
 </pre> 


 h4. lua mod_setenv 

 <pre> 
 -- examples 
 local r = lighty.r 
 r.req_header["DNT"] = "1" 
 r.req_env["TMOUT"] = "3" 
 r.resp_header["Cache-Control"] = "max-age=0" 
 </pre> 


 h4. lua mod_simple_vhost 

 <pre> 
 local server_root = "/var/www/servers/" 
 local docroot = nil 
 local req_attr = lighty.r.req_attr 

 local host = string.match(req_attr["uri.authority"], "^(%w[^:/]*)") 
 if (host) then 
   docroot = server_root .. host 
   local st = lighty.c.stat(docroot) 
   if (not st or not st.is_dir) then 
     docroot = nil 
   end 
 end 

 if (not docroot) then -- set default docroot 
   docroot = server_root .. "default.example.com" 
 end 

 req_attr["physical.doc_root"] = docroot 
 req_attr["physical.basedir"] = docroot 
 req_attr["physical.path"] = docroot .. req_attr["physical.rel-path"] 
 </pre> 


 h4. lua mod_staticfile 

 <pre> 
 local r = lighty.r 
 local path = r.req_attr["physical.path"] 
 local st = lighty.c.stat(path) 
 if (st and st.is_file) then 
   r.resp_body:set({ { filename = path } }) 
   return 200 
 end 
 </pre> 


 h4. lua mod_userdir 

 <pre> 
 local req_attr = lighty.r.req_attr 
 local user, relpath = string.match(req_attr["uri.path"], "^/~([^/]+)(/.*)?$") 
 if (user) then 
   local basedir = "/u/" .. user .. "/web" 
   req_attr["physical.basedir"] = basedir 
   req_attr["physical.path"] = basedir .. relpath 
 end 
 </pre> 


 h4. lua mod_usertrack 

 <pre> 
 local cookies = lighty.c.cookie_tokens(lighty.r.req_header['Cookie']) 
 if (cookies["TRACKID"]) then return 0 end -- cookie already exists 

 -- create cookie 
 local li = lighty.c 
 local md = li.md("sha256", lighty.r.req_attr["uri.path"] 
                            .. "+" .. tostring(li.time()) .. tostring(li.rand())) 
 local usertrack_cookie = 
   "TRACKID=" .. md .. "; Path=/; Version=1; Domain=example.com; max-age=86400" 

 local resp_header = lighty.r.resp_header 
 local set_cookie = resp_header["Set-Cookie"] 
 if (set_cookie) set_cookie = set_cookie .. "\r\nSet-Cookie: " 
 resp_header["Set-Cookie"] = set_cookie .. usertrack_cookie 
 </pre>