-- | Stolen from rack: serves files below the +root+ given, according to the 
--   path info of the Rack request.

module Hack.Contrib.Middleware.File (file) where

import Hack
import Hack.Contrib.Utils
import Hack.Contrib.Response
import Hack.Contrib.Constants
import Hack.Contrib.Middleware.Mime

import MPSUTF8
import Prelude hiding ((.), (^), (>), (+), (/), readFile)

import Data.Default
import Data.Maybe
import Data.List (isInfixOf)
import System.Directory
import System.FilePath


file :: Maybe String -> Middleware
file root _ = \env -> do
  let path = env.path_info .unescape_uri
  
  if ".." `isInfixOf` path
    then forbidden
    else path.serve root


serve :: Maybe String -> String -> IO Response
serve root fname = do
  cwd <- getCurrentDirectory
  let my_root = root.fromMaybe cwd
  let path = my_root / makeRelative "/" fname
  
  exist <- doesFileExist path

  if not exist
      then path.not_found
      else
        do 
          can_read <- path.getPermissions ^ readable
          if not can_read
            then path.no_permission
            else path.serving
    
    where 
      serving path = do
        content <- path.b2u.read_binary_file
        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.safe_lookup
        
        return $ 
          def 
            .set_body content
            .set_content_length size
            .set_content_type content_type
            .set_last_modified mtime_str
            .set_status 200

no_permission :: String -> IO Response
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 :: String -> IO Response
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 :: IO Response
forbidden = return $ 
  def
    .set_status 403
    .set_content_type _TextPlain
    .set_content_length (msg.length)
    .set_body msg

  where msg = "Forbidden\n"