diff --git a/main.go b/main.go index ff19afa..41b6eb9 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,6 @@ package main import ( - "bufio" "bytes" "context" "crypto" @@ -3519,35 +3518,10 @@ func cmdMessageParse(c *cmd) { err = enc.Encode(part) xcheckf(err, "write") - hasNonASCII := func(r io.Reader) bool { - br := bufio.NewReader(r) - for { - b, err := br.ReadByte() - if err == io.EOF { - break - } - xcheckf(err, "read header") - if b > 0x7f { - return true - } - } - return false - } - - var walk func(p *message.Part) bool - walk = func(p *message.Part) bool { - if hasNonASCII(p.HeaderReader()) { - return true - } - for _, pp := range p.Parts { - if walk(&pp) { - return true - } - } - return false - } if smtputf8 { - fmt.Println("message needs smtputf8:", walk(&part)) + needs, err := part.NeedsSMTPUTF8() + xcheckf(err, "checking if message needs smtputf8") + fmt.Println("message needs smtputf8:", needs) } } diff --git a/message/part.go b/message/part.go index bf1b8b7..faba013 100644 --- a/message/part.go +++ b/message/part.go @@ -21,6 +21,7 @@ import ( "net/textproto" "strings" "time" + "unicode" "golang.org/x/text/encoding/ianaindex" @@ -598,6 +599,38 @@ func (p *Part) IsDSN() bool { (p.Parts[1].MediaSubType == "DELIVERY-STATUS" || p.Parts[1].MediaSubType == "GLOBAL-DELIVERY-STATUS") } +func hasNonASCII(r io.Reader) (bool, error) { + br := bufio.NewReader(r) + for { + b, err := br.ReadByte() + if err == io.EOF { + break + } else if err != nil { + return false, err + } + if b > unicode.MaxASCII { + return true, nil + } + } + return false, nil +} + +// NeedsSMTPUTF8 returns whether the part needs the SMTPUTF8 extension to be +// transported, due to non-ascii in message headers. +func (p *Part) NeedsSMTPUTF8() (bool, error) { + if has, err := hasNonASCII(p.HeaderReader()); err != nil { + return false, fmt.Errorf("reading header: %w", err) + } else if has { + return true, nil + } + for _, pp := range p.Parts { + if has, err := pp.NeedsSMTPUTF8(); err != nil || has { + return has, err + } + } + return false, nil +} + var ErrParamEncoding = errors.New("bad header parameter encoding") // DispositionFilename tries to parse the disposition header and the "filename" diff --git a/smtpserver/server.go b/smtpserver/server.go index e90f0c9..424c88c 100644 --- a/smtpserver/server.go +++ b/smtpserver/server.go @@ -1940,47 +1940,32 @@ func (c *conn) cmdRcpt(p *parser) { c.bwritecodeline(smtp.C250Completed, smtp.SeAddr1Other0, "now on the list", nil) } -// ../rfc/6531:497 -func (c *conn) isSMTPUTF8Required(part *message.Part) bool { - hasNonASCII := func(r io.Reader) bool { - br := bufio.NewReader(r) - for { - b, err := br.ReadByte() - if err == io.EOF { - break - } - xcheckf(err, "read header") - if b > unicode.MaxASCII { - return true - } - } - return false - } - var hasNonASCIIPartHeader func(p *message.Part) bool - hasNonASCIIPartHeader = func(p *message.Part) bool { - if hasNonASCII(p.HeaderReader()) { +func hasNonASCII(s string) bool { + for _, c := range []byte(s) { + if c > unicode.MaxASCII { return true } - for _, pp := range p.Parts { - if hasNonASCIIPartHeader(&pp) { - return true - } - } - return false } + return false +} +// ../rfc/6531:497 +func (c *conn) isSMTPUTF8Required(part *message.Part) bool { // Check "MAIL FROM". - if hasNonASCII(strings.NewReader(string(c.mailFrom.Localpart))) { + if hasNonASCII(string(c.mailFrom.Localpart)) { return true } // Check all "RCPT TO". for _, rcpt := range c.recipients { - if hasNonASCII(strings.NewReader(string(rcpt.Addr.Localpart))) { + if hasNonASCII(string(rcpt.Addr.Localpart)) { return true } } + // Check header in all message parts. - return hasNonASCIIPartHeader(part) + smtputf8, err := part.NeedsSMTPUTF8() + xcheckf(err, "checking if smtputf8 is required") + return smtputf8 } // ../rfc/5321:1992 ../rfc/5321:1098