{-# LANGUAGE OverloadedStrings #-}


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

module Hack2.Contrib.Middleware.File (file) 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.Lazy.Char8 as B
import Data.ByteString.Lazy.Char8 (ByteString)

file :: Maybe ByteString -> Middleware
file root _ = \env -> do
  let path = env.path_info .as_string unescape_uri
  
  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 path

  if not exist
      then path.B.pack.not_found
      else
        do 
          can_read <- path.getPermissions ^ readable
          if not can_read
            then path.B.pack.no_permission
            else path.serving
    
    where 
      serving path = do
        content <- path.B.readFile
        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 content
            .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 = 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"