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