diff --git a/bin/evening_detective_server b/bin/evening_detective_server index 2839c95..fb50677 100755 Binary files a/bin/evening_detective_server and b/bin/evening_detective_server differ diff --git a/internal/modules/email_sender/sender.go b/internal/modules/email_sender/sender.go index 521fea8..5f4d48a 100644 --- a/internal/modules/email_sender/sender.go +++ b/internal/modules/email_sender/sender.go @@ -133,7 +133,7 @@ func buildMessage(from, fromName, to, subject, replyTo, body, html string) []byt b.Grow(len(from) + len(fromName) + len(to) + len(subject) + len(body) + len(html) + 512) b.WriteString("From: ") if fromName != "" { - b.WriteString(mime.QEncoding.Encode("utf-8", fromName)) + b.WriteString(mime.BEncoding.Encode("utf-8", fromName)) b.WriteString(" <") b.WriteString(from) b.WriteString(">\r\n") diff --git a/internal/modules/email_sender/sender_test.go b/internal/modules/email_sender/sender_test.go index 176e4e7..45a7c3e 100644 --- a/internal/modules/email_sender/sender_test.go +++ b/internal/modules/email_sender/sender_test.go @@ -10,6 +10,7 @@ import ( "crypto/x509/pkix" "math/big" "net" + "net/mail" "strings" "testing" "time" @@ -183,7 +184,7 @@ func TestSendSuccess(t *testing.T) { raw := string(data) for _, want := range []string{ // From с отображаемым именем (RFC 2047). - "From: =?utf-8?q?", + "From: =?utf-8?b?", "", "To: user@example.com", "Subject: =?utf-8?q?", @@ -270,7 +271,7 @@ func TestBuildMessage(t *testing.T) { "текст", "текст", )) for _, want := range []string{ - "From: =?utf-8?q?", + "From: =?utf-8?b?", "", "To: user@example.com", "Subject: =?utf-8?q?", @@ -354,3 +355,19 @@ func TestValidateMessage(t *testing.T) { t.Error("пустая тема принята") } } + +func TestBuildMessageSenderNameRoundTrip(t *testing.T) { + const name = "Вечерний детектив" + raw := buildMessage("sender@example.com", name, "user@example.com", "Привет", "", "текст", "") + message, err := mail.ReadMessage(strings.NewReader(string(raw))) + if err != nil { + t.Fatal(err) + } + from, err := message.Header.AddressList("From") + if err != nil { + t.Fatal(err) + } + if len(from) != 1 || from[0].Name != name || from[0].Address != "sender@example.com" { + t.Fatalf("неверный отправитель: %+v", from) + } +} diff --git a/internal/services/users_service/emails.go b/internal/services/users_service/emails.go index f386b6a..8714523 100644 --- a/internal/services/users_service/emails.go +++ b/internal/services/users_service/emails.go @@ -42,15 +42,15 @@ func welcomePlain(username, email, password, loginURL string) string { // welcomeHTML возвращает HTML-версию письма о регистрации. func welcomeHTML(username, email, password, loginURL string) string { var content strings.Builder - content.WriteString("

Привет, ") + content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") - content.WriteString("

Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:

\n") + content.WriteString("

Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } - content.WriteString("

Если ты не регистрировался — просто проигнорируй это письмо.

") + content.WriteString("

Если ты не регистрировался — просто проигнорируй это письмо.

") return wrapHTML(content.String(), loginURL) } @@ -80,16 +80,16 @@ func resetPlain(username, email, password, loginURL string) string { // resetHTML возвращает HTML-версию письма о сбросе пароля. func resetHTML(username, email, password, loginURL string) string { var content strings.Builder - content.WriteString("

Привет, ") + content.WriteString("

Привет, ") content.WriteString(html.EscapeString(username)) content.WriteString("!

\n") - content.WriteString("

Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:

\n") + content.WriteString("

Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:

\n") content.WriteString(credentialsBlockHTML(email, password)) if loginURL != "" { content.WriteString(buttonHTML(loginURL, "Войти в игру")) } - content.WriteString("

Никому не сообщай пароль и не пересылай это письмо.

\n") - content.WriteString("

Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.

") + content.WriteString("

Никому не сообщай пароль и не пересылай это письмо.

\n") + content.WriteString("

Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.

") return wrapHTML(content.String(), loginURL) } @@ -107,10 +107,10 @@ func greeting(username string) string { // совместимой с Gmail. func credentialsBlockHTML(email, password string) string { return ` - - - - + + + +
Логин
` + html.EscapeString(email) + `
Пароль
` + html.EscapeString(password) + `
Логин
` + html.EscapeString(email) + `
Пароль
` + html.EscapeString(password) + `
` } @@ -127,14 +127,17 @@ func buttonHTML(link, label string) string { // wrapHTML — каркас письма: тёмная шапка с названием сервиса, контент, // подвал. Основное оформление — inline; при блокировке текстур и шрифта -// остаются фон bisque и системный шрифт. +// остаются светлый фон и системный шрифт. func wrapHTML(content, baseURL string) string { return ` + + @@ -149,7 +152,7 @@ a:hover { color:#63e2b7 !important; } - + ` + content + `