{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Snap.Internal.Util.FileUploads
(
handleFormUploads
, foldMultipart
, PartFold
, FormParam
, FormFile (..)
, storeAsLazyByteString
, withTemporaryStore
, handleFileUploads
, handleMultipart
, PartProcessor
, PartInfo(..)
, PartDisposition(..)
, toPartDisposition
, UploadPolicy(..)
, defaultUploadPolicy
, doProcessFormInputs
, setProcessFormInputs
, getMaximumFormInputSize
, setMaximumFormInputSize
, getMaximumNumberOfFormInputs
, setMaximumNumberOfFormInputs
, getMinimumUploadRate
, setMinimumUploadRate
, getMinimumUploadSeconds
, setMinimumUploadSeconds
, getUploadTimeout
, setUploadTimeout
, FileUploadPolicy(..)
, defaultFileUploadPolicy
, setMaximumFileSize
, setMaximumNumberOfFiles
, setSkipFilesWithoutNames
, setMaximumSkippedFileSize
, PartUploadPolicy(..)
, disallow
, allowWithMaximumSize
, FileUploadException(..)
, fileUploadExceptionReason
, BadPartException(..)
, PolicyViolationException(..)
) where
import Control.Applicative (Alternative ((<|>)), Applicative (pure, (*>), (<*)))
import Control.Arrow (Arrow (first))
import Control.Exception.Lifted (Exception, SomeException (..), bracket, catch, finally, fromException, mask, throwIO, toException)
import qualified Control.Exception.Lifted as E (try)
import Control.Monad (Functor (fmap), Monad (return, (>>=)), MonadPlus (mzero), forM_, guard, liftM, sequence, unless, void, when, (>=>))
import Control.Monad.IO.Class (liftIO)
import Data.Attoparsec.ByteString.Char8 (Parser, isEndOfLine, string, takeWhile)
import qualified Data.Attoparsec.ByteString.Char8 as Atto (try)
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as S
import Data.ByteString.Internal (c2w)
import qualified Data.ByteString.Lazy.Internal as LB (ByteString (Empty), chunk)
import qualified Data.CaseInsensitive as CI (mk)
import Data.Int (Int, Int64)
import qualified Data.IORef as IORef
import Data.List (find, map, (++))
import qualified Data.Map as Map (insertWith)
import Data.Maybe (Maybe (..), fromMaybe, isJust, maybe)
import Data.Text (Text)
import qualified Data.Text as T (concat, pack, unpack)
import qualified Data.Text.Encoding as TE (decodeUtf8)
import Data.Typeable (Typeable, cast)
import Prelude (Bool (..), Double, Either (..), Eq (..), FilePath, IO, Ord (..), Show (..), String, const, either, foldr, fst, id, max, not, otherwise, seq, snd, succ, ($), ($!), (.), (^), (||))
import Snap.Core (HasHeaders (headers), Headers, MonadSnap, Request (rqParams, rqPostParams), getHeader, getRequest, getTimeoutModifier, putRequest, runRequestBody)
import Snap.Internal.Parsing (crlf, fullyParse, pContentTypeWithParameters, pHeaders, pValueWithParameters')
import qualified Snap.Types.Headers as H (fromList)
import System.Directory (removeFile)
import System.FilePath ((</>))
import System.IO (BufferMode (NoBuffering), Handle, hClose, hSetBuffering, openBinaryTempFile)
import System.IO.Error (isDoesNotExistError)
import System.IO.Streams (InputStream, MatchInfo (..), TooManyBytesReadException, search)
import qualified System.IO.Streams as Streams
import System.IO.Streams.Attoparsec (parseFromStream)
import System.PosixCompat.Temp (mkstemp)
handleFileUploads ::
(MonadSnap m) =>
FilePath
-> UploadPolicy
-> (PartInfo -> PartUploadPolicy)
-> (PartInfo -> Either PolicyViolationException FilePath -> IO a)
-> m [a]
handleFileUploads :: forall (m :: * -> *) a.
MonadSnap m =>
FilePath
-> UploadPolicy
-> (PartInfo -> PartUploadPolicy)
-> (PartInfo -> Either PolicyViolationException FilePath -> IO a)
-> m [a]
handleFileUploads FilePath
tmpdir UploadPolicy
uploadPolicy PartInfo -> PartUploadPolicy
partPolicy PartInfo -> Either PolicyViolationException FilePath -> IO a
partHandler =
forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy -> PartProcessor a -> m [a]
handleMultipart UploadPolicy
uploadPolicy PartInfo -> InputStream ByteString -> IO a
go
where
go :: PartInfo -> InputStream ByteString -> IO a
go PartInfo
partInfo InputStream ByteString
stream = forall b a. b -> (a -> b) -> Maybe a -> b
maybe IO a
disallowed Int64 -> IO a
takeIt Maybe Int64
mbFs
where
ctText :: ByteString
ctText = PartInfo -> ByteString
partContentType PartInfo
partInfo
fnText :: ByteString
fnText = forall a. a -> Maybe a -> a
fromMaybe ByteString
"" forall a b. (a -> b) -> a -> b
$ PartInfo -> Maybe ByteString
partFileName PartInfo
partInfo
ct :: Text
ct = ByteString -> Text
TE.decodeUtf8 ByteString
ctText
fn :: Text
fn = ByteString -> Text
TE.decodeUtf8 ByteString
fnText
(PartUploadPolicy Maybe Int64
mbFs) = PartInfo -> PartUploadPolicy
partPolicy PartInfo
partInfo
takeIt :: Int64 -> IO a
takeIt Int64
maxSize = do
InputStream ByteString
str' <- Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.throwIfProducesMoreThan Int64
maxSize InputStream ByteString
stream
forall a.
FilePath
-> (PartInfo -> Either PolicyViolationException FilePath -> IO a)
-> PartProcessor a
fileReader FilePath
tmpdir PartInfo -> Either PolicyViolationException FilePath -> IO a
partHandler PartInfo
partInfo InputStream ByteString
str' forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {a}. Show a => a -> TooManyBytesReadException -> IO a
tooMany Int64
maxSize
tooMany :: a -> TooManyBytesReadException -> IO a
tooMany a
maxSize (TooManyBytesReadException
_ :: TooManyBytesReadException) =
PartInfo -> Either PolicyViolationException FilePath -> IO a
partHandler PartInfo
partInfo
(forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$
Text -> PolicyViolationException
PolicyViolationException forall a b. (a -> b) -> a -> b
$
[Text] -> Text
T.concat [ Text
"File \""
, Text
fn
, Text
"\" exceeded maximum allowable size "
, FilePath -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> FilePath
show a
maxSize ])
disallowed :: IO a
disallowed =
PartInfo -> Either PolicyViolationException FilePath -> IO a
partHandler PartInfo
partInfo
(forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$
Text -> PolicyViolationException
PolicyViolationException forall a b. (a -> b) -> a -> b
$
[Text] -> Text
T.concat [ Text
"Policy disallowed upload of file \""
, Text
fn
, Text
"\" with content-type \""
, Text
ct
, Text
"\"" ] )
data FormFile a = FormFile
{ forall a. FormFile a -> ByteString
formFileName :: !ByteString
, forall a. FormFile a -> a
formFileValue :: a
} deriving (FormFile a -> FormFile a -> Bool
forall a. Eq a => FormFile a -> FormFile a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: FormFile a -> FormFile a -> Bool
$c/= :: forall a. Eq a => FormFile a -> FormFile a -> Bool
== :: FormFile a -> FormFile a -> Bool
$c== :: forall a. Eq a => FormFile a -> FormFile a -> Bool
Eq, FormFile a -> FormFile a -> Bool
FormFile a -> FormFile a -> Ordering
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall {a}. Ord a => Eq (FormFile a)
forall a. Ord a => FormFile a -> FormFile a -> Bool
forall a. Ord a => FormFile a -> FormFile a -> Ordering
forall a. Ord a => FormFile a -> FormFile a -> FormFile a
min :: FormFile a -> FormFile a -> FormFile a
$cmin :: forall a. Ord a => FormFile a -> FormFile a -> FormFile a
max :: FormFile a -> FormFile a -> FormFile a
$cmax :: forall a. Ord a => FormFile a -> FormFile a -> FormFile a
>= :: FormFile a -> FormFile a -> Bool
$c>= :: forall a. Ord a => FormFile a -> FormFile a -> Bool
> :: FormFile a -> FormFile a -> Bool
$c> :: forall a. Ord a => FormFile a -> FormFile a -> Bool
<= :: FormFile a -> FormFile a -> Bool
$c<= :: forall a. Ord a => FormFile a -> FormFile a -> Bool
< :: FormFile a -> FormFile a -> Bool
$c< :: forall a. Ord a => FormFile a -> FormFile a -> Bool
compare :: FormFile a -> FormFile a -> Ordering
$ccompare :: forall a. Ord a => FormFile a -> FormFile a -> Ordering
Ord, Int -> FormFile a -> ShowS
forall a. Show a => Int -> FormFile a -> ShowS
forall a. Show a => [FormFile a] -> ShowS
forall a. Show a => FormFile a -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [FormFile a] -> ShowS
$cshowList :: forall a. Show a => [FormFile a] -> ShowS
show :: FormFile a -> FilePath
$cshow :: forall a. Show a => FormFile a -> FilePath
showsPrec :: Int -> FormFile a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> FormFile a -> ShowS
Show)
data UploadState a = UploadState
{ forall a. UploadState a -> Int
numUploadedFiles :: !Int
, forall a. UploadState a -> [FormFile a] -> [FormFile a]
uploadedFiles :: !([FormFile a] -> [FormFile a])
}
handleFormUploads ::
(MonadSnap m) =>
UploadPolicy
-> FileUploadPolicy
-> (PartInfo -> InputStream ByteString -> IO a)
-> m ([FormParam], [FormFile a])
handleFormUploads :: forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy
-> FileUploadPolicy
-> (PartInfo -> InputStream ByteString -> IO a)
-> m ([FormParam], [FormFile a])
handleFormUploads UploadPolicy
uploadPolicy FileUploadPolicy
filePolicy PartInfo -> InputStream ByteString -> IO a
partHandler = do
([FormParam]
params, !UploadState a
st) <- forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy -> PartFold a -> a -> m ([FormParam], a)
foldMultipart UploadPolicy
uploadPolicy PartInfo
-> InputStream ByteString -> UploadState a -> IO (UploadState a)
go (forall a. Int -> ([FormFile a] -> [FormFile a]) -> UploadState a
UploadState Int
0 forall a. a -> a
id)
forall (m :: * -> *) a. Monad m => a -> m a
return ([FormParam]
params, forall a. UploadState a -> [FormFile a] -> [FormFile a]
uploadedFiles UploadState a
st [])
where
go :: PartInfo
-> InputStream ByteString -> UploadState a -> IO (UploadState a)
go !PartInfo
partInfo InputStream ByteString
stream !UploadState a
st = do
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
numUploads forall a. Ord a => a -> a -> Bool
>= Int
maxFiles) forall {a}. IO a
throwTooManyFiles
case PartInfo -> Maybe ByteString
partFileName PartInfo
partInfo of
Maybe ByteString
Nothing -> IO (UploadState a)
onEmptyName
Just ByteString
_ -> IO (UploadState a)
takeIt
where
numUploads :: Int
numUploads = forall a. UploadState a -> Int
numUploadedFiles UploadState a
st
files :: [FormFile a] -> [FormFile a]
files = forall a. UploadState a -> [FormFile a] -> [FormFile a]
uploadedFiles UploadState a
st
maxFiles :: Int
maxFiles = FileUploadPolicy -> Int
maxNumberOfFiles FileUploadPolicy
filePolicy
maxFileSize :: Int64
maxFileSize = FileUploadPolicy -> Int64
maxFileUploadSize FileUploadPolicy
filePolicy
fnText :: ByteString
fnText = forall a. a -> Maybe a -> a
fromMaybe ByteString
"" forall a b. (a -> b) -> a -> b
$ PartInfo -> Maybe ByteString
partFileName PartInfo
partInfo
fn :: Text
fn = ByteString -> Text
TE.decodeUtf8 ByteString
fnText
takeIt :: IO (UploadState a)
takeIt = do
InputStream ByteString
str' <- Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.throwIfProducesMoreThan Int64
maxFileSize InputStream ByteString
stream
a
r <- PartInfo -> InputStream ByteString -> IO a
partHandler PartInfo
partInfo InputStream ByteString
str' forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {m :: * -> *} {a} {a}.
(MonadBase IO m, Show a) =>
a -> TooManyBytesReadException -> m a
tooMany Int64
maxFileSize
let f :: FormFile a
f = forall a. ByteString -> a -> FormFile a
FormFile (PartInfo -> ByteString
partFieldName PartInfo
partInfo) a
r
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall a. Int -> ([FormFile a] -> [FormFile a]) -> UploadState a
UploadState (forall a. Enum a => a -> a
succ Int
numUploads) ([FormFile a] -> [FormFile a]
files forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([FormFile a
f] forall a. [a] -> [a] -> [a]
++) )
skipIt :: Int64 -> IO (UploadState a)
skipIt Int64
maxSize = do
InputStream ByteString
str' <- Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.throwIfProducesMoreThan Int64
maxSize InputStream ByteString
stream
!()
_ <- forall a. InputStream a -> IO ()
Streams.skipToEof InputStream ByteString
str' forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {m :: * -> *} {a} {a}.
(MonadBase IO m, Show a) =>
a -> TooManyBytesReadException -> m a
tooMany Int64
maxSize
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall a. Int -> ([FormFile a] -> [FormFile a]) -> UploadState a
UploadState (forall a. Enum a => a -> a
succ Int
numUploads) [FormFile a] -> [FormFile a]
files
onEmptyName :: IO (UploadState a)
onEmptyName = if FileUploadPolicy -> Bool
skipEmptyFileName FileUploadPolicy
filePolicy
then Int64 -> IO (UploadState a)
skipIt (FileUploadPolicy -> Int64
maxEmptyFileNameSize FileUploadPolicy
filePolicy)
else IO (UploadState a)
takeIt
throwTooManyFiles :: IO a
throwTooManyFiles = forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> PolicyViolationException
PolicyViolationException forall a b. (a -> b) -> a -> b
$ [Text] -> Text
T.concat
[Text
"number of files exceeded the maximum of "
,FilePath -> Text
T.pack (forall a. Show a => a -> FilePath
show Int
maxFiles) ]
tooMany :: a -> TooManyBytesReadException -> m a
tooMany a
maxSize (TooManyBytesReadException
_ :: TooManyBytesReadException) =
forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> PolicyViolationException
PolicyViolationException forall a b. (a -> b) -> a -> b
$
[Text] -> Text
T.concat [ Text
"File \""
, Text
fn
, Text
"\" exceeded maximum allowable size "
, FilePath -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> FilePath
show a
maxSize ]
type PartFold a = PartInfo -> InputStream ByteString -> a -> IO a
foldMultipart ::
(MonadSnap m) =>
UploadPolicy
-> PartFold a
-> a
-> m ([FormParam], a)
foldMultipart :: forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy -> PartFold a -> a -> m ([FormParam], a)
foldMultipart UploadPolicy
uploadPolicy PartFold a
origPartHandler a
zero = do
Headers
hdrs <- forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM forall a. HasHeaders a => a -> Headers
headers forall (m :: * -> *). MonadSnap m => m Request
getRequest
let (ByteString
ct, Maybe ByteString
mbBoundary) = Headers -> (ByteString, Maybe ByteString)
getContentType Headers
hdrs
Int -> IO ()
tickleTimeout <- forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM (forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Ord a => a -> a -> a
max) forall (m :: * -> *). MonadSnap m => m ((Int -> Int) -> IO ())
getTimeoutModifier
let bumpTimeout :: IO ()
bumpTimeout = Int -> IO ()
tickleTimeout forall a b. (a -> b) -> a -> b
$ UploadPolicy -> Int
uploadTimeout UploadPolicy
uploadPolicy
let partHandler :: PartInfo -> InputStream ByteString -> a -> IO (Capture a)
partHandler = if UploadPolicy -> Bool
doProcessFormInputs UploadPolicy
uploadPolicy
then forall a.
Int64
-> PartFold a
-> PartInfo
-> InputStream ByteString
-> a
-> IO (Capture a)
captureVariableOrReadFile
(UploadPolicy -> Int64
getMaximumFormInputSize UploadPolicy
uploadPolicy)
PartFold a
origPartHandler
else \PartInfo
x InputStream ByteString
y a
acc -> forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM forall a. a -> Capture a
File forall a b. (a -> b) -> a -> b
$ PartFold a
origPartHandler PartInfo
x InputStream ByteString
y a
acc
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (ByteString
ct forall a. Eq a => a -> a -> Bool
== ByteString
"multipart/form-data")
ByteString
boundary <- forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall a b. (a -> b) -> a -> b
$ Text -> BadPartException
BadPartException
Text
"got multipart/form-data without boundary")
forall (m :: * -> *) a. Monad m => a -> m a
return
Maybe ByteString
mbBoundary
forall (m :: * -> *) a.
MonadSnap m =>
(InputStream ByteString -> IO a) -> m a
runRequestBody (IO ()
-> ByteString
-> (PartInfo -> InputStream ByteString -> a -> IO (Capture a))
-> InputStream ByteString
-> IO ([FormParam], a)
proc IO ()
bumpTimeout ByteString
boundary PartInfo -> InputStream ByteString -> a -> IO (Capture a)
partHandler)
where
uploadRate :: Double
uploadRate = UploadPolicy -> Double
minimumUploadRate UploadPolicy
uploadPolicy
uploadSecs :: Int
uploadSecs = UploadPolicy -> Int
minimumUploadSeconds UploadPolicy
uploadPolicy
maxFormVars :: Int
maxFormVars = UploadPolicy -> Int
maximumNumberOfFormInputs UploadPolicy
uploadPolicy
proc :: IO ()
-> ByteString
-> (PartInfo -> InputStream ByteString -> a -> IO (Capture a))
-> InputStream ByteString
-> IO ([FormParam], a)
proc IO ()
bumpTimeout ByteString
boundary PartInfo -> InputStream ByteString -> a -> IO (Capture a)
partHandler =
IO ()
-> Double
-> Int
-> InputStream ByteString
-> IO (InputStream ByteString)
Streams.throwIfTooSlow IO ()
bumpTimeout Double
uploadRate Int
uploadSecs forall (m :: * -> *) a b c.
Monad m =>
(a -> m b) -> (b -> m c) -> a -> m c
>=>
forall a.
Int
-> ByteString
-> (PartInfo -> InputStream ByteString -> a -> IO (Capture a))
-> a
-> InputStream ByteString
-> IO ([FormParam], a)
internalFoldMultipart Int
maxFormVars ByteString
boundary PartInfo -> InputStream ByteString -> a -> IO (Capture a)
partHandler a
zero
type PartProcessor a = PartInfo -> InputStream ByteString -> IO a
handleMultipart ::
(MonadSnap m) =>
UploadPolicy
-> PartProcessor a
-> m [a]
handleMultipart :: forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy -> PartProcessor a -> m [a]
handleMultipart UploadPolicy
uploadPolicy PartProcessor a
origPartHandler = do
([FormParam]
captures, [a] -> [a]
files) <- forall (m :: * -> *) a.
MonadSnap m =>
UploadPolicy -> PartFold a -> a -> m ([FormParam], a)
foldMultipart UploadPolicy
uploadPolicy forall {c}.
PartInfo -> InputStream ByteString -> ([a] -> c) -> IO ([a] -> c)
partFold forall a. a -> a
id
forall {f :: * -> *}. MonadSnap f => [FormParam] -> f ()
procCaptures [FormParam]
captures
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! [a] -> [a]
files []
where
partFold :: PartInfo -> InputStream ByteString -> ([a] -> c) -> IO ([a] -> c)
partFold PartInfo
info InputStream ByteString
input [a] -> c
acc = do
a
x <- PartProcessor a
origPartHandler PartInfo
info InputStream ByteString
input
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ [a] -> c
acc forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([a
x]forall a. [a] -> [a] -> [a]
++)
procCaptures :: [FormParam] -> f ()
procCaptures [] = forall (f :: * -> *) a. Applicative f => a -> f a
pure ()
procCaptures [FormParam]
params = do
Request
rq <- forall (m :: * -> *). MonadSnap m => m Request
getRequest
forall (m :: * -> *). MonadSnap m => Request -> m ()
putRequest forall a b. (a -> b) -> a -> b
$ (Params -> Params) -> Request -> Request
modifyParams (\Params
m -> forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr forall {k} {a}. Ord k => (k, a) -> Map k [a] -> Map k [a]
ins Params
m [FormParam]
params) Request
rq
ins :: (k, a) -> Map k [a] -> Map k [a]
ins (!k
k, !a
v) = forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
Map.insertWith (\[a]
_ [a]
ex -> (a
vforall a. a -> [a] -> [a]
:[a]
ex)) k
k [a
v]
modifyParams :: (Params -> Params) -> Request -> Request
modifyParams Params -> Params
f Request
r = Request
r { rqPostParams :: Params
rqPostParams = Params -> Params
f forall a b. (a -> b) -> a -> b
$ Request -> Params
rqPostParams Request
r
, rqParams :: Params
rqParams = Params -> Params
f forall a b. (a -> b) -> a -> b
$ Request -> Params
rqParams Request
r
}
data PartDisposition =
DispositionAttachment
| DispositionFile
| DispositionFormData
| DispositionOther ByteString
deriving (PartDisposition -> PartDisposition -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PartDisposition -> PartDisposition -> Bool
$c/= :: PartDisposition -> PartDisposition -> Bool
== :: PartDisposition -> PartDisposition -> Bool
$c== :: PartDisposition -> PartDisposition -> Bool
Eq, Int -> PartDisposition -> ShowS
[PartDisposition] -> ShowS
PartDisposition -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [PartDisposition] -> ShowS
$cshowList :: [PartDisposition] -> ShowS
show :: PartDisposition -> FilePath
$cshow :: PartDisposition -> FilePath
showsPrec :: Int -> PartDisposition -> ShowS
$cshowsPrec :: Int -> PartDisposition -> ShowS
Show)
data PartInfo =
PartInfo
{ PartInfo -> ByteString
partFieldName :: !ByteString
, PartInfo -> Maybe ByteString
partFileName :: !(Maybe ByteString)
, PartInfo -> ByteString
partContentType :: !ByteString
, PartInfo -> PartDisposition
partDisposition :: !PartDisposition
, :: !Headers
}
deriving (Int -> PartInfo -> ShowS
[PartInfo] -> ShowS
PartInfo -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [PartInfo] -> ShowS
$cshowList :: [PartInfo] -> ShowS
show :: PartInfo -> FilePath
$cshow :: PartInfo -> FilePath
showsPrec :: Int -> PartInfo -> ShowS
$cshowsPrec :: Int -> PartInfo -> ShowS
Show)
toPartDisposition :: ByteString -> PartDisposition
toPartDisposition :: ByteString -> PartDisposition
toPartDisposition ByteString
s | ByteString
s forall a. Eq a => a -> a -> Bool
== ByteString
"attachment" = PartDisposition
DispositionAttachment
| ByteString
s forall a. Eq a => a -> a -> Bool
== ByteString
"file" = PartDisposition
DispositionFile
| ByteString
s forall a. Eq a => a -> a -> Bool
== ByteString
"form-data" = PartDisposition
DispositionFormData
| Bool
otherwise = ByteString -> PartDisposition
DispositionOther ByteString
s
data FileUploadException = forall e . (ExceptionWithReason e, Show e) =>
WrappedFileUploadException e
deriving (Typeable)
class Exception e => ExceptionWithReason e where
exceptionReason :: e -> Text
instance Show FileUploadException where
show :: FileUploadException -> FilePath
show (WrappedFileUploadException e
e) = forall a. Show a => a -> FilePath
show e
e
instance Exception FileUploadException
fileUploadExceptionReason :: FileUploadException -> Text
fileUploadExceptionReason :: FileUploadException -> Text
fileUploadExceptionReason (WrappedFileUploadException e
e) = forall e. ExceptionWithReason e => e -> Text
exceptionReason e
e
uploadExceptionToException :: ExceptionWithReason e => e -> SomeException
uploadExceptionToException :: forall e. ExceptionWithReason e => e -> SomeException
uploadExceptionToException = forall e. Exception e => e -> SomeException
toException forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall e.
(ExceptionWithReason e, Show e) =>
e -> FileUploadException
WrappedFileUploadException
uploadExceptionFromException :: ExceptionWithReason e => SomeException -> Maybe e
uploadExceptionFromException :: forall e. ExceptionWithReason e => SomeException -> Maybe e
uploadExceptionFromException SomeException
x = do
WrappedFileUploadException e
e <- forall e. Exception e => SomeException -> Maybe e
fromException SomeException
x
forall a b. (Typeable a, Typeable b) => a -> Maybe b
cast e
e
data BadPartException = BadPartException {
BadPartException -> Text
badPartExceptionReason :: Text
}
deriving (Typeable)
instance Exception BadPartException where
toException :: BadPartException -> SomeException
toException = forall e. ExceptionWithReason e => e -> SomeException
uploadExceptionToException
fromException :: SomeException -> Maybe BadPartException
fromException = forall e. ExceptionWithReason e => SomeException -> Maybe e
uploadExceptionFromException
instance ExceptionWithReason BadPartException where
exceptionReason :: BadPartException -> Text
exceptionReason (BadPartException Text
e) = [Text] -> Text
T.concat [Text
"Bad part: ", Text
e]
instance Show BadPartException where
show :: BadPartException -> FilePath
show = Text -> FilePath
T.unpack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall e. ExceptionWithReason e => e -> Text
exceptionReason
data PolicyViolationException = PolicyViolationException {
PolicyViolationException -> Text
policyViolationExceptionReason :: Text
} deriving (Typeable)
instance Exception PolicyViolationException where
toException :: PolicyViolationException -> SomeException
toException e :: PolicyViolationException
e@(PolicyViolationException Text
_) =
forall e. ExceptionWithReason e => e -> SomeException
uploadExceptionToException PolicyViolationException
e
fromException :: SomeException -> Maybe PolicyViolationException
fromException = forall e. ExceptionWithReason e => SomeException -> Maybe e
uploadExceptionFromException
instance ExceptionWithReason PolicyViolationException where
exceptionReason :: PolicyViolationException -> Text
exceptionReason (PolicyViolationException Text
r) =
[Text] -> Text
T.concat [Text
"File upload policy violation: ", Text
r]
instance Show PolicyViolationException where
show :: PolicyViolationException -> FilePath
show (PolicyViolationException Text
s) = FilePath
"File upload policy violation: "
forall a. [a] -> [a] -> [a]
++ Text -> FilePath
T.unpack Text
s
data UploadPolicy = UploadPolicy {
UploadPolicy -> Bool
processFormInputs :: Bool
, UploadPolicy -> Int64
maximumFormInputSize :: Int64
, UploadPolicy -> Int
maximumNumberOfFormInputs :: Int
, UploadPolicy -> Double
minimumUploadRate :: Double
, UploadPolicy -> Int
minimumUploadSeconds :: Int
, UploadPolicy -> Int
uploadTimeout :: Int
}
defaultUploadPolicy :: UploadPolicy
defaultUploadPolicy :: UploadPolicy
defaultUploadPolicy = Bool -> Int64 -> Int -> Double -> Int -> Int -> UploadPolicy
UploadPolicy Bool
True Int64
maxSize Int
maxNum Double
minRate Int
minSeconds Int
tout
where
maxSize :: Int64
maxSize = Int64
2forall a b. (Num a, Integral b) => a -> b -> a
^(Int
17::Int)
maxNum :: Int
maxNum = Int
10
minRate :: Double
minRate = Double
1000
minSeconds :: Int
minSeconds = Int
10
tout :: Int
tout = Int
20
doProcessFormInputs :: UploadPolicy -> Bool
doProcessFormInputs :: UploadPolicy -> Bool
doProcessFormInputs = UploadPolicy -> Bool
processFormInputs
setProcessFormInputs :: Bool -> UploadPolicy -> UploadPolicy
setProcessFormInputs :: Bool -> UploadPolicy -> UploadPolicy
setProcessFormInputs Bool
b UploadPolicy
u = UploadPolicy
u { processFormInputs :: Bool
processFormInputs = Bool
b }
getMaximumFormInputSize :: UploadPolicy -> Int64
getMaximumFormInputSize :: UploadPolicy -> Int64
getMaximumFormInputSize = UploadPolicy -> Int64
maximumFormInputSize
setMaximumFormInputSize :: Int64 -> UploadPolicy -> UploadPolicy
setMaximumFormInputSize :: Int64 -> UploadPolicy -> UploadPolicy
setMaximumFormInputSize Int64
s UploadPolicy
u = UploadPolicy
u { maximumFormInputSize :: Int64
maximumFormInputSize = Int64
s }
getMaximumNumberOfFormInputs :: UploadPolicy -> Int
getMaximumNumberOfFormInputs :: UploadPolicy -> Int
getMaximumNumberOfFormInputs = UploadPolicy -> Int
maximumNumberOfFormInputs
setMaximumNumberOfFormInputs :: Int -> UploadPolicy -> UploadPolicy
setMaximumNumberOfFormInputs :: Int -> UploadPolicy -> UploadPolicy
setMaximumNumberOfFormInputs Int
s UploadPolicy
u = UploadPolicy
u { maximumNumberOfFormInputs :: Int
maximumNumberOfFormInputs = Int
s }
getMinimumUploadRate :: UploadPolicy -> Double
getMinimumUploadRate :: UploadPolicy -> Double
getMinimumUploadRate = UploadPolicy -> Double
minimumUploadRate
setMinimumUploadRate :: Double -> UploadPolicy -> UploadPolicy
setMinimumUploadRate :: Double -> UploadPolicy -> UploadPolicy
setMinimumUploadRate Double
s UploadPolicy
u = UploadPolicy
u { minimumUploadRate :: Double
minimumUploadRate = Double
s }
getMinimumUploadSeconds :: UploadPolicy -> Int
getMinimumUploadSeconds :: UploadPolicy -> Int
getMinimumUploadSeconds = UploadPolicy -> Int
minimumUploadSeconds
setMinimumUploadSeconds :: Int -> UploadPolicy -> UploadPolicy
setMinimumUploadSeconds :: Int -> UploadPolicy -> UploadPolicy
setMinimumUploadSeconds Int
s UploadPolicy
u = UploadPolicy
u { minimumUploadSeconds :: Int
minimumUploadSeconds = Int
s }
getUploadTimeout :: UploadPolicy -> Int
getUploadTimeout :: UploadPolicy -> Int
getUploadTimeout = UploadPolicy -> Int
uploadTimeout
setUploadTimeout :: Int -> UploadPolicy -> UploadPolicy
setUploadTimeout :: Int -> UploadPolicy -> UploadPolicy
setUploadTimeout Int
s UploadPolicy
u = UploadPolicy
u { uploadTimeout :: Int
uploadTimeout = Int
s }
data FileUploadPolicy = FileUploadPolicy
{ FileUploadPolicy -> Int64
maxFileUploadSize :: !Int64
, FileUploadPolicy -> Int
maxNumberOfFiles :: !Int
, FileUploadPolicy -> Bool
skipEmptyFileName :: !Bool
, FileUploadPolicy -> Int64
maxEmptyFileNameSize :: !Int64
}
defaultFileUploadPolicy :: FileUploadPolicy
defaultFileUploadPolicy :: FileUploadPolicy
defaultFileUploadPolicy = Int64 -> Int -> Bool -> Int64 -> FileUploadPolicy
FileUploadPolicy Int64
maxFileSize Int
maxFiles
Bool
skipEmptyName Int64
maxEmptySize
where
maxFileSize :: Int64
maxFileSize = Int64
1048576
maxFiles :: Int
maxFiles = Int
10
skipEmptyName :: Bool
skipEmptyName = Bool
True
maxEmptySize :: Int64
maxEmptySize = Int64
0
setMaximumFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy
setMaximumFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy
setMaximumFileSize Int64
maxSize FileUploadPolicy
s =
FileUploadPolicy
s { maxFileUploadSize :: Int64
maxFileUploadSize = Int64
maxSize }
setMaximumNumberOfFiles :: Int -> FileUploadPolicy -> FileUploadPolicy
setMaximumNumberOfFiles :: Int -> FileUploadPolicy -> FileUploadPolicy
setMaximumNumberOfFiles Int
maxFiles FileUploadPolicy
s =
FileUploadPolicy
s { maxNumberOfFiles :: Int
maxNumberOfFiles = Int
maxFiles }
setSkipFilesWithoutNames :: Bool -> FileUploadPolicy -> FileUploadPolicy
setSkipFilesWithoutNames :: Bool -> FileUploadPolicy -> FileUploadPolicy
setSkipFilesWithoutNames Bool
shouldSkip FileUploadPolicy
s =
FileUploadPolicy
s { skipEmptyFileName :: Bool
skipEmptyFileName = Bool
shouldSkip }
setMaximumSkippedFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy
setMaximumSkippedFileSize :: Int64 -> FileUploadPolicy -> FileUploadPolicy
setMaximumSkippedFileSize Int64
maxSize FileUploadPolicy
s =
FileUploadPolicy
s { maxEmptyFileNameSize :: Int64
maxEmptyFileNameSize = Int64
maxSize }
data PartUploadPolicy = PartUploadPolicy (Maybe Int64)
disallow :: PartUploadPolicy
disallow :: PartUploadPolicy
disallow = Maybe Int64 -> PartUploadPolicy
PartUploadPolicy forall a. Maybe a
Nothing
allowWithMaximumSize :: Int64 -> PartUploadPolicy
allowWithMaximumSize :: Int64 -> PartUploadPolicy
allowWithMaximumSize = Maybe Int64 -> PartUploadPolicy
PartUploadPolicy forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just
storeAsLazyByteString :: InputStream ByteString -> IO LB.ByteString
storeAsLazyByteString :: InputStream ByteString -> IO ByteString
storeAsLazyByteString !InputStream ByteString
str = do
ByteString -> ByteString
f <- forall s a. (s -> a -> s) -> s -> InputStream a -> IO s
Streams.fold (\ByteString -> ByteString
f ByteString
c -> ByteString -> ByteString
f forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> ByteString -> ByteString
LB.chunk ByteString
c) forall a. a -> a
id InputStream ByteString
str
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! ByteString -> ByteString
f ByteString
LB.Empty
withTemporaryStore ::
MonadSnap m
=> FilePath
-> String
-> ((InputStream ByteString -> IO FilePath) -> m a)
-> m a
withTemporaryStore :: forall (m :: * -> *) a.
MonadSnap m =>
FilePath
-> FilePath
-> ((InputStream ByteString -> IO FilePath) -> m a)
-> m a
withTemporaryStore FilePath
tempdir FilePath
pat (InputStream ByteString -> IO FilePath) -> m a
act = do
IORef [FilePath]
ioref <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ forall a. a -> IO (IORef a)
IORef.newIORef []
let
modifyIORef' :: IORef a -> (a -> a) -> IO ()
modifyIORef' IORef a
ref a -> a
f = do
a
x <- forall a. IORef a -> IO a
IORef.readIORef IORef a
ref
let x' :: a
x' = a -> a
f a
x
a
x' seq :: forall a b. a -> b -> b
`seq` forall a. IORef a -> a -> IO ()
IORef.writeIORef IORef a
ref a
x'
go :: InputStream ByteString -> IO FilePath
go InputStream ByteString
input = do
(FilePath
fn, Handle
h) <- FilePath -> FilePath -> IO (FilePath, Handle)
openBinaryTempFile FilePath
tempdir FilePath
pat
forall {a}. IORef a -> (a -> a) -> IO ()
modifyIORef' IORef [FilePath]
ioref (FilePath
fnforall a. a -> [a] -> [a]
:)
Handle -> BufferMode -> IO ()
hSetBuffering Handle
h BufferMode
NoBuffering
OutputStream ByteString
output <- Handle -> IO (OutputStream ByteString)
Streams.handleToOutputStream Handle
h
forall a. InputStream a -> OutputStream a -> IO ()
Streams.connect InputStream ByteString
input OutputStream ByteString
output
Handle -> IO ()
hClose Handle
h
forall (f :: * -> *) a. Applicative f => a -> f a
pure FilePath
fn
cleanup :: m ()
cleanup = forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ do
[FilePath]
files <- forall a. IORef a -> IO a
IORef.readIORef IORef [FilePath]
ioref
forall (t :: * -> *) (m :: * -> *) a b.
(Foldable t, Monad m) =>
t a -> (a -> m b) -> m ()
forM_ [FilePath]
files forall a b. (a -> b) -> a -> b
$ \FilePath
fn ->
FilePath -> IO ()
removeFile FilePath
fn forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {f :: * -> *}. MonadBase IO f => IOError -> f ()
handleExists
handleExists :: IOError -> f ()
handleExists IOError
e = forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless (IOError -> Bool
isDoesNotExistError IOError
e) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO IOError
e
(InputStream ByteString -> IO FilePath) -> m a
act InputStream ByteString -> IO FilePath
go forall (m :: * -> *) a b.
MonadBaseControl IO m =>
m a -> m b -> m a
`finally` m ()
cleanup
captureVariableOrReadFile ::
Int64
-> PartFold a
-> PartInfo -> InputStream ByteString
-> a
-> IO (Capture a)
captureVariableOrReadFile :: forall a.
Int64
-> PartFold a
-> PartInfo
-> InputStream ByteString
-> a
-> IO (Capture a)
captureVariableOrReadFile Int64
maxSize PartFold a
fileHandler PartInfo
partInfo InputStream ByteString
stream a
acc =
if Bool
isFile
then forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM forall a. a -> Capture a
File forall a b. (a -> b) -> a -> b
$ PartFold a
fileHandler PartInfo
partInfo InputStream ByteString
stream a
acc
else forall {a}. IO (Capture a)
variable forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {m :: * -> *} {a}.
MonadBase IO m =>
TooManyBytesReadException -> m a
handler
where
isFile :: Bool
isFile = forall a. Maybe a -> Bool
isJust (PartInfo -> Maybe ByteString
partFileName PartInfo
partInfo) Bool -> Bool -> Bool
||
PartInfo -> PartDisposition
partDisposition PartInfo
partInfo forall a. Eq a => a -> a -> Bool
== PartDisposition
DispositionFile
variable :: IO (Capture a)
variable = do
!ByteString
x <- forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM [ByteString] -> ByteString
S.concat forall a b. (a -> b) -> a -> b
$
Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.throwIfProducesMoreThan Int64
maxSize InputStream ByteString
stream forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall a. InputStream a -> IO [a]
Streams.toList
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall a. ByteString -> ByteString -> Capture a
Capture ByteString
fieldName ByteString
x
fieldName :: ByteString
fieldName = PartInfo -> ByteString
partFieldName PartInfo
partInfo
handler :: TooManyBytesReadException -> m a
handler (TooManyBytesReadException
_ :: TooManyBytesReadException) =
forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall a b. (a -> b) -> a -> b
$ Text -> PolicyViolationException
PolicyViolationException forall a b. (a -> b) -> a -> b
$
[Text] -> Text
T.concat [ Text
"form input '"
, ByteString -> Text
TE.decodeUtf8 ByteString
fieldName
, Text
"' exceeded maximum permissible size ("
, FilePath -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> FilePath
show Int64
maxSize
, Text
" bytes)" ]
data Capture a = Capture !ByteString !ByteString
| File a
fileReader :: FilePath
-> (PartInfo -> Either PolicyViolationException FilePath -> IO a)
-> PartProcessor a
fileReader :: forall a.
FilePath
-> (PartInfo -> Either PolicyViolationException FilePath -> IO a)
-> PartProcessor a
fileReader FilePath
tmpdir PartInfo -> Either PolicyViolationException FilePath -> IO a
partProc PartInfo
partInfo InputStream ByteString
input =
forall a.
FilePath -> FilePath -> ((FilePath, Handle) -> IO a) -> IO a
withTempFile FilePath
tmpdir FilePath
"snap-upload-" forall a b. (a -> b) -> a -> b
$ \(FilePath
fn, Handle
h) -> do
Handle -> BufferMode -> IO ()
hSetBuffering Handle
h BufferMode
NoBuffering
OutputStream ByteString
output <- Handle -> IO (OutputStream ByteString)
Streams.handleToOutputStream Handle
h
forall a. InputStream a -> OutputStream a -> IO ()
Streams.connect InputStream ByteString
input OutputStream ByteString
output
Handle -> IO ()
hClose Handle
h
PartInfo -> Either PolicyViolationException FilePath -> IO a
partProc PartInfo
partInfo forall a b. (a -> b) -> a -> b
$ forall a b. b -> Either a b
Right FilePath
fn
data MultipartState a = MultipartState
{ forall a. MultipartState a -> Int
numFormVars :: {-# UNPACK #-} !Int
, forall a. MultipartState a -> Int
numFormFiles :: {-# UNPACK #-} !Int
, forall a. MultipartState a -> [FormParam] -> [FormParam]
capturedFields :: !([FormParam] -> [FormParam])
, forall a. MultipartState a -> a
accumulator :: !a
}
type FormParam = (ByteString, ByteString)
addCapture :: ByteString -> ByteString -> MultipartState a -> MultipartState a
addCapture :: forall a.
ByteString -> ByteString -> MultipartState a -> MultipartState a
addCapture !ByteString
k !ByteString
v !MultipartState a
ms =
let !kv :: FormParam
kv = (ByteString
k,ByteString
v)
f :: [FormParam] -> [FormParam]
f = forall a. MultipartState a -> [FormParam] -> [FormParam]
capturedFields MultipartState a
ms forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([FormParam
kv]forall a. [a] -> [a] -> [a]
++)
!ms' :: MultipartState a
ms' = MultipartState a
ms { capturedFields :: [FormParam] -> [FormParam]
capturedFields = [FormParam] -> [FormParam]
f
, numFormVars :: Int
numFormVars = forall a. Enum a => a -> a
succ (forall a. MultipartState a -> Int
numFormVars MultipartState a
ms) }
in MultipartState a
ms'
internalFoldMultipart ::
Int
-> ByteString
-> (PartInfo -> InputStream ByteString -> a -> IO (Capture a))
-> a
-> InputStream ByteString
-> IO ([FormParam], a)
internalFoldMultipart :: forall a.
Int
-> ByteString
-> (PartInfo -> InputStream ByteString -> a -> IO (Capture a))
-> a
-> InputStream ByteString
-> IO ([FormParam], a)
internalFoldMultipart !Int
maxFormVars !ByteString
boundary PartInfo -> InputStream ByteString -> a -> IO (Capture a)
clientHandler !a
zeroAcc !InputStream ByteString
stream = IO ([FormParam], a)
go
where
initialState :: MultipartState a
initialState = forall a.
Int -> Int -> ([FormParam] -> [FormParam]) -> a -> MultipartState a
MultipartState Int
0 Int
0 forall a. a -> a
id a
zeroAcc
go :: IO ([FormParam], a)
go = do
ByteString
_ <- forall r. Parser r -> InputStream ByteString -> IO r
parseFromStream (ByteString -> Parser ByteString ByteString
parseFirstBoundary ByteString
boundary) InputStream ByteString
stream
InputStream MatchInfo
bmstream <- ByteString -> InputStream ByteString -> IO (InputStream MatchInfo)
search (ByteString -> ByteString
fullBoundary ByteString
boundary) InputStream ByteString
stream
MultipartState a
ms <- forall a.
(InputStream ByteString
-> MultipartState a -> IO (MultipartState a))
-> InputStream MatchInfo
-> MultipartState a
-> IO (MultipartState a)
foldParts InputStream ByteString -> MultipartState a -> IO (MultipartState a)
goPart InputStream MatchInfo
bmstream MultipartState a
initialState
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ (forall a. MultipartState a -> [FormParam] -> [FormParam]
capturedFields MultipartState a
ms [], forall a. MultipartState a -> a
accumulator MultipartState a
ms)
pBoundary :: ByteString -> Parser ByteString ByteString
pBoundary !ByteString
b = forall i a. Parser i a -> Parser i a
Atto.try forall a b. (a -> b) -> a -> b
$ do
ByteString
_ <- ByteString -> Parser ByteString ByteString
string ByteString
"--"
ByteString -> Parser ByteString ByteString
string ByteString
b
fullBoundary :: ByteString -> ByteString
fullBoundary !ByteString
b = [ByteString] -> ByteString
S.concat [ByteString
"\r\n", ByteString
"--", ByteString
b]
pLine :: Parser ByteString ByteString
pLine = (Char -> Bool) -> Parser ByteString ByteString
takeWhile (Bool -> Bool
not forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> Bool
isEndOfLine forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Word8
c2w) forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString ByteString
eol
parseFirstBoundary :: ByteString -> Parser ByteString ByteString
parseFirstBoundary !ByteString
b = ByteString -> Parser ByteString ByteString
pBoundary ByteString
b forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (Parser ByteString ByteString
pLine forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> ByteString -> Parser ByteString ByteString
parseFirstBoundary ByteString
b)
takeHeaders :: InputStream ByteString -> IO Headers
takeHeaders !InputStream ByteString
str = IO Headers
hdrs forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> (e -> m a) -> m a
`catch` forall {m :: * -> *} {a}.
MonadBase IO m =>
TooManyBytesReadException -> m a
handler
where
hdrs :: IO Headers
hdrs = do
InputStream ByteString
str' <- Int64 -> InputStream ByteString -> IO (InputStream ByteString)
Streams.throwIfProducesMoreThan Int64
mAX_HDRS_SIZE InputStream ByteString
str
forall (m :: * -> *) a1 r. Monad m => (a1 -> r) -> m a1 -> m r
liftM [FormParam] -> Headers
toHeaders forall a b. (a -> b) -> a -> b
$ forall r. Parser r -> InputStream ByteString -> IO r
parseFromStream Parser [FormParam]
pHeadersWithSeparator InputStream ByteString
str'
handler :: TooManyBytesReadException -> m a
handler (TooManyBytesReadException
_ :: TooManyBytesReadException) =
forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall a b. (a -> b) -> a -> b
$ Text -> BadPartException
BadPartException Text
"headers exceeded maximum size"
goPart :: InputStream ByteString -> MultipartState a -> IO (MultipartState a)
goPart !InputStream ByteString
str !MultipartState a
state = do
Headers
hdrs <- InputStream ByteString -> IO Headers
takeHeaders InputStream ByteString
str
let (ByteString
contentType, Maybe ByteString
mboundary) = Headers -> (ByteString, Maybe ByteString)
getContentType Headers
hdrs
let (ByteString
fieldName, Maybe ByteString
fileName, PartDisposition
disposition) = Headers -> (ByteString, Maybe ByteString, PartDisposition)
getFieldHeaderInfo Headers
hdrs
if ByteString
contentType forall a. Eq a => a -> a -> Bool
== ByteString
"multipart/mixed"
then forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall a b. (a -> b) -> a -> b
$ Text -> BadPartException
BadPartException forall a b. (a -> b) -> a -> b
$
Text
"got multipart/mixed without boundary")
(ByteString
-> InputStream ByteString
-> MultipartState a
-> ByteString
-> IO (MultipartState a)
processMixed ByteString
fieldName InputStream ByteString
str MultipartState a
state)
Maybe ByteString
mboundary
else do
let info :: PartInfo
info = ByteString
-> Maybe ByteString
-> ByteString
-> PartDisposition
-> Headers
-> PartInfo
PartInfo ByteString
fieldName Maybe ByteString
fileName ByteString
contentType PartDisposition
disposition Headers
hdrs
PartInfo
-> InputStream ByteString
-> MultipartState a
-> IO (MultipartState a)
handlePart PartInfo
info InputStream ByteString
str MultipartState a
state
handlePart :: PartInfo
-> InputStream ByteString
-> MultipartState a
-> IO (MultipartState a)
handlePart !PartInfo
info !InputStream ByteString
str !MultipartState a
ms = do
Capture a
r <- PartInfo -> InputStream ByteString -> a -> IO (Capture a)
clientHandler PartInfo
info InputStream ByteString
str (forall a. MultipartState a -> a
accumulator MultipartState a
ms)
case Capture a
r of
Capture !ByteString
k !ByteString
v -> do
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
maxFormVars forall a. Ord a => a -> a -> Bool
<= forall a. MultipartState a -> Int
numFormVars MultipartState a
ms) forall {a}. IO a
throwTooMuchVars
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall a.
ByteString -> ByteString -> MultipartState a -> MultipartState a
addCapture ByteString
k ByteString
v MultipartState a
ms
File !a
newAcc -> forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! MultipartState a
ms { accumulator :: a
accumulator = a
newAcc
, numFormFiles :: Int
numFormFiles = forall a. Enum a => a -> a
succ (forall a. MultipartState a -> Int
numFormFiles MultipartState a
ms)
}
throwTooMuchVars :: IO a
throwTooMuchVars =
forall (m :: * -> *) e a. (MonadBase IO m, Exception e) => e -> m a
throwIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> PolicyViolationException
PolicyViolationException
forall a b. (a -> b) -> a -> b
$ [Text] -> Text
T.concat [ Text
"number of form inputs exceeded maximum of "
, FilePath -> Text
T.pack forall a b. (a -> b) -> a -> b
$ forall a. Show a => a -> FilePath
show Int
maxFormVars ]
processMixed :: ByteString
-> InputStream ByteString
-> MultipartState a
-> ByteString
-> IO (MultipartState a)
processMixed !ByteString
fieldName !InputStream ByteString
str !MultipartState a
state !ByteString
mixedBoundary = do
ByteString
_ <- forall r. Parser r -> InputStream ByteString -> IO r
parseFromStream (ByteString -> Parser ByteString ByteString
parseFirstBoundary ByteString
mixedBoundary) InputStream ByteString
str
InputStream MatchInfo
bm <- ByteString -> InputStream ByteString -> IO (InputStream MatchInfo)
search (ByteString -> ByteString
fullBoundary ByteString
mixedBoundary) InputStream ByteString
str
forall a.
(InputStream ByteString
-> MultipartState a -> IO (MultipartState a))
-> InputStream MatchInfo
-> MultipartState a
-> IO (MultipartState a)
foldParts (ByteString
-> InputStream ByteString
-> MultipartState a
-> IO (MultipartState a)
mixedStream ByteString
fieldName) InputStream MatchInfo
bm MultipartState a
state
mixedStream :: ByteString
-> InputStream ByteString
-> MultipartState a
-> IO (MultipartState a)
mixedStream !ByteString
fieldName !InputStream ByteString
str !MultipartState a
acc = do
Headers
hdrs <- InputStream ByteString -> IO Headers
takeHeaders InputStream ByteString
str
let (ByteString
contentType, Maybe ByteString
_) = Headers -> (ByteString, Maybe ByteString)
getContentType Headers
hdrs
let (ByteString
_, Maybe ByteString
fileName, PartDisposition
disposition) = Headers -> (ByteString, Maybe ByteString, PartDisposition)
getFieldHeaderInfo Headers
hdrs
let info :: PartInfo
info = ByteString
-> Maybe ByteString
-> ByteString
-> PartDisposition
-> Headers
-> PartInfo
PartInfo ByteString
fieldName Maybe ByteString
fileName ByteString
contentType PartDisposition
disposition Headers
hdrs
PartInfo
-> InputStream ByteString
-> MultipartState a
-> IO (MultipartState a)
handlePart PartInfo
info InputStream ByteString
str MultipartState a
acc
getContentType :: Headers
-> (ByteString, Maybe ByteString)
getContentType :: Headers -> (ByteString, Maybe ByteString)
getContentType Headers
hdrs = (ByteString
contentType, Maybe ByteString
boundary)
where
contentTypeValue :: ByteString
contentTypeValue = forall a. a -> Maybe a -> a
fromMaybe ByteString
"text/plain" forall a b. (a -> b) -> a -> b
$
forall a. HasHeaders a => CI ByteString -> a -> Maybe ByteString
getHeader CI ByteString
"content-type" Headers
hdrs
eCT :: Either FilePath (ByteString, [(CI ByteString, ByteString)])
eCT = forall a. ByteString -> Parser a -> Either FilePath a
fullyParse ByteString
contentTypeValue Parser (ByteString, [(CI ByteString, ByteString)])
pContentTypeWithParameters
(!ByteString
contentType, ![(CI ByteString, ByteString)]
params) = forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (forall a b. a -> b -> a
const (ByteString
"text/plain", [])) forall a. a -> a
id Either FilePath (ByteString, [(CI ByteString, ByteString)])
eCT
boundary :: Maybe ByteString
boundary = forall a b. Eq a => a -> [(a, b)] -> Maybe b
findParam CI ByteString
"boundary" [(CI ByteString, ByteString)]
params
getFieldHeaderInfo :: Headers -> (ByteString, Maybe ByteString, PartDisposition)
Headers
hdrs = (ByteString
fieldName, Maybe ByteString
fileName, PartDisposition
disposition)
where
contentDispositionValue :: ByteString
contentDispositionValue = forall a. a -> Maybe a -> a
fromMaybe ByteString
"unknown" forall a b. (a -> b) -> a -> b
$
forall a. HasHeaders a => CI ByteString -> a -> Maybe ByteString
getHeader CI ByteString
"content-disposition" Headers
hdrs
eDisposition :: Either FilePath (ByteString, [(CI ByteString, ByteString)])
eDisposition = forall a. ByteString -> Parser a -> Either FilePath a
fullyParse ByteString
contentDispositionValue forall a b. (a -> b) -> a -> b
$ (Char -> Bool)
-> Parser (ByteString, [(CI ByteString, ByteString)])
pValueWithParameters' (forall a b. a -> b -> a
const Bool
True)
(!ByteString
dispositionType, [(CI ByteString, ByteString)]
dispositionParameters) =
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (forall a b. a -> b -> a
const (ByteString
"unknown", [])) forall a. a -> a
id Either FilePath (ByteString, [(CI ByteString, ByteString)])
eDisposition
disposition :: PartDisposition
disposition = ByteString -> PartDisposition
toPartDisposition ByteString
dispositionType
fieldName :: ByteString
fieldName = forall a. a -> Maybe a -> a
fromMaybe ByteString
"" forall a b. (a -> b) -> a -> b
$ forall a b. Eq a => a -> [(a, b)] -> Maybe b
findParam CI ByteString
"name" [(CI ByteString, ByteString)]
dispositionParameters
fileName :: Maybe ByteString
fileName = forall a b. Eq a => a -> [(a, b)] -> Maybe b
findParam CI ByteString
"filename" [(CI ByteString, ByteString)]
dispositionParameters
findParam :: (Eq a) => a -> [(a, b)] -> Maybe b
findParam :: forall a b. Eq a => a -> [(a, b)] -> Maybe b
findParam a
p = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a b. (a, b) -> b
snd forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((forall a. Eq a => a -> a -> Bool
== a
p) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst)
partStream :: InputStream MatchInfo -> IO (InputStream ByteString)
partStream :: InputStream MatchInfo -> IO (InputStream ByteString)
partStream InputStream MatchInfo
st = forall a. IO (Maybe a) -> IO (InputStream a)
Streams.makeInputStream IO (Maybe ByteString)
go
where
go :: IO (Maybe ByteString)
go = do
Maybe MatchInfo
s <- forall a. InputStream a -> IO (Maybe a)
Streams.read InputStream MatchInfo
st
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! Maybe MatchInfo
s forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall {m :: * -> *}. MonadPlus m => MatchInfo -> m ByteString
f
f :: MatchInfo -> m ByteString
f (NoMatch ByteString
s) = forall (m :: * -> *) a. Monad m => a -> m a
return ByteString
s
f MatchInfo
_ = forall (m :: * -> *) a. MonadPlus m => m a
mzero
foldParts :: (InputStream ByteString -> MultipartState a -> IO (MultipartState a))
-> InputStream MatchInfo
-> (MultipartState a)
-> IO (MultipartState a)
foldParts :: forall a.
(InputStream ByteString
-> MultipartState a -> IO (MultipartState a))
-> InputStream MatchInfo
-> MultipartState a
-> IO (MultipartState a)
foldParts InputStream ByteString -> MultipartState a -> IO (MultipartState a)
partFunc InputStream MatchInfo
stream = MultipartState a -> IO (MultipartState a)
go
where
part :: MultipartState a
-> InputStream ByteString -> IO (Maybe (MultipartState a))
part MultipartState a
acc InputStream ByteString
pStream = do
Bool
isLast <- forall r. Parser r -> InputStream ByteString -> IO r
parseFromStream Parser ByteString Bool
pBoundaryEnd InputStream ByteString
pStream
if Bool
isLast
then forall (m :: * -> *) a. Monad m => a -> m a
return forall a. Maybe a
Nothing
else do
!MultipartState a
x <- InputStream ByteString -> MultipartState a -> IO (MultipartState a)
partFunc InputStream ByteString
pStream MultipartState a
acc
forall a. InputStream a -> IO ()
Streams.skipToEof InputStream ByteString
pStream
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! forall a. a -> Maybe a
Just MultipartState a
x
go :: MultipartState a -> IO (MultipartState a)
go !MultipartState a
acc = do
Maybe (MultipartState a)
cap <- InputStream MatchInfo -> IO (InputStream ByteString)
partStream InputStream MatchInfo
stream forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= MultipartState a
-> InputStream ByteString -> IO (Maybe (MultipartState a))
part MultipartState a
acc
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (m :: * -> *) a. Monad m => a -> m a
return MultipartState a
acc) MultipartState a -> IO (MultipartState a)
go Maybe (MultipartState a)
cap
pBoundaryEnd :: Parser ByteString Bool
pBoundaryEnd = (Parser ByteString ByteString
eol forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
False) forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (ByteString -> Parser ByteString ByteString
string ByteString
"--" forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> forall (f :: * -> *) a. Applicative f => a -> f a
pure Bool
True)
eol :: Parser ByteString
eol :: Parser ByteString ByteString
eol = (ByteString -> Parser ByteString ByteString
string ByteString
"\n") forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> (ByteString -> Parser ByteString ByteString
string ByteString
"\r\n")
pHeadersWithSeparator :: Parser [(ByteString,ByteString)]
= Parser [FormParam]
pHeaders forall (f :: * -> *) a b. Applicative f => f a -> f b -> f a
<* Parser ByteString ByteString
crlf
toHeaders :: [(ByteString,ByteString)] -> Headers
[FormParam]
kvps = [(CI ByteString, ByteString)] -> Headers
H.fromList [(CI ByteString, ByteString)]
kvps'
where
kvps' :: [(CI ByteString, ByteString)]
kvps' = forall a b. (a -> b) -> [a] -> [b]
map (forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (b, d) (c, d)
first forall s. FoldCase s => s -> CI s
CI.mk) [FormParam]
kvps
mAX_HDRS_SIZE :: Int64
mAX_HDRS_SIZE :: Int64
mAX_HDRS_SIZE = Int64
32768
withTempFile :: FilePath
-> String
-> ((FilePath, Handle) -> IO a)
-> IO a
withTempFile :: forall a.
FilePath -> FilePath -> ((FilePath, Handle) -> IO a) -> IO a
withTempFile FilePath
tmpl FilePath
temp (FilePath, Handle) -> IO a
handler =
forall (m :: * -> *) b.
MonadBaseControl IO m =>
((forall a. m a -> m a) -> m b) -> m b
mask forall a b. (a -> b) -> a -> b
$ \forall a. IO a -> IO a
restore -> forall (m :: * -> *) a b c.
MonadBaseControl IO m =>
m a -> (a -> m b) -> (a -> m c) -> m c
bracket IO (FilePath, Handle)
make (FilePath, Handle) -> IO [()]
cleanup (forall a. IO a -> IO a
restore forall b c a. (b -> c) -> (a -> b) -> a -> c
. (FilePath, Handle) -> IO a
handler)
where
make :: IO (FilePath, Handle)
make = FilePath -> IO (FilePath, Handle)
mkstemp forall a b. (a -> b) -> a -> b
$ FilePath
tmpl FilePath -> ShowS
</> (FilePath
temp forall a. [a] -> [a] -> [a]
++ FilePath
"XXXXXXX")
cleanup :: (FilePath, Handle) -> IO [()]
cleanup (FilePath
fp,Handle
h) = forall (t :: * -> *) (m :: * -> *) a.
(Traversable t, Monad m) =>
t (m a) -> m (t a)
sequence forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map forall {z}. IO z -> IO ()
gobble [Handle -> IO ()
hClose Handle
h, FilePath -> IO ()
removeFile FilePath
fp]
t :: IO z -> IO (Either SomeException z)
t :: forall z. IO z -> IO (Either SomeException z)
t = forall (m :: * -> *) e a.
(MonadBaseControl IO m, Exception e) =>
m a -> m (Either e a)
E.try
gobble :: IO z -> IO ()
gobble = forall (f :: * -> *) a. Functor f => f a -> f ()
void forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall z. IO z -> IO (Either SomeException z)
t