Mercurial > prosody-hg
comparison plugins/mod_http_files.lua @ 5239:cf4954f1c35a
Merge 0.9->trunk
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Tue, 11 Dec 2012 23:41:02 +0100 |
| parents | 0cc0359d8c39 |
| children | 9257f4c47ffa |
comparison
equal
deleted
inserted
replaced
| 5231:4f9135e6c2f9 | 5239:cf4954f1c35a |
|---|---|
| 7 -- | 7 -- |
| 8 | 8 |
| 9 module:depends("http"); | 9 module:depends("http"); |
| 10 local lfs = require "lfs"; | 10 local lfs = require "lfs"; |
| 11 | 11 |
| 12 local os_date = os.date; | |
| 12 local open = io.open; | 13 local open = io.open; |
| 13 local stat = lfs.attributes; | 14 local stat = lfs.attributes; |
| 14 | 15 |
| 15 local http_base = module:get_option_string("http_files_dir", module:get_option_string("http_path", "www_files")); | 16 local http_base = module:get_option_string("http_files_dir", module:get_option_string("http_path", "www_files")); |
| 17 local dir_indices = module:get_option("http_files_index", { "index.html", "index.htm" }); | |
| 18 local show_file_list = module:get_option_boolean("http_files_show_list"); | |
| 16 | 19 |
| 17 -- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?) | 20 local mime_map = module:shared("mime").types; |
| 18 local mime_map = { | 21 if not mime_map then |
| 19 html = "text/html"; | 22 mime_map = { |
| 20 htm = "text/html"; | 23 html = "text/html", htm = "text/html", |
| 21 xml = "text/xml"; | 24 xml = "application/xml", |
| 22 xsl = "text/xml"; | 25 txt = "text/plain", |
| 23 txt = "text/plain; charset=utf-8"; | 26 css = "text/css", |
| 24 js = "text/javascript"; | 27 js = "application/javascript", |
| 25 css = "text/css"; | 28 png = "image/png", |
| 26 }; | 29 gif = "image/gif", |
| 30 jpeg = "image/jpeg", jpg = "image/jpeg", | |
| 31 svg = "image/svg+xml", | |
| 32 }; | |
| 33 module:shared("mime").types = mime_map; | |
| 34 | |
| 35 local mime_types, err = open(module:get_option_string("mime_types_file", "/etc/mime.types"),"r"); | |
| 36 if mime_types then | |
| 37 local mime_data = mime_types:read("*a"); | |
| 38 mime_types:close(); | |
| 39 setmetatable(mime_map, { | |
| 40 __index = function(t, ext) | |
| 41 local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; | |
| 42 t[ext] = typ; | |
| 43 return typ; | |
| 44 end | |
| 45 }); | |
| 46 end | |
| 47 end | |
| 48 | |
| 49 local cache = setmetatable({}, { __mode = "kv" }); -- Let the garbage collector have it if it wants to. | |
| 27 | 50 |
| 28 function serve_file(event, path) | 51 function serve_file(event, path) |
| 29 local response = event.response; | 52 local request, response = event.request, event.response; |
| 30 local orig_path = event.request.path; | 53 local orig_path = request.path; |
| 31 local full_path = http_base.."/"..path; | 54 local full_path = http_base.."/"..path; |
| 32 if stat(full_path, "mode") == "directory" then | 55 local attr = stat(full_path); |
| 56 if not attr then | |
| 57 return 404; | |
| 58 end | |
| 59 | |
| 60 response.headers.last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); | |
| 61 | |
| 62 local tag = ("%02x-%x-%x-%x"):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); | |
| 63 response.headers.etag = tag; | |
| 64 if tag == request.headers.if_none_match then | |
| 65 return 304; | |
| 66 end | |
| 67 | |
| 68 local data = cache[path]; | |
| 69 if data then | |
| 70 response.headers.content_type = data.content_type; | |
| 71 data = data.data; | |
| 72 elseif attr.mode == "directory" then | |
| 33 if full_path:sub(-1) ~= "/" then | 73 if full_path:sub(-1) ~= "/" then |
| 34 response.headers.location = orig_path.."/"; | 74 response.headers.location = orig_path.."/"; |
| 35 return 301; | 75 return 301; |
| 36 end | 76 end |
| 37 if stat(full_path.."index.html", "mode") == "file" then | 77 for i=1,#dir_indices do |
| 38 return serve_file(event, path.."index.html"); | 78 if stat(full_path..dir_indices[i], "mode") == "file" then |
| 79 return serve_file(event, path..dir_indices[i]); | |
| 80 end | |
| 39 end | 81 end |
| 40 | 82 |
| 41 -- TODO File listing | 83 if not show_file_list then |
| 42 return 403; | 84 return 403; |
| 85 else | |
| 86 local html = require"util.stanza".stanza("html") | |
| 87 :tag("head"):tag("title"):text(path):up() | |
| 88 :tag("meta", { charset="utf-8" }):up() | |
| 89 :up() | |
| 90 :tag("body"):tag("h1"):text(path):up() | |
| 91 :tag("ul"); | |
| 92 for file in lfs.dir(full_path) do | |
| 93 if file:sub(1,1) ~= "." then | |
| 94 local attr = stat(full_path..file) or {}; | |
| 95 html:tag("li", { class = attr.mode }) | |
| 96 :tag("a", { href = file }):text(file) | |
| 97 :up():up(); | |
| 98 end | |
| 99 end | |
| 100 data = "<!DOCTYPE html>\n"..tostring(html); | |
| 101 cache[path] = { data = html, content_type = mime_map.html; hits = 0 }; | |
| 102 response.headers.content_type = mime_map.html; | |
| 103 end | |
| 104 | |
| 105 else | |
| 106 local f = open(full_path, "rb"); | |
| 107 data = f and f:read("*a"); | |
| 108 f:close(); | |
| 109 if not data then | |
| 110 return 403; | |
| 111 end | |
| 112 local ext = path:match("%.([^.]*)$"); | |
| 113 local content_type = mime_map[ext]; | |
| 114 cache[path] = { data = data; content_type = content_type; }; | |
| 115 response.headers.content_type = content_type; | |
| 43 end | 116 end |
| 44 local f, err = open(full_path, "rb"); | 117 |
| 45 if not f then | |
| 46 module:log("warn", "Failed to open file: %s", err); | |
| 47 return 404; | |
| 48 end | |
| 49 local data = f:read("*a"); | |
| 50 f:close(); | |
| 51 if not data then | |
| 52 return 403; | |
| 53 end | |
| 54 local ext = path:match("%.([^.]*)$"); | |
| 55 response.headers.content_type = mime_map[ext]; -- Content-Type should be nil when not known | |
| 56 return response:send(data); | 118 return response:send(data); |
| 57 end | 119 end |
| 58 | 120 |
| 59 module:provides("http", { | 121 module:provides("http", { |
| 60 route = { | 122 route = { |
