Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Never save attachments with empty content type #780

Merged
merged 1 commit into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 44 additions & 29 deletions attachments.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"net/http"
"net/url"
"path/filepath"
"strings"

"github.com/h2non/filetype"
"github.com/nyaruka/courier/utils"
Expand Down Expand Up @@ -107,45 +108,59 @@
return nil, err
}

mimeType := ""
extension := filepath.Ext(parsedURL.Path)
if extension != "" {
extension = extension[1:]
mimeType, extension := getAttachmentType(trace)

storageURL, err := b.SaveAttachment(ctx, channel, mimeType, trace.ResponseBody, extension)
if err != nil {
return nil, err
}

return &Attachment{ContentType: mimeType, URL: storageURL, Size: len(trace.ResponseBody)}, nil
}

func getAttachmentType(t *httpx.Trace) (string, string) {
var typ string

// use extension from url path if it exists
ext := filepath.Ext(t.Request.URL.Path)

// prioritize to use the response content type header if provided
contentTypeHeader := trace.Response.Header.Get("Content-Type")
if contentTypeHeader != "" && contentTypeHeader != "application/octet-stream" {
mimeType, _, _ = mime.ParseMediaType(contentTypeHeader)
if extension == "" {
extensions, err := mime.ExtensionsByType(mimeType)
if extensions == nil || err != nil {
extension = ""
} else {
extension = extensions[0][1:]
contentTypeHeader := t.Response.Header.Get("Content-Type")
if contentTypeHeader != "" {
typ, _, _ = mime.ParseMediaType(contentTypeHeader)
}

// if we didn't get a meaningful content type from the header, try to guess it from the body
if typ == "" || typ == "application/octet-stream" {
fileType, _ := filetype.Match(t.ResponseBody[:300])
if fileType != filetype.Unknown {
typ = fileType.MIME.Value
if ext == "" {
ext = fileType.Extension
}
}
} else {
}

// first try getting our mime type from the first 300 bytes of our body
fileType, _ := filetype.Match(trace.ResponseBody[:300])
// if we still don't have a type but the path has an extension, try to use that
if typ == "" && ext != "" {
fileType := filetype.GetType(ext)
if fileType != filetype.Unknown {
mimeType = fileType.MIME.Value
extension = fileType.Extension
} else {
// if that didn't work, try from our extension
fileType = filetype.GetType(extension)
if fileType != filetype.Unknown {
mimeType = fileType.MIME.Value
extension = fileType.Extension
}
typ = fileType.MIME.Value

Check warning on line 148 in attachments.go

View check run for this annotation

Codecov / codecov/patch

attachments.go#L148

Added line #L148 was not covered by tests
}
}

storageURL, err := b.SaveAttachment(ctx, channel, mimeType, trace.ResponseBody, extension)
if err != nil {
return nil, err
// if we have a type but no extension, try to get one from the type
if ext == "" {
extensions, err := mime.ExtensionsByType(typ)
if len(extensions) > 0 && err == nil {
ext = extensions[0][1:]
}
}

return &Attachment{ContentType: mimeType, URL: storageURL, Size: len(trace.ResponseBody)}, nil
// got to default to something...
if typ == "" {
typ = "application/octet-stream"
}

return typ, strings.TrimPrefix(ext, ".")
}
9 changes: 9 additions & 0 deletions attachments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ func TestFetchAndStoreAttachment(t *testing.T) {
"http://mock.com/media/hello.txt": {
httpx.NewMockResponse(200, nil, []byte(`hi`)),
},
"http://mock.com/media/hello7": {
httpx.NewMockResponse(200, nil, []byte(`hello world`)),
},
}))

defer uuids.SetGenerator(uuids.DefaultGenerator)
Expand Down Expand Up @@ -93,6 +96,12 @@ func TestFetchAndStoreAttachment(t *testing.T) {
assert.Equal(t, "https://backend.com/attachments/338ff339-5663-49ed-8ef6-384876655d1b.jpg", att.URL)
assert.Equal(t, 17301, att.Size)

att, err = courier.FetchAndStoreAttachment(ctx, mb, mockChannel, "http://mock.com/media/hello7", clog)
assert.NoError(t, err)
assert.Equal(t, "application/octet-stream", att.ContentType)
assert.Equal(t, "https://backend.com/attachments/9b955e36-ac16-4c6b-8ab6-9b9af5cd042a.", att.URL)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trailing period here is a product of the test backend, not this code

assert.Equal(t, 11, att.Size)

// an actual error on our part should be returned as an error
mb.SetStorageError(errors.New("boom"))

Expand Down
Loading