Software / code / prosody
Comparison
plugins/mod_http_files.lua @ 5262:4e58fde55594
mod_http_files: Export function can be used by other modules to serve files. Don't serve files by default unless http_files_dir is set
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Fri, 21 Dec 2012 17:54:43 +0100 |
| parent | 5261:b14f02671439 |
| child | 5263:736e7ec8cd2e |
comparison
equal
deleted
inserted
replaced
| 5261:b14f02671439 | 5262:4e58fde55594 |
|---|---|
| 12 local os_date = os.date; | 12 local os_date = os.date; |
| 13 local open = io.open; | 13 local open = io.open; |
| 14 local stat = lfs.attributes; | 14 local stat = lfs.attributes; |
| 15 local build_path = require"socket.url".build_path; | 15 local build_path = require"socket.url".build_path; |
| 16 | 16 |
| 17 local base_path = module:get_option_string("http_files_dir", module:get_option_string("http_path", "www_files")); | 17 local base_path = module:get_option_string("http_files_dir", module:get_option_string("http_path")); |
| 18 local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" }); | 18 local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" }); |
| 19 local directory_index = module:get_option_boolean("http_dir_listing"); | 19 local directory_index = module:get_option_boolean("http_dir_listing"); |
| 20 | 20 |
| 21 local mime_map = module:shared("mime").types; | 21 local mime_map = module:shared("mime").types; |
| 22 if not mime_map then | 22 if not mime_map then |
| 47 end | 47 end |
| 48 end | 48 end |
| 49 | 49 |
| 50 local cache = setmetatable({}, { __mode = "kv" }); -- Let the garbage collector have it if it wants to. | 50 local cache = setmetatable({}, { __mode = "kv" }); -- Let the garbage collector have it if it wants to. |
| 51 | 51 |
| 52 function serve_file(event, path) | 52 function serve(opts) |
| 53 local request, response = event.request, event.response; | 53 local base_path = opts.path; |
| 54 local orig_path = request.path; | 54 local dir_indices = opts.index_files or dir_indices; |
| 55 local full_path = base_path.."/"..path; | 55 local directory_index = opts.directory_index; |
| 56 local attr = stat(full_path); | 56 local function serve_file(event, path) |
| 57 if not attr then | 57 local request, response = event.request, event.response; |
| 58 return 404; | 58 local orig_path = request.path; |
| 59 local full_path = base_path .. (path and "/"..path or ""); | |
| 60 local attr = stat(full_path); | |
| 61 if not attr then | |
| 62 return 404; | |
| 63 end | |
| 64 | |
| 65 local request_headers, response_headers = request.headers, response.headers; | |
| 66 | |
| 67 local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); | |
| 68 response_headers.last_modified = last_modified; | |
| 69 | |
| 70 local etag = ("%02x-%x-%x-%x"):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); | |
| 71 response_headers.etag = etag; | |
| 72 | |
| 73 local if_none_match = request_headers.if_none_match | |
| 74 local if_modified_since = request_headers.if_modified_since; | |
| 75 if etag == if_none_match | |
| 76 or (not if_none_match and last_modified == if_modified_since) then | |
| 77 return 304; | |
| 78 end | |
| 79 | |
| 80 local data = cache[path]; | |
| 81 if data and data.etag == etag then | |
| 82 response_headers.content_type = data.content_type; | |
| 83 data = data.data; | |
| 84 elseif attr.mode == "directory" then | |
| 85 if full_path:sub(-1) ~= "/" then | |
| 86 local path = { is_absolute = true, is_directory = true }; | |
| 87 for dir in orig_path:gmatch("[^/]+") do path[#path+1]=dir; end | |
| 88 response_headers.location = build_path(path); | |
| 89 return 301; | |
| 90 end | |
| 91 for i=1,#dir_indices do | |
| 92 if stat(full_path..dir_indices[i], "mode") == "file" then | |
| 93 return serve_file(event, path..dir_indices[i]); | |
| 94 end | |
| 95 end | |
| 96 | |
| 97 if not directory_index then | |
| 98 return 403; | |
| 99 else | |
| 100 local html = require"util.stanza".stanza("html") | |
| 101 :tag("head"):tag("title"):text(path):up() | |
| 102 :tag("meta", { charset="utf-8" }):up() | |
| 103 :up() | |
| 104 :tag("body"):tag("h1"):text(path):up() | |
| 105 :tag("ul"); | |
| 106 for file in lfs.dir(full_path) do | |
| 107 if file:sub(1,1) ~= "." then | |
| 108 local attr = stat(full_path..file) or {}; | |
| 109 html:tag("li", { class = attr.mode }) | |
| 110 :tag("a", { href = file }):text(file) | |
| 111 :up():up(); | |
| 112 end | |
| 113 end | |
| 114 data = "<!DOCTYPE html>\n"..tostring(html); | |
| 115 cache[path] = { data = data, content_type = mime_map.html; etag = etag; }; | |
| 116 response_headers.content_type = mime_map.html; | |
| 117 end | |
| 118 | |
| 119 else | |
| 120 local f, err = open(full_path, "rb"); | |
| 121 if f then | |
| 122 data, err = f:read("*a"); | |
| 123 f:close(); | |
| 124 end | |
| 125 if not data then | |
| 126 module:log("debug", "Could not open or read %s. Error was %s", full_path, err); | |
| 127 return 403; | |
| 128 end | |
| 129 local ext = path:match("%.([^./]+)$"); | |
| 130 local content_type = ext and mime_map[ext]; | |
| 131 cache[path] = { data = data; content_type = content_type; etag = etag }; | |
| 132 response_headers.content_type = content_type; | |
| 133 end | |
| 134 | |
| 135 return response:send(data); | |
| 59 end | 136 end |
| 60 | 137 |
| 61 local request_headers, response_headers = request.headers, response.headers; | 138 return serve_file; |
| 62 | |
| 63 local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification); | |
| 64 response_headers.last_modified = last_modified; | |
| 65 | |
| 66 local etag = ("%02x-%x-%x-%x"):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0); | |
| 67 response_headers.etag = etag; | |
| 68 | |
| 69 local if_none_match = request_headers.if_none_match | |
| 70 local if_modified_since = request_headers.if_modified_since; | |
| 71 if etag == if_none_match | |
| 72 or (not if_none_match and last_modified == if_modified_since) then | |
| 73 return 304; | |
| 74 end | |
| 75 | |
| 76 local data = cache[path]; | |
| 77 if data and data.etag == etag then | |
| 78 response_headers.content_type = data.content_type; | |
| 79 data = data.data; | |
| 80 elseif attr.mode == "directory" then | |
| 81 if full_path:sub(-1) ~= "/" then | |
| 82 local path = { is_absolute = true, is_directory = true }; | |
| 83 for dir in orig_path:gmatch("[^/]+") do path[#path+1]=dir; end | |
| 84 response_headers.location = build_path(path); | |
| 85 return 301; | |
| 86 end | |
| 87 for i=1,#dir_indices do | |
| 88 if stat(full_path..dir_indices[i], "mode") == "file" then | |
| 89 return serve_file(event, path..dir_indices[i]); | |
| 90 end | |
| 91 end | |
| 92 | |
| 93 if not directory_index then | |
| 94 return 403; | |
| 95 else | |
| 96 local html = require"util.stanza".stanza("html") | |
| 97 :tag("head"):tag("title"):text(path):up() | |
| 98 :tag("meta", { charset="utf-8" }):up() | |
| 99 :up() | |
| 100 :tag("body"):tag("h1"):text(path):up() | |
| 101 :tag("ul"); | |
| 102 for file in lfs.dir(full_path) do | |
| 103 if file:sub(1,1) ~= "." then | |
| 104 local attr = stat(full_path..file) or {}; | |
| 105 html:tag("li", { class = attr.mode }) | |
| 106 :tag("a", { href = file }):text(file) | |
| 107 :up():up(); | |
| 108 end | |
| 109 end | |
| 110 data = "<!DOCTYPE html>\n"..tostring(html); | |
| 111 cache[path] = { data = data, content_type = mime_map.html; etag = etag; }; | |
| 112 response_headers.content_type = mime_map.html; | |
| 113 end | |
| 114 | |
| 115 else | |
| 116 local f, err = open(full_path, "rb"); | |
| 117 if f then | |
| 118 data, err = f:read("*a"); | |
| 119 f:close(); | |
| 120 end | |
| 121 if not data then | |
| 122 module:log("debug", "Could not open or read %s. Error was %s", full_path, err); | |
| 123 return 403; | |
| 124 end | |
| 125 local ext = path:match("%.([^./]+)$"); | |
| 126 local content_type = ext and mime_map[ext]; | |
| 127 cache[path] = { data = data; content_type = content_type; etag = etag }; | |
| 128 response_headers.content_type = content_type; | |
| 129 end | |
| 130 | |
| 131 return response:send(data); | |
| 132 end | 139 end |
| 133 | 140 |
| 134 module:provides("http", { | |
| 135 route = { | |
| 136 ["GET /*"] = serve_file; | |
| 137 }; | |
| 138 }); | |
| 139 | 141 |
| 142 if base_path then | |
| 143 module:provides("http", { | |
| 144 route = { | |
| 145 ["GET /*"] = serve { | |
| 146 path = base_path; | |
| 147 directory_index = directory_index; | |
| 148 } | |
| 149 }; | |
| 150 }); | |
| 151 else | |
| 152 module:log("debug", "http_files_dir not set, assuming use by some other module"); | |
| 153 end | |
| 154 |