File

plugins/mod_http_files.lua @ 5524:e9090966c803

util.prosodyctl: Initialize storagemanager on the host before initializing usermanager. This fixes brokenness when the auth provider opens the store on load (as they all do since eeea0eb2602a) (thanks nulani)
author Matthew Wild <mwild1@gmail.com>
date Sat, 27 Apr 2013 13:11:03 +0100
parent 5269:7bc52402966d
child 5688:345761f0c2be
child 5716:8a0465de172e
line wrap: on
line source

-- 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.
--

module:depends("http");
local server = require"net.http.server";
local lfs = require "lfs";

local os_date = os.date;
local open = io.open;
local stat = lfs.attributes;
local build_path = require"socket.url".build_path;

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;
if not mime_map then
	mime_map = {
		html = "text/html", htm = "text/html",
		xml = "application/xml",
		txt = "text/plain",
		css = "text/css",
		js = "application/javascript",
		png = "image/png",
		gif = "image/gif",
		jpeg = "image/jpeg", jpg = "image/jpeg",
		svg = "image/svg+xml",
	};
	module:shared("mime").types = mime_map;

	local mime_types, err = open(module:get_option_string("mime_types_file", "/etc/mime.types"),"r");
	if mime_types then
		local mime_data = mime_types:read("*a");
		mime_types:close();
		setmetatable(mime_map, {
			__index = function(t, ext)
				local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream";
				t[ext] = typ;
				return typ;
			end
		});
	end
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);
		if not attr then
			return 404;
		end

		local request_headers, response_headers = request.headers, response.headers;

		local last_modified = os_date('!%a, %d %b %Y %H:%M:%S GMT', attr.modification);
		response_headers.last_modified = last_modified;

		local etag = ("%02x-%x-%x-%x"):format(attr.dev or 0, attr.ino or 0, attr.size or 0, attr.modification or 0);
		response_headers.etag = etag;

		local if_none_match = request_headers.if_none_match
		local if_modified_since = request_headers.if_modified_since;
		if etag == if_none_match
		or (not if_none_match and last_modified == if_modified_since) then
			return 304;
		end

		local data = cache[orig_path];
		if data and data.etag == etag then
			response_headers.content_type = data.content_type;
			data = data.data;
		elseif attr.mode == "directory" and path then
			if full_path:sub(-1) ~= "/" then
				local path = { is_absolute = true, is_directory = true };
				for dir in orig_path:gmatch("[^/]+") do path[#path+1]=dir; end
				response_headers.location = build_path(path);
				return 301;
			end
			for i=1,#dir_indices do
				if stat(full_path..dir_indices[i], "mode") == "file" then
					return serve_file(event, path..dir_indices[i]);
				end
			end

			if directory_index then
				data = server._events.fire_event("directory-index", { path = request.path, full_path = full_path });
			end
			if not data then
				return 403;
			end
			cache[orig_path] = { data = data, content_type = mime_map.html; etag = etag; };
			response_headers.content_type = mime_map.html;

		else
			local f, err = open(full_path, "rb");
			if f then
				data, err = f:read("*a");
				f:close();
			end
			if not data then
				module:log("debug", "Could not open or read %s. Error was %s", full_path, err);
				return 403;
			end
			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;
		end

		return response:send(data);
	end

	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", {
		route = {
			["GET /*"] = serve {
				path = base_path;
				directory_index = directory_index;
			}
		};
	});
else
	module:log("debug", "http_files_dir not set, assuming use by some other module");
end