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

Code before:
		}
		mw.Close()
	} else {
		fmt.Fprintf(&buf, "MIME-Version: 1.0\r\n")
		fmt.Fprintf(&buf, "Content-Type: %s; charset=UTF-8\r\n", m.contentType)
		fmt.Fprintf(&buf, "Content-Transfer-Encoding: quoted-printable\r\n\r\n")
		qpWrite(&buf, m.body)
	}

	n, err := w.Write(buf.Bytes())
	return int64(n), err
}

func qpWrite(w io.Writer, s string) {
	for i := 0; i < len(s); i++ {
		b := s[i]
		switch {
		case b == '\r' || b == '\n':
			fmt.Fprintf(w, "%c", b)
		case b == '=' || b > 126 || (b < 32 && b != '\t'):
			fmt.Fprintf(w, "=%02X", b)
		default:
			fmt.Fprintf(w, "%c", b)
		}
	}
}

// FormatAddress formats an email address with a display name per RFC 5322.
func FormatAddress(address, name string) string {
	addr := mail.Address{Name: name, Address: address}
	return addr.String()
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, htmlBody string) *Message {
	log.Trace("NewMessageFrom (htmlBody):\n%s", htmlBody)

	header := make(map[string][]string)
	header["From"] = []string{from}
	header["To"] = to
	header["Subject"] = []string{conf.Email.SubjectPrefix + subject}
	header["Date"] = []string{time.Now().Format(time.RFC1123Z)}

	contentType := "text/html"
	body := htmlBody
	switchedToPlaintext := false
	if conf.Email.UsePlainText || conf.Email.AddPlainTextAlt {
		plainBody, err := html2text.FromString(htmlBody)
		if err != nil {
			log.Error("html2text.FromString: %v", err)
		} else {

Review comment (suggestion):
This custom quoted-printable implementation is not RFC 2045 compliant: it doesn't enforce 76-char line limits (soft breaks '=\r\n') and doesn't encode trailing spaces/tabs at end-of-line, which can corrupt content or cause MTAs to reject messages. Prefer stdlib mime/quotedprintable for encoding and add proper line wrapping/CRLF handling.

Code after:
		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}
	}
	clientOpts = append(clientOpts, gomail.WithTLSConfig(tlsconfig))

	if len(opts.User) > 0 {
		clientOpts = append(clientOpts,
			gomail.WithSMTPAuth(gomail.SMTPAuthAutoDiscover),
			gomail.WithUsername(opts.User),
			gomail.WithPassword(opts.Password),
		)
	}

	return gomail.NewClient(host, clientOpts...)
}

func sendMessage(msg *message) error {
	client, err := newSMTPClient()
	if err != nil {
		return err
	}
	return client.DialAndSend(msg.msg)
}

func processMailQueue() {
	for msg := range mailQueue {
		to := strings.Join(msg.msg.GetToString(), ", ")
		log.Trace("New e-mail sending request %s: %s", to, msg.info)
		if err := sendMessage(msg); err != nil {
			log.Error("Failed to send emails %s: %s - %v", to, msg.info, err)
		} else {
			log.Trace("E-mails sent %s: %s", to, msg.info)
		}
		msg.confirmChan <- struct{}{}
	}
}