Comparison

plugins/mod_http_files.lua @ 9950:afc48785f738

mod_http_files: Use net.http.files
author Kim Alvefur <zash@zash.se>
date Fri, 05 Apr 2019 17:12:19 +0200
parent 9463:f7530c846f98
child 9951:f1594893998f
comparison
equal deleted inserted replaced
9949:a39f3681d685 9950:afc48785f738
5 -- This project is MIT/X11 licensed. Please see the 5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information. 6 -- COPYING file in the source package for more information.
7 -- 7 --
8 8
9 module:depends("http"); 9 module:depends("http");
10 local server = require"net.http.server";
11 local lfs = require "lfs";
12 10
13 local os_date = os.date;
14 local open = io.open; 11 local open = io.open;
15 local stat = lfs.attributes; 12 local fileserver = require"net.http.files";
16 local build_path = require"socket.url".build_path;
17 local path_sep = package.config:sub(1,1);
18 13
19 local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path")); 14 local base_path = module:get_option_path("http_files_dir", module:get_option_path("http_path"));
20 local cache_size = module:get_option_number("http_files_cache_size", 128); 15 local cache_size = module:get_option_number("http_files_cache_size", 128);
21 local cache_max_file_size = module:get_option_number("http_files_cache_max_file_size", 4096); 16 local cache_max_file_size = module:get_option_number("http_files_cache_max_file_size", 4096);
22 local dir_indices = module:get_option_array("http_index_files", { "index.html", "index.htm" }); 17 local dir_indices = module:get_option_array("http_index_files", { "index.html", "index.htm" });
49 end 44 end
50 }); 45 });
51 end 46 end
52 end 47 end
53 48
54 local forbidden_chars_pattern = "[/%z]"; 49 -- COMPAT -- TODO deprecate
55 if prosody.platform == "windows" then
56 forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]"
57 end
58
59 local urldecode = require "util.http".urldecode;
60 function sanitize_path(path)
61 if not path then return end
62 local out = {};
63
64 local c = 0;
65 for component in path:gmatch("([^/]+)") do
66 component = urldecode(component);
67 if component:find(forbidden_chars_pattern) then
68 return nil;
69 elseif component == ".." then
70 if c <= 0 then
71 return nil;
72 end
73 out[c] = nil;
74 c = c - 1;
75 elseif component ~= "." then
76 c = c + 1;
77 out[c] = component;
78 end
79 end
80 if path:sub(-1,-1) == "/" then
81 out[c+1] = "";
82 end
83 return "/"..table.concat(out, "/");
84 end
85
86 local cache = require "util.cache".new(cache_size);
87
88 function serve(opts) 50 function serve(opts)
89 if type(opts) ~= "table" then -- assume path string 51 if type(opts) ~= "table" then -- assume path string
90 opts = { path = opts }; 52 opts = { path = opts };
91 end 53 end
92 -- luacheck: ignore 431 54 if opts.directory_index == nil then
93 local base_path = opts.path; 55 opts.directory_index = directory_index;
94 local dir_indices = opts.index_files or dir_indices;
95 local directory_index = opts.directory_index;
96 local function serve_file(event, path)
97 local request, response = event.request, event.response;
98 local sanitized_path = sanitize_path(path);
99 if path and not sanitized_path then
100 return 400;
101 end
102 path = sanitized_path;
103 local orig_path = sanitize_path(request.path);
104 local full_path = base_path .. (path or ""):gsub("/", path_sep);
105 local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows
106 if not attr then
107 return 404;
108 end
109
110 local request_headers, response_headers = request.headers, response.headers;
111
112 local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification);
113 response_headers.last_modified = last_modified;
114
115 local etag = ('"%02x-%x-%x-%x"'):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0);
116 response_headers.etag = etag;
117
118 local if_none_match = request_headers.if_none_match
119 local if_modified_since = request_headers.if_modified_since;
120 if etag == if_none_match
121 or (not if_none_match and last_modified == if_modified_since) then
122 return 304;
123 end
124
125 local data = cache:get(orig_path);
126 if data and data.etag == etag then
127 response_headers.content_type = data.content_type;
128 data = data.data;
129 elseif attr.mode == "directory" and path then
130 if full_path:sub(-1) ~= "/" then
131 local dir_path = { is_absolute = true, is_directory = true };
132 for dir in orig_path:gmatch("[^/]+") do dir_path[#dir_path+1]=dir; end
133 response_headers.location = build_path(dir_path);
134 return 301;
135 end
136 for i=1,#dir_indices do
137 if stat(full_path..dir_indices[i], "mode") == "file" then
138 return serve_file(event, path..dir_indices[i]);
139 end
140 end
141
142 if directory_index then
143 data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path });
144 end
145 if not data then
146 return 403;
147 end
148 cache:set(orig_path, { data = data, content_type = mime_map.html; etag = etag; });
149 response_headers.content_type = mime_map.html;
150
151 else
152 local f, err = open(full_path, "rb");
153 if not f then
154 module:log("debug", "Could not open %s. Error was %s", full_path, err);
155 return 403;
156 end
157 local ext = full_path:match("%.([^./]+)$");
158 local content_type = ext and mime_map[ext];
159 response_headers.content_type = content_type;
160 if attr.size > cache_max_file_size then
161 response_headers.content_length = attr.size;
162 module:log("debug", "%d > cache_max_file_size", attr.size);
163 return response:send_file(f);
164 else
165 data = f:read("*a");
166 f:close();
167 end
168 cache:set(orig_path, { data = data; content_type = content_type; etag = etag });
169 end
170
171 return response:send(data);
172 end 56 end
173 57 if opts.mime_map == nil then
174 return serve_file; 58 opts.mime_map = mime_map;
59 end
60 if opts.cache_size == nil then
61 opts.cache_size = cache_size;
62 end
63 if opts.cache_max_file_size == nil then
64 opts.cache_max_file_size = cache_max_file_size;
65 end
66 if opts.index_files == nil then
67 opts.index_files = dir_indices;
68 end
69 -- TODO Crank up to warning
70 module:log("debug", "Use of mod_http_files.serve() should be updated to use net.http.files");
71 return fileserver.serve(opts);
175 end 72 end
176 73
177 function wrap_route(routes) 74 function wrap_route(routes)
75 module:log("debug", "Use of mod_http_files.wrap_route() should be updated to use net.http.files");
178 for route,handler in pairs(routes) do 76 for route,handler in pairs(routes) do
179 if type(handler) ~= "function" then 77 if type(handler) ~= "function" then
180 routes[route] = serve(handler); 78 routes[route] = fileserver.serve(handler);
181 end 79 end
182 end 80 end
183 return routes; 81 return routes;
184 end 82 end
185 83
186 if base_path then 84 module:provides("http", {
187 module:provides("http", { 85 route = {
188 route = { 86 ["GET /*"] = fileserver.serve({
189 ["GET /*"] = serve { 87 path = base_path;
190 path = base_path; 88 directory_index = directory_index;
191 directory_index = directory_index; 89 mime_map = mime_map;
192 } 90 cache_size = cache_size;
193 }; 91 cache_max_file_size = cache_max_file_size;
194 }); 92 index_files = dir_indices;
195 else 93 });
196 module:log("debug", "http_files_dir not set, assuming use by some other module"); 94 };
197 end 95 });
198