hsemail-2: Parsec parsers for the RFC2822 Internet Message format

Copyright | (c) 2013 Peter Simons
License | BSD3
Maintainer | simons@cryp.to
Stability | provisional
Portability | portable
Safe Haskell | Safe
Language | Haskell2010

Text.Parsec.Rfc2821

Contents

- ESMTP State Machine
- Data Types for SMTP Commands
- Data Types for SMTP Replies
- Command Parsers
- Argument Parsers
- Helper Functions

Description

This module exports parser combinators for the grammar described in RFC2821, "Simple Mail Transfer Protocol", http://www.faqs.org/rfcs/rfc2821.html.

Synopsis

- data SessionState
  - = Unknown
  - | HaveHelo
  - | HaveMailFrom
  - | HaveRcptTo
  - | HaveData
  - | HaveQuit
- data Event
  - = Greeting
  - | SayHelo String
  - | SayHeloAgain String
  - | SayEhlo String
  - | SayEhloAgain String
  - | SetMailFrom Mailbox
  - | AddRcptTo Mailbox
  - | StartData
  - | Deliver
  - | NeedHeloFirst
  - | NeedMailFromFirst
  - | NeedRcptToFirst
  - | NotImplemened
  - | ResetState
  - | SayOK
  - | SeeksHelp String
  - | Shutdown
  - | SyntaxErrorIn String
  - | Unrecognized String
- type SmtpdFSM = State SessionState Event
- smtpdFSM :: String -> SmtpdFSM
- handleSmtpCmd :: SmtpCmd -> SmtpdFSM
- data SmtpCmd
  - = Helo String
  - | Ehlo String
  - | MailFrom Mailbox
  - | RcptTo Mailbox
  - | Data
  - | Rset
  - | Send Mailbox
  - | Soml Mailbox
  - | Saml Mailbox
  - | Vrfy String
  - | Expn String
  - | Help String
  - | Noop
  - | Quit
  - | Turn
  - | WrongArg String ParseError
- data Mailbox = Mailbox [String] String String
- nullPath :: Mailbox
- postmaster :: Mailbox
- data SmtpReply = Reply SmtpCode [String]
- data SmtpCode = Code SuccessCode Category Int
- data SuccessCode
  - = Unused0
  - | PreliminarySuccess
  - | Success
  - | IntermediateSuccess
  - | TransientFailure
  - | PermanentFailure
- data Category
  - = Syntax
  - | Information
  - | Connection
  - | Unspecified3
  - | Unspecified4
  - | MailSystem
- reply :: Int -> Int -> Int -> [String] -> SmtpReply
- isSuccess :: SmtpReply -> Bool
- isFailure :: SmtpReply -> Bool
- isShutdown :: SmtpReply -> Bool
- smtpCmd :: Stream s m Char => ParsecT s u m SmtpCmd
- smtpData :: Stream s m Char => ParsecT s u m SmtpCmd
- rset :: Stream s m Char => ParsecT s u m SmtpCmd
- quit :: Stream s m Char => ParsecT s u m SmtpCmd
- turn :: Stream s m Char => ParsecT s u m SmtpCmd
- helo :: Stream s m Char => ParsecT s u m SmtpCmd
- ehlo :: Stream s m Char => ParsecT s u m SmtpCmd
- mail :: Stream s m Char => ParsecT s u m SmtpCmd
- rcpt :: Stream s m Char => ParsecT s u m SmtpCmd
- send :: Stream s m Char => ParsecT s u m SmtpCmd
- soml :: Stream s m Char => ParsecT s u m SmtpCmd
- saml :: Stream s m Char => ParsecT s u m SmtpCmd
- vrfy :: Stream s m Char => ParsecT s u m SmtpCmd
- expn :: Stream s m Char => ParsecT s u m SmtpCmd
- help :: Stream s m Char => ParsecT s u m SmtpCmd
- noop :: Stream s m Char => ParsecT s u m SmtpCmd
- from_path :: Stream s m Char => ParsecT s u m Mailbox
- to_path :: Stream s m Char => ParsecT s u m Mailbox
- path :: Stream s m Char => ParsecT s u m Mailbox
- mailbox :: Stream s m Char => ParsecT s u m Mailbox
- local_part :: Stream s m Char => ParsecT s u m String
- domain :: Stream s m Char => ParsecT s u m String
- a_d_l :: Stream s m Char => ParsecT s u m [String]
- at_domain :: Stream s m Char => ParsecT s u m String
- address_literal :: Stream s m Char => ParsecT s u m String
- ipv4_literal :: Stream s m Char => ParsecT s u m String
- ipv4addr :: Stream s m Char => ParsecT s u m String
- subdomain :: Stream s m Char => ParsecT s u m String
- dot_string :: Stream s m Char => ParsecT s u m String
- atom :: Stream s m Char => ParsecT s u m String
- snum :: Stream s m Char => ParsecT s u m String
- number :: Stream s m Char => ParsecT s u m String
- word :: Stream s m Char => ParsecT s u m String
- fixCRLF :: String -> String
- mkCmd0 :: Stream s m Char => String -> a -> ParsecT s u m a
- mkCmd1 :: Stream s m Char => String -> (a -> SmtpCmd) -> ParsecT s u m a -> ParsecT s u m SmtpCmd
- tokenList :: Stream s m Char => ParsecT s u m String -> Char -> ParsecT s u m String

# ESMTP State Machine

data SessionState Source #

Constructors

Unknown |
HaveHelo |
HaveMailFrom |
HaveRcptTo |
HaveData |
HaveQuit |

Instances

Bounded SessionState Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods minBound :: SessionState Source # maxBound :: SessionState Source #
Enum SessionState Source # |

Instance details

Defined in Text.Parsec.Rfc2821

Methods

succ :: SessionState -> SessionState Source #

pred :: SessionState -> SessionState Source #

toEnum :: Int -> SessionState Source #

fromEnum :: SessionState -> Int Source #

enumFrom :: SessionState -> [SessionState] Source #

enumFromThen :: SessionState -> SessionState -> [SessionState] Source #

enumFromTo :: SessionState -> SessionState -> [SessionState] Source #

enumFromThenTo :: SessionState -> SessionState -> SessionState -> [SessionState] Source #

Eq SessionState Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods (==) :: SessionState -> SessionState -> Bool Source # (/=) :: SessionState -> SessionState -> Bool Source #
Ord SessionState Source # |

Instance details

Defined in Text.Parsec.Rfc2821

Methods

compare :: SessionState -> SessionState -> Ordering Source #

(<) :: SessionState -> SessionState -> Bool Source #

(<=) :: SessionState -> SessionState -> Bool Source #

(>) :: SessionState -> SessionState -> Bool Source #

(>=) :: SessionState -> SessionState -> Bool Source #

max :: SessionState -> SessionState -> SessionState Source #

min :: SessionState -> SessionState -> SessionState Source #

Show SessionState Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> SessionState -> ShowS Source # show :: SessionState -> String Source # showList :: [SessionState] -> ShowS Source #

data Event Source #

Constructors

Greeting | reserved for the user
SayHelo String |
SayHeloAgain String |
SayEhlo String |
SayEhloAgain String |
SetMailFrom Mailbox |
AddRcptTo Mailbox |
StartData |
Deliver | reserved for the user
NeedHeloFirst |
NeedMailFromFirst |
NeedRcptToFirst |
NotImplemened | `Turn`, `Send`, `Soml`, `Saml`, `Vrfy`, and `Expn`.
ResetState |
SayOK | Triggered in case of `Noop` or when `Rset` is used before we even have a state.
SeeksHelp String | The parameter may be `[]`.
Shutdown |
SyntaxErrorIn String |
Unrecognized String |

Instances

Eq Event Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods (==) :: Event -> Event -> Bool Source # (/=) :: Event -> Event -> Bool Source #
Show Event Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> Event -> ShowS Source # show :: Event -> String Source # showList :: [Event] -> ShowS Source #

type SmtpdFSM = State SessionState Event Source #

smtpdFSM :: String -> SmtpdFSM Source #

Parse a line of SMTP dialogue and run `handleSmtpCmd` to determine the `Event`. In case of syntax errors, `SyntaxErrorIn` or `Unrecognized` will be returned. Inputs must be terminated with `crlf`. See `fixCRLF`.

handleSmtpCmd :: SmtpCmd -> SmtpdFSM Source #

For those who want to parse the `SmtpCmd` themselves. Calling this function in `HaveQuit` or `HaveData` will fail an assertion. If `assert` is disabled, it will return respectively `Shutdown` and `StartData` again.

# Data Types for SMTP Commands

data SmtpCmd Source #

The `smtpCmd` parser will create this data type from a string. Note that all command parsers expect their input to be terminated with `crlf`.

Constructors

Helo String |
Ehlo String |
MailFrom Mailbox | Might be `nullPath`.
RcptTo Mailbox | Might be `postmaster`.
Data |
Rset |
Send Mailbox |
Soml Mailbox |
Saml Mailbox |
Vrfy String |
Expn String |
Help String | Might be `[]`.
Noop | Optional argument ignored.
Quit |
Turn |
WrongArg String ParseError | When a valid command has been recognized, but the argument parser fails, then this type will be returned. The `String` contains the name of the command (in all upper-case) and the `ParseError` is, obviously, the error description.

Instances

Show SmtpCmd Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> SmtpCmd -> ShowS Source # show :: SmtpCmd -> String Source # showList :: [SmtpCmd] -> ShowS Source #

data Mailbox Source #

The most general e-mail address has the form: `<[@route,...:]user@domain>`. This type, too, supports `show` and `read`. Note that a "shown" address is always enclosed in angular brackets. When comparing two mailboxes for equality, the hostname is case-insensitive.

Constructors

Mailbox [String] String String |

Instances

Eq Mailbox Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods (==) :: Mailbox -> Mailbox -> Bool Source # (/=) :: Mailbox -> Mailbox -> Bool Source #
Read Mailbox Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods readsPrec :: Int -> ReadS Mailbox Source # readList :: ReadS [Mailbox] Source # readPrec :: ReadPrec Mailbox Source # readListPrec :: ReadPrec [Mailbox] Source #
Show Mailbox Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> Mailbox -> ShowS Source # show :: Mailbox -> String Source # showList :: [Mailbox] -> ShowS Source #

nullPath :: Mailbox Source #

`nullPath` `=` ``Mailbox` [] "" "" = "<>"`

postmaster :: Mailbox Source #

`postmaster` `=` ``Mailbox` [] "postmaster" "" = "<postmaster>"`

# Data Types for SMTP Replies

data SmtpReply Source #

An SMTP reply is a three-digit return code plus some waste of bandwidth called "comments". This is what the list of strings is for; one string per line in the reply. `show` will append an "`\r\n`" end-of-line marker to each entry in that list, so that the resulting string is ready to be sent back to the peer. For example:

    >>> show $ Reply (Code Success MailSystem 0) ["worked", "like", "a charm" ]
    "250-worked\r\n250-like\r\n250 a charm\r\n"

If the message is an empty list `[]`, a default text will be constructed:

    >>> show $ Reply (Code Success MailSystem 0) []
    "250 Success in category MailSystem\r\n"

Constructors

Reply SmtpCode [String] |

Instances

Show SmtpReply Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> SmtpReply -> ShowS Source # show :: SmtpReply -> String Source # showList :: [SmtpReply] -> ShowS Source #

data SmtpCode Source #

Constructors

Code SuccessCode Category Int |

Instances

Show SmtpCode Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> SmtpCode -> ShowS Source # show :: SmtpCode -> String Source # showList :: [SmtpCode] -> ShowS Source #

data SuccessCode Source #

Constructors

Unused0 |
PreliminarySuccess |
Success |
IntermediateSuccess |
TransientFailure |
PermanentFailure |

Instances

Bounded SuccessCode Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods minBound :: SuccessCode Source # maxBound :: SuccessCode Source #
Enum SuccessCode Source # |

Instance details

Defined in Text.Parsec.Rfc2821

Methods

succ :: SuccessCode -> SuccessCode Source #

pred :: SuccessCode -> SuccessCode Source #

toEnum :: Int -> SuccessCode Source #

fromEnum :: SuccessCode -> Int Source #

enumFrom :: SuccessCode -> [SuccessCode] Source #

enumFromThen :: SuccessCode -> SuccessCode -> [SuccessCode] Source #

enumFromTo :: SuccessCode -> SuccessCode -> [SuccessCode] Source #

enumFromThenTo :: SuccessCode -> SuccessCode -> SuccessCode -> [SuccessCode] Source #

Eq SuccessCode Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods (==) :: SuccessCode -> SuccessCode -> Bool Source # (/=) :: SuccessCode -> SuccessCode -> Bool Source #
Ord SuccessCode Source # |

Instance details

Defined in Text.Parsec.Rfc2821

Methods

compare :: SuccessCode -> SuccessCode -> Ordering Source #

(<) :: SuccessCode -> SuccessCode -> Bool Source #

(<=) :: SuccessCode -> SuccessCode -> Bool Source #

(>) :: SuccessCode -> SuccessCode -> Bool Source #

(>=) :: SuccessCode -> SuccessCode -> Bool Source #

max :: SuccessCode -> SuccessCode -> SuccessCode Source #

min :: SuccessCode -> SuccessCode -> SuccessCode Source #

Show SuccessCode Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> SuccessCode -> ShowS Source # show :: SuccessCode -> String Source # showList :: [SuccessCode] -> ShowS Source #

data Category Source #

Constructors

Syntax |
Information |
Connection |
Unspecified3 |
Unspecified4 |
MailSystem |

Instances

Bounded Category Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods minBound :: Category Source # maxBound :: Category Source #
Enum Category Source # |

Instance details

Defined in Text.Parsec.Rfc2821

Methods

succ :: Category -> Category Source #

pred :: Category -> Category Source #

toEnum :: Int -> Category Source #

fromEnum :: Category -> Int Source #

enumFrom :: Category -> [Category] Source #

enumFromThen :: Category -> Category -> [Category] Source #

enumFromTo :: Category -> Category -> [Category] Source #

enumFromThenTo :: Category -> Category -> Category -> [Category] Source #

Eq Category Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods (==) :: Category -> Category -> Bool Source # (/=) :: Category -> Category -> Bool Source #
Ord Category Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods compare :: Category -> Category -> Ordering Source # (<) :: Category -> Category -> Bool Source # (<=) :: Category -> Category -> Bool Source # (>) :: Category -> Category -> Bool Source # (>=) :: Category -> Category -> Bool Source # max :: Category -> Category -> Category Source # min :: Category -> Category -> Category Source #
Show Category Source # |
Instance details Defined in Text.Parsec.Rfc2821 Methods showsPrec :: Int -> Category -> ShowS Source # show :: Category -> String Source # showList :: [Category] -> ShowS Source #

reply :: Int -> Int -> Int -> [String] -> SmtpReply Source #

Construct a `SmtpReply`. Fails `assert` if invalid numbers are given.

isSuccess :: SmtpReply -> Bool Source #

A reply constitutes "success" if the status code is any of `PreliminarySuccess`, `Success`, or `IntermediateSuccess`.

isFailure :: SmtpReply -> Bool Source #

A reply constitutes "failure" if the status code is either `PermanentFailure` or `TransientFailure`.

isShutdown :: SmtpReply -> Bool Source #

The replies `221` and `421` signify `Shutdown`.

# Command Parsers

smtpCmd :: Stream s m Char => ParsecT s u m SmtpCmd Source #

The SMTP parsers defined here correspond to the commands specified in RFC2821, so I won't document them individually.

This parser recognizes any of the SMTP commands defined below. Note that all command parsers expect their input to be terminated with `crlf`.

smtpData :: Stream s m Char => ParsecT s u m SmtpCmd Source #

The parser name "data" was taken.

rset :: Stream s m Char => ParsecT s u m SmtpCmd Source #

quit :: Stream s m Char => ParsecT s u m SmtpCmd Source #

turn :: Stream s m Char => ParsecT s u m SmtpCmd Source #

helo :: Stream s m Char => ParsecT s u m SmtpCmd Source #

ehlo :: Stream s m Char => ParsecT s u m SmtpCmd Source #

mail :: Stream s m Char => ParsecT s u m SmtpCmd Source #

rcpt :: Stream s m Char => ParsecT s u m SmtpCmd Source #

send :: Stream s m Char => ParsecT s u m SmtpCmd Source #

soml :: Stream s m Char => ParsecT s u m SmtpCmd Source #

saml :: Stream s m Char => ParsecT s u m SmtpCmd Source #

vrfy :: Stream s m Char => ParsecT s u m SmtpCmd Source #

expn :: Stream s m Char => ParsecT s u m SmtpCmd Source #

help :: Stream s m Char => ParsecT s u m SmtpCmd Source #

noop :: Stream s m Char => ParsecT s u m SmtpCmd Source #

May have an optional `word` argument, but it is ignored.

# Argument Parsers

from_path :: Stream s m Char => ParsecT s u m Mailbox Source #

to_path :: Stream s m Char => ParsecT s u m Mailbox Source #

path :: Stream s m Char => ParsecT s u m Mailbox Source #

mailbox :: Stream s m Char => ParsecT s u m Mailbox Source #

local_part :: Stream s m Char => ParsecT s u m String Source #

domain :: Stream s m Char => ParsecT s u m String Source #

a_d_l :: Stream s m Char => ParsecT s u m [String] Source #

at_domain :: Stream s m Char => ParsecT s u m String Source #

address_literal :: Stream s m Char => ParsecT s u m String Source #

TODO: Add IPv6 address and general literals

ipv4_literal :: Stream s m Char => ParsecT s u m String Source #

ipv4addr :: Stream s m Char => ParsecT s u m String Source #

subdomain :: Stream s m Char => ParsecT s u m String Source #

dot_string :: Stream s m Char => ParsecT s u m String Source #

atom :: Stream s m Char => ParsecT s u m String Source #

snum :: Stream s m Char => ParsecT s u m String Source #

number :: Stream s m Char => ParsecT s u m String Source #

word :: Stream s m Char => ParsecT s u m String Source #

This is a useful addition: The parser accepts an `atom` or a `quoted_string`.

# Helper Functions

fixCRLF :: String -> String Source #

Make the string `crlf` terminated no matter what. '`\n`' is expanded, otherwise `crlf` is appended. Note that if the string was terminated incorrectly before, it still is. This function is useful when reading input with `hGetLine` which removes the end-of-line delimiter.

mkCmd0 :: Stream s m Char => String -> a -> ParsecT s u m a Source #

Construct a parser for a command without arguments. Expects `crlf`!

mkCmd1 :: Stream s m Char => String -> (a -> SmtpCmd) -> ParsecT s u m a -> ParsecT s u m SmtpCmd Source #

Construct a parser for a command with an argument, which the given parser will handle. The result of the argument parser will be applied to the type constructor before it is returned. Expects `crlf`!

tokenList :: Stream s m Char => ParsecT s u m String -> Char -> ParsecT s u m String Source #