1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 18:17:03 +00:00

webui, rest: Render UTF-8 addresses correctly, fixes #117

This commit is contained in:
James Hillyerd
2018-10-22 18:29:03 -07:00
parent fcb4bc20e0
commit 30e3892cb0
10 changed files with 88 additions and 18 deletions

View File

@@ -1,6 +1,8 @@
Date: %DATE% Date: %DATE%
To: %TO_ADDRESS% To: %TO_ADDRESS%,
From: %FROM_ADDRESS% =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?= <recipient@inbucket.org>
From: =?utf-8?q?X-=C3=A4=C3=A9=C3=9F_Y-=C3=A4=C3=A9=C3=9F?=
<fromuser@inbucket.org>
Subject: =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?= Subject: =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?=
Thread-Topic: =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?= Thread-Topic: =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?=
Thread-Index: Ac6+4nH7mOymA+1JRQyk2LQPe1bEcw== Thread-Index: Ac6+4nH7mOymA+1JRQyk2LQPe1bEcw==

View File

@@ -33,7 +33,7 @@ func MailboxListV1(w http.ResponseWriter, req *http.Request, ctx *web.Context) (
jmessages[i] = &model.JSONMessageHeaderV1{ jmessages[i] = &model.JSONMessageHeaderV1{
Mailbox: name, Mailbox: name,
ID: msg.ID, ID: msg.ID,
From: msg.From.String(), From: stringutil.StringAddress(msg.From),
To: stringutil.StringAddressList(msg.To), To: stringutil.StringAddressList(msg.To),
Subject: msg.Subject, Subject: msg.Subject,
Date: msg.Date, Date: msg.Date,
@@ -79,7 +79,7 @@ func MailboxShowV1(w http.ResponseWriter, req *http.Request, ctx *web.Context) (
&model.JSONMessageV1{ &model.JSONMessageV1{
Mailbox: name, Mailbox: name,
ID: msg.ID, ID: msg.ID,
From: msg.From.String(), From: stringutil.StringAddress(msg.From),
To: stringutil.StringAddressList(msg.To), To: stringutil.StringAddressList(msg.To),
Subject: msg.Subject, Subject: msg.Subject,
Date: msg.Date, Date: msg.Date,

View File

@@ -8,11 +8,13 @@ import (
"strings" "strings"
"time" "time"
"github.com/jhillyerd/inbucket/pkg/stringutil"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
// TemplateFuncs declares functions made available to all templates (including partials) // TemplateFuncs declares functions made available to all templates (including partials)
var TemplateFuncs = template.FuncMap{ var TemplateFuncs = template.FuncMap{
"address": stringutil.StringAddress,
"friendlyTime": FriendlyTime, "friendlyTime": FriendlyTime,
"reverse": Reverse, "reverse": Reverse,
"stringsJoin": strings.Join, "stringsJoin": strings.Join,

View File

@@ -19,13 +19,28 @@ func HashMailboxName(mailbox string) string {
return fmt.Sprintf("%x", h.Sum(nil)) return fmt.Sprintf("%x", h.Sum(nil))
} }
// StringAddressList converts a list of addresses to a list of strings // StringAddress converts an Address to a UTF-8 string.
func StringAddress(a *mail.Address) string {
b := &strings.Builder{}
if a != nil {
if a.Name != "" {
b.WriteString(a.Name)
b.WriteRune(' ')
}
if a.Address != "" {
b.WriteRune('<')
b.WriteString(a.Address)
b.WriteRune('>')
}
}
return b.String()
}
// StringAddressList converts a list of addresses to a list of UTF-8 strings.
func StringAddressList(addrs []*mail.Address) []string { func StringAddressList(addrs []*mail.Address) []string {
s := make([]string, len(addrs)) s := make([]string, len(addrs))
for i, a := range addrs { for i, a := range addrs {
if a != nil { s[i] = StringAddress(a)
s[i] = a.String()
}
} }
return s return s
} }

View File

@@ -17,10 +17,14 @@ func TestHashMailboxName(t *testing.T) {
func TestStringAddressList(t *testing.T) { func TestStringAddressList(t *testing.T) {
input := []*mail.Address{ input := []*mail.Address{
{Name: "Fred B. Fish", Address: "fred@fish.org"}, {Name: "Fred ß. Fish", Address: "fred@fish.org"},
{Name: "User", Address: "user@domain.org"}, {Name: "User", Address: "user@domain.org"},
{Address: "a@b.com"},
} }
want := []string{`"Fred B. Fish" <fred@fish.org>`, `"User" <user@domain.org>`} want := []string{
`Fred ß. Fish <fred@fish.org>`,
`User <user@domain.org>`,
`<a@b.com>`}
output := stringutil.StringAddressList(input) output := stringutil.StringAddressList(input)
if len(output) != len(want) { if len(output) != len(want) {
t.Fatalf("Got %v strings, want: %v", len(output), len(want)) t.Fatalf("Got %v strings, want: %v", len(output), len(want))

View File

@@ -44,6 +44,7 @@ func TestSuite(t *testing.T) {
}{ }{
{"basic", testBasic}, {"basic", testBasic},
{"fullname", testFullname}, {"fullname", testFullname},
{"encodedHeader", testEncodedHeader},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, tc.test) t.Run(tc.name, tc.test)
@@ -59,13 +60,13 @@ func testBasic(t *testing.T) {
to := []string{"recipient@inbucket.org"} to := []string{"recipient@inbucket.org"}
input := readTestData("basic.txt") input := readTestData("basic.txt")
// Send mail // Send mail.
err = smtpclient.SendMail(smtpHost, nil, from, to, input) err = smtpclient.SendMail(smtpHost, nil, from, to, input)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Confirm receipt // Confirm receipt.
msg, err := client.GetMessage("recipient", "latest") msg, err := client.GetMessage("recipient", "latest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -88,13 +89,13 @@ func testFullname(t *testing.T) {
to := []string{"recipient@inbucket.org"} to := []string{"recipient@inbucket.org"}
input := readTestData("fullname.txt") input := readTestData("fullname.txt")
// Send mail // Send mail.
err = smtpclient.SendMail(smtpHost, nil, from, to, input) err = smtpclient.SendMail(smtpHost, nil, from, to, input)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// Confirm receipt // Confirm receipt.
msg, err := client.GetMessage("recipient", "latest") msg, err := client.GetMessage("recipient", "latest")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -108,6 +109,35 @@ func testFullname(t *testing.T) {
goldiff.File(t, got, "testdata", "fullname.golden") goldiff.File(t, got, "testdata", "fullname.golden")
} }
func testEncodedHeader(t *testing.T) {
client, err := client.New(restBaseURL)
if err != nil {
t.Fatal(err)
}
from := "fromuser@inbucket.org"
to := []string{"recipient@inbucket.org"}
input := readTestData("encodedheader.txt")
// Send mail.
err = smtpclient.SendMail(smtpHost, nil, from, to, input)
if err != nil {
t.Fatal(err)
}
// Confirm receipt.
msg, err := client.GetMessage("recipient", "latest")
if err != nil {
t.Fatal(err)
}
if msg == nil {
t.Errorf("Got nil message, wanted non-nil message.")
}
// Compare to golden.
got := formatMessage(msg)
goldiff.File(t, got, "testdata", "encodedheader.golden")
}
func formatMessage(m *client.Message) []byte { func formatMessage(m *client.Message) []byte {
b := &bytes.Buffer{} b := &bytes.Buffer{}
fmt.Fprintf(b, "Mailbox: %v\n", m.Mailbox) fmt.Fprintf(b, "Mailbox: %v\n", m.Mailbox)

12
pkg/test/testdata/encodedheader.golden vendored Normal file
View File

@@ -0,0 +1,12 @@
Mailbox: recipient
From: X-äéß Y-äéß <fromuser@inbucket.org>
To: [Test of ȇɲʢȯȡɪɴʛ <recipient@inbucket.org>]
Subject: Test of ȇɲʢȯȡɪɴʛ
Size: 351
BODY TEXT:
Basic message.
BODY HTML:

5
pkg/test/testdata/encodedheader.txt vendored Normal file
View File

@@ -0,0 +1,5 @@
From: =?utf-8?q?X-=C3=A4=C3=A9=C3=9F_Y-=C3=A4=C3=A9=C3=9F?= <fromuser@inbucket.org>
To: =?utf-8?B?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?= <recipient@inbucket.org>
Subject: =?utf-8?b?VGVzdCBvZiDIh8myyqLIr8ihyarJtMqb?=
Basic message.

View File

@@ -1,6 +1,6 @@
Mailbox: recipient Mailbox: recipient
From: "From User" <fromuser@inbucket.org> From: From User <fromuser@inbucket.org>
To: ["Rec I. Pient" <recipient@inbucket.org>] To: [Rec I. Pient <recipient@inbucket.org>]
Subject: basic subject Subject: basic subject
Size: 246 Size: 246

View File

@@ -51,12 +51,12 @@
<div class="panel-body"> <div class="panel-body">
<dl class="dl-horizontal"> <dl class="dl-horizontal">
<dt>From:</dt> <dt>From:</dt>
<dd>{{.message.From}}</dd> <dd>{{.message.From | address}}</dd>
<dt>To:</dt> <dt>To:</dt>
<dd> <dd>
{{range $i, $addr := .message.To}} {{range $i, $addr := .message.To}}
{{- if $i}},{{end}} {{- if $i}},{{end}}
{{$addr -}} {{$addr | address -}}
{{end}} {{end}}
</dd> </dd>
<dt>Date:</dt> <dt>Date:</dt>