module Network.Wai.Internal where
import Blaze.ByteString.Builder (Builder)
import Control.Exception (IOException, try)
import qualified Data.ByteString as B hiding (pack)
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Char8 as B (pack, readInteger)
import qualified Data.ByteString.Lazy as L
#if __GLASGOW_HASKELL__ < 709
import Data.Functor ((<$>))
#endif
import Data.Maybe (listToMaybe)
import Data.Text (Text)
import Data.Typeable (Typeable)
import Data.Vault.Lazy (Vault)
import Data.Word (Word64)
import qualified Network.HTTP.Types as H
import qualified Network.HTTP.Types.Header as HH
import Network.Socket (SockAddr)
import Numeric (showInt)
import Data.List (intercalate)
import qualified System.PosixCompat.Files as P
data Request = Request {
requestMethod :: H.Method
, httpVersion :: H.HttpVersion
, rawPathInfo :: B.ByteString
, rawQueryString :: B.ByteString
, requestHeaders :: H.RequestHeaders
, isSecure :: Bool
, remoteHost :: SockAddr
, pathInfo :: [Text]
, queryString :: H.Query
, requestBody :: IO B.ByteString
, vault :: Vault
, requestBodyLength :: RequestBodyLength
, requestHeaderHost :: Maybe B.ByteString
, requestHeaderRange :: Maybe B.ByteString
}
deriving (Typeable)
instance Show Request where
show Request{..} = "Request {" ++ intercalate ", " [a ++ " = " ++ b | (a,b) <- fields] ++ "}"
where
fields =
[("requestMethod",show requestMethod)
,("httpVersion",show httpVersion)
,("rawPathInfo",show rawPathInfo)
,("rawQueryString",show rawQueryString)
,("requestHeaders",show requestHeaders)
,("isSecure",show isSecure)
,("remoteHost",show remoteHost)
,("pathInfo",show pathInfo)
,("queryString",show queryString)
,("requestBody","<IO ByteString>")
,("vault","<Vault>")
,("requestBodyLength",show requestBodyLength)
,("requestHeaderHost",show requestHeaderHost)
,("requestHeaderRange",show requestHeaderRange)
]
data Response
= ResponseFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)
| ResponseBuilder H.Status H.ResponseHeaders Builder
| ResponseStream H.Status H.ResponseHeaders StreamingBody
| ResponseRaw (IO B.ByteString -> (B.ByteString -> IO ()) -> IO ()) Response
deriving Typeable
type StreamingBody = (Builder -> IO ()) -> IO () -> IO ()
data RequestBodyLength = ChunkedBody | KnownLength Word64 deriving Show
data FilePart = FilePart
{ filePartOffset :: Integer
, filePartByteCount :: Integer
, filePartFileSize :: Integer
} deriving Show
data ResponseReceived = ResponseReceived
deriving Typeable
tryGetFileSize :: FilePath -> IO (Either IOException Integer)
tryGetFileSize path =
fmap (fromIntegral . P.fileSize) <$> try (P.getFileStatus path)
hContentRange :: H.HeaderName
hContentRange = "Content-Range"
hAcceptRanges :: H.HeaderName
hAcceptRanges = "Accept-Ranges"
contentRangeHeader :: Integer -> Integer -> Integer -> H.Header
contentRangeHeader beg end total = (hContentRange, range)
where
range = B.pack
$ 'b' : 'y': 't' : 'e' : 's' : ' '
: (if beg > end then ('*':) else
showInt beg
. ('-' :)
. showInt end)
( '/'
: showInt total "")
chooseFilePart :: Integer -> Maybe B.ByteString -> FilePart
chooseFilePart size Nothing = FilePart 0 size size
chooseFilePart size (Just range) = case parseByteRanges range >>= listToMaybe of
Nothing -> FilePart 0 size size
Just hrange -> checkRange hrange
where
checkRange (H.ByteRangeFrom beg) = fromRange beg (size 1)
checkRange (H.ByteRangeFromTo beg end) = fromRange beg (min (size 1) end)
checkRange (H.ByteRangeSuffix count) = fromRange (max 0 (size count)) (size 1)
fromRange beg end = FilePart beg (end beg + 1) size
adjustForFilePart :: H.Status -> H.ResponseHeaders -> FilePart -> (H.Status, H.ResponseHeaders)
adjustForFilePart s h part = (s', h'')
where
off = filePartOffset part
len = filePartByteCount part
size = filePartFileSize part
contentRange = contentRangeHeader off (off + len 1) size
lengthBS = L.toStrict $ B.toLazyByteString $ B.integerDec len
s' = if filePartByteCount part /= size then H.partialContent206 else s
h' = (H.hContentLength, lengthBS):(hAcceptRanges, "bytes"):h
h'' = (if len == size then id else (contentRange:)) h'
parseByteRanges :: B.ByteString -> Maybe HH.ByteRanges
parseByteRanges bs1 = do
bs2 <- stripPrefix "bytes=" bs1
(r, bs3) <- range bs2
ranges (r:) bs3
where
range bs2 = do
(i, bs3) <- B.readInteger bs2
if i < 0
then Just (HH.ByteRangeSuffix (negate i), bs3)
else do
bs4 <- stripPrefix "-" bs3
case B.readInteger bs4 of
Just (j, bs5) | j >= i -> Just (HH.ByteRangeFromTo i j, bs5)
_ -> Just (HH.ByteRangeFrom i, bs4)
ranges front bs3
| B.null bs3 = Just (front [])
| otherwise = do
bs4 <- stripPrefix "," bs3
(r, bs5) <- range bs4
ranges (front . (r:)) bs5
stripPrefix x y
| x `B.isPrefixOf` y = Just (B.drop (B.length x) y)
| otherwise = Nothing