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

Code before:
	altParts    []altPart
	confirmChan chan struct{}
}

type altPart struct {
	contentType string
	body        string
}

func (m *Message) GetHeader(field string) []string {
	return m.header[field]
}

func (m *Message) WriteTo(w io.Writer) (int64, error) {
	var buf bytes.Buffer

	for _, field := range []string{"From", "To", "Subject", "Date"} {
		vals := m.header[field]
		for _, v := range vals {
			encoded := v
			if field == "Subject" {
				encoded = mime.QEncoding.Encode("utf-8", v)
			}
			fmt.Fprintf(&buf, "%s: %s\r\n", field, encoded)
		}
	}

	if len(m.altParts) > 0 {
		mw := multipart.NewWriter(&buf)
		fmt.Fprintf(&buf, "MIME-Version: 1.0\r\n")
		fmt.Fprintf(&buf, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", mw.Boundary())

		mainHeader := textproto.MIMEHeader{}
		mainHeader.Set("Content-Type", m.contentType+"; charset=UTF-8")
		mainHeader.Set("Content-Transfer-Encoding", "quoted-printable")
		part, err := mw.CreatePart(mainHeader)
		if err != nil {
			return 0, errors.Wrap(err, "create main part")
		}
		qpWrite(part, m.body)

		for _, alt := range m.altParts {
			altHeader := textproto.MIMEHeader{}
			altHeader.Set("Content-Type", alt.contentType+"; charset=UTF-8")
			altHeader.Set("Content-Transfer-Encoding", "quoted-printable")
			part, err := mw.CreatePart(altHeader)
			if err != nil {
				return 0, errors.Wrap(err, "create alternative part")
			}
			qpWrite(part, alt.body)
		}

Review comment (security):
Header values are written verbatim into the RFC 5322 header block. Because callers can pass user-controlled strings (e.g., issue titles via MailSubject) into Subject, this allows CR/LF header injection (additional headers or body preamble). Reject or sanitize any header value containing '\r' or '\n' before formatting, and ensure only a single well-formed header line is emitted per field.

Code after:
	if err := m.To(to...); err != nil {
		return nil, errors.Wrap(err, "set To addresses")
	}
	m.Subject(conf.Email.SubjectPrefix + subject)
	m.SetDate()

	if conf.Email.UsePlainText || conf.Email.AddPlainTextAlt {
		plainBody, err := html2text.FromString(htmlBody)
		if err != nil {
			return nil, errors.Wrap(err, "convert HTML to plain text")
		}
		if conf.Email.UsePlainText {
			m.SetBodyString(gomail.TypeTextPlain, plainBody)
		} else {
			m.SetBodyString(gomail.TypeTextPlain, plainBody)
			m.AddAlternativeString(gomail.TypeTextHTML, htmlBody)
		}
	} 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))