use gabriel-vasile/mimetype for the mail attachments

This commit is contained in:
Gani Georgiev 2025-03-18 00:50:35 +02:00
parent 824c7db388
commit 9f1946057f
4 changed files with 108 additions and 2 deletions

View File

@ -1,3 +1,8 @@
## v0.27.0 (WIP)
- Updated the mail attachments auto MIME type detection to use `gabriel-vasile/mimetype` for consistency and broader sniffing signatures support.
## v0.26.1 ## v0.26.1
- Removed the wrapping of `io.EOF` error when reading files since currently `io.ReadAll` doesn't check for wrapped errors ([#6600](https://github.com/pocketbase/pocketbase/issues/6600)). - Removed the wrapping of `io.EOF` error when reading files since currently `io.ReadAll` doesn't check for wrapped errors ([#6600](https://github.com/pocketbase/pocketbase/issues/6600)).

View File

@ -1,9 +1,11 @@
package mailer package mailer
import ( import (
"bytes"
"io" "io"
"net/mail" "net/mail"
"github.com/gabriel-vasile/mimetype"
"github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/hook"
) )
@ -54,3 +56,17 @@ func addressesToStrings(addresses []mail.Address, withName bool) []string {
return result return result
} }
// detectReaderMimeType reads the first couple bytes of the reader to detect its MIME type.
//
// Returns a new combined reader from the partial read + the remaining of the original reader.
func detectReaderMimeType(r io.Reader) (io.Reader, string, error) {
readCopy := new(bytes.Buffer)
mime, err := mimetype.DetectReader(io.TeeReader(r, readCopy))
if err != nil {
return nil, "", err
}
return io.MultiReader(readCopy, r), mime.String(), nil
}

View File

@ -0,0 +1,77 @@
package mailer
import (
"fmt"
"io"
"net/mail"
"strings"
"testing"
)
func TestAddressesToStrings(t *testing.T) {
t.Parallel()
scenarios := []struct {
withName bool
addresses []mail.Address
expected []string
}{
{
true,
[]mail.Address{{Name: "John Doe", Address: "test1@example.com"}, {Name: "Jane Doe", Address: "test2@example.com"}},
[]string{`"John Doe" <test1@example.com>`, `"Jane Doe" <test2@example.com>`},
},
{
true,
[]mail.Address{{Name: "John Doe", Address: "test1@example.com"}, {Address: "test2@example.com"}},
[]string{`"John Doe" <test1@example.com>`, `test2@example.com`},
},
{
false,
[]mail.Address{{Name: "John Doe", Address: "test1@example.com"}, {Name: "Jane Doe", Address: "test2@example.com"}},
[]string{`test1@example.com`, `test2@example.com`},
},
}
for _, s := range scenarios {
t.Run(fmt.Sprintf("%v_%v", s.withName, s.addresses), func(t *testing.T) {
result := addressesToStrings(s.addresses, s.withName)
if len(s.expected) != len(result) {
t.Fatalf("Expected\n%v\ngot\n%v", s.expected, result)
}
for k, v := range s.expected {
if v != result[k] {
t.Fatalf("Expected %d address %q, got %q", k, v, result[k])
}
}
})
}
}
func TestDetectReaderMimeType(t *testing.T) {
t.Parallel()
str := "#!/bin/node\n" + strings.Repeat("a", 10000) // ensure that it is large enough to remain after the signature sniffing
r, mime, err := detectReaderMimeType(strings.NewReader(str))
if err != nil {
t.Fatal(err)
}
expectedMime := "text/javascript"
if mime != expectedMime {
t.Fatalf("Expected mime %q, got %q", expectedMime, mime)
}
raw, err := io.ReadAll(r)
if err != nil {
t.Fatal(err)
}
rawStr := string(raw)
if rawStr != str {
t.Fatalf("Expected content\n%s\ngot\n%s", str, rawStr)
}
}

View File

@ -116,12 +116,20 @@ func (c *SMTPClient) send(m *Message) error {
// add regular attachements (if any) // add regular attachements (if any)
for name, data := range m.Attachments { for name, data := range m.Attachments {
yak.Attach(name, data) r, mime, err := detectReaderMimeType(data)
if err != nil {
return err
}
yak.AttachWithMimeType(name, r, mime)
} }
// add inline attachments (if any) // add inline attachments (if any)
for name, data := range m.InlineAttachments { for name, data := range m.InlineAttachments {
yak.AttachInline(name, data) r, mime, err := detectReaderMimeType(data)
if err != nil {
return err
}
yak.AttachInlineWithMimeType(name, r, mime)
} }
// add custom headers (if any) // add custom headers (if any)