Repository: gogs/gogs
File: internal/email/message.go (Go)

Code before:

// foldHeaderValue inserts RFC 5322 folding whitespace (CRLF + space) into a
// header value so that no line exceeds 78 characters. It folds at comma
// boundaries, which is appropriate for address lists.
func foldHeaderValue(prefixLen int, value string) string {
	const maxLine = 78
	if prefixLen+len(value) <= maxLine {
		return value
	}

	var buf strings.Builder
	lineLen := prefixLen
	for i, part := range strings.Split(value, ",") {
		segment := part
		if i > 0 {
			segment = "," + segment
		}
		if lineLen+len(segment) > maxLine && lineLen > prefixLen {
			buf.WriteString("\r\n ")
			lineLen = 1
			segment = strings.TrimLeft(segment, " ")
		}
		buf.WriteString(segment)
		lineLen += len(segment)
	}
	return buf.String()
}

type countingWriter struct {
	w io.Writer
	n int64
}

func (cw *countingWriter) Write(p []byte) (int, error) {
	n, err := cw.w.Write(p)
	cw.n += int64(n)
	return n, err
}

func (m *message) WriteTo(w io.Writer) (int64, error) {
	cw := &countingWriter{w: w}

	for _, field := range []string{"From", "To", "Subject", "Date"} {
		vals := m.header[field]
		if len(vals) == 0 {
			continue
		}

		var encoded string
		if field == "Subject" {
			encoded = mime.QEncoding.Encode("utf-8", vals[0])

Review comment (suggestion):
`foldHeaderValue` splits on every comma to decide fold points. This will corrupt address headers when a display name is quoted and contains a comma (valid in RFC 5322), because the comma is not necessarily an address-list separator. Consider removing this custom folding, or folding based on parsed `net/mail` addresses (or only folding at safe points you control, e.g. between the original `To` slice elements).
```suggestion
// foldHeaderValue previously attempted to insert RFC 5322 folding whitespace
// (CRLF + space) into a header value by splitting on commas. This could
// corrupt address headers when display names contain commas. To avoid
// altering header semantics, we now return the value unchanged.
func foldHeaderValue(prefixLen int, value string) string {
	return value
```

Code after:
		}
	} else {
		m.SetBodyString(gomail.TypeTextHTML, htmlBody)
	}

	return &message{
		msg:         m,
		confirmChan: make(chan struct{}),
	}, nil
}

func newMessage(to []string, subject, body string) (*message, error) {
	return newMessageFrom(to, conf.Email.From, subject, body)
}

func newSMTPClient() (*gomail.Client, error) {
	opts := conf.Email

	host, portStr, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return nil, err
	}
	port, err := strconv.Atoi(portStr)
	if err != nil {
		return nil, err
	}

	clientOpts := []gomail.Option{
		gomail.WithPort(port),
	}

	if port == 465 {
		clientOpts = append(clientOpts, gomail.WithSSL())
	} else {
		clientOpts = append(clientOpts, gomail.WithTLSPolicy(gomail.TLSOpportunistic))
	}

	if opts.HELOHostname != "" {
		clientOpts = append(clientOpts, gomail.WithHELO(opts.HELOHostname))
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}
	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return nil, err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}