151 lines
5.3 KiB
Haskell
151 lines
5.3 KiB
Haskell
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE CPP #-}
|
|
module Yesod.Internal.Request
|
|
( parseWaiRequest
|
|
, Request (..)
|
|
, RequestBodyContents
|
|
, FileInfo
|
|
, fileName
|
|
, fileContentType
|
|
, fileSource
|
|
, fileMove
|
|
, mkFileInfoLBS
|
|
, mkFileInfoFile
|
|
, mkFileInfoSource
|
|
, FileUpload (..)
|
|
-- The below are exported for testing.
|
|
, randomString
|
|
, parseWaiRequest'
|
|
) where
|
|
|
|
import Control.Applicative ((<$>))
|
|
import Control.Arrow (second)
|
|
import qualified Network.Wai.Parse as NWP
|
|
import Yesod.Internal
|
|
import qualified Network.Wai as W
|
|
import System.Random (RandomGen, newStdGen, randomRs)
|
|
import Web.Cookie (parseCookiesText)
|
|
import Data.ByteString (ByteString)
|
|
import qualified Data.ByteString.Char8 as S8
|
|
import Data.Text (Text, pack)
|
|
import Network.HTTP.Types (queryToQueryText)
|
|
import Control.Monad (join)
|
|
import Data.Maybe (fromMaybe, catMaybes)
|
|
import qualified Data.ByteString.Lazy as L
|
|
import qualified Data.Set as Set
|
|
import qualified Data.Text as T
|
|
import Data.Text.Encoding (decodeUtf8With)
|
|
import Data.Text.Encoding.Error (lenientDecode)
|
|
import Data.Conduit
|
|
import Data.Conduit.List (sourceList)
|
|
import Data.Conduit.Binary (sourceFile, sinkFile)
|
|
import Data.Word (Word64)
|
|
|
|
-- | The parsed request information.
|
|
data Request = Request
|
|
{ reqGetParams :: [(Text, Text)]
|
|
, reqCookies :: [(Text, Text)]
|
|
, reqWaiRequest :: W.Request
|
|
-- | Languages which the client supports.
|
|
, reqLangs :: [Text]
|
|
-- | A random, session-specific token used to prevent CSRF attacks.
|
|
, reqToken :: Maybe Text
|
|
-- | Size of the request body.
|
|
, reqBodySize :: Word64
|
|
}
|
|
|
|
parseWaiRequest :: W.Request
|
|
-> [(Text, ByteString)] -- ^ session
|
|
-> Bool
|
|
-> Word64
|
|
-> IO Request
|
|
parseWaiRequest env session' useToken bodySize =
|
|
parseWaiRequest' env session' useToken bodySize <$> newStdGen
|
|
|
|
parseWaiRequest' :: RandomGen g
|
|
=> W.Request
|
|
-> [(Text, ByteString)] -- ^ session
|
|
-> Bool
|
|
-> Word64
|
|
-> g
|
|
-> Request
|
|
parseWaiRequest' env session' useToken bodySize gen =
|
|
Request gets'' cookies' env langs'' token bodySize
|
|
where
|
|
gets' = queryToQueryText $ W.queryString env
|
|
gets'' = map (second $ fromMaybe "") gets'
|
|
reqCookie = lookup "Cookie" $ W.requestHeaders env
|
|
cookies' = maybe [] parseCookiesText reqCookie
|
|
acceptLang = lookup "Accept-Language" $ W.requestHeaders env
|
|
langs = map (pack . S8.unpack) $ maybe [] NWP.parseHttpAccept acceptLang
|
|
|
|
lookupText k = fmap (decodeUtf8With lenientDecode) . lookup k
|
|
|
|
-- The language preferences are prioritized as follows:
|
|
langs' = catMaybes [ join $ lookup langKey gets' -- Query _LANG
|
|
, lookup langKey cookies' -- Cookie _LANG
|
|
, lookupText langKey session' -- Session _LANG
|
|
] ++ langs -- Accept-Language(s)
|
|
|
|
-- Github issue #195. We want to add an extra two-letter version of any
|
|
-- language in the list.
|
|
langs'' = addTwoLetters (id, Set.empty) langs'
|
|
|
|
-- If sessions are disabled tokens should not be used (any
|
|
-- tokenKey present in the session is ignored). If sessions
|
|
-- are enabled and a session has no tokenKey a new one is
|
|
-- generated.
|
|
token = if not useToken
|
|
then Nothing
|
|
else Just $ maybe
|
|
(pack $ randomString 10 gen)
|
|
(decodeUtf8With lenientDecode)
|
|
(lookup tokenKey session')
|
|
|
|
addTwoLetters :: ([Text] -> [Text], Set.Set Text) -> [Text] -> [Text]
|
|
addTwoLetters (toAdd, exist) [] =
|
|
filter (flip Set.notMember exist) $ toAdd []
|
|
addTwoLetters (toAdd, exist) (l:ls) =
|
|
l : addTwoLetters (toAdd', exist') ls
|
|
where
|
|
(toAdd', exist')
|
|
| T.length l > 2 = (toAdd . (T.take 2 l:), exist)
|
|
| otherwise = (toAdd, Set.insert l exist)
|
|
|
|
-- | Generate a random String of alphanumerical characters
|
|
-- (a-z, A-Z, and 0-9) of the given length using the given
|
|
-- random number generator.
|
|
randomString :: RandomGen g => Int -> g -> String
|
|
randomString len = take len . map toChar . randomRs (0, 61)
|
|
where
|
|
toChar i
|
|
| i < 26 = toEnum $ i + fromEnum 'A'
|
|
| i < 52 = toEnum $ i + fromEnum 'a' - 26
|
|
| otherwise = toEnum $ i + fromEnum '0' - 52
|
|
|
|
-- | A tuple containing both the POST parameters and submitted files.
|
|
type RequestBodyContents =
|
|
( [(Text, Text)]
|
|
, [(Text, FileInfo)]
|
|
)
|
|
|
|
data FileInfo = FileInfo
|
|
{ fileName :: Text
|
|
, fileContentType :: Text
|
|
, fileSource :: Source (ResourceT IO) ByteString
|
|
, fileMove :: FilePath -> IO ()
|
|
}
|
|
|
|
mkFileInfoLBS :: Text -> Text -> L.ByteString -> FileInfo
|
|
mkFileInfoLBS name ct lbs = FileInfo name ct (sourceList $ L.toChunks lbs) (\fp -> L.writeFile fp lbs)
|
|
|
|
mkFileInfoFile :: Text -> Text -> FilePath -> FileInfo
|
|
mkFileInfoFile name ct fp = FileInfo name ct (sourceFile fp) (\dst -> runResourceT $ sourceFile fp $$ sinkFile dst)
|
|
|
|
mkFileInfoSource :: Text -> Text -> Source (ResourceT IO) ByteString -> FileInfo
|
|
mkFileInfoSource name ct src = FileInfo name ct src (\dst -> runResourceT $ src $$ sinkFile dst)
|
|
|
|
data FileUpload = FileUploadMemory (NWP.BackEnd L.ByteString)
|
|
| FileUploadDisk (NWP.BackEnd FilePath)
|
|
| FileUploadSource (NWP.BackEnd (Source (ResourceT IO) ByteString))
|