LDAPv3 0.1.0.0 → 0.1.1.0
raw patch · 12 files changed
+1125/−59 lines, 12 filesdep ~quickcheck-instancesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: quickcheck-instances
API changes (from Hackage documentation)
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.AttributeDescription.AttributeDescription
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.AttributeDescription.KeyString
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.AttributeDescription.MatchingRuleId
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.AttributeDescription.OID
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.AttributeDescription.Option
- LDAPv3.StringRepr: instance LDAPv3.StringRepr.StringRepr LDAPv3.Message.Filter
+ LDAPv3.ASN1String: ASN1String'BMPString :: BMPString -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'IA5String :: IA5String -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'NumericString :: NumericString -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'OCTET_STRING :: ShortByteString -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'PrintableString :: PrintableString -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'UTF8String :: ShortText -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'UniversalString :: UniversalString -> ASN1StringChoice
+ LDAPv3.ASN1String: ASN1String'VisibleString :: VisibleString -> ASN1StringChoice
+ LDAPv3.ASN1String: UTF8String :: ShortText -> UTF8String
+ LDAPv3.ASN1String: [utf8String'toShortText] :: UTF8String -> ShortText
+ LDAPv3.ASN1String: asn1StringChoice'decode :: ByteString -> Maybe ASN1StringChoice
+ LDAPv3.ASN1String: asn1StringChoice'encode :: ASN1StringChoice -> ByteString
+ LDAPv3.ASN1String: asn1string'decode :: (ASN1String a, ASN1 a) => ByteString -> Maybe a
+ LDAPv3.ASN1String: asn1string'encode :: (ASN1String a, ASN1 a) => a -> ByteString
+ LDAPv3.ASN1String: asn1string'fromCodePoints :: ASN1String a => [Char] -> Maybe a
+ LDAPv3.ASN1String: asn1string'supportsCodePoint :: ASN1String a => Proxy a -> Char -> Bool
+ LDAPv3.ASN1String: asn1string'toCodePoints :: ASN1String a => a -> [Char]
+ LDAPv3.ASN1String: bmpString'fromUcs2CodePoints :: [Word16] -> BMPString
+ LDAPv3.ASN1String: bmpString'toUcs2CodePoints :: BMPString -> [Word16]
+ LDAPv3.ASN1String: class ASN1String a
+ LDAPv3.ASN1String: data ASN1StringChoice
+ LDAPv3.ASN1String: data BMPString
+ LDAPv3.ASN1String: data IA5String
+ LDAPv3.ASN1String: data NumericString
+ LDAPv3.ASN1String: data PrintableString
+ LDAPv3.ASN1String: data UniversalString
+ LDAPv3.ASN1String: data VisibleString
+ LDAPv3.ASN1String: ia5String'fromShortText :: ShortText -> Maybe IA5String
+ LDAPv3.ASN1String: ia5String'toShortText :: IA5String -> ShortText
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance Data.ASN1.ASN1 LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.ASN1StringChoice
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance Data.Binary.Class.Binary LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.ASN1StringChoice
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance GHC.Classes.Eq LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance GHC.Classes.Ord LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.ASN1StringChoice
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance GHC.Show.Show LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.BMPString
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.IA5String
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.NumericString
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.PrintableString
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.UTF8String
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.UniversalString
+ LDAPv3.ASN1String: instance LDAPv3.ASN1String.ASN1String LDAPv3.ASN1String.VisibleString
+ LDAPv3.ASN1String: newtype UTF8String
+ LDAPv3.ASN1String: numericString'fromShortText :: ShortText -> Maybe NumericString
+ LDAPv3.ASN1String: numericString'toShortText :: NumericString -> ShortText
+ LDAPv3.ASN1String: printableString'fromShortText :: ShortText -> Maybe PrintableString
+ LDAPv3.ASN1String: printableString'toShortText :: PrintableString -> ShortText
+ LDAPv3.ASN1String: visibleString'fromShortText :: ShortText -> Maybe VisibleString
+ LDAPv3.ASN1String: visibleString'toShortText :: VisibleString -> ShortText
+ LDAPv3.StringRepr: DistinguishedName :: [NonEmpty (Either KeyString OID, Either OCTET_STRING ShortText)] -> DistinguishedName
+ LDAPv3.StringRepr: newtype DistinguishedName
+ LDAPv3.StringRepr: rfc4514coreAttributes :: [(KeyString, OID)]
- LDAPv3.Message: AuthenticationChoice'sasl :: ( 'CONTEXTUAL 3 `IMPLICIT` SaslCredentials) -> AuthenticationChoice
+ LDAPv3.Message: AuthenticationChoice'sasl :: ('CONTEXTUAL 3 `IMPLICIT` SaslCredentials) -> AuthenticationChoice
- LDAPv3.Message: AuthenticationChoice'simple :: ( 'CONTEXTUAL 0 `IMPLICIT` OCTET_STRING) -> AuthenticationChoice
+ LDAPv3.Message: AuthenticationChoice'simple :: ('CONTEXTUAL 0 `IMPLICIT` OCTET_STRING) -> AuthenticationChoice
- LDAPv3.Message: BindResponse :: COMPONENTS_OF LDAPResult -> Maybe ( 'CONTEXTUAL 7 `IMPLICIT` OCTET_STRING) -> BindResponse
+ LDAPv3.Message: BindResponse :: COMPONENTS_OF LDAPResult -> Maybe ('CONTEXTUAL 7 `IMPLICIT` OCTET_STRING) -> BindResponse
- LDAPv3.Message: Control :: LDAPOID -> BOOLEAN_DEFAULT 'False -> Maybe OCTET_STRING -> Control
+ LDAPv3.Message: Control :: LDAPOID -> BOOLEAN_DEFAULT 'False -> Maybe OCTET_STRING -> Control
- LDAPv3.Message: ExtendedRequest :: ( 'CONTEXTUAL 0 `IMPLICIT` LDAPOID) -> Maybe ( 'CONTEXTUAL 1 `IMPLICIT` OCTET_STRING) -> ExtendedRequest
+ LDAPv3.Message: ExtendedRequest :: ('CONTEXTUAL 0 `IMPLICIT` LDAPOID) -> Maybe ('CONTEXTUAL 1 `IMPLICIT` OCTET_STRING) -> ExtendedRequest
- LDAPv3.Message: ExtendedResponse :: COMPONENTS_OF LDAPResult -> Maybe ( 'CONTEXTUAL 10 `IMPLICIT` LDAPOID) -> Maybe ( 'CONTEXTUAL 11 `IMPLICIT` OCTET_STRING) -> ExtendedResponse
+ LDAPv3.Message: ExtendedResponse :: COMPONENTS_OF LDAPResult -> Maybe ('CONTEXTUAL 10 `IMPLICIT` LDAPOID) -> Maybe ('CONTEXTUAL 11 `IMPLICIT` OCTET_STRING) -> ExtendedResponse
- LDAPv3.Message: Filter'and :: ( 'CONTEXTUAL 0 `IMPLICIT` SET1 Filter) -> Filter
+ LDAPv3.Message: Filter'and :: ('CONTEXTUAL 0 `IMPLICIT` SET1 Filter) -> Filter
- LDAPv3.Message: Filter'approxMatch :: ( 'CONTEXTUAL 8 `IMPLICIT` AttributeValueAssertion) -> Filter
+ LDAPv3.Message: Filter'approxMatch :: ('CONTEXTUAL 8 `IMPLICIT` AttributeValueAssertion) -> Filter
- LDAPv3.Message: Filter'equalityMatch :: ( 'CONTEXTUAL 3 `IMPLICIT` AttributeValueAssertion) -> Filter
+ LDAPv3.Message: Filter'equalityMatch :: ('CONTEXTUAL 3 `IMPLICIT` AttributeValueAssertion) -> Filter
- LDAPv3.Message: Filter'extensibleMatch :: ( 'CONTEXTUAL 9 `IMPLICIT` MatchingRuleAssertion) -> Filter
+ LDAPv3.Message: Filter'extensibleMatch :: ('CONTEXTUAL 9 `IMPLICIT` MatchingRuleAssertion) -> Filter
- LDAPv3.Message: Filter'greaterOrEqual :: ( 'CONTEXTUAL 5 `IMPLICIT` AttributeValueAssertion) -> Filter
+ LDAPv3.Message: Filter'greaterOrEqual :: ('CONTEXTUAL 5 `IMPLICIT` AttributeValueAssertion) -> Filter
- LDAPv3.Message: Filter'lessOrEqual :: ( 'CONTEXTUAL 6 `IMPLICIT` AttributeValueAssertion) -> Filter
+ LDAPv3.Message: Filter'lessOrEqual :: ('CONTEXTUAL 6 `IMPLICIT` AttributeValueAssertion) -> Filter
- LDAPv3.Message: Filter'not :: ( 'CONTEXTUAL 2 `EXPLICIT` Filter) -> Filter
+ LDAPv3.Message: Filter'not :: ('CONTEXTUAL 2 `EXPLICIT` Filter) -> Filter
- LDAPv3.Message: Filter'or :: ( 'CONTEXTUAL 1 `IMPLICIT` SET1 Filter) -> Filter
+ LDAPv3.Message: Filter'or :: ('CONTEXTUAL 1 `IMPLICIT` SET1 Filter) -> Filter
- LDAPv3.Message: Filter'present :: ( 'CONTEXTUAL 7 `IMPLICIT` AttributeDescription) -> Filter
+ LDAPv3.Message: Filter'present :: ('CONTEXTUAL 7 `IMPLICIT` AttributeDescription) -> Filter
- LDAPv3.Message: Filter'substrings :: ( 'CONTEXTUAL 4 `IMPLICIT` SubstringFilter) -> Filter
+ LDAPv3.Message: Filter'substrings :: ('CONTEXTUAL 4 `IMPLICIT` SubstringFilter) -> Filter
- LDAPv3.Message: IntermediateResponse :: Maybe ( 'CONTEXTUAL 0 `IMPLICIT` LDAPOID) -> Maybe ( 'CONTEXTUAL 1 `IMPLICIT` OCTET_STRING) -> IntermediateResponse
+ LDAPv3.Message: IntermediateResponse :: Maybe ('CONTEXTUAL 0 `IMPLICIT` LDAPOID) -> Maybe ('CONTEXTUAL 1 `IMPLICIT` OCTET_STRING) -> IntermediateResponse
- LDAPv3.Message: LDAPMessage :: MessageID -> CHOICE ProtocolOp -> Maybe ( 'CONTEXTUAL 0 `IMPLICIT` Controls) -> LDAPMessage
+ LDAPv3.Message: LDAPMessage :: MessageID -> CHOICE ProtocolOp -> Maybe ('CONTEXTUAL 0 `IMPLICIT` Controls) -> LDAPMessage
- LDAPv3.Message: LDAPResult :: ENUMERATED ResultCode -> LDAPDN -> LDAPString -> Maybe ( 'CONTEXTUAL 3 `IMPLICIT` Referral) -> LDAPResult
+ LDAPv3.Message: LDAPResult :: ENUMERATED ResultCode -> LDAPDN -> LDAPString -> Maybe ('CONTEXTUAL 3 `IMPLICIT` Referral) -> LDAPResult
- LDAPv3.Message: MatchingRuleAssertion :: Maybe ( 'CONTEXTUAL 1 `IMPLICIT` MatchingRuleId) -> Maybe ( 'CONTEXTUAL 2 `IMPLICIT` AttributeDescription) -> ( 'CONTEXTUAL 3 `IMPLICIT` AssertionValue) -> ( 'CONTEXTUAL 4 `IMPLICIT` BOOLEAN_DEFAULT 'False) -> MatchingRuleAssertion
+ LDAPv3.Message: MatchingRuleAssertion :: Maybe ('CONTEXTUAL 1 `IMPLICIT` MatchingRuleId) -> Maybe ('CONTEXTUAL 2 `IMPLICIT` AttributeDescription) -> ('CONTEXTUAL 3 `IMPLICIT` AssertionValue) -> ('CONTEXTUAL 4 `IMPLICIT` BOOLEAN_DEFAULT 'False) -> MatchingRuleAssertion
- LDAPv3.Message: ModifyDNRequest :: LDAPDN -> RelativeLDAPDN -> Bool -> Maybe ( 'CONTEXTUAL 0 `IMPLICIT` LDAPDN) -> ModifyDNRequest
+ LDAPv3.Message: ModifyDNRequest :: LDAPDN -> RelativeLDAPDN -> Bool -> Maybe ('CONTEXTUAL 0 `IMPLICIT` LDAPDN) -> ModifyDNRequest
- LDAPv3.Message: SearchResultReference :: ( 'APPLICATION 19 `IMPLICIT` NonEmpty URI) -> SearchResultReference
+ LDAPv3.Message: SearchResultReference :: ('APPLICATION 19 `IMPLICIT` NonEmpty URI) -> SearchResultReference
- LDAPv3.Message: Substring'any :: ( 'CONTEXTUAL 1 `IMPLICIT` AssertionValue) -> Substring
+ LDAPv3.Message: Substring'any :: ('CONTEXTUAL 1 `IMPLICIT` AssertionValue) -> Substring
- LDAPv3.Message: Substring'final :: ( 'CONTEXTUAL 2 `IMPLICIT` AssertionValue) -> Substring
+ LDAPv3.Message: Substring'final :: ('CONTEXTUAL 2 `IMPLICIT` AssertionValue) -> Substring
- LDAPv3.Message: Substring'initial :: ( 'CONTEXTUAL 0 `IMPLICIT` AssertionValue) -> Substring
+ LDAPv3.Message: Substring'initial :: ('CONTEXTUAL 0 `IMPLICIT` AssertionValue) -> Substring
- LDAPv3.Message: [_BindResponse'serverSaslCreds] :: BindResponse -> Maybe ( 'CONTEXTUAL 7 `IMPLICIT` OCTET_STRING)
+ LDAPv3.Message: [_BindResponse'serverSaslCreds] :: BindResponse -> Maybe ('CONTEXTUAL 7 `IMPLICIT` OCTET_STRING)
- LDAPv3.Message: [_Control'criticality] :: Control -> BOOLEAN_DEFAULT 'False
+ LDAPv3.Message: [_Control'criticality] :: Control -> BOOLEAN_DEFAULT 'False
- LDAPv3.Message: [_ExtendedRequest'responseName] :: ExtendedRequest -> 'CONTEXTUAL 0 `IMPLICIT` LDAPOID
+ LDAPv3.Message: [_ExtendedRequest'responseName] :: ExtendedRequest -> 'CONTEXTUAL 0 `IMPLICIT` LDAPOID
- LDAPv3.Message: [_ExtendedRequest'responseValue] :: ExtendedRequest -> Maybe ( 'CONTEXTUAL 1 `IMPLICIT` OCTET_STRING)
+ LDAPv3.Message: [_ExtendedRequest'responseValue] :: ExtendedRequest -> Maybe ('CONTEXTUAL 1 `IMPLICIT` OCTET_STRING)
- LDAPv3.Message: [_ExtendedResponse'responseName] :: ExtendedResponse -> Maybe ( 'CONTEXTUAL 10 `IMPLICIT` LDAPOID)
+ LDAPv3.Message: [_ExtendedResponse'responseName] :: ExtendedResponse -> Maybe ('CONTEXTUAL 10 `IMPLICIT` LDAPOID)
- LDAPv3.Message: [_ExtendedResponse'responseValue] :: ExtendedResponse -> Maybe ( 'CONTEXTUAL 11 `IMPLICIT` OCTET_STRING)
+ LDAPv3.Message: [_ExtendedResponse'responseValue] :: ExtendedResponse -> Maybe ('CONTEXTUAL 11 `IMPLICIT` OCTET_STRING)
- LDAPv3.Message: [_IntermediateResponse'responseName] :: IntermediateResponse -> Maybe ( 'CONTEXTUAL 0 `IMPLICIT` LDAPOID)
+ LDAPv3.Message: [_IntermediateResponse'responseName] :: IntermediateResponse -> Maybe ('CONTEXTUAL 0 `IMPLICIT` LDAPOID)
- LDAPv3.Message: [_IntermediateResponse'responseValue] :: IntermediateResponse -> Maybe ( 'CONTEXTUAL 1 `IMPLICIT` OCTET_STRING)
+ LDAPv3.Message: [_IntermediateResponse'responseValue] :: IntermediateResponse -> Maybe ('CONTEXTUAL 1 `IMPLICIT` OCTET_STRING)
- LDAPv3.Message: [_LDAPMessage'controls] :: LDAPMessage -> Maybe ( 'CONTEXTUAL 0 `IMPLICIT` Controls)
+ LDAPv3.Message: [_LDAPMessage'controls] :: LDAPMessage -> Maybe ('CONTEXTUAL 0 `IMPLICIT` Controls)
- LDAPv3.Message: [_LDAPResult'referral] :: LDAPResult -> Maybe ( 'CONTEXTUAL 3 `IMPLICIT` Referral)
+ LDAPv3.Message: [_LDAPResult'referral] :: LDAPResult -> Maybe ('CONTEXTUAL 3 `IMPLICIT` Referral)
- LDAPv3.Message: [_MatchingRuleAssertion'dnAttributes] :: MatchingRuleAssertion -> 'CONTEXTUAL 4 `IMPLICIT` BOOLEAN_DEFAULT 'False
+ LDAPv3.Message: [_MatchingRuleAssertion'dnAttributes] :: MatchingRuleAssertion -> 'CONTEXTUAL 4 `IMPLICIT` BOOLEAN_DEFAULT 'False
- LDAPv3.Message: [_MatchingRuleAssertion'matchValue] :: MatchingRuleAssertion -> 'CONTEXTUAL 3 `IMPLICIT` AssertionValue
+ LDAPv3.Message: [_MatchingRuleAssertion'matchValue] :: MatchingRuleAssertion -> 'CONTEXTUAL 3 `IMPLICIT` AssertionValue
- LDAPv3.Message: [_MatchingRuleAssertion'matchingRule] :: MatchingRuleAssertion -> Maybe ( 'CONTEXTUAL 1 `IMPLICIT` MatchingRuleId)
+ LDAPv3.Message: [_MatchingRuleAssertion'matchingRule] :: MatchingRuleAssertion -> Maybe ('CONTEXTUAL 1 `IMPLICIT` MatchingRuleId)
- LDAPv3.Message: [_MatchingRuleAssertion'type] :: MatchingRuleAssertion -> Maybe ( 'CONTEXTUAL 2 `IMPLICIT` AttributeDescription)
+ LDAPv3.Message: [_MatchingRuleAssertion'type] :: MatchingRuleAssertion -> Maybe ('CONTEXTUAL 2 `IMPLICIT` AttributeDescription)
- LDAPv3.Message: [_ModifyDNRequest'newSuperior] :: ModifyDNRequest -> Maybe ( 'CONTEXTUAL 0 `IMPLICIT` LDAPDN)
+ LDAPv3.Message: [_ModifyDNRequest'newSuperior] :: ModifyDNRequest -> Maybe ('CONTEXTUAL 0 `IMPLICIT` LDAPDN)
- LDAPv3.Message: type AbandonRequest = ( 'APPLICATION 16 `IMPLICIT` MessageID)
+ LDAPv3.Message: type AbandonRequest = ('APPLICATION 16 `IMPLICIT` MessageID)
- LDAPv3.Message: type AddResponse = ( 'APPLICATION 9 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type AddResponse = ('APPLICATION 9 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type CompareResponse = ( 'APPLICATION 15 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type CompareResponse = ('APPLICATION 15 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type DelRequest = ( 'APPLICATION 10 `IMPLICIT` LDAPDN)
+ LDAPv3.Message: type DelRequest = ('APPLICATION 10 `IMPLICIT` LDAPDN)
- LDAPv3.Message: type DelResponse = ( 'APPLICATION 11 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type DelResponse = ('APPLICATION 11 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type ModifyDNResponse = ( 'APPLICATION 13 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type ModifyDNResponse = ('APPLICATION 13 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type ModifyResponse = ( 'APPLICATION 7 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type ModifyResponse = ('APPLICATION 7 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type Referral = ( 'CONTEXTUAL 3 `IMPLICIT` NonEmpty URI)
+ LDAPv3.Message: type Referral = ('CONTEXTUAL 3 `IMPLICIT` NonEmpty URI)
- LDAPv3.Message: type SearchResultDone = ( 'APPLICATION 5 `IMPLICIT` LDAPResult)
+ LDAPv3.Message: type SearchResultDone = ('APPLICATION 5 `IMPLICIT` LDAPResult)
- LDAPv3.Message: type UIntBounds lb ub t = (KnownNat lb, KnownNat ub, lb <= ub, IsBelowMaxBound ub (IntBaseType t) ~ 'True)
+ LDAPv3.Message: type UIntBounds lb ub t = (KnownNat lb, KnownNat ub, lb <= ub, IsBelowMaxBound ub (IntBaseType t) ~ 'True)
- LDAPv3.Message: type UnbindRequest = ( 'APPLICATION 2 `IMPLICIT` NULL)
+ LDAPv3.Message: type UnbindRequest = ('APPLICATION 2 `IMPLICIT` NULL)
Files
- ChangeLog.md +5/−0
- LDAPv3.cabal +6/−3
- src/Common.hs +9/−0
- src/Data/ASN1/Prim.hs +1/−2
- src/LDAPv3/ASN1String.hs +555/−0
- src/LDAPv3/AttributeDescription.hs +25/−5
- src/LDAPv3/DistinguishedName.hs +342/−0
- src/LDAPv3/SearchFilter.hs +9/−0
- src/LDAPv3/StringRepr.hs +13/−47
- src/LDAPv3/StringRepr/Class.hs +40/−0
- test/Arbitrary.hs +56/−0
- test/main.hs +64/−2
ChangeLog.md view
@@ -1,5 +1,10 @@ See also http://pvp.haskell.org/faq +### 0.1.1.0++- Add support for _String Representation of Distinguished Names_ as per RFC4514+- Add support for encoding/decoding common ASN.1 string types from their ASN.1 BER representation+ ## 0.1.0.0 Major API restructuring getting rid of `newtype` annotation wrappers in exposed API.
LDAPv3.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: LDAPv3-version: 0.1.0.0+version: 0.1.1.0 synopsis: Lightweight Directory Access Protocol (LDAP) version 3 license: GPL-2.0-or-later@@ -16,7 +16,7 @@ . Serializing and deserializing to and from the wire <https://en.wikipedia.org/wiki/ASN.1 ASN.1> encoding for the purpose of implementing network clients and servers is supported via 'Binary' instances (see <//hackage.haskell.org/package/binary 'binary' package>). .- Moreover, this library also implements /String Representation of Search Filters/ as per <https://tools.ietf.org/html/rfc4515 RFC4515> (see "LDAPv3.StringRepr")+ Moreover, this library also implements /String Representation of Search Filters/ as per <https://tools.ietf.org/html/rfc4515 RFC4515> and /String Representation of Distinguished Names/ as per <https://tools.ietf.org/html/rfc4514 RFC4514> (see "LDAPv3.StringRepr" for details). extra-source-files: ChangeLog.md @@ -76,13 +76,16 @@ exposed-modules: LDAPv3.Message LDAPv3.StringRepr+ LDAPv3.ASN1String other-modules: Common Data.Int.Subtypes Data.ASN1 Data.ASN1.Prim LDAPv3.AttributeDescription+ LDAPv3.DistinguishedName LDAPv3.SearchFilter+ LDAPv3.StringRepr.Class LDAPv3.ResultCode LDAPv3.Message.Types LDAPv3.Message.Annotated@@ -104,4 +107,4 @@ , tasty-quickcheck ^>= 0.10.1 , tasty-hunit ^>= 0.10.0 , base-encoding ^>= 0.1.0- , quickcheck-instances ^>= 0.3.22+ , quickcheck-instances ^>= 0.3.22 && < 0.3.26
src/Common.hs view
@@ -9,6 +9,7 @@ import Control.Newtype as X (Newtype (..)) import Data.Bits as X import Data.ByteString as X (ByteString)+import Data.ByteString.Short as X (ShortByteString) import Data.Foldable as X (asum) import Data.Functor.Identity as X import Data.Int as X@@ -24,6 +25,8 @@ import GHC.TypeLits as X hiding (Text) import Numeric.Natural as X (Natural) +import qualified Text.Parsec as P+ {-# INLINE rwhnf #-} rwhnf :: a -> () rwhnf x = seq x ()@@ -36,3 +39,9 @@ impossible :: a impossible = error "The impossible just happened!"++sepBy1' :: P.Stream s m t => P.ParsecT s u m a -> P.ParsecT s u m sep -> P.ParsecT s u m (NonEmpty a)+sepBy1' p set = f <$> P.sepBy1 p set+ where+ f [] = impossible+ f (x:xs) = x:|xs
src/Data/ASN1/Prim.hs view
@@ -251,8 +251,7 @@ getVarInteger :: Word64 -> Get Integer getVarInteger sz | sz <= 8 = toInteger <$> getVarInt64 sz- | otherwise = fail "INTEGER: FIXME/TODO"-+ | otherwise = fail "unsupported INTEGER size" -- FIXME/TODO putVarInt64 :: Int64 -> PutM Word64 putVarInt64 i = do
+ src/LDAPv3/ASN1String.hs view
@@ -0,0 +1,555 @@+-- Copyright (c) 2020 Herbert Valerio Riedel <hvr@gnu.org>+--+-- This file is free software: you may copy, redistribute and/or modify it+-- under the terms of the GNU General Public License as published by the+-- Free Software Foundation, either version 2 of the License, or (at your+-- option) any later version.+--+-- This file is distributed in the hope that it will be useful, but+-- WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+-- General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program (see `LICENSE`). If not, see+-- <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.++{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE TypeOperators #-}++-- | ASN.1 String Types+--+-- This modules features types and associated functions for encoding/decoding common ASN.1 string types from their ASN.1 BER representation according to their standard /universal/ ASN.1 tag number.+--+-- @since 0.1.1+module LDAPv3.ASN1String+ ( ASN1String(..)++ -- * Convenience Sum-type++ , ASN1StringChoice(..)+ , asn1StringChoice'encode+ , asn1StringChoice'decode++ -- * UTF8String++ , UTF8String(UTF8String, utf8String'toShortText)++ -- * UniversalString++ , UniversalString++ -- * BMPString++ , BMPString+ , bmpString'toUcs2CodePoints+ , bmpString'fromUcs2CodePoints++ -- * IA5String++ , IA5String+ , ia5String'toShortText+ , ia5String'fromShortText++ -- * VisibleString++ , VisibleString+ , visibleString'toShortText+ , visibleString'fromShortText++ -- * PrintableString++ , PrintableString+ , printableString'toShortText+ , printableString'fromShortText++ -- * NumericString++ , NumericString+ , numericString'toShortText+ , numericString'fromShortText+ ) where++import Common hiding (Option, many, option, some, (<|>))++import qualified Data.ByteString.Char8 as BSC+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString.Short as SBS+import Data.Char (chr, ord)+import qualified Data.Text.Short as TS++import Data.ASN1+import Data.ASN1.Prim++import qualified Data.Binary as Bin+import qualified Data.Binary.Get as Bin+import qualified Data.Binary.Put as Bin++-- | Typeclass abstracting over common ASN.1 string operations+--+-- @since 0.1.1+class ASN1String a where+ -- | Decode ASN.1 string type from its ASN.1 BER encoding+ asn1string'decode :: ByteString -> Maybe a+ default asn1string'decode :: ASN1 a => ByteString -> Maybe a+ asn1string'decode = runGetMaybe (toBinaryGet asn1decode)++ -- | Encode ASN.1 string type to its ASN.1 BER encoding+ asn1string'encode :: a -> ByteString+ default asn1string'encode :: ASN1 a => a -> ByteString+ asn1string'encode = BL.toStrict . Bin.runPut . void . toBinaryPut . asn1encode++ -- | Predicate for determining whether given code-point is allowed by the respective ASN.1 string type+ asn1string'supportsCodePoint :: Proxy a -> Char -> Bool++ -- | Convert ASN.1 string type to list of code-points+ asn1string'toCodePoints :: a -> [Char]++ -- | Construct ASN.1 string type from list of code-points+ --+ -- This returns 'Nothing' if a code-point cannot be expressed in the respective ASN.1 string type.+ asn1string'fromCodePoints :: [Char] -> Maybe a++-- | Convenient Sum-type combining a subset of the standard ASN.1 string-like types+--+-- See specific string types in "LDAPv3.ASN1String" for details.+data ASN1StringChoice+ = ASN1String'OCTET_STRING ShortByteString+ | ASN1String'UniversalString UniversalString+ | ASN1String'UTF8String ShortText+ | ASN1String'BMPString BMPString+ | ASN1String'IA5String IA5String+ | ASN1String'VisibleString VisibleString+ | ASN1String'PrintableString PrintableString+ | ASN1String'NumericString NumericString+ deriving (Show,Eq)++-- | Encodes as ASN.1 BER+instance Bin.Binary ASN1StringChoice where+ put = void . toBinaryPut . go+ where+ go (ASN1String'BMPString t) = asn1encode t+ go (ASN1String'IA5String t) = asn1encode t+ go (ASN1String'NumericString t) = asn1encode t+ go (ASN1String'OCTET_STRING b) = asn1encode b+ go (ASN1String'PrintableString t) = asn1encode t+ go (ASN1String'UTF8String t) = asn1encode (UTF8String t)+ go (ASN1String'UniversalString t) = asn1encode t+ go (ASN1String'VisibleString t) = asn1encode t++ get = toBinaryGet go+ where+ go = dec'CHOICE+ [ ASN1String'OCTET_STRING <$> asn1decode+ , ASN1String'UTF8String . utf8String'toShortText <$> asn1decode+ , ASN1String'PrintableString <$> asn1decode+ , ASN1String'IA5String <$> asn1decode+ , ASN1String'BMPString <$> asn1decode+ , ASN1String'UniversalString <$> asn1decode+ , ASN1String'VisibleString <$> asn1decode+ , ASN1String'NumericString <$> asn1decode+ ]++-- | Encode ASN.1 string choice to its ASN.1 BER encoding+--+-- @since 0.1.1+asn1StringChoice'encode :: ASN1StringChoice -> ByteString+asn1StringChoice'encode = BL.toStrict . Bin.runPut . Bin.put++-- | Decode ASN.1 string choice from its ASN.1 BER encoding+--+-- @since 0.1.1+asn1StringChoice'decode :: ByteString -> Maybe ASN1StringChoice+asn1StringChoice'decode = runGetMaybe Bin.get++----------------------------------------------------------------------------++-- | ASN.1 UTF8String+--+-- > UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING+--+-- @since 0.1.1+newtype UTF8String = UTF8String { utf8String'toShortText :: ShortText } deriving (Eq,Ord)++instance ASN1String UTF8String where+ asn1string'supportsCodePoint _ = not . isSurr+ asn1string'toCodePoints (UTF8String t) = TS.unpack t+ asn1string'fromCodePoints cps+ | all (not . isSurr) cps = Just $! UTF8String (TS.pack cps)+ | otherwise = Nothing++instance Show UTF8String where+ show (UTF8String s) = show s+ showsPrec p (UTF8String s) = showsPrec p s++instance ASN1 UTF8String where+ asn1defTag _ = Universal 12+ asn1encode (UTF8String t) = asn1encode (IMPLICIT t :: 'UNIVERSAL 12 `IMPLICIT` ShortText)+ asn1decode = unwrap <$> asn1decode+ where+ unwrap :: 'UNIVERSAL 12 `IMPLICIT` ShortText -> UTF8String+ unwrap (IMPLICIT t) = UTF8String t++-- | Encodes as ASN.1 BER+instance Bin.Binary UTF8String where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 PrintableString+--+-- > PrintableString ::= [UNIVERSAL 19] IMPLICIT OCTET STRING+--+-- @since 0.1.1+newtype PrintableString = PrintableString ShortText deriving (Eq,Ord)++instance ASN1String PrintableString where+ asn1string'supportsCodePoint _ = isPrintableChar+ asn1string'toCodePoints (PrintableString t) = TS.unpack t+ asn1string'fromCodePoints cps+ | all isPrintableChar cps = Just $! PrintableString (TS.pack cps)+ | otherwise = Nothing++instance Show PrintableString where+ show (PrintableString s) = show s+ showsPrec p (PrintableString s) = showsPrec p s++printableString'fromShortText :: ShortText -> Maybe PrintableString+printableString'fromShortText t+ | TS.all isPrintableChar t = Just $! PrintableString t+ | otherwise = Nothing++printableString'fromByteString :: ByteString -> Maybe PrintableString+printableString'fromByteString bs+ | BSC.all isPrintableChar bs = PrintableString <$> TS.fromByteString bs+ | otherwise = Nothing++printableString'toShortText :: PrintableString -> ShortText+printableString'toShortText (PrintableString t) = t++isPrintableChar :: Char -> Bool+isPrintableChar c = case c of+ ' ' -> True+ '*' -> False+ ':' -> True+ '=' -> True+ '?' -> True+ _ | c `inside` ('A','Z') -> True+ | c `inside` ('a','z') -> True+ | c `inside` ('0','9') -> True+ | c `inside` ('\x27','\x2f') -> True -- "'()*+,-./"+ | otherwise -> False++instance ASN1 PrintableString where+ asn1defTag _ = Universal 19+ asn1encode (PrintableString t) = asn1encode (IMPLICIT t :: 'UNIVERSAL 19 `IMPLICIT` ShortText)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in PrintableString") Right . printableString'fromByteString)+ where+ unwrap :: 'UNIVERSAL 19 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary PrintableString where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 NumericString+--+-- > NumericString ::= [UNIVERSAL 18] IMPLICIT OCTET STRING+--+-- @since 0.1.1+newtype NumericString = NumericString ShortText deriving (Eq,Ord)++instance Show NumericString where+ show (NumericString s) = show s+ showsPrec p (NumericString s) = showsPrec p s++instance ASN1String NumericString where+ asn1string'supportsCodePoint _ = isNumericChar+ asn1string'toCodePoints (NumericString t) = TS.unpack t+ asn1string'fromCodePoints cps+ | all isNumericChar cps = Just $! NumericString (TS.pack cps)+ | otherwise = Nothing++numericString'fromShortText :: ShortText -> Maybe NumericString+numericString'fromShortText t+ | TS.all isNumericChar t = Just $! NumericString t+ | otherwise = Nothing++numericString'fromByteString :: ByteString -> Maybe NumericString+numericString'fromByteString bs+ | BSC.all isNumericChar bs = NumericString <$> TS.fromByteString bs+ | otherwise = Nothing++numericString'toShortText :: NumericString -> ShortText+numericString'toShortText (NumericString t) = t++isNumericChar :: Char -> Bool+isNumericChar ' ' = True+isNumericChar c = c `inside` ('0','9')++instance ASN1 NumericString where+ asn1defTag _ = Universal 18+ asn1encode (NumericString t) = asn1encode (IMPLICIT t :: 'UNIVERSAL 18 `IMPLICIT` ShortText)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in NumericString") Right . numericString'fromByteString)+ where+ unwrap :: 'UNIVERSAL 18 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary NumericString where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 VisibleString+--+-- > VisibleString ::= [UNIVERSAL 26] IMPLICIT OCTET STRING+--+-- @since 0.1.1+newtype VisibleString = VisibleString ShortText deriving (Eq,Ord)++instance ASN1String VisibleString where+ asn1string'supportsCodePoint _ = isVisibleChar+ asn1string'toCodePoints (VisibleString t) = TS.unpack t+ asn1string'fromCodePoints cps+ | all isVisibleChar cps = Just $! VisibleString (TS.pack cps)+ | otherwise = Nothing++instance Show VisibleString where+ show (VisibleString s) = show s+ showsPrec p (VisibleString s) = showsPrec p s++visibleString'fromShortText :: ShortText -> Maybe VisibleString+visibleString'fromShortText t+ | TS.all isVisibleChar t = Just $! VisibleString t+ | otherwise = Nothing++visibleString'fromByteString :: ByteString -> Maybe VisibleString+visibleString'fromByteString bs+ | BSC.all isVisibleChar bs = VisibleString <$> TS.fromByteString bs+ | otherwise = Nothing++visibleString'toShortText :: VisibleString -> ShortText+visibleString'toShortText (VisibleString t) = t++isVisibleChar :: Char -> Bool+isVisibleChar c = '\x20' <= c && c <= '\x7e' -- aka 'isPrint && isAscii'++instance ASN1 VisibleString where+ asn1defTag _ = Universal 26+ asn1encode (VisibleString t) = asn1encode (IMPLICIT t :: 'UNIVERSAL 26 `IMPLICIT` ShortText)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in VisibleString") Right . visibleString'fromByteString)+ where+ unwrap :: 'UNIVERSAL 26 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary VisibleString where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 IA5String+--+-- > IA5String ::= [UNIVERSAL 22] IMPLICIT OCTET STRING+--+-- @since 0.1.1+newtype IA5String = IA5String ShortText deriving (Eq,Ord)++instance ASN1String IA5String where+ asn1string'supportsCodePoint _ = isIA5Char+ asn1string'toCodePoints (IA5String t) = TS.unpack t+ asn1string'fromCodePoints cps+ | all isIA5Char cps = Just $! IA5String (TS.pack cps)+ | otherwise = Nothing++instance Show IA5String where+ show (IA5String s) = show s+ showsPrec p (IA5String s) = showsPrec p s++ia5String'fromShortText :: ShortText -> Maybe IA5String+ia5String'fromShortText t+ | TS.all isIA5Char t = Just $! IA5String t+ | otherwise = Nothing++ia5String'fromByteString :: ByteString -> Maybe IA5String+ia5String'fromByteString bs+ | BSC.all isIA5Char bs = IA5String <$> TS.fromByteString bs+ | otherwise = Nothing++ia5String'toShortText :: IA5String -> ShortText+ia5String'toShortText (IA5String t) = t++isIA5Char :: Char -> Bool+isIA5Char c = c <= '\x7f'++instance ASN1 IA5String where+ asn1defTag _ = Universal 22+ asn1encode (IA5String t) = asn1encode (IMPLICIT t :: 'UNIVERSAL 22 `IMPLICIT` ShortText)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in IA5String") Right . ia5String'fromByteString)+ where+ unwrap :: 'UNIVERSAL 22 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary IA5String where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 BMPString+--+-- > BMPString ::= [UNIVERSAL 30] IMPLICIT OCTET STRING+--+-- NB: The surrogate-pair range U+D800 through U+DFFF is tolerated and thus the responsibility of code converting to and+-- from 'BMPString'+--+-- @since 0.1.1+newtype BMPString = BMPString SBS.ShortByteString deriving (Eq,Ord)++instance ASN1String BMPString where+ asn1string'supportsCodePoint _ = (<= '\xffff')+ asn1string'toCodePoints = bmpString'toString+ asn1string'fromCodePoints = bmpString'fromString++instance Show BMPString where+ show = show . bmpString'toString+ showsPrec p = showsPrec p . bmpString'toString++bmpString'toUcs2CodePoints :: BMPString -> [Word16]+bmpString'toUcs2CodePoints (BMPString sbs) = go (SBS.unpack sbs)+ where+ go (h:l:rest) = (fromIntegral h*0x100)+fromIntegral l : go rest+ go [] = []+ go [_] = impossible -- forbidden by invariant++bmpString'fromUcs2CodePoints :: [Word16] -> BMPString+bmpString'fromUcs2CodePoints cps = BMPString (SBS.pack $ go cps)+ where+ go (cp:rest) = fromIntegral (cp `unsafeShiftR` 8) : fromIntegral (cp .&. 0xff) : go rest+ go [] = []++-- NB: Surrogate pair code-points (U+D800 through U+DFFF) are transparently emitted as surrogate 'Char' code-points+bmpString'toString :: BMPString -> String+bmpString'toString = map (chr . fromIntegral) . bmpString'toUcs2CodePoints++-- NB: Surrogate pair code-points (U+D800 through U+DFFF) are not rejected in order for 'bmpString'toString' to be an inverse operation.+bmpString'fromString :: String -> Maybe BMPString+bmpString'fromString s+ | all (\c -> c <= '\xffff') s = Just $! bmpString'fromUcs2CodePoints $ map (fromIntegral . ord) s+ | otherwise = Nothing++bmpString'fromByteString :: ByteString -> Maybe BMPString+bmpString'fromByteString bs+ | even (BSC.length bs) = Just $! BMPString (SBS.toShort bs)+ | otherwise = Nothing++instance ASN1 BMPString where+ asn1defTag _ = Universal 30+ asn1encode (BMPString t) = asn1encode (IMPLICIT (SBS.fromShort t) :: 'UNIVERSAL 30 `IMPLICIT` OCTET_STRING)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in BMPString") Right . bmpString'fromByteString)+ where+ unwrap :: 'UNIVERSAL 30 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary BMPString where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------++-- | ASN.1 UniversalString+--+-- > UniversalString ::= [UNIVERSAL 28] IMPLICIT OCTET STRING+--+-- NB: The surrogate-pair range U+D800 through U+DFFF is tolerated and thus becomes the responsibility of code converting to and from 'UniversalString'+--+-- @since 0.1.1+newtype UniversalString = UniversalString SBS.ShortByteString+ deriving (Eq,Ord)++instance ASN1String UniversalString where+ asn1string'supportsCodePoint _ = const True+ asn1string'toCodePoints = universalString'toString+ asn1string'fromCodePoints = \s -> Just $! universalString'fromString s++instance Show UniversalString where+ show = show . universalString'toString+ showsPrec p = showsPrec p . universalString'toString++-- NB: Surrogate pair code-points (U+D800 through U+DFFF) are transparently emitted as surrogate 'Char' code-points+universalString'toString :: UniversalString -> String+universalString'toString = maybe impossible id . bsToUcs4 . (\(UniversalString x) -> SBS.fromShort x)++-- NB: Surrogate pair code-points (U+D800 through U+DFFF) are not rejected in order for 'universalString'toString' to be an inverse operation.+universalString'fromString :: String -> UniversalString+universalString'fromString = UniversalString . SBS.toShort . ucs4ToBs++universalString'fromByteString :: ByteString -> Maybe UniversalString+universalString'fromByteString bs+ | Just _ <- bsToUcs4 bs = Just (UniversalString $ SBS.toShort bs)+ | otherwise = Nothing++-- internal+bsToUcs4 :: ByteString -> Maybe [Char]+bsToUcs4 bs+ | (n,0) <- BSC.length bs `quotRem` 4 = runGetMaybe (repGet n f) bs+ | otherwise = Nothing+ where+ f = do x <- Bin.getWord32be+ guard (x <= 0x10ffff)+ pure $! chr (fromIntegral x)++ucs4ToBs :: [Char] -> ByteString+ucs4ToBs cs = BL.toStrict $ Bin.runPut (mapM_ Bin.putWord32be cs')+ where+ cs' :: [Word32]+ cs' = map (fromIntegral . ord) cs++instance ASN1 UniversalString where+ asn1defTag _ = Universal 28+ asn1encode (UniversalString t) = asn1encode (IMPLICIT (SBS.fromShort t) :: 'UNIVERSAL 28 `IMPLICIT` OCTET_STRING)+ asn1decode = (unwrap <$> asn1decode) `transformVia`+ (maybe (Left "Invalid code-point in UniversalString") Right . universalString'fromByteString)+ where+ unwrap :: 'UNIVERSAL 28 `IMPLICIT` OCTET_STRING -> ByteString+ unwrap (IMPLICIT t) = t++-- | Encodes as ASN.1 BER+instance Bin.Binary UniversalString where+ get = toBinaryGet asn1decode+ put = void . toBinaryPut . asn1encode++----------------------------------------------------------------------------+-- helpers++runGetMaybe :: Bin.Get a -> ByteString -> Maybe a+runGetMaybe g bs = case Bin.runGetOrFail g (BL.fromStrict bs) of+ Left _ -> Nothing+ Right (rest,_,x)+ | BL.null rest -> Just $! x+ | otherwise -> Nothing++repGet :: Int -> Bin.Get a -> Bin.Get [a]+repGet n g = go [] n+ where+ go xs 0 = return $! reverse xs+ go xs i = do { x <- g; x `seq` go (x:xs) (i-1) }++isSurr :: Char -> Bool+isSurr c = c >= '\xd800' && c <= '\xdfff'
src/LDAPv3/AttributeDescription.hs view
@@ -47,10 +47,14 @@ , p'OID , ts'OID , r'OID++ , p'DescrOrOID+ ) where import Common hiding (Option, many, option, some, (<|>)) import Data.ASN1+import LDAPv3.StringRepr.Class import qualified Data.ByteString.Char8 as BSC import qualified Data.ByteString.Short as SBS@@ -102,6 +106,10 @@ asn1decode = asn1decodeParsec "AttributeDescription" p'AttributeDescription asn1encode = asn1encode . ts'AttributeDescription +instance StringRepr AttributeDescription where+ asParsec = p'AttributeDescription+ renderShortText = ts'AttributeDescription+ instance S.IsString AttributeDescription where fromString = _fromString "AttributeDescription" p'AttributeDescription @@ -148,6 +156,10 @@ showsPrec p (Option s) = showsPrec p s show (Option s) = show s +instance StringRepr Option where+ asParsec = p'Option+ renderShortText = ts'Option+ instance S.IsString Option where fromString = _fromString "Option" p'Option @@ -186,6 +198,11 @@ asn1encode oid = asn1encode (BSC.pack (s'OID oid)) asn1decode = asn1decodeParsec "OID" p'OID +instance StringRepr OID where+ asBuilder = r'OID+ renderShortText = ts'OID+ asParsec = p'OID+ r'OID :: OID -> Builder r'OID = B.fromString . s'OID @@ -211,11 +228,6 @@ then pure 0 else read . (ldigit:) <$> many digit - sepBy1' p set = f <$> sepBy1 p set- where- f [] = error "the impossible happened"- f (x:xs) = x:|xs- {- | Case-insensitive string used to denote OID short names > keystring = leadkeychar *keychar@@ -242,6 +254,10 @@ instance S.IsString KeyString where fromString = _fromString "KeyString" p'KeyString +instance StringRepr KeyString where+ asParsec = p'KeyString+ renderShortText = ts'KeyString+ ts'KeyString :: KeyString -> ShortText ts'KeyString (KeyString s) = s @@ -279,6 +295,10 @@ instance S.IsString MatchingRuleId where fromString = _fromString "MatchingRuleId" p'MatchingRuleId++instance StringRepr MatchingRuleId where+ asParsec = p'MatchingRuleId+ renderShortText = ts'MatchingRuleId ts'MatchingRuleId :: MatchingRuleId -> ShortText ts'MatchingRuleId (MatchingRuleId mrid) = ts'DescrOrOID mrid
+ src/LDAPv3/DistinguishedName.hs view
@@ -0,0 +1,342 @@+-- Copyright (c) 2020 Herbert Valerio Riedel <hvr@gnu.org>+--+-- This file is free software: you may copy, redistribute and/or modify it+-- under the terms of the GNU General Public License as published by the+-- Free Software Foundation, either version 2 of the License, or (at your+-- option) any later version.+--+-- This file is distributed in the hope that it will be useful, but+-- WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+-- General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program (see `LICENSE`). If not, see+-- <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.++{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++-- internal module+module LDAPv3.DistinguishedName+ ( DistinguishedName(..)+ , rfc4514coreAttributes+ ) where++import Common hiding (Option, many, option, some, (<|>))+import LDAPv3.AttributeDescription+import LDAPv3.Message (OCTET_STRING)+import LDAPv3.StringRepr.Class++import qualified Data.ByteString as BS+import Data.Char (chr)+import Data.List as L+import Data.Text.Lazy.Builder as B+import qualified Data.Text.Lazy.Builder.Int as B+import qualified Data.Text.Short as TS++import Text.Parsec as P+++-- | Haskell representation of the table below as defined in <https://tools.ietf.org/search/rfc4514#section-3 RFC4514 Section 3>.+--+-- +--------+-----------------------------------------------++-- | String | X.500 AttributeType |+-- +========+===============================================++-- | CN | commonName (2.5.4.3) |+-- | L | localityName (2.5.4.7) |+-- | ST | stateOrProvinceName (2.5.4.8) |+-- | O | organizationName (2.5.4.10) |+-- | OU | organizationalUnitName (2.5.4.11) |+-- | C | countryName (2.5.4.6) |+-- | STREET | streetAddress (2.5.4.9) |+-- | DC | domainComponent (0.9.2342.19200300.100.1.25) |+-- | UID | userId (0.9.2342.19200300.100.1.1) |+-- +--------+-----------------------------------------------++--+-- @since 0.1.1+rfc4514coreAttributes :: [(KeyString,OID)]+rfc4514coreAttributes =+ [ ("CN" {- commonName -} , oid [2,5,4,3] )+ , ("L" {- localityName -} , oid [2,5,4,7] )+ , ("ST" {- stateOrProvinceName -} , oid [2,5,4,8] )+ , ("O" {- organizationName -} , oid [2,5,4,10] )+ , ("OU" {- organizationalUnitName -} , oid [2,5,4,11] )+ , ("C" {- countryName -} , oid [2,5,4,6] )+ , ("STREET" {- streetAddress -} , oid [2,5,4,9] )+ , ("DC" {- domainComponent -} , oid [0,9,2342,19200300,100,1,25] )+ , ("UID" {- userId -} , oid [0,9,2342,19200300,100,1,1] )+ ]+ where+ oid = \(n:ns) -> OID (n :| ns)++-- | Decoded non-normalizing string representation of @DistinguishedName@+--+-- > DistinguishedName ::= RDNSequence+-- >+-- > RDNSequence ::= SEQUENCE OF RelativeDistinguishedName+-- >+-- > RelativeDistinguishedName ::= SET SIZE (1..MAX) OF+-- > AttributeTypeAndValue+-- >+-- > AttributeTypeAndValue ::= SEQUENCE {+-- > type AttributeType,+-- > value AttributeValue }+--+-- Raw ASN.1 Hex-encoded @AttributeValue@s are represented as 'OCTET_STRING' (which implies they MUST not be a size-0 'OCTET_STRING') whereas 'ShortText' is used for textually encoded (possibly containing escaped characters) values.+--+-- As defined in RFC4514, the RDNSequence is serialized in reverse order.+--+-- @since 0.1.1+newtype DistinguishedName = DistinguishedName [NonEmpty (Either KeyString OID,Either OCTET_STRING ShortText)]+ deriving (Eq,Show)++instance StringRepr DistinguishedName where+ asBuilder = r'DistinguishedName+ asParsec = p'DistinguishedName+++r'DistinguishedName :: DistinguishedName -> Builder+r'DistinguishedName (DistinguishedName rdns) = case L.reverse rdns of+ [] -> mempty+ r:rs -> sepby r'rdn ',' (r :| rs)+ where+ r'rdn = sepby r'atav '+'++ r'atav (k,v) = either asBuilder asBuilder k <> B.singleton '=' <> either r'hexval r'textval v++ r'hexval = mconcat . (B.singleton '#' :) . map r'word8hex . BS.unpack++ r'word8hex x+ | x < 0x10 = B.singleton '0' <> B.hexadecimal x+ | otherwise = B.hexadecimal x++ r'textval t+ | needEscape t = B.fromString $ goEsc $ TS.unpack t+ | otherwise = b'ShortText t++ goEsc [] = ""+ goEsc (' ':rest) = '\\':' ':goEsc1 rest+ goEsc ('#':rest) = '\\':'#':goEsc1 rest+ goEsc rest = goEsc1 rest++ goEsc1 [] = ""+ goEsc1 " " = "\\ "+ goEsc1 (c:rest)+ | c == '\0' = '\\':'0':'0':goEsc1 rest+ | needEsc1 c = '\\':c:goEsc1 rest+ | otherwise = c:goEsc1 rest++ needEscape t+ | TS.null t = False+ | Just c <- TS.indexMaybe t 0+ , c == '#' || c == ' ' = True+ | Just c <- TS.indexEndMaybe t 0+ , c == ' ' = True+ | TS.any (\c -> needEsc1 c || c == '\0') t = True+ | otherwise = False++ needEsc1 '"' = True+ needEsc1 '+' = True+ needEsc1 ',' = True+ needEsc1 ';' = True+ needEsc1 '<' = True+ needEsc1 '>' = True+ needEsc1 '\\' = True+ needEsc1 _ = False++ sepby rend c (x :| xs) = rend x <> go xs+ where+ go [] = mempty+ go (y:ys) = B.singleton c <> rend y <> go ys++p'DistinguishedName :: Stream s Identity Char => Parsec s () DistinguishedName+p'DistinguishedName = DistinguishedName . L.reverse <$> p'distinguishedName -- optional+ where+ -- distinguishedName = [ relativeDistinguishedName *( COMMA relativeDistinguishedName ) ]+ p'distinguishedName = p'relativeDistinguishedName `sepBy` char ','++ -- relativeDistinguishedName = attributeTypeAndValue *( PLUS attributeTypeAndValue )+ p'relativeDistinguishedName = p'attributeTypeAndValue `sepBy1'` char '+'++ -- attributeTypeAndValue = attributeType EQUALS attributeValue+ -- attributeType = descr / numericoid+ -- attributeValue = string / hexstring++ p'attributeTypeAndValue = do+ ty <- p'DescrOrOID+ _ <- char '='+ va <- (Left <$> p'hexstring) <|> (Right <$> p'string)+ pure (ty,va)++ -- ; The following characters are to be escaped when they appear+ -- ; in the value to be encoded: ESC, one of <escaped>, leading+ -- ; SHARP or SPACE, trailing SPACE, and NULL.+ -- string = [ ( leadchar / pair ) [ *( stringchar / pair ) ( trailchar / pair ) ] ]+ p'string = do+ mc0 <- optionMaybe $ (C <$> satisfy isLeadchar) <|> p'pair+ case mc0 of+ Nothing -> pure mempty+ Just c0 -> do+ -- since the grammar above doesn't lend itself to be expressed directly with Parsec+ -- combinators, we defer the unescaped-trailing-space check to keep things simple...+ cs <- many ((C <$> satisfy isStringchar) <|> p'pair)+ case cs of+ [] -> pure ()+ _:_ -> when (last cs == C ' ') $ fail "trailing unescaped SPACE encountered in <string>"++ pure $ TS.fromString $ map unescape (c0:cs)++ -- leadchar = LUTF1 / UTFMB+ -- LUTF1 = %x01-1F / %x21 / %x24-2A / %x2D-3A / %x3D / %x3F-5B / %x5D-7F+ isLeadchar c = case c of+ '\x00' -> False+ '\x20' -> False -- ' '+ '\x22' -> False -- '"'+ '\x23' -> False -- '#'+ '\x2B' -> False -- '+'+ '\x2C' -> False -- ','+ '\x3B' -> False -- ';'+ '\x3C' -> False -- '<'+ '\x3E' -> False -- '>'+ '\x5C' -> False -- '\\'+ _ -> True++ -- trailchar = TUTF1 / UTFMB+ -- TUTF1 = %x01-1F / %x21 / %x23-2A / %x2D-3A / %x3D / %x3F-5B / %x5D-7F++ -- stringchar = SUTF1 / UTFMB+ -- SUTF1 = %x01-21 / %x23-2A / %x2D-3A / %x3D / %x3F-5B / %x5D-7F+ isStringchar c = case c of+ '\x00' -> False+ '\x22' -> False -- '"'+ '\x2B' -> False -- '+'+ '\x2C' -> False -- ','+ '\x3B' -> False -- ';'+ '\x3C' -> False -- '<'+ '\x3E' -> False -- '>'+ '\x5C' -> False -- '\\'+ _ -> True++ -- pair = ESC ( ESC / special / hexpair )+ p'pair = do+ _ <- char '\\'+ (CEsc <$> satisfy isEscOrSpecial) <|> (CHex <$> p'hexpairsUtf8)++ -- special = escaped / SPACE / SHARP / EQUALS+ -- escaped = DQUOTE / PLUS / COMMA / SEMI / LANGLE / RANGLE+ isEscOrSpecial c = case c of+ '\\' -> True++ '"' -> True+ '+' -> True+ ',' -> True+ ';' -> True+ '<' -> True+ '>' -> True++ ' ' -> True+ '#' -> True+ '=' -> True++ _ -> False++ -- hexstring = SHARP 1*hexpair+ p'hexstring = do+ _ <- char '#'+ octets <- many1 p'hexpair+ pure $ BS.pack octets++data C = C { unescape :: !Char } -- unescaped character+ | CEsc { unescape :: !Char } -- backslash escaped character+ | CHex { unescape :: !Char } -- hex pairs encoded utf8 code-point+ deriving (Show,Eq)++-- ; Any UTF-8 [RFC3629] encoded Unicode [Unicode] character+p'hexpairsUtf8 :: Stream s Identity Char => Parsec s () Char+p'hexpairsUtf8 = do+ -- UTF8 = UTF1 / UTFMB+ -- UTFMB = UTF2 / UTF3 / UTF4+ o0 <- p'hexpair+ case () of+ -- UTF1 = %x00-7F+ _ | o0 <= 0x7f -> pure $! chr (fromIntegral o0)++ -- UTF2 = %xC2-DF UTF0+ | o0 `inside` (0xc2,0xdf) -> do+ let o0' = fromIntegral (o0 .&. 0x1f) `unsafeShiftL` 6+ o1' <- p'utf0+ pure $! chr (o0' .|. o1')++ -- UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / %xED %x80-9F UTF0 / %xEE-EF 2(UTF0)+ | o0 == 0xe0 -> do+ let o0' = fromIntegral (o0 .&. 0x0f) `unsafeShiftL` 12+ o1' <- (`unsafeShiftL` 6) <$> p'utf0' 0xa0 0xbf+ o2' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2')+ | o0 == 0xed -> do+ let o0' = fromIntegral (o0 .&. 0x0f) `unsafeShiftL` 12+ o1' <- (`unsafeShiftL` 6) <$> p'utf0' 0x80 0x9f+ o2' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2')+ | o0 `inside` (0xe1,0xef) -> do -- NB: 0xed excluded due to preceding case+ let o0' = fromIntegral (o0 .&. 0x0f) `unsafeShiftL` 12+ o1' <- (`unsafeShiftL` 6) <$> p'utf0+ o2' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2')++ -- UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / %xF4 %x80-8F 2(UTF0)+ | o0 == 0xf0 -> do+ let o0' = fromIntegral (o0 .&. 0x07) `unsafeShiftL` 18+ o1' <- (`unsafeShiftL` 12) <$> p'utf0' 0x90 0xbf+ o2' <- (`unsafeShiftL` 6) <$> p'utf0+ o3' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2' .|. o3')+ | o0 `inside` (0xf1,0xf3) -> do+ let o0' = fromIntegral (o0 .&. 0x07) `unsafeShiftL` 18+ o1' <- (`unsafeShiftL` 12) <$> p'utf0+ o2' <- (`unsafeShiftL` 6) <$> p'utf0+ o3' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2' .|. o3')+ | o0 == 0xf4 -> do+ let o0' = fromIntegral (o0 .&. 0x07) `unsafeShiftL` 18+ o1' <- (`unsafeShiftL` 12) <$> p'utf0' 0x80 0x8f+ o2' <- (`unsafeShiftL` 6) <$> p'utf0+ o3' <- p'utf0+ pure $! chr (o0' .|. o1' .|. o2' .|. o3')++ -- everything else is not a valid UTF8 encoded code-point+ | otherwise -> utf8fail++ where+ -- UTF0 = %x80-BF+ p'utf0 = p'utf0' 0x80 0xbf++ p'utf0' lb ub = do+ _ <- char '\\'+ o <- p'hexpair+ unless (o `inside` (lb,ub)) $ utf8fail+ pure $ (fromIntegral $ o .&. 0x3f)++ utf8fail = fail "unexpected hex-encoded UTF8 octet"++-- hexpair = HEX HEX+p'hexpair :: Stream s Identity Char => Parsec s () Word8+p'hexpair = ((\hi lo -> hi*16 + lo) <$> p'HEX <*> p'HEX)++p'HEX :: Stream s Identity Char => Parsec s () Word8+p'HEX = (fromIntegral :: Int -> Word8) . go . fromEnum <$> hexDigit+ where+ go n+ | n `inside` (0x30,0x39) = n - 0x30+ | n `inside` (0x61,0x66) = n - (0x61 - 10)+ | n `inside` (0x41,0x46) = n - (0x41 - 10)+ | otherwise = impossible++b'ShortText :: ShortText -> Builder+b'ShortText = fromText . TS.toText
src/LDAPv3/SearchFilter.hs view
@@ -19,6 +19,8 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} +{-# OPTIONS_GHC -fno-warn-orphans #-}+ -- | String representation of LDAPv3 search 'Filter's as defined by <https://tools.ietf.org/html/rfc4515 RFC4515>. -- -- @since 0.1.0@@ -30,6 +32,7 @@ import Common hiding (many, option, some, (<|>)) import LDAPv3.AttributeDescription import LDAPv3.Message+import LDAPv3.StringRepr.Class import qualified Data.ByteString as BS import qualified Data.List.NonEmpty as NE@@ -39,6 +42,12 @@ import Data.Text.Lazy.Builder.Int (hexadecimal) import Text.Parsec as P++-- NB: technically an orphan; we mitigate this by ensuring that all modules by which the 'StringRepr' class is exported+-- imports this module as to avoid making this observable from outside this package.+instance StringRepr Filter where+ asBuilder = r'Filter+ asParsec = p'Filter -- -- | Render LDAPv3 search 'Filter's into <https://tools.ietf.org/html/rfc4515 RFC4515> text representation -- renderFilter :: Filter -> Text
src/LDAPv3/StringRepr.hs view
@@ -14,10 +14,11 @@ -- along with this program (see `LICENSE`). If not, see -- <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>. -{-# LANGUAGE FlexibleContexts #-}---- | String representation of LDAPv3 search 'Filter's as defined by <https://tools.ietf.org/html/rfc4515 RFC4515>.+-- | String representation of --+-- * LDAPv3 search 'Filter's as defined by <https://tools.ietf.org/html/rfc4515 RFC4515>+-- * LDAPv3 'DistinguishedName's as defined by <https://tools.ietf.org/html/rfc4514 RFC4514>+-- -- @since 0.1.0 module LDAPv3.StringRepr ( StringRepr ( asParsec@@ -29,32 +30,22 @@ , parseShortText , parseText , parseString++ -- * Distinguished Names++ , DistinguishedName(DistinguishedName)+ , rfc4514coreAttributes ) where import Common hiding (Option, many, option, some, (<|>)) -import qualified Data.Text.Lazy as T (toStrict)-import Data.Text.Lazy.Builder as B import qualified Data.Text.Short as TS import Text.Parsec as P -import LDAPv3.AttributeDescription-import LDAPv3.Message (Filter)-import LDAPv3.SearchFilter---- | Convert to and from string representations as defined by <https://tools.ietf.org/html/rfc4515 RFC4515>.------ @since 0.1.0-class StringRepr a where- asParsec :: Stream s Identity Char => Parsec s () a-- asBuilder :: a -> Builder- asBuilder = fromText . TS.toText . renderShortText-- renderShortText :: a -> ShortText- renderShortText = TS.fromText . T.toStrict . B.toLazyText . asBuilder-- {-# MINIMAL asParsec, (renderShortText | asBuilder) #-}+import LDAPv3.AttributeDescription ()+import LDAPv3.DistinguishedName (DistinguishedName (..), rfc4514coreAttributes)+import LDAPv3.SearchFilter ()+import LDAPv3.StringRepr.Class -- | Convenience 'StringRepr' operation for rendering as 'Text' --@@ -85,28 +76,3 @@ -- @since 0.1.0 parseString :: StringRepr a => String -> Maybe a parseString = either (const Nothing) Just . parse (asParsec <* eof) ""--instance StringRepr AttributeDescription where- asParsec = p'AttributeDescription- renderShortText = ts'AttributeDescription--instance StringRepr Option where- asParsec = p'Option- renderShortText = ts'Option--instance StringRepr OID where- asBuilder = r'OID- renderShortText = ts'OID- asParsec = p'OID--instance StringRepr KeyString where- asParsec = p'KeyString- renderShortText = ts'KeyString--instance StringRepr MatchingRuleId where- asParsec = p'MatchingRuleId- renderShortText = ts'MatchingRuleId--instance StringRepr Filter where- asBuilder = r'Filter- asParsec = p'Filter
+ src/LDAPv3/StringRepr/Class.hs view
@@ -0,0 +1,40 @@+-- Copyright (c) 2019 Herbert Valerio Riedel <hvr@gnu.org>+--+-- This file is free software: you may copy, redistribute and/or modify it+-- under the terms of the GNU General Public License as published by the+-- Free Software Foundation, either version 2 of the License, or (at your+-- option) any later version.+--+-- This file is distributed in the hope that it will be useful, but+-- WITHOUT ANY WARRANTY; without even the implied warranty of+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU+-- General Public License for more details.+--+-- You should have received a copy of the GNU General Public License+-- along with this program (see `LICENSE`). If not, see+-- <https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.++{-# LANGUAGE FlexibleContexts #-}++module LDAPv3.StringRepr.Class where++import Common hiding (Option, many, option, some, (<|>))++import qualified Data.Text.Lazy as T (toStrict)+import Data.Text.Lazy.Builder as B+import qualified Data.Text.Short as TS+import Text.Parsec as P++-- | Convert to and from string representations as defined by <https://tools.ietf.org/html/rfc4515 RFC4515>.+--+-- @since 0.1.0+class StringRepr a where+ asParsec :: Stream s Identity Char => Parsec s () a++ asBuilder :: a -> Builder+ asBuilder = fromText . TS.toText . renderShortText++ renderShortText :: a -> ShortText+ renderShortText = TS.fromText . T.toStrict . B.toLazyText . asBuilder++ {-# MINIMAL asParsec, (renderShortText | asBuilder) #-}
test/Arbitrary.hs view
@@ -18,18 +18,22 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Arbitrary () where +import LDAPv3.ASN1String import LDAPv3.Message+import LDAPv3.StringRepr (DistinguishedName(..)) import qualified Data.ByteString as BS import qualified Data.Char as C import Data.Coerce (coerce) import Data.Int import Data.List.NonEmpty (NonEmpty (..))+import Data.Proxy (Proxy(..)) import Data.String (fromString) import qualified Data.Text.Short as TS import Test.QuickCheck.Instances ()@@ -286,3 +290,55 @@ instance Arbitrary OID where arbitrary = OID <$> arbitrary++instance Arbitrary DistinguishedName where+ shrink (DistinguishedName rdns) = map DistinguishedName $ genericShrink rdns++ arbitrary = (DistinguishedName <$> arbitrary) `suchThat` nonEmptyOctets+ where+ nonEmptyOctets (DistinguishedName rdns) = all (all ok) rdns+ where+ ok = either (not . BS.null) (const True) . snd++instance Arbitrary ASN1StringChoice where+ arbitrary = oneof+ [ ASN1String'OCTET_STRING <$> arbitrary+ , ASN1String'UniversalString <$> arbitrary+ , ASN1String'UTF8String <$> arbitrary+ , ASN1String'BMPString <$> arbitrary+ , ASN1String'IA5String <$> arbitrary+ , ASN1String'VisibleString <$> arbitrary+ , ASN1String'PrintableString <$> arbitrary+ , ASN1String'NumericString <$> arbitrary+ ]++instance Arbitrary UniversalString where+ arbitrary = (asn1string'fromCodePoints <$> arbitrary) `suchThatMap` id++instance Arbitrary BMPString where+ arbitrary = bmpString'fromUcs2CodePoints <$> arbitrary++instance Arbitrary IA5String where+ arbitrary = a'ia5subtype++instance Arbitrary VisibleString where+ arbitrary = a'ia5subtype++instance Arbitrary LDAPv3.ASN1String.PrintableString where+ arbitrary = a'ia5subtype++instance Arbitrary NumericString where+ arbitrary = a'ia5subtype++a'ia5subtype :: forall s . ASN1String s => Gen s+a'ia5subtype = do+ s <- asn1string'fromCodePoints <$> listOf a'char+ case s of+ Nothing -> error "internal error in ia5subtype generator"+ Just x -> pure x+ where+ a'hasChar = asn1string'supportsCodePoint (Proxy :: Proxy s)+ a'char = choose (min'char, max'char) `suchThat` a'hasChar++ max'char = last $ filter a'hasChar ['\0'..'\x7f']+ min'char = head $ filter a'hasChar ['\0'..'\x7f']
test/main.hs view
@@ -20,6 +20,7 @@ module Main (main) where +import LDAPv3.ASN1String import LDAPv3.Message import LDAPv3.StringRepr @@ -41,9 +42,9 @@ main :: IO () main = defaultMain tests -tests, qcPropsRFC4511, qcPropsRFC4515, unitTestsRFC4511, unitTestsRFC4515 :: TestTree+tests, qcPropsRFC4511, qcPropsRFC4515, unitTestsRFC4511, unitTestsRFC4515, unitTestsRFC4514, qcPropsRFC4514, qcPropsASN1String :: TestTree -tests = testGroup "Tests" [unitTestsRFC4515, unitTestsRFC4511, qcPropsRFC4515, qcPropsRFC4511]+tests = testGroup "Tests" [unitTestsRFC4515, unitTestsRFC4511, qcPropsRFC4515, qcPropsRFC4511, unitTestsRFC4514, qcPropsRFC4514, qcPropsASN1String] ---------------------------------------------------------------------------------------------------- @@ -673,6 +674,67 @@ ) ) ]+ ]++----------------------------------------------------------------------------------------------------++unitTestsRFC4514 = testGroup "Golden tests (RFC4514)"+ [ testGroup tlabel $+ [ testCase "parseDN" $ parseShortText ref_string @?= Just ref_dn+ , testCase "renderDN" $ renderShortText ref_dn @?= ref_string2+ ] +++ [ testCase "parseDN #2" $ parseShortText ref_string2 @?= Just ref_dn+ | ref_string2 /= ref_string+ ]+ | (tlabel,(ref_string,ref_string2),ref_dn) <-+ [+ ( "RFC4514 example #1"+ , dup"UID=jsmith,DC=example,DC=net"+ , DistinguishedName [(Left "DC",Right "net") :| [],(Left "DC",Right "example") :| [],(Left "UID",Right "jsmith") :| []]+ )+ ,+ ( "RFC4514 example #2"+ , dup"OU=Sales+CN=J. Smith,DC=example,DC=net"+ , DistinguishedName [(Left "DC",Right "net") :| [],(Left "DC",Right "example") :| [],(Left "OU",Right "Sales") :| [(Left "CN",Right "J. Smith")]]+ )+ ,+ ( "RFC4514 example #3"+ , ( "CN=James\\ \\22Jim\\22\\20Smith\\2c III,DC=example,DC=net"+ , "CN=James \\\"Jim\\\" Smith\\, III,DC=example,DC=net"+ )+ , DistinguishedName [(Left "DC",Right "net") :| [],(Left "DC",Right "example") :| [],(Left "CN",Right "James \"Jim\" Smith, III") :| []]+ )+ ,+ ( "RFC4514 example #4"+ , ( "CN=Before\\0dAfter,DC=example,DC=net"+ , "CN=Before\rAfter,DC=example,DC=net"+ )+ , DistinguishedName [(Left "DC",Right "net") :| [],(Left "DC",Right "example") :| [],(Left "CN",Right "Before\rAfter") :| []]+ )+ ,+ ( "RFC4514 example #5"+ , dup"1.3.6.1.4.1.1466.0=#04024869"+ , DistinguishedName [(Right (OID (1 :| [3,6,1,4,1,1466,0])),Left "\EOT\STXHi") :| []]+ )++ ,+ ( "RFC4514 example #6"+ , ( "CN=Lu\\C4\\8Di\\c4\\87\\00"+ , "CN=Lu\269i\263\\00"+ )+ , DistinguishedName [(Left "CN",Right "Lu\269i\263\NUL") :| []]+ )+ ]+ ]++qcPropsRFC4514 = testGroup "Properties (RFC4514)"+ [ QC.testProperty "parse/render DN roundtrip" $+ \f -> parseShortText (renderShortText (f :: DistinguishedName)) === Just f+ ]++qcPropsASN1String = testGroup "Properties (ASN1String)"+ [ QC.testProperty "encode/decode ASN1StringChoice roundtrip" $+ \s -> asn1StringChoice'decode (asn1StringChoice'encode (s :: ASN1StringChoice)) === Just s ] ----------------------------------------------------------------------------------------------------