----------------------------------------------------------------------------- -- Xavante File handler -- -- Authors: Javier Guerra and Andre Carregal -- Copyright (c) 2004-2007 Kepler Project -- -- $Id: filehandler.lua,v 1.26 2009/08/11 01:48:07 mascarenhas Exp $ ---------------------------------------------------------------------------- local lfs = require "lfs" local url = require "socket.url" local mime = require "xavante.mime" local enc = require "xavante.encoding" local xavante = require "xavante" local httpd = require "xavante.httpd" xavante.mimetypes = xavante.mimetypes or {} xavante.encodings = xavante.encodings or {} -- gets the mimetype from the filename's extension local function mimefrompath (path) local _,_,exten = string.find (path, "%.([^.]*)$") if exten then return xavante.mimetypes [exten] else return nil end end -- gets the encoding from the filename's extension local function encodingfrompath (path) local _,_,exten = string.find (path, "%.([^.]*)$") if exten then return xavante.encodings [exten] else return nil end end -- on partial requests seeks the file to -- the start of the requested range and returns -- the number of bytes requested. -- on full requests returns nil local function getrange (req, f) local range = req.headers["range"] if not range then return nil end local s,e, r_A, r_B = string.find (range, "(%d*)%s*-%s*(%d*)") if s and e then r_A = tonumber (r_A) r_B = tonumber (r_B) if r_A then f:seek ("set", r_A) if r_B then return r_B + 1 - r_A end else if r_B then f:seek ("end", - r_B) end end end return nil end -- sends data from the open file f -- to the response object res -- sends only numbytes, or until the end of f -- if numbytes is nil local function sendfile (f, res, numbytes) local block local whole = not numbytes local left = numbytes local blocksize = 8192 if not whole then blocksize = math.min (blocksize, left) end while whole or left > 0 do block = f:read (blocksize) if not block then return end if not whole then left = left - string.len (block) blocksize = math.min (blocksize, left) end res:send_data (block) end end local function in_base(path) local l = 0 if path:sub(1, 1) ~= "/" then path = "/" .. path end for dir in path:gmatch("/([^/]+)") do if dir == ".." then l = l - 1 elseif dir ~= "." then l = l + 1 end if l < 0 then return false end end return true end -- main handler local function filehandler (req, res, baseDir) if req.cmd_mth ~= "GET" and req.cmd_mth ~= "HEAD" then return httpd.err_405 (req, res) end if not in_base(req.relpath) then return httpd.err_403 (req, res) end local path = baseDir .."/".. req.relpath res.headers ["Content-Type"] = mimefrompath (path) res.headers ["Content-Encoding"] = encodingfrompath (path) local attr = lfs.attributes (path) if not attr then return httpd.err_404 (req, res) end assert (type(attr) == "table") if attr.mode == "directory" then req.parsed_url.path = req.parsed_url.path .. "/" res.statusline = "HTTP/1.1 301 Moved Permanently" res.headers["Location"] = url.build (req.parsed_url) res.content = "redirect" return res end res.headers["Content-Length"] = attr.size local f = io.open (path, "rb") if not f then return httpd.err_404 (req, res) end res.headers["last-modified"] = os.date ("!%a, %d %b %Y %H:%M:%S GMT", attr.modification) local lms = req.headers["if-modified-since"] or 0 local lm = res.headers["last-modified"] or 1 if lms == lm then res.headers["Content-Length"] = 0 res.statusline = "HTTP/1.1 304 Not Modified" res.content = "" res.chunked = false res:send_headers() f:close() return res end if req.cmd_mth == "GET" then local range_len = getrange (req, f) if range_len then res.statusline = "HTTP/1.1 206 Partial Content" res.headers["Content-Length"] = range_len end sendfile (f, res, range_len) else res.content = "" res:send_headers () end f:close () return res end return function (baseDir) if type(baseDir) == "table" then baseDir = baseDir.baseDir end return function (req, res) return filehandler (req, res, baseDir) end end