module Text.LDIF.Parser (
parseLDIFStr,
parseLDIFFile,
parseDNStr,
preproc,
defaulLDIFConf,
LDIFParserConfig(..)
)
where
import Prelude
import Text.LDIF.Types
import Text.LDIF.Consts
import Text.LDIF.Preproc
import Text.Parsec as PR
import Text.Parsec.ByteString
import Text.Parsec.Pos (initialPos)
import Text.Parsec.Error (Message(..), newErrorMessage)
import qualified Data.ByteString.Char8 as BC
import Data.Char
import Data.Maybe (fromJust, isNothing)
import Numeric (readHex)
data LDIFParserConfig = LDIFParserConfig { lpExpectedType :: Maybe LDIFType
, lpCaseSensitive :: Bool }
deriving Show
defaulLDIFConf :: LDIFParserConfig
defaulLDIFConf = LDIFParserConfig Nothing True
parseLDIFStr :: LDIFParserConfig -> FilePath -> BC.ByteString -> Either ParseError LDIF
parseLDIFStr conf name xs = case eldif of
Left err -> Left $ transposePos ptab err
Right ldf -> checkExpectedType ldf
where
(input, ptab) = preproc xs
eldif = parse (pLdif conf) name input
checkExpectedType ldf | (isNothing $ lpExpectedType conf) = Right ldf
| (getLDIFType ldf) == (fromJust $ lpExpectedType conf) = Right ldf
| otherwise = Left $ newErrorMessage (UnExpect "Invalid LDIF Type") (initialPos name)
parseLDIFFile :: LDIFParserConfig -> FilePath -> IO (Either ParseError LDIF)
parseLDIFFile conf name = do
input <- BC.readFile name
return $ parseLDIFStr conf name input
parseDNStr :: LDIFParserConfig -> BC.ByteString -> Either ParseError DN
parseDNStr conf = parse (pDN conf) "(param)"
pLdif :: LDIFParserConfig -> Parser LDIF
pLdif conf = do
pSEPs conf
ver <- optionMaybe pVersionSpec
recs <- sepEndBy (pRec conf) (pSEPs1 conf)
_ <- optionMaybe pSearchResult
eof
recs `seq` return $ LDIF ver recs
where
pVersionSpec :: Parser BC.ByteString
pVersionSpec = do
_ <- string "version:"
pFILL conf
xs <- many1 digit
pSEPs1 conf
let ys = xs `seq` BC.pack xs
ys `seq` return $ ys
pSearchResult :: Parser ()
pSearchResult = do
_ <- string "search:"
pFILL conf
_ <- many1 digit
pSEP conf
_ <- string "result:"
pFILL conf
_ <- pSafeString conf
pSEPs conf
return ()
pRec :: LDIFParserConfig -> Parser LDIFRecord
pRec conf = do
dn <- pDNSpec
pSEP conf
try (pChangeRec dn) <|> (pAttrValRec dn)
where
pDNSpec :: Parser DN
pDNSpec = do
_ <- string "dn:"
pDN conf
pAttrValRec :: DN -> Parser LDIFRecord
pAttrValRec dn = do
attrVals <- sepEndBy1 (pAttrValSpec conf) (pSEP conf)
attrVals `seq` return $ ContentRecord dn attrVals
pChangeRec :: DN -> Parser LDIFRecord
pChangeRec dn = do
_ <- string "changetype:"
pFILL conf
try (pChangeAdd conf dn)
<|> try (pChangeDel conf dn)
<|> try (pChangeMod conf dn)
<|> (pChangeModDN conf dn)
pChangeAdd :: LDIFParserConfig -> DN -> Parser LDIFRecord
pChangeAdd conf dn = do
_ <- string "add"
pSEP conf
vals <- sepEndBy1 (pAttrValSpec conf) (pSEP conf)
return $ ChangeRecord dn (ChangeAdd vals)
pChangeDel :: LDIFParserConfig -> DN -> Parser LDIFRecord
pChangeDel conf dn = do
_ <- string "delete"
pSEP conf
return $ ChangeRecord dn ChangeDelete
pChangeMod :: LDIFParserConfig -> DN -> Parser LDIFRecord
pChangeMod conf dn = do
_ <- string "modify"
pSEP conf
mods <- sepEndBy1 (pModSpec conf) (char '-' >> pSEP conf)
return $ ChangeRecord dn (ChangeModify mods)
pChangeModDN :: LDIFParserConfig -> DN -> Parser LDIFRecord
pChangeModDN conf dn = do
_ <- string "modrdn"
pSEP conf
_ <- string "newrdn:"
pFILL conf
_ <- pRDN conf
pSEP conf
_ <- string "deleteoldrdn:"
pFILL conf
_ <- oneOf "01"
pSEP conf
return $ ChangeRecord dn ChangeModDN
pRDN :: LDIFParserConfig -> Parser BC.ByteString
pRDN conf = pSafeString conf
pDN :: LDIFParserConfig -> Parser DN
pDN conf = do
pFILL conf
avals <- sepEndBy (pAttrEqValue conf) (char ',')
avals `seq` return $ DN avals
pAttrEqValue :: LDIFParserConfig -> Parser AttrValue
pAttrEqValue conf = do
pFILL conf
att <- pAttributeType conf
_ <- char '='
val <- pAttrValueDN conf
att `seq` val `seq` return (att,val)
pAttrValueDN :: LDIFParserConfig -> Parser Value
pAttrValueDN conf = do
xs <- many1 allChar
let ys = xs `seq` (mkVal conf $ BC.pack xs)
ys `seq` return $ ys
where
allChar = noneOf (escapedDNChars ++ "\n")
<|> try (hexChar)
<|> (escChar)
escChar = do
_ <- char '\\'
oneOf escapedDNChars
hexChar = do
_ <- char '\\'
hval <- PR.count 2 hexDigit
case readHex hval of
[(val,[])] -> return $ chr val
_ -> fail $ "invalid hex value: " ++ hval
pModSpec :: LDIFParserConfig -> Parser Modify
pModSpec conf = do
modType <- pModType conf
pFILL conf
att <- pAttributeDescription conf
pSEP conf
vals <- sepEndBy (pAttrValSpec conf) (pSEP conf)
return $ mkMod modType att vals
mkMod :: String -> Attribute -> [AttrValue] -> Modify
mkMod modType att vals | modType == "add:" = ModAdd att (map (snd) vals)
| modType == "delete:" = ModDelete att (map (snd) vals)
| modType == "replace:" = ModReplace att (map (snd) vals)
| otherwise = error $ "unexpected mod:" ++ modType
pModType :: LDIFParserConfig -> Parser String
pModType _ = try (string "add:")
<|> try (string "delete:")
<|> string "replace:"
pAttributeDescription :: LDIFParserConfig -> Parser Attribute
pAttributeDescription conf = pAttributeType conf
pAttributeType :: LDIFParserConfig -> Parser Attribute
pAttributeType _ = try pLdapOid
<|> pCharType
where
pDotOid = do
_ <- string "."
n <- many1 digit
let xs = n `seq` '.':n
xs `seq` return xs
pLdapOid = do
num <- many1 digit
rest <- many1 pDotOid
let xs = num `seq` rest `seq` num ++ concat rest
xs `seq` return (Attribute $ BC.pack xs)
pCharType = do
l <- letter
o <- pAttrTypeChars
let xs = l `seq` o `seq` l `BC.cons` o
xs `seq` return $ Attribute xs
where
pAttrTypeChars :: Parser BC.ByteString
pAttrTypeChars = do
xs <- many (satisfy (\x -> isAlphaNum x || x == '-'))
let ys = xs `seq` BC.pack xs
ys `seq` return ys
pAttrValSpec :: LDIFParserConfig -> Parser AttrValue
pAttrValSpec conf = do
name <- pAttributeDescription conf
val <- pValueSpec
name `seq` val `seq` return (name, val)
where
pValueSpec :: Parser Value
pValueSpec = try (char ':' >> pFILL conf >> pSafeString' conf >>= (\x -> return $ mkVal conf x))
<|> try (char ':' >> char ':' >> pFILL conf >> pBase64String conf >>= (\x -> return $ mkVal conf x))
<|> (char ':' >> char '<' >> pFILL conf >> pURL conf >>= (\x -> return $ mkVal conf x))
pURL :: LDIFParserConfig -> Parser BC.ByteString
pURL conf = pSafeString conf
pSafeString :: LDIFParserConfig -> Parser BC.ByteString
pSafeString _ = do
c <- noneOf "\n :<"
r <- many (noneOf "\n")
let xs = r `seq` c:r
let ys = xs `seq` BC.pack xs
ys `seq` return ys
pSafeString' :: LDIFParserConfig -> Parser BC.ByteString
pSafeString' _ = do
r <- many (noneOf "\n")
let ys = r `seq` BC.pack r
ys `seq` return ys
pBase64String :: LDIFParserConfig -> Parser BC.ByteString
pBase64String conf = pSafeString conf
pFILL :: LDIFParserConfig -> Parser ()
pFILL _ = skipMany (oneOf [' ', '\t'])
pSEP :: LDIFParserConfig -> Parser ()
pSEP _ = do
_ <- newline
return ()
pSEPs :: LDIFParserConfig -> Parser ()
pSEPs conf = many (pSEP conf) >> return ()
pSEPs1 :: LDIFParserConfig -> Parser ()
pSEPs1 conf = many1 (pSEP conf) >> return ()
mkVal :: LDIFParserConfig -> BC.ByteString -> Value
mkVal conf v | (lpCaseSensitive conf) = Value v
| otherwise = ValueI v