HAppS-Server-0.9.2: src/HAppS/Server/MessageWrap.hs
{-# OPTIONS -fglasgow-exts #-}
module HAppS.Server.MessageWrap where
{-
(ToMessage(..), toMessage, FromMessage(..),
-- Index(..),
reqPath, pathEls, -- getPath,
ReadString(..),
look, lookS, lookM, lookMb,
lookMbRead, mbRead, lookInput
,bodyToMap,bodyToList
) where
-}
import Control.Monad.Identity
import qualified Data.ByteString.Char8 as P
import qualified Data.ByteString.Lazy.Char8 as L
import qualified Data.List as List
import qualified Data.Map as M
import Data.Maybe
import HAppS.Server.HTTP.Types as H
import HAppS.Server.JSON
import HAppS.Server.MinHaXML
import HAppS.Server.HTTP.Multipart
import HAppS.Server.SURI as SURI
import HAppS.Util.Common
import qualified Text.XML.HaXml.Types as HaXml
import Data.Generics
{-
class ToMessage x where
toMessageM :: Monad m => x -> m H.Result
toMessageM x = do s <- toMessageBodyM x
H.sresult' 200 [s]
toMessageBodyM :: Monad m => x -> m P.ByteString
toMessageBodyM x = (P.concat . H.rsBody) `liftM` toMessageM x
-- | Non-monadic variant of toMessageM, mostly for backwards support.
toMessage :: ToMessage x => x -> Result
toMessage x = runIdentity $ toMessageM x
class FromMessage x where
fromMessage :: H.Request -> x
fromMessage msg = x where Identity x = fromMessageM msg
fromMessageM :: (Monad m,Functor m) => H.Request -> m x
fromMessageM = return . fromMessage
instance ToMessage H.Result where toMessageM = return
instance ToMessage String where toMessageM = H.sresult 200
instance ToMessage ([(String,String)],String) where
toMessageM (hdrs,val) =
do
msg <- toMessageM val
return $ foldr (\ (n,v) res ->setHeader n v res) msg hdrs
instance FromMessage () where fromMessage = const ()
instance FromMessage H.Request where fromMessage = id
instance (FromMessage m,FromMessage a) => FromMessage (m,a) where
fromMessageM m = liftM2 (,) (fromMessageM m) (fromMessageM m)
instance FromMessage m => FromMessage (Maybe m) where
fromMessageM m = return $ fromMessageM m
-}
{--instance (ToElement x)=>ToMessage (XML x) where
toMessageM (XML style obj) = liftM (addHeader "Content-Type" $ ctype el)
(toMessageM doc)
where
doc = (if ctype el == "text/html" then simpleDoc' NoStyle
else simpleDoc style) el
el = HaXml.Elem "alksjd" [] []
el' = toElement obj
ctype (HaXml.Elem "html" _ _) = "text/html"
ctype _ = "text/xxxxml"
-- ctype (HaXml.Elem "html" _ _) = error "html!!!"
instance (ToJSON x) => ToMessage (JSONCall x) where
toMessageM (JCall cb x) = liftM (addHeader "Content-Type" "text/javascript")
(toMessageM $ concat [cb,"(",jsonToString $ toJSON x,")"])
--}
{-
bodyToList :: Request -> [(String, String)]
bodyToList = map unInput . bodyToList'
where unInput (name, input) = (name, P.unpack $ inputValue input)
bodyToList' :: Request -> [(String, Input)]
bodyToList' req = queryInput req ++ bodyInput req
bodyToMap :: Request -> M.Map String String
bodyToMap = M.fromList . bodyToList
bodyToMap' :: Request -> M.Map String Input
bodyToMap' = M.fromList . bodyToList'
-}
queryInput :: SURI -> [(String, Input)]
queryInput uri = formDecode (case SURI.query $ uri of
'?':r -> r
xs -> xs)
bodyInput :: Request -> [(String, Input)]
bodyInput req | rqMethod req /= POST = []
bodyInput req =
let ctype = getHeader "content-type" req >>= parseContentType . P.unpack
getBS (Body bs) = bs
in decodeBody ctype (getBS $ rqBody req)
-- Decodes application\/x-www-form-urlencoded inputs.
formDecode :: String -> [(String, Input)]
formDecode [] = []
formDecode qString =
if null pairString then rest else
(SURI.unEscape name,simpleInput $ SURI.unEscape val):rest
where (pairString,qString')= split (=='&') qString
(name,val)=split (=='=') pairString
rest=if null qString' then [] else formDecode qString'
decodeBody :: Maybe ContentType
-> L.ByteString
-> [(String,Input)]
decodeBody ctype inp
= case ctype of
Just (ContentType "application" "x-www-form-urlencoded" _)
-> formDecode (L.unpack inp)
Just (ContentType "multipart" "form-data" ps)
-> multipartDecode ps inp
Just _ -> [] -- unknown content-type, the user will have to
-- deal with it by looking at the raw content
-- No content-type given, assume x-www-form-urlencoded
Nothing -> formDecode (L.unpack inp)
-- | Decodes multipart\/form-data input.
multipartDecode :: [(String,String)] -- ^ Content-type parameters
-> L.ByteString -- ^ Request body
-> [(String,Input)] -- ^ Input variables and values.
multipartDecode ps inp =
case lookup "boundary" ps of
Just b -> case parseMultipartBody b inp of
Just (MultiPart bs) -> map bodyPartToInput bs
Nothing -> [] -- FIXME: report parse error
Nothing -> [] -- FIXME: report that there was no boundary
bodyPartToInput :: BodyPart -> (String,Input)
bodyPartToInput (BodyPart hs b) =
case getContentDisposition hs of
Just (ContentDisposition "form-data" ps) ->
(fromMaybe "" $ lookup "name" ps,
Input { inputValue = b,
inputFilename = lookup "filename" ps,
inputContentType = ctype })
_ -> ("ERROR",simpleInput "ERROR") -- FIXME: report error
where ctype = fromMaybe defaultInputType (getContentType hs)
simpleInput :: String -> Input
simpleInput v
= Input { inputValue = L.pack v
, inputFilename = Nothing
, inputContentType = defaultInputType
}
-- | The default content-type for variables.
defaultInputType :: ContentType
defaultInputType = ContentType "text" "plain" [] -- FIXME: use some default encoding?
{-
-- | Get the path from a Request.
reqPath :: Request -> String
reqPath = SURI.path . rqURI
-}
-- | Get the path components from a String.
pathEls :: String -> [String]
pathEls = (drop 1) . map SURI.unEscape . splitList '/'
-- filter (not.null) $ map URI.unEscapeString $ splitList '/' path
{-
-- | Get the path components from the Request.
getPath :: Request -> [String]
getPath req= pathEls $ reqPath req
-}
-- | Like 'Read' except Strings and Chars not quoted.
class (Read a)=>ReadString a where readString::String->a; readString =read
instance ReadString Int
instance ReadString Double
instance ReadString Float
instance ReadString SURI.SURI where readString = read . show
instance ReadString [Char] where readString=id
instance ReadString Char where
readString s= if length t==1 then head t else read t where t=trim s
{-
-- | Read the named field from the request and if it fails use the
-- given default value.
look :: (Read a) => Request -> String -> a -> a
look msg name other = maybe other id $ lookup name m >>= mbRead
where m = bodyToList msg
-- | Get the named field from the request with a maximum length.
-- If the field is not defined return the empty String.
lookS :: Int -> Request -> String -> String
lookS maxLength msg name = take maxLength $ fromMaybe "" $ lookup name m
where m = bodyToList msg
-- ml = if maxLength < 0 then 2^32 else maxLength
-- | Read a named field from the request. May fail if the field doens't exist.
lookM :: Monad m => Request -> String -> m String
lookM = lookMb return
lookMb :: (Monad m) => (String -> m b) -> Request -> String -> m b
lookMb p msg name = M.lookup name m >>= p
where m = bodyToMap msg
-- | Read the named field from the request.
--lookMbRead :: (Read a,Monad m) => Request -> String -> m a
lookMbRead msg = maybeM . lookMb mbRead msg
lookInput :: Monad m => Request -> String -> m Input
lookInput req name
= M.lookup name (bodyToMap' req)
-- | Read a value from a String.
mbRead :: (Read a) => String -> Maybe a
mbRead = msum . map (Just . fst) . readsPrec 5
-}