spacecookie-1.0.0.0: server/Network/Spacecookie/FileType.hs
{-# LANGUAGE OverloadedStrings #-}
module Network.Spacecookie.FileType
( PathError (..)
, gopherFileType
-- exposed for tests
, lookupSuffix
) where
import Control.Applicative ((<|>))
import qualified Data.ByteString as B
import qualified Data.Map as M
import Data.Maybe (fromMaybe)
import Network.Gopher (GopherFileType (..))
import Network.Gopher.Util (boolToMaybe, asciiToLower)
import System.Directory (doesDirectoryExist, doesFileExist)
import System.FilePath.Posix.ByteString ( RawFilePath, takeExtension
, splitDirectories, decodeFilePath)
fileTypeMap :: M.Map RawFilePath GopherFileType
fileTypeMap = M.fromList
[ (".gif", GifFile)
, (".png", ImageFile)
, (".jpg", ImageFile)
, (".jpeg", ImageFile)
, (".tiff", ImageFile)
, (".tif", ImageFile)
, (".bmp", ImageFile)
, (".webp", ImageFile)
, (".apng", ImageFile)
, (".mng", ImageFile)
, (".heif", ImageFile)
, (".heifs", ImageFile)
, (".heic", ImageFile)
, (".heics", ImageFile)
, (".avci", ImageFile)
, (".avcs", ImageFile)
, (".avif", ImageFile)
, (".avifs", ImageFile)
, (".ico", ImageFile)
, (".svg", ImageFile)
, (".raw", ImageFile) -- TODO: RAW files should maybe be binary files?
, (".cr2", ImageFile)
, (".nef", ImageFile)
, (".json", File)
, (".txt", File)
, (".text", File)
, (".md", File)
, (".mdown", File)
, (".mkdn", File)
, (".mkd", File)
, (".markdown", File)
, (".adoc", File)
, (".rst", File)
, (".zip", BinaryFile)
, (".tar", BinaryFile)
, (".gz", BinaryFile)
, (".bzip2", BinaryFile)
, (".xz", BinaryFile)
, (".tgz", BinaryFile)
, (".doc", BinaryFile)
, (".hqx", BinHexMacintoshFile)
]
lookupSuffix :: RawFilePath -> GopherFileType
lookupSuffix = fromMaybe File
. (flip M.lookup) fileTypeMap
. B.map asciiToLower
data PathError
= PathDoesNotExist
| PathIsNotAllowed
deriving (Show, Eq, Ord, Enum)
-- | Action in the 'Either' monad which causes a
-- failure if there's any dot files or directory
-- in the given path
checkNoDotFiles :: RawFilePath -> Either PathError ()
checkNoDotFiles path = do
-- this prevents relative directories from being
-- forbidden while singular '.' in the path somewhere
-- get flagged and "." stays allowed.
let segments = splitDirectories $ fromMaybe path
$ boolToMaybe ("./" `B.isPrefixOf` path) (B.tail path)
<|> boolToMaybe ("." == path) ""
if any ((== ".") . B.take 1) segments
then Left PathIsNotAllowed
else Right ()
-- | calculates the file type identifier used in the Gopher
-- protocol for a given file and returns a descriptive error
-- if the file is not accessible or a dot file (and thus not
-- allowed to access)
gopherFileType :: RawFilePath -> IO (Either PathError GopherFileType)
gopherFileType path = (checkNoDotFiles path >>) <$> do
let pathWide = decodeFilePath path
isDir <- doesDirectoryExist pathWide
if isDir
then pure $ Right Directory
else do
fileExists <- doesFileExist pathWide
pure $
if fileExists
then Right $ lookupSuffix $ takeExtension path
else Left $ PathDoesNotExist