Skip to content

net/textproto: does not accept multiline error message #10230

Description

@DSpeichert

Summary

(r *Reader) ReadResponse(expectCode int) does not properly read all lines of a single error message (here's why: https://github.com/jnwhiteh/golang/blob/master/src/net/textproto/reader.go#L251).

Use case

The above behavior causes a multitude of problems. In my use case case I use net.smtp to open a single SMTP session to Gmail MX server, and then I repeat multiple times (below is a gist of it) for every message I have in a queue for this server (to avoid opening & closing a separate connection for every message).:

for ....... {
    if err = c.Reset(); err != nil {return err}
    if err = c.Mail(); err != nil {return err}
    if err = c.Rcpt(); err != nil {return err}
    if err = c.Data(); err != nil {return err}
}

Assuming that a recipient's email address doesn't exist, Gmail would return a multiline 550 error message, like this one:

550 5.1.1 The email account that you tried to reach does not exist. Please try
550 5.1.1 double-checking the recipient's email address for typos or
550 5.1.1 unnecessary spaces. Learn more at
550 5.1.1 http://support.google.com/mail/bin/answer.py?answer=6596 k3si4281809wjy.17 - gsmtp

However, err returned by c.Rcpt() would only have the first line.
Following lines will be returned by c.Reset in the following iteration of the loop, line by line!
This completely prevents me from reusing the connection, as there is no reliable way of flushing the remaining error message lines to get a "clean" smtp.Client. If c.Reset() doesn't do that, what will?

Workaround

I have written the following patch which fixes the problem. It passes the current reader test for a multi-line message but there is no test for a multi-line error (and I did not write one yet). The logic seems correct and I have tested this with smtp.Client - the problem mentioned above goes away and c.Rcpt() correctly returns a multiline error and smtp.Client may be reused for another message.

--- /root/reader.go     2015-03-24 01:51:58.000000000 +0100
+++ /usr/local/go/src/net/textproto/reader.go   2015-03-24 03:14:01.818576915 +0100
@@ -248,21 +248,43 @@
 //
 func (r *Reader) ReadResponse(expectCode int) (code int, message string, err error) {
        code, continued, message, err := r.readCodeLine(expectCode)
-       for err == nil && continued {
-               line, err := r.ReadLine()
-               if err != nil {
-                       return 0, "", err
-               }
+       if err == nil {
+               for err == nil && continued {
+                       line, err := r.ReadLine()
+                       if err != nil {
+                               return 0, "", err
+                       }

-               var code2 int
-               var moreMessage string
-               code2, continued, moreMessage, err = parseCodeLine(line, expectCode)
-               if err != nil || code2 != code {
-                       message += "\n" + strings.TrimRight(line, "\r\n")
-                       continued = true
-                       continue
+                       var code2 int
+                       var moreMessage string
+                       code2, continued, moreMessage, err = parseCodeLine(line, expectCode)
+                       if err != nil || code2 != code {
+                               message += "\n" + strings.TrimRight(line, "\r\n")
+                               continued = true
+                               continue
+                       }
+                       message += "\n" + moreMessage
+               }
+       } else {
+               var moreErrMessage string
+               for continued {
+                       line, err2 := r.ReadLine()
+                       if err2 != nil {
+                               break
+                       }
+                       var code2 int
+                       var moreMessage string
+                       code2, continued, moreMessage, err2 = parseCodeLine(line, 0)
+                       if err2 != nil || code2 != code {
+                               moreErrMessage += "\n" + strings.TrimRight(line, "\r\n")
+                               continued = true
+                               continue
+                       }
+                       moreErrMessage += "\n" + moreMessage
+               }
+               if moreErrMessage != "" {
+                       err = &Error{code, message+moreErrMessage}
                }
-               message += "\n" + moreMessage
        }
        return
 }

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions