Comparison

net/http/files.lua @ 10411:db2a06b9ff98

Merge 0.11->trunk
author Kim Alvefur <zash@zash.se>
date Sat, 16 Nov 2019 16:52:31 +0100
parent 9463:plugins/mod_http_files.lua@f7530c846f98
parent 10327:plugins/mod_http_files.lua@34f7a0e8fa59
child 10434:8f709577fe8e
comparison
equal deleted inserted replaced
10410:659b577f280c 10411:db2a06b9ff98
1 -- Prosody IM
2 -- Copyright (C) 2008-2010 Matthew Wild
3 -- Copyright (C) 2008-2010 Waqas Hussain
4 --
5 -- This project is MIT/X11 licensed. Please see the
6 -- COPYING file in the source package for more information.
7 --
8
9 local server = require"net.http.server";
10 local lfs = require "lfs";
11 local new_cache = require "util.cache".new;
12 local log = require "util.logger".init("net.http.files");
13
14 local os_date = os.date;
15 local open = io.open;
16 local stat = lfs.attributes;
17 local build_path = require"socket.url".build_path;
18 local path_sep = package.config:sub(1,1);
19
20
21 local forbidden_chars_pattern = "[/%z]";
22 if package.config:sub(1,1) == "\\" then
23 forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]"
24 end
25
26 local urldecode = require "util.http".urldecode;
27 local function sanitize_path(path) --> util.paths or util.http?
28 if not path then return end
29 local out = {};
30
31 local c = 0;
32 for component in path:gmatch("([^/]+)") do
33 component = urldecode(component);
34 if component:find(forbidden_chars_pattern) then
35 return nil;
36 elseif component == ".." then
37 if c <= 0 then
38 return nil;
39 end
40 out[c] = nil;
41 c = c - 1;
42 elseif component ~= "." then
43 c = c + 1;
44 out[c] = component;
45 end
46 end
47 if path:sub(-1,-1) == "/" then
48 out[c+1] = "";
49 end
50 return "/"..table.concat(out, "/");
51 end
52
53 local function serve(opts)
54 if type(opts) ~= "table" then -- assume path string
55 opts = { path = opts };
56 end
57 local mime_map = opts.mime_map or { html = "text/html" };
58 local cache = new_cache(opts.cache_size or 256);
59 local cache_max_file_size = tonumber(opts.cache_max_file_size) or 1024
60 -- luacheck: ignore 431
61 local base_path = opts.path;
62 local dir_indices = opts.index_files or { "index.html", "index.htm" };
63 local directory_index = opts.directory_index;
64 local function serve_file(event, path)
65 local request, response = event.request, event.response;
66 local sanitized_path = sanitize_path(path);
67 if path and not sanitized_path then
68 return 400;
69 end
70 path = sanitized_path;
71 local orig_path = sanitize_path(request.path);
72 local full_path = base_path .. (path or ""):gsub("/", path_sep);
73 local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows
74 if not attr then
75 return 404;
76 end
77
78 local request_headers, response_headers = request.headers, response.headers;
79
80 local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification);
81 response_headers.last_modified = last_modified;
82
83 local etag = ('"%02x-%x-%x-%x"'):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0);
84 response_headers.etag = etag;
85
86 local if_none_match = request_headers.if_none_match
87 local if_modified_since = request_headers.if_modified_since;
88 if etag == if_none_match
89 or (not if_none_match and last_modified == if_modified_since) then
90 return 304;
91 end
92
93 local data = cache:get(orig_path);
94 if data and data.etag == etag then
95 response_headers.content_type = data.content_type;
96 data = data.data;
97 cache:set(orig_path, data);
98 elseif attr.mode == "directory" and path then
99 if full_path:sub(-1) ~= "/" then
100 local dir_path = { is_absolute = true, is_directory = true };
101 for dir in orig_path:gmatch("[^/]+") do dir_path[#dir_path+1]=dir; end
102 response_headers.location = build_path(dir_path);
103 return 301;
104 end
105 for i=1,#dir_indices do
106 if stat(full_path..dir_indices[i], "mode") == "file" then
107 return serve_file(event, path..dir_indices[i]);
108 end
109 end
110
111 if directory_index then
112 data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path });
113 end
114 if not data then
115 return 403;
116 end
117 cache:set(orig_path, { data = data, content_type = mime_map.html; etag = etag; });
118 response_headers.content_type = mime_map.html;
119
120 else
121 local f, err = open(full_path, "rb");
122 if not f then
123 log("debug", "Could not open %s. Error was %s", full_path, err);
124 return 403;
125 end
126 local ext = full_path:match("%.([^./]+)$");
127 local content_type = ext and mime_map[ext];
128 response_headers.content_type = content_type;
129 if attr.size > cache_max_file_size then
130 response_headers.content_length = ("%d"):format(attr.size);
131 log("debug", "%d > cache_max_file_size", attr.size);
132 return response:send_file(f);
133 else
134 data = f:read("*a");
135 f:close();
136 end
137 cache:set(orig_path, { data = data; content_type = content_type; etag = etag });
138 end
139
140 return response:send(data);
141 end
142
143 return serve_file;
144 end
145
146 return {
147 serve = serve;
148 }
149