feat(GODT-3113): Only force UTF-8 charset for HTML part when needed.

This commit is contained in:
Romain Le Jeune 2023-11-10 12:50:15 +00:00
parent fa430ee0fb
commit 83bbdbd63e
5 changed files with 30 additions and 22 deletions

View File

@ -197,9 +197,6 @@ func convertForeignEncodings(p *parser.Parser) error {
return p.NewWalker().
RegisterContentTypeHandler("text/html", func(p *parser.Part) error {
if p.IsAttachment() {
return nil
}
if err := p.ConvertToUTF8(); err != nil {
return err
}

View File

@ -41,6 +41,8 @@ type Part struct {
children Parts
}
const utf8Charset = "UTF-8"
func (p *Part) ContentType() (string, map[string]string, error) {
t, params, err := p.Header.ContentType()
if err != nil {
@ -116,7 +118,7 @@ func (p *Part) ConvertToUTF8() error {
params = make(map[string]string)
}
params["charset"] = "UTF-8"
params["charset"] = utf8Charset
p.Header.SetContentType(t, params)
@ -129,6 +131,8 @@ func (p *Part) ConvertMetaCharset() error {
return err
}
// Override charset to UTF-8 in meta headers only if needed.
var metaModified = false
goquery.NewDocumentFromNode(doc).Find("meta").Each(func(n int, sel *goquery.Selection) {
if val, ok := sel.Attr("content"); ok {
t, params, err := pmmime.ParseMediaType(val)
@ -136,24 +140,31 @@ func (p *Part) ConvertMetaCharset() error {
return
}
params["charset"] = "UTF-8"
if charset, ok := params["charset"]; ok && charset != utf8Charset {
params["charset"] = utf8Charset
}
sel.SetAttr("content", mime.FormatMediaType(t, params))
metaModified = true
}
if _, ok := sel.Attr("charset"); ok {
sel.SetAttr("charset", "UTF-8")
if charset, ok := sel.Attr("charset"); ok && charset != utf8Charset {
sel.SetAttr("charset", utf8Charset)
metaModified = true
}
})
buf := new(bytes.Buffer)
// Override the body part only if modification was applied
// as html.render will sanitise the html headers.
if metaModified {
buf := new(bytes.Buffer)
if err := html.Render(buf, doc); err != nil {
return err
if err := html.Render(buf, doc); err != nil {
return err
}
p.Body = buf.Bytes()
}
p.Body = buf.Bytes()
return nil
}

View File

@ -431,7 +431,7 @@ func TestParseTextHTML(t *testing.T) {
assert.Equal(t, `"Sender" <sender@pm.me>`, m.Sender.String())
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
assert.Equal(t, "<html><head></head><body>This is body of <b>HTML mail</b> without attachment</body></html>", string(m.RichBody))
assert.Equal(t, "<html><body>This is body of <b>HTML mail</b> without attachment</body></html>", string(m.RichBody))
assert.Equal(t, "This is body of *HTML mail* without attachment", string(m.PlainBody))
assert.Len(t, m.Attachments, 0)
@ -446,7 +446,7 @@ func TestParseTextHTMLAlready7Bit(t *testing.T) {
assert.Equal(t, `"Sender" <sender@pm.me>`, m.Sender.String())
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
assert.Equal(t, "<html><head></head><body>This is body of <b>HTML mail</b> without attachment</body></html>", string(m.RichBody))
assert.Equal(t, "<html><body>This is body of <b>HTML mail</b> without attachment</body></html>", string(m.RichBody))
assert.Equal(t, "This is body of *HTML mail* without attachment", string(m.PlainBody))
assert.Len(t, m.Attachments, 0)
@ -461,7 +461,7 @@ func TestParseTextHTMLWithOctetAttachment(t *testing.T) {
assert.Equal(t, `"Sender" <sender@pm.me>`, m.Sender.String())
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
assert.Equal(t, "<html><head></head><body>This is body of <b>HTML mail</b> with attachment</body></html>", string(m.RichBody))
assert.Equal(t, "<html><body>This is body of <b>HTML mail</b> with attachment</body></html>", string(m.RichBody))
assert.Equal(t, "This is body of *HTML mail* with attachment", string(m.PlainBody))
require.Len(t, m.Attachments, 1)
@ -478,7 +478,7 @@ func TestParseTextHTMLWithPlainAttachment(t *testing.T) {
assert.Equal(t, `"Receiver" <receiver@pm.me>`, m.ToList[0].String())
// BAD: plainBody should not be empty!
assert.Equal(t, "<html><head></head><body>This is body of <b>HTML mail</b> with attachment</body></html>", string(m.RichBody))
assert.Equal(t, "<html><body>This is body of <b>HTML mail</b> with attachment</body></html>", string(m.RichBody))
assert.Equal(t, "This is body of *HTML mail* with attachment", string(m.PlainBody))
require.Len(t, m.Attachments, 1)
@ -496,7 +496,7 @@ func TestParseTextHTMLWithImageInline(t *testing.T) {
require.Len(t, m.Attachments, 1)
assert.Equal(t, fmt.Sprintf(`<html><head></head><body>This is body of <b>HTML mail</b> with attachment</body></html><html><body><img src="cid:%v"/></body></html>`, m.Attachments[0].ContentID), string(m.RichBody))
assert.Equal(t, fmt.Sprintf(`<html><body>This is body of <b>HTML mail</b> with attachment</body></html><html><body><img src="cid:%v"/></body></html>`, m.Attachments[0].ContentID), string(m.RichBody))
assert.Equal(t, "This is body of *HTML mail* with attachment", string(m.PlainBody))
// The inline image is an 8x8 mic-dropping gopher.
@ -627,7 +627,7 @@ func TestParseWithTrailingEndOfMailIndicator(t *testing.T) {
assert.Equal(t, `"Sender" <sender@sender.com>`, m.Sender.String())
assert.Equal(t, `"Receiver" <receiver@receiver.com>`, m.ToList[0].String())
assert.Equal(t, "<!DOCTYPE html><html><head></head><body>boo!</body></html>", string(m.RichBody))
assert.Equal(t, "<!DOCTYPE HTML>\n<html><body>boo!</body></html>", string(m.RichBody))
assert.Equal(t, "boo!", string(m.PlainBody))
}

View File

@ -241,6 +241,7 @@ Feature: SMTP sending of HTMl messages to Internal recipient
{
"content-type": "application/pgp-keys",
"content-disposition": "attachment",
"transfer-encoding": "base64"
}
]
@ -763,7 +764,7 @@ Feature: SMTP sending of HTMl messages to Internal recipient
"content-disposition": "attachment",
"content-disposition-filename": "index.html",
"transfer-encoding": "base64",
"body-is": "PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+Cjx0aXRsZT5QYWdlIFRpdGxlPC90aXRsZT4KPC9o\r\nZWFkPgo8Ym9keT4KCjxoMT5NeSBGaXJzdCBIZWFkaW5nPC9oMT4KPHA+TXkgZmlyc3QgcGFyYWdy\r\nYXBoLjwvcD4KCgogPC9ib2R5PjwvaHRtbD4="
"body-is": "IDwhRE9DVFlQRSBodG1sPg0KPGh0bWw+DQo8aGVhZD4NCjx0aXRsZT5QYWdlIFRpdGxlPC90aXRs\r\nZT4NCjwvaGVhZD4NCjxib2R5Pg0KDQo8aDE+TXkgRmlyc3QgSGVhZGluZzwvaDE+DQo8cD5NeSBm\r\naXJzdCBwYXJhZ3JhcGguPC9wPg0KDQo8L2JvZHk+DQo8L2h0bWw+IA=="
},
{
"content-type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@ -1070,7 +1071,6 @@ Feature: SMTP sending of HTMl messages to Internal recipient
}
"""
Scenario: Replying to a message after enabling attach public key
When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]":
"""

View File

@ -1242,7 +1242,7 @@ Feature: SMTP sending of PLAIN messages to Internal recipient
"content-disposition": "attachment",
"content-disposition-filename": "index.html",
"transfer-encoding": "base64",
"body-is": "PCFET0NUWVBFIGh0bWw+PGh0bWw+PGhlYWQ+Cjx0aXRsZT5QYWdlIFRpdGxlPC90aXRsZT4KPC9o\r\nZWFkPgo8Ym9keT4KCjxoMT5NeSBGaXJzdCBIZWFkaW5nPC9oMT4KPHA+TXkgZmlyc3QgcGFyYWdy\r\nYXBoLjwvcD4KCgogPC9ib2R5PjwvaHRtbD4="
"body-is": "IDwhRE9DVFlQRSBodG1sPg0KPGh0bWw+DQo8aGVhZD4NCjx0aXRsZT5QYWdlIFRpdGxlPC90aXRs\r\nZT4NCjwvaGVhZD4NCjxib2R5Pg0KDQo8aDE+TXkgRmlyc3QgSGVhZGluZzwvaDE+DQo8cD5NeSBm\r\naXJzdCBwYXJhZ3JhcGguPC9wPg0KDQo8L2JvZHk+DQo8L2h0bWw+IA=="
},
{
"content-type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",