module Hack.Contrib.File where import Hack import Hack.Utils import Hack.Response import Hack.Constants import Hack.Contrib.Hub import Hack.Contrib.Mime import MPSUTF8 import Prelude hiding ((.), (^), (>), (+), (/), readFile) import System.Directory (getCurrentDirectory) import Data.Default import Data.Maybe import Data.List (isInfixOf) import System.Directory import System.FilePath file :: Maybe String -> MiddleWare file root app = \env -> do let path = env.path_info .url2unicode if ".." `isInfixOf` path then forbidden else serve root path env serve :: Maybe String -> FilePath -> Env -> IO Response serve root fname env = do cwd <- get_current_directory let my_root = root.fromMaybe cwd let path = my_root / makeRelative "/" fname exist <- file_exist path if not exist then path.not_found else do can_read <- path.get_permissions ^ readable if not can_read then path.no_permission else path.serving where serving path = do content <- path.read_binary_file size <- file_size path ^ from_i mtime_str <- file_mtime path ^ httpdate let default_content_type = "application/octet-stream" let safe_lookup = lookup_mime_type > fromMaybe default_content_type let content_type = path.takeExtension.safe_lookup return $ def .set_body content .set_content_length size .set_content_type content_type .set_last_modified mtime_str no_permission path = return $ def .set_status 404 .set_content_type _TextPlain .set_content_length (msg.length) .set_body msg where msg = "No permission: " ++ path ++ "\n" not_found path = return $ def .set_status 404 .set_content_type _TextPlain .set_content_length (msg.length) .set_body msg where msg = "File not found: " ++ path ++ "\n" forbidden = return $ def .set_status 403 .set_content_type _TextPlain .set_content_length (msg.length) .set_body msg where msg = "Forbidden\n"