mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-20 08:20:04 +00:00
Adds a repository setting for the default squash merge commit message body with GitHub's options `PR_BODY`, `COMMIT_MESSAGES` and `BLANK`, also settable via `squash_merge_commit_message` in the API. The commit title stays the pull request title. 1. Breaking: API squash merges without `merge_message_field` now get the same default message as the web merge box instead of a title-only commit, which affects clients sending only `Do` like Renovate. An empty value keeps title-only. 2. Breaking: `[repository.pull-request] DEFAULT_SQUASH_MERGE_MESSAGE` replaces `POPULATE_SQUASH_COMMENT_WITH_COMMIT_MESSAGES`, which still works as a deprecated fallback. Repositories store the default when created or next saved. 3. Deprecated app.ini keys now migrate through one table instead of per-loader fallbacks, which also fixes the ignored `[mailer] CERT_FILE` and `KEY_FILE` fallbacks. Deprecation warnings no longer claim a removal version. <img width="1891" height="571" alt="grafik" src="https://github.com/user-attachments/assets/0fdcde13-1f11-4156-b598-d80eb0d04cbe" /> --------- Co-authored-by: silverwind <me@silverwind.io>
255 lines
8.3 KiB
Go
255 lines
8.3 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package setting
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/mail"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"gitea.dev/modules/log"
|
|
|
|
"github.com/kballard/go-shellquote"
|
|
)
|
|
|
|
// Mailer represents mail service.
|
|
type Mailer struct {
|
|
// Mailer
|
|
Name string `ini:"NAME"`
|
|
From string `ini:"FROM"`
|
|
EnvelopeFrom string `ini:"ENVELOPE_FROM"`
|
|
OverrideEnvelopeFrom bool `ini:"-"`
|
|
FromName string `ini:"-"`
|
|
FromEmail string `ini:"-"`
|
|
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
|
|
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
|
|
OverrideHeader map[string][]string `ini:"-"`
|
|
|
|
// Embed attachment images as inline base64 img src attribute
|
|
EmbedAttachmentImages bool
|
|
|
|
// SMTP sender
|
|
Protocol string `ini:"PROTOCOL"`
|
|
SMTPAddr string `ini:"SMTP_ADDR"`
|
|
SMTPPort string `ini:"SMTP_PORT"`
|
|
User string `ini:"USER"`
|
|
Passwd string `ini:"PASSWD"`
|
|
EnableHelo bool `ini:"ENABLE_HELO"`
|
|
HeloHostname string `ini:"HELO_HOSTNAME"`
|
|
ForceTrustServerCert bool `ini:"FORCE_TRUST_SERVER_CERT"`
|
|
UseClientCert bool `ini:"USE_CLIENT_CERT"`
|
|
ClientCertFile string `ini:"CLIENT_CERT_FILE"`
|
|
ClientKeyFile string `ini:"CLIENT_KEY_FILE"`
|
|
|
|
// Sendmail sender
|
|
SendmailPath string `ini:"SENDMAIL_PATH"`
|
|
SendmailArgs []string `ini:"-"`
|
|
SendmailTimeout time.Duration `ini:"SENDMAIL_TIMEOUT"`
|
|
SendmailConvertCRLF bool `ini:"SENDMAIL_CONVERT_CRLF"`
|
|
|
|
// Customization
|
|
FromDisplayNameFormat string `ini:"FROM_DISPLAY_NAME_FORMAT"`
|
|
FromDisplayNameFormatTemplate *template.Template `ini:"-"`
|
|
}
|
|
|
|
// MailService the global mailer
|
|
var MailService *Mailer
|
|
|
|
func loadMailsFrom(rootCfg ConfigProvider) {
|
|
loadMailerFrom(rootCfg)
|
|
loadRegisterMailFrom(rootCfg)
|
|
loadNotifyMailFrom(rootCfg)
|
|
loadIncomingEmailFrom(rootCfg)
|
|
}
|
|
|
|
func loadMailerFrom(rootCfg ConfigProvider) {
|
|
sec := rootCfg.Section("mailer")
|
|
// Check mailer setting.
|
|
if !sec.Key("ENABLED").MustBool() {
|
|
return
|
|
}
|
|
|
|
// Handle Deprecations and map on to new configuration
|
|
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
|
|
// if these are removed, the warning will not be shown
|
|
deprecatedSetting(rootCfg, "mailer", "HOST", "mailer", "SMTP_ADDR")
|
|
if sec.HasKey("HOST") && !sec.HasKey("SMTP_ADDR") {
|
|
givenHost := sec.Key("HOST").String()
|
|
addr, port, err := net.SplitHostPort(givenHost)
|
|
if err != nil && strings.Contains(err.Error(), "missing port in address") {
|
|
addr = givenHost
|
|
} else if err != nil {
|
|
log.Fatal("Invalid mailer.HOST (%s): %v", givenHost, err)
|
|
}
|
|
if addr == "" {
|
|
addr = "127.0.0.1"
|
|
}
|
|
sec.Key("SMTP_ADDR").MustString(addr) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("SMTP_PORT").MustString(port) // FIXME: INI-MUST-SIDE-EFFECT
|
|
}
|
|
|
|
if sec.HasKey("PROTOCOL") && sec.Key("PROTOCOL").String() == "smtp+startls" {
|
|
log.Error("Deprecated fallback `[mailer]` `PROTOCOL = smtp+startls` present. Use `[mailer]` `PROTOCOL = smtp+starttls` instead.")
|
|
sec.Key("PROTOCOL").SetValue("smtp+starttls")
|
|
}
|
|
|
|
// Set default values & validate
|
|
sec.Key("NAME").MustString(AppName) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("PROTOCOL").In("", []string{"smtp", "smtps", "smtp+starttls", "smtp+unix", "sendmail", "dummy"})
|
|
sec.Key("ENABLE_HELO").MustBool(true) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("FORCE_TRUST_SERVER_CERT").MustBool(false) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("USE_CLIENT_CERT").MustBool(false) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("SENDMAIL_PATH").MustString("sendmail") // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("SENDMAIL_TIMEOUT").MustDuration(5 * time.Minute) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("SENDMAIL_CONVERT_CRLF").MustBool(true) // FIXME: INI-MUST-SIDE-EFFECT
|
|
sec.Key("FROM").MustString(sec.Key("USER").String()) // FIXME: INI-MUST-SIDE-EFFECT
|
|
|
|
// Now map the values on to the MailService
|
|
MailService = &Mailer{}
|
|
if err := sec.MapTo(MailService); err != nil {
|
|
log.Fatal("Unable to map [mailer] section on to MailService. Error: %v", err)
|
|
}
|
|
|
|
overrideHeader := rootCfg.Section("mailer.override_header").Keys()
|
|
MailService.OverrideHeader = make(map[string][]string)
|
|
for _, key := range overrideHeader {
|
|
MailService.OverrideHeader[key.Name()] = key.Strings(",")
|
|
}
|
|
|
|
// Infer SMTPPort if not set
|
|
if MailService.SMTPPort == "" {
|
|
switch MailService.Protocol {
|
|
case "smtp":
|
|
MailService.SMTPPort = "25"
|
|
case "smtps":
|
|
MailService.SMTPPort = "465"
|
|
case "smtp+starttls":
|
|
MailService.SMTPPort = "587"
|
|
}
|
|
}
|
|
|
|
// Infer Protocol
|
|
if MailService.Protocol == "" {
|
|
if strings.ContainsAny(MailService.SMTPAddr, "/\\") {
|
|
MailService.Protocol = "smtp+unix"
|
|
} else {
|
|
switch MailService.SMTPPort {
|
|
case "25":
|
|
MailService.Protocol = "smtp"
|
|
case "465":
|
|
MailService.Protocol = "smtps"
|
|
case "587":
|
|
MailService.Protocol = "smtp+starttls"
|
|
default:
|
|
log.Error("unable to infer unspecified mailer.PROTOCOL from mailer.SMTP_PORT = %q, assume using smtps", MailService.SMTPPort)
|
|
MailService.Protocol = "smtps"
|
|
if MailService.SMTPPort == "" {
|
|
MailService.SMTPPort = "465"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// we want to warn if users use SMTP on a non-local IP;
|
|
// we might as well take the opportunity to check that it has an IP at all
|
|
// This check is not needed for sendmail
|
|
switch MailService.Protocol {
|
|
case "sendmail":
|
|
var err error
|
|
MailService.SendmailArgs, err = shellquote.Split(sec.Key("SENDMAIL_ARGS").String())
|
|
if err != nil {
|
|
log.Error("Failed to parse Sendmail args: '%s' with error %v", sec.Key("SENDMAIL_ARGS").String(), err)
|
|
}
|
|
case "smtp", "smtps", "smtp+starttls", "smtp+unix":
|
|
ips := tryResolveAddr(MailService.SMTPAddr)
|
|
if MailService.Protocol == "smtp" {
|
|
for _, ip := range ips {
|
|
if !ip.IP.IsLoopback() {
|
|
log.Warn("connecting over insecure SMTP protocol to non-local address is not recommended")
|
|
break
|
|
}
|
|
}
|
|
}
|
|
case "dummy": // just mention and do nothing
|
|
}
|
|
|
|
if MailService.From != "" {
|
|
parsed, err := mail.ParseAddress(MailService.From)
|
|
if err != nil {
|
|
log.Fatal("Invalid mailer.FROM (%s): %v", MailService.From, err)
|
|
}
|
|
MailService.FromName = parsed.Name
|
|
MailService.FromEmail = parsed.Address
|
|
} else {
|
|
log.Error("no mailer.FROM provided, email system may not work.")
|
|
}
|
|
|
|
MailService.FromDisplayNameFormatTemplate, _ = template.New("mailFrom").Parse("{{ .DisplayName }}")
|
|
if MailService.FromDisplayNameFormat != "" {
|
|
template, err := template.New("mailFrom").Parse(MailService.FromDisplayNameFormat)
|
|
if err != nil {
|
|
log.Error("mailer.FROM_DISPLAY_NAME_FORMAT is no valid template: %v", err)
|
|
} else {
|
|
MailService.FromDisplayNameFormatTemplate = template
|
|
}
|
|
}
|
|
|
|
switch MailService.EnvelopeFrom {
|
|
case "":
|
|
MailService.OverrideEnvelopeFrom = false
|
|
case "<>":
|
|
MailService.EnvelopeFrom = ""
|
|
MailService.OverrideEnvelopeFrom = true
|
|
default:
|
|
parsed, err := mail.ParseAddress(MailService.EnvelopeFrom)
|
|
if err != nil {
|
|
log.Fatal("Invalid mailer.ENVELOPE_FROM (%s): %v", MailService.EnvelopeFrom, err)
|
|
}
|
|
MailService.OverrideEnvelopeFrom = true
|
|
MailService.EnvelopeFrom = parsed.Address
|
|
}
|
|
}
|
|
|
|
func loadRegisterMailFrom(rootCfg ConfigProvider) {
|
|
if !rootCfg.Section("service").Key("REGISTER_EMAIL_CONFIRM").MustBool() {
|
|
return
|
|
} else if MailService == nil {
|
|
log.Warn("Register Mail Service: Mail Service is not enabled")
|
|
return
|
|
}
|
|
Service.RegisterEmailConfirm = true
|
|
}
|
|
|
|
func loadNotifyMailFrom(rootCfg ConfigProvider) {
|
|
if !rootCfg.Section("service").Key("ENABLE_NOTIFY_MAIL").MustBool() {
|
|
return
|
|
} else if MailService == nil {
|
|
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
|
return
|
|
}
|
|
Service.EnableNotifyMail = true
|
|
}
|
|
|
|
func tryResolveAddr(addr string) []net.IPAddr {
|
|
if strings.HasPrefix(addr, "[") && strings.HasSuffix(addr, "]") {
|
|
addr = addr[1 : len(addr)-1]
|
|
}
|
|
ip := net.ParseIP(addr)
|
|
if ip != nil {
|
|
return []net.IPAddr{{IP: ip}}
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
ips, err := net.DefaultResolver.LookupIPAddr(ctx, addr)
|
|
if err != nil {
|
|
log.Warn("could not look up mailer.SMTP_ADDR: %v", err)
|
|
return nil
|
|
}
|
|
return ips
|
|
}
|