Repository: go
Issue: mime/quotedprintable: single line with only period cause message truncation when used in SMTP DATA
Labels: NeedsInvestigation

<!--
Please answer these questions before submitting your issue. Thanks!
-->

### What version of Go are you using (`go version`)?

<pre>
$ go version
go version devel go1.21-a618094c2a Sat Jul 8 13:45:19 2023 +0700 linux/amd64
</pre>

### Does this issue reproduce with the latest release?

Yes.

### What operating system and processor architecture are you using (`go env`)?

<details><summary><code>go env</code> Output</summary><br><pre>
$ go env
GO111MODULE='on'
GOARCH='amd64'
GOBIN='/home/ms/go/bin'
GOCACHE='/home/ms/.cache/go-build'
GOENV='/home/ms/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/ms/go/pkg/mod'
GONOPROXY='git.sr.ht'
GONOSUMDB='git.sr.ht'
GOOS='linux'
GOPATH='/home/ms/go'
GOPRIVATE='git.sr.ht'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/home/ms/opt/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/ms/opt/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='devel go1.21-a618094c2a Sat Jul 8 13:45:19 2023 +0700'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='0'
GOMOD='/dev/null'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build721925861=/tmp/go-build -gno-record-gcc-switches'
</pre></details>

### What did you do?

Given the following example of message body,

```
A line that precisely have length 75 with . + LF will cause DATA truncation.\n
\n
Footer.\n
```

The quotedprintable Writer will encode the message into,

```
A line that precisely have length 75 with . + LF will cause DATA truncation=\r\n
.\r\n
\r\n
Footer.\r\n
```

If we pass the Writer output into SMTP DATA command, the server read the "\r\n.\r\n" as the end of DATA which cause the message truncated on the receiver.

<details>
<summary>Reproduceable code</summary><br>
<pre>

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/shuLhan/share/lib/email"
	"github.com/shuLhan/share/lib/smtp"
)

func main() {
	// Set up authentication information.
	var (
		smtpUser       = os.Getenv(`SMTP_USER`)
		smtpPass       = os.Getenv(`SMTP_PASS`)
		smtpServer     = os.Getenv(`SMTP_SERVER`)
		smtpServerPort = os.Getenv(`SMTP_SERVER_PORT`)
		from           = os.Getenv(`SMTP_FROM`)
		to             = os.Getenv(`SMTP_TO`)
	)

	fmt.Printf("SMTP user: %s\n", smtpUser)
	fmt.Printf("SMTP pass: %s\n", smtpPass)
	fmt.Printf("SMTP addr: %s:%s\n", smtpServer, smtpServerPort)
	fmt.Printf("From     : %s\n", from)
	fmt.Printf("To       : %s\n", to)

	var (
		subject  = `Truncated message`
		bodyText = "A line that precisely have length 75 with . + LF will cause DATA truncation.\n" +
			"\n" +
			"This line will not be received.\n"

		msg    email.Message
		mailtx *smtp.MailTx
		data   []byte
		err    error
	)

	err = msg.SetFrom(from)
	if err != nil {
		log.Fatal(err)
	}
	err = msg.SetTo(to)
	if err != nil {
		log.Fatal(err)
	}
	msg.SetSubject(subject)
	err = msg.SetBodyText([]byte(bodyText))
	if err != nil {
		log.Fatal(err)
	}
	data, err = msg.Pack()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Data: %s\n", data)

	mailtx = smtp.NewMailTx(from, []string{to}, data)

	var (
		clientOpts = smtp.ClientOptions{
			ServerUrl:     fmt.Sprintf(`smtps://%s:%s`, smtpServer, smtpServerPort),
			AuthUser:      smtpUser,
			AuthPass:      smtpPass,
			AuthMechanism: smtp.SaslMechanismPlain,
		}
		cl *smtp.Client
	)

	cl, err = smtp.NewClient(clientOpts)
	if err != nil {
		log.Fatal(err)
	}

	res, err := cl.MailTx(mailtx)
	if err != nil {
		log.Fatal(`MailTx:`, err)
	}

	log.Printf(`MailTx: response: %+v`, res)

	_, err = cl.Quit()
	if err != nil {
		log.Fatal(err)
	}
}
</pre>
</details>

### What did you expect to see?

Full message received as,

```
from: <redacted>
to: <redacted>
subject: Truncated message
date: Sat, 8 Jul 2023 16:47:43 +0700
message-id: <1688809663.1vIVqALM@inspiro>
mime-version: 1.0
content-type: text/plain; charset="utf-8"
content-transfer-encoding: quoted-printable

A line that precisely have length 75 with . + LF will cause DATA truncation=
=2E

This line will not be received.

```

### What did you see instead?

Message received as,

```
from: <redacted>
to: <redacted>
subject: Truncated message
date: Sat, 8 Jul 2023 16:38:41 +0700
message-id: <1688809121.Iz9VWWN7@inspiro>
mime-version: 1.0
content-type: text/plain; charset="utf-8"
content-transfer-encoding: quoted-printable

A line that precisely have length 75 with . + LF will cause DATA truncation=

```