| 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 | |
| 10 | local httpserver = require "net.httpserver"; |
| 11 | local lfs = require "lfs"; |
| 12 | |
| 13 | local open = io.open; |
| 14 | local t_concat = table.concat; |
| 15 | local stat = lfs.attributes; |
| 16 | |
| 17 | local http_base = config.get("*", "core", "http_path") or "www_files"; |
| 18 | |
| 19 | local response_400 = { status = "400 Bad Request", body = "<h1>Bad Request</h1>Sorry, we didn't understand your request :(" }; |
| 20 | local response_403 = { status = "403 Forbidden", body = "<h1>Forbidden</h1>You don't have permission to view the contents of this directory :(" }; |
| 21 | local response_404 = { status = "404 Not Found", body = "<h1>Page Not Found</h1>Sorry, we couldn't find what you were looking for :(" }; |
| 22 | |
| 23 | -- TODO: Should we read this from /etc/mime.types if it exists? (startup time...?) |
| 24 | local mime_map = { |
| 25 | html = "text/html"; |
| 26 | htm = "text/html"; |
| 27 | xml = "text/xml"; |
| 28 | xsl = "text/xml"; |
| 29 | txt = "text/plain; charset=utf-8"; |
| 30 | js = "text/javascript"; |
| 31 | css = "text/css"; |
| 32 | }; |
| 33 | |
| 34 | local function preprocess_path(path) |
| 35 | if path:sub(1,1) ~= "/" then |
| 36 | path = "/"..path; |
| 37 | end |
| 38 | local level = 0; |
| 39 | for component in path:gmatch("([^/]+)/") do |
| 40 | if component == ".." then |
| 41 | level = level - 1; |
| 42 | elseif component ~= "." then |
| 43 | level = level + 1; |
| 44 | end |
| 45 | if level < 0 then |
| 46 | return nil; |
| 47 | end |
| 48 | end |
| 49 | return path; |
| 50 | end |
| 51 | |
| 52 | function serve_file(path) |
| 53 | local full_path = http_base..path; |
| 54 | if stat(full_path, "mode") == "directory" then |
| 55 | if stat(full_path.."/index.html", "mode") == "file" then |
| 56 | return serve_file(path.."/index.html"); |
| 57 | end |
| 58 | return response_403; |
| 59 | end |
| 60 | local f, err = open(full_path, "rb"); |
| 61 | if not f then return response_404; end |
| 62 | local data = f:read("*a"); |
| 63 | f:close(); |
| 64 | if not data then |
| 65 | return response_403; |
| 66 | end |
| 67 | local ext = path:match("%.([^.]*)$"); |
| 68 | local mime = mime_map[ext]; -- Content-Type should be nil when not known |
| 69 | return { |
| 70 | headers = { ["Content-Type"] = mime; }; |
| 71 | body = data; |
| 72 | }; |
| 73 | end |
| 74 | |
| 75 | local function handle_file_request(method, body, request) |
| 76 | local path = preprocess_path(request.url.path); |
| 77 | if not path then return response_400; end |
| 78 | path = path:gsub("^/[^/]+", ""); -- Strip /files/ |
| 79 | return serve_file(path); |
| 80 | end |
| 81 | |
| 82 | local function handle_default_request(method, body, request) |
| 83 | local path = preprocess_path(request.url.path); |
| 84 | if not path then return response_400; end |
| 85 | return serve_file(path); |
| 86 | end |
| 87 | |
| 88 | local function setup() |
| 89 | local ports = config.get(module.host, "core", "http_ports") or { 5280 }; |
| 90 | httpserver.set_default_handler(handle_default_request); |
| 91 | httpserver.new_from_config(ports, handle_file_request, { base = "files" }); |
| 92 | end |
| 93 | if prosody.start_time then -- already started |
| 94 | setup(); |
| 95 | else |
| 96 | prosody.events.add_handler("server-started", setup); |
| 97 | end |