hsaml2-0.2.0: SAML2/XML.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE TypeOperators #-}
module SAML2.XML
( module SAML2.XML.Types
, module SAML2.Core.Datatypes
, URI
, xpTrimAnyElem
, xpTrimElemNS
, xpXmlLang
, IP, xpIP
, Identified(..)
, Identifiable(..)
, unidentify
, xpIdentified
, xpIdentifier
, IdentifiedURI
, samlToDoc
, samlToDocFirstChild
, samlToXML
, docToSAML
, docToXMLWithoutRoot
, docToXMLWithRoot
, xmlToSAML
, xmlToDoc
, xmlToDocE
) where
import qualified Data.ByteString.Lazy as BSL
import qualified Data.ByteString.Lazy.UTF8 as BSLU
import Data.Default (Default(..))
import qualified Data.Invertible as Inv
import Data.Maybe (listToMaybe)
import Network.URI (URI)
import qualified Text.XML.HXT.Core as HXT
import qualified Text.XML.HXT.DOM.ShowXml
import Text.XML.HXT.DOM.XmlNode (getChildren)
import qualified Data.Tree.NTree.TypeDefs as HXT
import SAML2.XML.Types
import SAML2.Core.Datatypes
import qualified Text.XML.HXT.Arrow.Pickle.Xml.Invertible as XP
import qualified SAML2.XML.Schema as XS
xpTrimAnyElem :: XP.PU HXT.XmlTree
xpTrimAnyElem = XP.xpTrim XP.xpAnyElem
xpTrimElemNS :: Namespace -> String -> XP.PU a -> XP.PU a
xpTrimElemNS ns n c = XP.xpTrim $ XP.xpElemQN (mkNName ns n) (c XP.>* XP.xpWhitespace)
xpXmlLang :: XP.PU XS.Language
xpXmlLang = XP.xpAttrQN (mkNName xmlNS "lang") XS.xpLanguage
type IP = XS.String
xpIP :: XP.PU IP
xpIP = XS.xpString
data Identified b a
= Identified !a
| Unidentified !b
deriving (Eq, Show)
instance Default a => Default (Identified b a) where
def = Identified def
class Eq b => Identifiable b a | a -> b where
identifier :: a -> b
identifiedValues :: [a]
default identifiedValues :: (Bounded a, Enum a) => [a]
identifiedValues = [minBound..maxBound]
reidentify :: b -> Identified b a
reidentify u = maybe (Unidentified u) Identified $ lookup u l where
l = [ (identifier a, a) | a <- identifiedValues ]
unidentify :: Identifiable b a => Identified b a -> b
unidentify (Identified a) = identifier a
unidentify (Unidentified b) = b
identify :: Identifiable b a => b Inv.<-> Identified b a
identify = reidentify Inv.:<->: unidentify
xpIdentified :: Identifiable b a => XP.PU b -> XP.PU (Identified b a)
xpIdentified = Inv.fmap identify
xpIdentifier :: Identifiable b a => XP.PU b -> String -> XP.PU a
xpIdentifier b t = XP.xpWrapEither
( \u -> case reidentify u of
Identified a -> Right a
Unidentified _ -> Left ("invalid " ++ t)
, identifier
) b
type IdentifiedURI = Identified URI
instance Identifiable URI a => XP.XmlPickler (Identified URI a) where
xpickle = xpIdentified XS.xpAnyURI
samlToDoc :: XP.XmlPickler a => a -> HXT.XmlTree
samlToDoc = head
. HXT.runLA (HXT.processChildren $ HXT.cleanupNamespaces HXT.collectPrefixUriPairs)
. XP.pickleDoc XP.xpickle
-- | From the input xml forest, take the first child of the first tree.
samlToDocFirstChild :: XP.XmlPickler a => a -> HXT.XmlTree
samlToDocFirstChild = head . getChildren . head
. HXT.runLA (HXT.processChildren $ HXT.cleanupNamespaces HXT.collectPrefixUriPairs)
. XP.pickleDoc XP.xpickle
-- | see also 'docToXMLWithRoot'
docToXMLWithoutRoot :: HXT.XmlTree -> BSL.ByteString
docToXMLWithoutRoot = BSL.concat . HXT.runLA (HXT.xshowBlob HXT.getChildren)
-- | 'docToXML' chops off the root element from the tree. 'docToXMLWithRoot' does not do
-- this. it may make sense to remove 'docToXMLWithoutRoot', but since i don't understand this
-- code enough to be confident not to break anything, i'll just leave this extra function for
-- reference.
docToXMLWithRoot :: HXT.XmlTree -> BSL.ByteString
docToXMLWithRoot = Text.XML.HXT.DOM.ShowXml.xshowBlob . (:[])
samlToXML :: XP.XmlPickler a => a -> BSL.ByteString
samlToXML = docToXMLWithoutRoot . samlToDoc
xmlToDoc :: BSL.ByteString -> Maybe HXT.XmlTree
xmlToDoc = either (const Nothing) Just . xmlToDocE
xmlToDocE :: BSL.ByteString -> Either String HXT.XmlTree
xmlToDocE = fix . xmlToDocUnsafe
where
fix Nothing =
Left "Nothing"
fix (Just (HXT.NTree (HXT.XError num msg) shouldBeEmpty)) =
Left $ show num ++ ": " ++ msg ++ (if null shouldBeEmpty then "" else show shouldBeEmpty)
fix (Just good) =
Right good
-- | Take a UTF-8 encoded bytestring and return an xml tree. This is unsafe and returns xml
-- trees containing parse errors on occasion; call 'xmlToDocE' instead.
xmlToDocUnsafe :: BSL.ByteString -> Maybe HXT.XmlTree
xmlToDocUnsafe = listToMaybe . HXT.runLA
(HXT.xreadDoc
HXT.>>> HXT.removeWhiteSpace
HXT.>>> HXT.neg HXT.isXmlPi
HXT.>>> HXT.propagateNamespaces)
. BSLU.toString
docToSAML :: XP.XmlPickler a => HXT.XmlTree -> Either String a
docToSAML = XP.unpickleDoc' XP.xpickle
. head
. HXT.runLA (HXT.processBottomUp (HXT.processAttrl (HXT.neg HXT.isNamespaceDeclAttr)))
xmlToSAML :: XP.XmlPickler a => BSL.ByteString -> Either String a
xmlToSAML = maybe (Left "invalid XML") docToSAML . xmlToDoc