module Ldap.Asn1.FromAsn1
( parseAsn1
, FromAsn1
) where
#if __GLASGOW_HASKELL__ >= 710
import Control.Applicative (Alternative(..), liftA2, optional)
#else
import Control.Applicative (Applicative(..), Alternative(..), liftA2, optional)
#endif
import Control.Monad (MonadPlus(..), (>=>), guard)
import Data.ASN1.Types (ASN1)
import qualified Data.ASN1.Types as Asn1
import Data.Foldable (asum)
import Data.List.NonEmpty (some1)
import qualified Data.Text.Encoding as Text
import Ldap.Asn1.Type
parseAsn1 :: FromAsn1 a => [ASN1] -> Maybe ([ASN1], a)
parseAsn1 = parse fromAsn1
class FromAsn1 a where
fromAsn1 :: Parser [ASN1] a
instance FromAsn1 op => FromAsn1 (LdapMessage op) where
fromAsn1 = do
Asn1.Start Asn1.Sequence <- next
i <- fromAsn1
op <- fromAsn1
Asn1.End Asn1.Sequence <- next
return (LdapMessage i op Nothing)
instance FromAsn1 Id where
fromAsn1 = do
Asn1.IntVal i <- next
return (Id (fromIntegral i))
instance FromAsn1 LdapString where
fromAsn1 = do
Asn1.OctetString s <- next
case Text.decodeUtf8' s of
Right t -> return (LdapString t)
Left _ -> empty
instance FromAsn1 LdapOid where
fromAsn1 = do
Asn1.OctetString s <- next
case Text.decodeUtf8' s of
Right t -> return (LdapOid t)
Left _ -> empty
instance FromAsn1 LdapDn where
fromAsn1 = fmap LdapDn fromAsn1
instance FromAsn1 AttributeDescription where
fromAsn1 = fmap AttributeDescription fromAsn1
instance FromAsn1 AttributeValue where
fromAsn1 = do
Asn1.OctetString s <- next
return (AttributeValue s)
instance FromAsn1 PartialAttribute where
fromAsn1 = do
Asn1.Start Asn1.Sequence <- next
d <- fromAsn1
Asn1.Start Asn1.Set <- next
vs <- many fromAsn1
Asn1.End Asn1.Set <- next
Asn1.End Asn1.Sequence <- next
return (PartialAttribute d vs)
instance FromAsn1 LdapResult where
fromAsn1 = do
resultCode <- do
Asn1.Enumerated x <- next
case x of
0 -> pure Success
1 -> pure OperationError
2 -> pure ProtocolError
3 -> pure TimeLimitExceeded
4 -> pure SizeLimitExceeded
5 -> pure CompareFalse
6 -> pure CompareTrue
7 -> pure AuthMethodNotSupported
8 -> pure StrongerAuthRequired
10 -> pure Referral
11 -> pure AdminLimitExceeded
12 -> pure UnavailableCriticalExtension
13 -> pure ConfidentialityRequired
14 -> pure SaslBindInProgress
16 -> pure NoSuchAttribute
17 -> pure UndefinedAttributeType
18 -> pure InappropriateMatching
19 -> pure ConstraintViolation
20 -> pure AttributeOrValueExists
21 -> pure InvalidAttributeSyntax
32 -> pure NoSuchObject
33 -> pure AliasProblem
34 -> pure InvalidDNSyntax
36 -> pure AliasDereferencingProblem
48 -> pure InappropriateAuthentication
49 -> pure InvalidCredentials
50 -> pure InsufficientAccessRights
51 -> pure Busy
52 -> pure Unavailable
53 -> pure UnwillingToPerform
54 -> pure LoopDetect
64 -> pure NamingViolation
65 -> pure ObjectClassViolation
66 -> pure NotAllowedOnNonLeaf
67 -> pure NotAllowedOnRDN
68 -> pure EntryAlreadyExists
69 -> pure ObjectClassModsProhibited
71 -> pure AffectsMultipleDSAs
80 -> pure Other
_ -> empty
matchedDn <- fromAsn1
diagnosticMessage
<- fromAsn1
referral <- optional $ do
Asn1.Start (Asn1.Container Asn1.Context 0) <- next
x <- fromAsn1
Asn1.End (Asn1.Container Asn1.Context 0) <- next
return x
return (LdapResult resultCode matchedDn diagnosticMessage referral)
instance FromAsn1 ReferralUris where
fromAsn1 = do
Asn1.Start Asn1.Sequence <- next
xs <- some1 fromAsn1
Asn1.End Asn1.Sequence <- next
return (ReferralUris xs)
instance FromAsn1 Uri where
fromAsn1 = fmap Uri fromAsn1
instance FromAsn1 ProtocolServerOp where
fromAsn1 = asum
[ fmap (\res -> BindResponse res Nothing) (app 1)
, fmap (uncurry SearchResultEntry) (app 4)
, fmap SearchResultDone (app 5)
, fmap ModifyResponse (app 7)
, fmap AddResponse (app 9)
, fmap DeleteResponse (app 11)
, fmap ModifyDnResponse (app 13)
, fmap CompareResponse (app 15)
, do
Asn1.Start (Asn1.Container Asn1.Application 19) <- next
uris <- some1 fromAsn1
Asn1.End (Asn1.Container Asn1.Application 19) <- next
return (SearchResultReference uris)
, do
Asn1.Start (Asn1.Container Asn1.Application 24) <- next
res <- fromAsn1
utf8Name <- optional $ do
Asn1.Other Asn1.Context 10 s <- next
return s
name <- maybe (return Nothing) (\n -> case Text.decodeUtf8' n of
Left _ -> empty
Right name -> return (Just name)) utf8Name
value <- optional $ do
Asn1.Other Asn1.Context 11 s <- next
return s
Asn1.End (Asn1.Container Asn1.Application 24) <- next
return (ExtendedResponse res (fmap LdapOid name) value)
, do
Asn1.Start (Asn1.Container Asn1.Application 25) <- next
name <- optional fromAsn1
value <- optional $ do
Asn1.OctetString s <- next
return s
Asn1.End (Asn1.Container Asn1.Application 25) <- next
return (IntermediateResponse name value)
]
where
app l = do
Asn1.Start (Asn1.Container Asn1.Application x) <- next
guard (x == l)
res <- fromAsn1
Asn1.End (Asn1.Container Asn1.Application y) <- next
guard (y == l)
return res
instance FromAsn1 PartialAttributeList where
fromAsn1 = do
Asn1.Start Asn1.Sequence <- next
xs <- many fromAsn1
Asn1.End Asn1.Sequence <- next
return (PartialAttributeList xs)
instance (FromAsn1 a, FromAsn1 b) => FromAsn1 (a, b) where
fromAsn1 = liftA2 (,) fromAsn1 fromAsn1
newtype Parser s a = Parser { unParser :: s -> Maybe (s, a) }
instance Functor (Parser s) where
fmap f (Parser g) = Parser (fmap (fmap f) . g)
instance Applicative (Parser s) where
pure x = Parser (\s -> pure (s, x))
Parser mf <*> Parser mx = Parser $ \s -> do
(s', f) <- mf s
(s'', x) <- mx s'
pure (s'', f x)
instance Alternative (Parser s) where
empty = Parser (\_ -> empty)
Parser ma <|> Parser mb =
Parser (\s -> ma s <|> mb s)
instance Monad (Parser s) where
return x = Parser (\s -> return (s, x))
Parser mx >>= k =
Parser (mx >=> \(s', x) -> unParser (k x) s')
fail _ = empty
instance MonadPlus (Parser s) where
mzero = Parser (\_ -> mzero)
Parser ma `mplus` Parser mb =
Parser (\s -> ma s `mplus` mb s)
parse :: Parser s a -> s -> Maybe (s, a)
parse = unParser
next :: Parser [s] s
next = Parser (\s -> case s of [] -> Nothing; x : xs -> Just (xs, x))