module Hack2.Contrib.Middleware.File (file, serve) where
import Data.Default
import Data.List (isInfixOf)
import Data.Maybe
import Hack2
import Hack2.Contrib.Constants
import Hack2.Contrib.Mime
import Hack2.Contrib.Response
import Hack2.Contrib.Utils
import Hack2.Contrib.AirBackports
import Air.Env hiding (Default, def)
import Prelude ()
import System.Directory
import System.FilePath
import qualified Data.ByteString.Char8 as B
import Data.ByteString.Char8 (ByteString)
file :: Maybe ByteString -> Middleware
file root _ = \env -> do
let path = env.path_info .as_string (unescape_uri > u2b)
if B.unpack ".." `isInfixOf` B.unpack path
then forbidden
else serve root path
serve :: Maybe ByteString -> ByteString -> IO Response
serve root fname = do
cwd <- getCurrentDirectory
let my_root = ( root ^ B.unpack ).fromMaybe cwd
let path = my_root / makeRelative "/" (fname.B.unpack)
exist <- doesFileExist b2u path
if not exist
then do
path.B.pack.not_found
else
do
can_read <- path.b2u.getPermissions ^ readable
if not can_read
then path.B.pack.no_permission
else path.serving
where
serving path = do
file_data <- B.readFile b2u path
size <- path.b2u.file_size ^ from_i
mtime_str <- path.b2u.file_mtime ^ httpdate
let default_content_type = "application/octet-stream"
let safe_lookup = lookup_mime_type > fromMaybe default_content_type
let content_type = path.takeExtension.B.pack.safe_lookup
return
def
.set_body_bytestring file_data
.set_content_length size
.set_content_type content_type
.set_last_modified (B.pack mtime_str)
.set_status 200
no_permission :: ByteString -> IO Response
no_permission path = do
putStrLn "no_permission"
return
def
.set_status 404
.set_content_type _TextPlain
.set_content_length (msg.B.length)
.set_body_bytestring msg
where msg = "No permission: " + path + "\n"
not_found :: ByteString -> IO Response
not_found path = return $
def
.set_status 404
.set_content_type _TextPlain
.set_content_length (msg.B.length)
.set_body_bytestring msg
where msg = "File not found: " + path + "\n"
forbidden :: IO Response
forbidden = return
def
.set_status 403
.set_content_type _TextPlain
.set_content_length (msg.B.length)
.set_body_bytestring msg
where msg = "Forbidden\n"