123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- package rfc5321
- import (
- "errors"
- "net"
- )
- // Parse productions according to ABNF in RFC5322
- type RFC5322 struct {
- AddressList
- Parser
- addr SingleAddress
- }
- type AddressList struct {
- List []SingleAddress
- Group string
- }
- type SingleAddress struct {
- DisplayName string
- DisplayNameQuoted bool
- LocalPart string
- LocalPartQuoted bool
- Domain string
- IP net.IP
- NullPath bool
- }
- var (
- errNotAtom = errors.New("not atom")
- errExpectingAngleAddress = errors.New("not angle address")
- errNotAWord = errors.New("not a word")
- errExpectingColon = errors.New("expecting : ")
- errExpectingSemicolon = errors.New("expecting ; ")
- errExpectingAngleClose = errors.New("expecting >")
- errExpectingAngleOpen = errors.New("< expected")
- errQuotedUnclosed = errors.New("quoted string not closed")
- )
- // Address parses the "address" production specified in RFC5322
- // address = mailbox / group
- func (s *RFC5322) Address(input []byte) (AddressList, error) {
- s.set(input)
- s.next()
- s.List = nil
- s.addr = SingleAddress{}
- if err := s.mailbox(); err != nil {
- if s.ch == ':' {
- if groupErr := s.group(); groupErr != nil {
- return s.AddressList, groupErr
- } else {
- err = nil
- }
- }
- return s.AddressList, err
- }
- return s.AddressList, nil
- }
- // group = display-name ":" [group-List] ";" [CFWS]
- func (s *RFC5322) group() error {
- if s.addr.DisplayName == "" {
- if err := s.displayName(); err != nil {
- return err
- }
- } else {
- s.Group = s.addr.DisplayName
- s.addr.DisplayName = ""
- }
- if s.ch != ':' {
- return errExpectingColon
- }
- s.next()
- _ = s.groupList()
- s.skipSpace()
- if s.ch != ';' {
- return errExpectingSemicolon
- }
- return nil
- }
- // mailbox = name-addr / addr-spec
- func (s *RFC5322) mailbox() error {
- pos := s.pos // save the position
- if err := s.nameAddr(); err != nil {
- if err == errExpectingAngleAddress && s.ch != ':' { // ':' means it's a group
- // we'll attempt to parse as an email address without angle brackets
- s.addr.DisplayName = ""
- s.addr.DisplayNameQuoted = false
- s.pos = pos - 1 //- 1 // rewind to the saved position
- if s.pos > -1 {
- s.ch = s.buf[s.pos]
- }
- if err = s.Parser.mailbox(); err != nil {
- return err
- }
- s.addAddress()
- } else {
- return err
- }
- }
- return nil
- }
- // addAddress ads the current address to the List
- func (s *RFC5322) addAddress() {
- s.addr.LocalPart = s.LocalPart
- s.addr.LocalPartQuoted = s.LocalPartQuotes
- s.addr.Domain = s.Domain
- s.addr.IP = s.IP
- s.List = append(s.List, s.addr)
- s.addr = SingleAddress{}
- }
- // nameAddr consumes the name-addr production.
- // name-addr = [display-name] angle-addr
- func (s *RFC5322) nameAddr() error {
- _ = s.displayName()
- if s.ch == '<' {
- if err := s.angleAddr(); err != nil {
- return err
- }
- s.next()
- if s.ch != '>' {
- return errExpectingAngleClose
- }
- s.addAddress()
- return nil
- } else {
- return errExpectingAngleAddress
- }
- }
- // angleAddr consumes the angle-addr production
- // angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr
- func (s *RFC5322) angleAddr() error {
- s.skipSpace()
- if s.ch != '<' {
- return errExpectingAngleOpen
- }
- // addr-spec = local-part "@" domain
- if err := s.Parser.mailbox(); err != nil {
- return err
- }
- s.skipSpace()
- return nil
- }
- // displayName consumes the display-name production:
- // display-name = phrase
- // phrase = 1*word / obs-phrase
- func (s *RFC5322) displayName() error {
- defer func() {
- if s.accept.Len() > 0 {
- s.addr.DisplayName = s.accept.String()
- s.accept.Reset()
- }
- }()
- // phrase
- if err := s.word(); err != nil {
- return err
- }
- for {
- err := s.word()
- if err != nil {
- return nil
- }
- }
- }
- // quotedString consumes a quoted-string production
- func (s *RFC5322) quotedString() error {
- if s.ch == '"' {
- if err := s.Parser.QcontentSMTP(); err != nil {
- return err
- }
- if s.ch != '"' {
- return errQuotedUnclosed
- } else {
- // accept the "
- s.next()
- }
- }
- return nil
- }
- // word = atom / quoted-string
- func (s *RFC5322) word() error {
- if s.ch == '"' {
- s.addr.DisplayNameQuoted = true
- return s.quotedString()
- } else if s.isAtext(s.ch) || s.ch == ' ' || s.ch == '\t' {
- return s.atom()
- }
- return errNotAWord
- }
- // atom = [CFWS] 1*atext [CFWS]
- func (s *RFC5322) atom() error {
- s.skipSpace()
- if !s.isAtext(s.ch) {
- return errNotAtom
- }
- for {
- if s.isAtext(s.ch) {
- s.accept.WriteByte(s.ch)
- s.next()
- } else {
- skipped := s.skipSpace()
- if !s.isAtext(s.ch) {
- return nil
- }
- if skipped > 0 {
- s.accept.WriteByte(' ')
- }
- s.accept.WriteByte(s.ch)
- s.next()
- }
- }
- }
- // groupList consumes the "group-List" production:
- // group-List = mailbox-List / CFWS / obs-group-List
- func (s *RFC5322) groupList() error {
- // mailbox-list = (mailbox *("," mailbox))
- if err := s.mailbox(); err != nil {
- return err
- }
- s.next()
- for {
- s.skipSpace()
- if s.ch != ',' {
- return nil
- }
- s.next()
- s.skipSpace()
- if err := s.mailbox(); err != nil {
- return err
- }
- s.next()
- }
- }
- // skipSpace skips vertical space by calling next(), returning the count of spaces skipped
- func (s *RFC5322) skipSpace() int {
- var skipped int
- for {
- if s.ch != ' ' && s.ch != 9 {
- return skipped
- }
- s.next()
- skipped++
- }
- }
|