{-| Description: parsing of server replies. -} {-# LANGUAGE OverloadedStrings #-} module Network.Mail.SMTP.ReplyLine ( ReplyLine , Greeting , replyCode -- Attoparsec parsers for the datatype given above. The only way you can -- obtain a ReplyLine or Greeting is by parsing one from a ByteString. , greeting , replyLines ) where import qualified Data.ByteString as B import Data.Attoparsec.ByteString.Char8 import Control.Applicative import Network.Mail.SMTP.Types -- | A reply from a server: code and message. data ReplyLine = ReplyLine !ReplyCode !B.ByteString deriving (Show) -- | Projection onto ReplyCode. replyCode :: ReplyLine -> ReplyCode replyCode (ReplyLine x _) = x -- | A greeting from a server: domain/host name and message(s). data Greeting = Greeting !B.ByteString ![B.ByteString] deriving (Show) -- What follows is definitions for parsing ReplyLine and Greeting via -- Attoparsec combinators. -- Parser definitions pulled from RFC 5321 section 4.2 crlf :: Parser () crlf = char '\r' >> char '\n' >> pure () textstring :: Parser B.ByteString textstring = takeWhile1 predicate where -- 9 is horizontal tab, and [32, 126] is all printable US ASCII. -- Just check your table ;) predicate c' = let c = fromEnum c' in c == 9 || (c >= 32 && c <= 126) -- | Parser for one or more server replies. replyLines :: Parser [ReplyLine] replyLines = (++) <$> many' replyLine' <*> (pure <$> replyLine) replyLine :: Parser ReplyLine replyLine = ReplyLine <$> code <* space <*> option "" textstring <* crlf replyLine' :: Parser ReplyLine replyLine' = ReplyLine <$> code <* char '-' <*> option "" textstring <* crlf -- We deviate from the RFC on the response code, because it demands that -- an SMTP server SHOULD only send the codes listed in the spec. We just take -- any decimal numbere. code :: Parser ReplyCode code = decimal -- | Parser for a Greeting. greeting :: Parser Greeting greeting = manyGreetings <|> oneGreeting where oneGreeting :: Parser Greeting oneGreeting = do string "220 " bytestring <- takeWhile1 isASCIIPrintableNonWhitespace messages <- option [] (char ' ' *> (pure <$> textstring)) crlf return $ Greeting bytestring messages manyGreetings :: Parser Greeting manyGreetings = do string "220-" bytestring <- takeWhile1 isASCIIPrintableNonWhitespace greets <- option [] (char ' ' *> (pure <$> textstring)) crlf moreGreets <- many (string "220-" *> textstring <* crlf) string "220" lastgreet <- option [] (pure <$> (char ' ' >> textstring)) let messages = greets ++ moreGreets ++ lastgreet return $ Greeting bytestring messages isASCIIPrintableNonWhitespace :: Char -> Bool isASCIIPrintableNonWhitespace c' = let c = fromEnum c' in c > 32 && c <= 126