1module WaiAppStatic.Types
2    ( -- * Pieces
3      Piece
4    , toPiece
5    , fromPiece
6    , unsafeToPiece
7    , Pieces
8    , toPieces
9      -- * Caching
10    , MaxAge (..)
11      -- * File\/folder serving
12    , FolderName
13    , Folder (..)
14    , File (..)
15    , LookupResult (..)
16    , Listing
17      -- * Settings
18    , StaticSettings (..)
19    ) where
20
21import Data.Text (Text)
22import qualified Network.HTTP.Types as H
23import qualified Network.Wai as W
24import Data.ByteString (ByteString)
25import System.Posix.Types (EpochTime)
26import qualified Data.Text as T
27import Data.ByteString.Builder (Builder)
28import Network.Mime (MimeType)
29
30-- | An individual component of a path, or of a filepath.
31--
32-- This is the core type used by wai-app-static for doing lookups. It provides
33-- a smart constructor to avoid the possibility of constructing unsafe path
34-- segments (though @unsafeToPiece@ can get around that as necessary).
35--
36-- Individual file lookup backends must know how to convert from a @Piece@ to
37-- their storage system.
38newtype Piece = Piece { fromPiece :: Text }
39    deriving (Show, Eq, Ord)
40
41-- | Smart constructor for a @Piece@. Won\'t allow unsafe components, such as
42-- pieces beginning with a period or containing a slash. This /will/, however,
43-- allow null pieces.
44toPiece :: Text -> Maybe Piece
45toPiece t
46    | T.null t = Just $ Piece t
47    | T.head t == '.' = Nothing
48    | T.any (== '/') t = Nothing
49    | otherwise = Just $ Piece t
50
51-- | Construct a @Piece@ without input validation.
52unsafeToPiece :: Text -> Piece
53unsafeToPiece = Piece
54
55-- | Call @toPiece@ on a list.
56--
57-- > toPieces = mapM toPiece
58toPieces :: [Text] -> Maybe Pieces
59toPieces = mapM toPiece
60
61-- | Request coming from a user. Corresponds to @pathInfo@.
62type Pieces = [Piece]
63
64-- | Values for the max-age component of the cache-control response header.
65data MaxAge = NoMaxAge -- ^ no cache-control set
66            | MaxAgeSeconds Int -- ^ set to the given number of seconds
67            | MaxAgeForever -- ^ essentially infinite caching; in reality, probably one year
68
69-- | Just the name of a folder.
70type FolderName = Piece
71
72-- | Represent contents of a single folder, which can be itself either a file
73-- or a folder.
74data Folder = Folder
75    { folderContents :: [Either FolderName File]
76    }
77
78-- | Information on an individual file.
79data File = File
80    { -- | Size of file in bytes
81      fileGetSize :: Integer
82      -- | How to construct a WAI response for this file. Some files are stored
83      -- on the filesystem and can use @ResponseFile@, while others are stored
84      -- in memory and should use @ResponseBuilder@.
85    , fileToResponse :: H.Status -> H.ResponseHeaders -> W.Response
86      -- | Last component of the filename.
87    , fileName :: Piece
88      -- | Calculate a hash of the contents of this file, such as for etag.
89    , fileGetHash :: IO (Maybe ByteString)
90      -- | Last modified time, used for both display in listings and if-modified-since.
91    , fileGetModified :: Maybe EpochTime
92    }
93
94-- | Result of looking up a file in some storage backend.
95--
96-- The lookup is either a file or folder, or does not exist.
97data LookupResult = LRFile File
98                  | LRFolder Folder
99                  | LRNotFound
100
101-- | How to construct a directory listing page for the given request path and
102-- the resulting folder.
103type Listing = Pieces -> Folder -> IO Builder
104
105-- | All of the settings available to users for tweaking wai-app-static.
106--
107-- Note that you should use the settings type approach for modifying values.
108-- See <http://www.yesodweb.com/book/settings-types> for more information.
109data StaticSettings = StaticSettings
110    {
111      -- | Lookup a single file or folder. This is how you can control storage
112      -- backend (filesystem, embedded, etc) and where to lookup.
113      ssLookupFile :: Pieces -> IO LookupResult
114
115      -- | Determine the mime type of the given file. Note that this function
116      -- lives in @IO@ in case you want to perform more complicated mimetype
117      -- analysis, such as via the @file@ utility.
118    , ssGetMimeType :: File -> IO MimeType
119
120      -- | Ordered list of filenames to be used for indices. If the user
121      -- requests a folder, and a file with the given name is found in that
122      -- folder, that file is served. This supercedes any directory listing.
123    , ssIndices :: [Piece]
124
125      -- | How to perform a directory listing. Optional. Will be used when the
126      -- user requested a folder.
127    , ssListing :: Maybe Listing
128
129      -- | Value to provide for max age in the cache-control.
130    , ssMaxAge :: MaxAge
131
132      -- | Given a requested path and a new destination, construct a string
133      -- that will go there. Default implementation will use relative paths.
134    , ssMkRedirect :: Pieces -> ByteString -> ByteString
135
136      -- | If @True@, send a redirect to the user when a folder is requested
137      -- and an index page should be displayed. When @False@, display the
138      -- content immediately.
139    , ssRedirectToIndex :: Bool
140
141      -- | Prefer usage of etag caching to last-modified caching.
142    , ssUseHash :: Bool
143
144      -- | Force a trailing slash at the end of directories
145    , ssAddTrailingSlash :: Bool
146
147      -- | Optional `W.Application` to be used in case of 404 errors
148      --
149      -- Since 3.1.3
150    , ss404Handler :: Maybe W.Application
151    }
152