X-Git-Url: https://git.enpas.org/?a=blobdiff_plain;f=plugins%2Fmod_http_files.lua;h=0c542714e711aea1f3e09e8dc5b37b1b1b89c4d0;hb=1fd4988df04d0b2735c7ffce8fdd96b75929598e;hp=311b3a4b950e504ebf9a3e2804f4cfb4792ae478;hpb=8e7a8bf90cae028a5d6a018356ce34f1328800cb;p=prosody.git diff --git a/plugins/mod_http_files.lua b/plugins/mod_http_files.lua index 311b3a4b..0c542714 100644 --- a/plugins/mod_http_files.lua +++ b/plugins/mod_http_files.lua @@ -1,7 +1,7 @@ -- Prosody IM -- Copyright (C) 2008-2010 Matthew Wild -- Copyright (C) 2008-2010 Waqas Hussain --- +-- -- This project is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- @@ -14,12 +14,13 @@ local os_date = os.date; local open = io.open; local stat = lfs.attributes; local build_path = require"socket.url".build_path; +local path_sep = package.config:sub(1,1); local base_path = module:get_option_string("http_files_dir", module:get_option_string("http_path")); local dir_indices = module:get_option("http_index_files", { "index.html", "index.htm" }); local directory_index = module:get_option_boolean("http_dir_listing"); -local mime_map = module:shared("mime").types; +local mime_map = module:shared("/*/http_files/mime").types; if not mime_map then mime_map = { html = "text/html", htm = "text/html", @@ -32,7 +33,7 @@ if not mime_map then jpeg = "image/jpeg", jpg = "image/jpeg", svg = "image/svg+xml", }; - module:shared("mime").types = mime_map; + module:shared("/*/http_files/mime").types = mime_map; local mime_types, err = open(module:get_option_string("mime_types_file", "/etc/mime.types"),"r"); if mime_types then @@ -48,17 +49,52 @@ if not mime_map then end end +local forbidden_chars_pattern = "[/%z]"; +if prosody.platform == "windows" then + forbidden_chars_pattern = "[/%z\001-\031\127\"*:<>?|]" +end + +local urldecode = require "util.http".urldecode; +function sanitize_path(path) + local out = {}; + + local c = 0; + for component in path:gmatch("([^/]+)") do + component = urldecode(component); + if component:find(forbidden_chars_pattern) then + return nil; + elseif component == ".." then + if c <= 0 then + return nil; + end + out[c] = nil; + c = c - 1; + elseif component ~= "." then + c = c + 1; + out[c] = component; + end + end + return "/"..table.concat(out, "/"); +end + local cache = setmetatable({}, { __mode = "kv" }); -- Let the garbage collector have it if it wants to. function serve(opts) + if type(opts) ~= "table" then -- assume path string + opts = { path = opts }; + end local base_path = opts.path; local dir_indices = opts.index_files or dir_indices; local directory_index = opts.directory_index; local function serve_file(event, path) local request, response = event.request, event.response; - local orig_path = request.path; - local full_path = base_path .. (path and "/"..path or ""); - local attr = stat(full_path); + path = sanitize_path(path); + if not path then + return 400; + end + local orig_path = sanitize_path(request.path); + local full_path = base_path .. (path and "/"..path or ""):gsub("/", path_sep); + local attr = stat(full_path:match("^.*[^\\/]")); -- Strip trailing path separator because Windows if not attr then return 404; end @@ -114,7 +150,7 @@ function serve(opts) module:log("debug", "Could not open or read %s. Error was %s", full_path, err); return 403; end - local ext = orig_path:match("%.([^./]+)$"); + local ext = full_path:match("%.([^./]+)$"); local content_type = ext and mime_map[ext]; cache[orig_path] = { data = data; content_type = content_type; etag = etag }; response_headers.content_type = content_type; @@ -126,6 +162,14 @@ function serve(opts) return serve_file; end +function wrap_route(routes) + for route,handler in pairs(routes) do + if type(handler) ~= "function" then + routes[route] = serve(handler); + end + end + return routes; +end if base_path then module:provides("http", {