sendgrid-go
sendgrid-go copied to clipboard
fix: Add missing err checks to inbound.Parse
Fixes
Added missing err checks that cause panic for some emails.
Checklist
- [x] I acknowledge that all my contributions will be made under the project's license
- [ ] I have made a material change to the repo (functionality, testing, spelling, grammar)
- [ ] I have read the Contribution Guidelines and my PR follows them
- [ ] I have titled the PR appropriately
- [ ] I have updated my branch with the main branch
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have added the necessary documentation about the functionality in the appropriate .md file
- [ ] I have added inline documentation to the code I modified
I ran into the exact same problem. This PR fixes it. I'm urging the maintainers to please merge this PR.
Hi all,
Doing a few tests, I noticed that yes, this PR fixes the panics, but leads to leave out parts of the email, I had better luck with this version of the parseRawEmail
function:
func (email *ParsedEmail) parseRawEmail(rawEmail string) error {
sections := strings.SplitN(rawEmail, "\n\n", 2)
email.parseHeaders(sections[0])
raw, err := parseMultipart(strings.NewReader(sections[1]), email.Headers["Content-Type"])
if err != nil {
return err
}
// if Content-Type is not multipart just set the whole email
if raw == nil {
if len(sections) < 2 {
return nil
}
wholeEmail := sections[1]
// decode if needed
if email.Headers["Content-Transfer-Encoding"] == "quoted-printable" {
decoded, err := io.ReadAll(quotedprintable.NewReader(strings.NewReader(wholeEmail)))
if err != nil {
return err
}
wholeEmail = string(decoded)
}
email.Body[email.Headers["Content-Type"]] = wholeEmail
return nil
}
for {
emailPart, err := raw.NextPart()
// Check for both io.EOF and the wrapped multipart: NextPart: EOF
if err == io.EOF || (err != nil && err.Error() == "multipart: NextPart: EOF") {
break
}
if err != nil {
return err
}
rawEmailBody, err := parseMultipart(emailPart, emailPart.Header.Get("Content-Type"))
if err != nil {
return err
}
if rawEmailBody != nil {
for {
emailBodyPart, err := rawEmailBody.NextPart()
// Check for both io.EOF and the wrapped multipart: NextPart: EOF
if err == io.EOF || (err != nil && err.Error() == "multipart: NextPart: EOF") {
break
}
if err != nil {
return err
}
header := emailBodyPart.Header.Get("Content-Type")
b, err := io.ReadAll(emailPart)
if err != nil {
return err
}
email.Body[header] = string(b)
}
} else if emailPart.FileName() != "" {
b, err := io.ReadAll(emailPart)
if err != nil {
return err
}
email.Attachments[emailPart.FileName()] = b
} else {
header := emailPart.Header.Get("Content-Type")
b, err := io.ReadAll(emailPart)
if err != nil {
return err
}
email.Body[header] = string(b)
}
}
return nil
}
Basically, the diffence is that I added the (horrible, but errors.Unwrap()
didn't work on that error) check (err != nil && err.Error() == "multipart: NextPart: EOF")
along with the err == io.EOF
one. In this way, I still catch the EOF, but I don't stop too early.
For the record, the wrapped error multipart: NextPart: EOF
comes from here: multipart/multipart.go.
It's not clear to me why I can't just do
if err == io.EOF || (err != nil && errors.Unwrap(err) == io.EOF) {
break
}
If it can be useful, this is the same in patch format to highlight the changes:
diff --git a/helpers/inbound/inbound.go b/helpers/inbound/inbound.go
index fdda393..ab99188 100644
--- a/helpers/inbound/inbound.go
+++ b/helpers/inbound/inbound.go
@@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
- "io/ioutil"
"mime"
"mime/multipart"
"mime/quotedprintable"
@@ -178,7 +177,7 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error {
wholeEmail := sections[1]
// decode if needed
if email.Headers["Content-Transfer-Encoding"] == "quoted-printable" {
- decoded, err := ioutil.ReadAll(quotedprintable.NewReader(strings.NewReader(wholeEmail)))
+ decoded, err := io.ReadAll(quotedprintable.NewReader(strings.NewReader(wholeEmail)))
if err != nil {
return err
}
@@ -191,8 +190,12 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error {
for {
emailPart, err := raw.NextPart()
- if err == io.EOF {
- return nil
+ // Check for both io.EOF and the wrapped multipart: NextPart: EOF
+ if err == io.EOF || (err != nil && err.Error() == "multipart: NextPart: EOF") {
+ break
+ }
+ if err != nil {
+ return err
}
rawEmailBody, err := parseMultipart(emailPart, emailPart.Header.Get("Content-Type"))
if err != nil {
@@ -201,9 +204,13 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error {
if rawEmailBody != nil {
for {
emailBodyPart, err := rawEmailBody.NextPart()
- if err == io.EOF {
+ // Check for both io.EOF and the wrapped multipart: NextPart: EOF
+ if err == io.EOF || (err != nil && err.Error() == "multipart: NextPart: EOF") {
break
}
+ if err != nil {
+ return err
+ }
header := emailBodyPart.Header.Get("Content-Type")
b, err := io.ReadAll(emailPart)
if err != nil {
@@ -228,6 +235,7 @@ func (email *ParsedEmail) parseRawEmail(rawEmail string) error {
email.Body[header] = string(b)
}
}
+ return nil
}
func parseMultipart(body io.Reader, contentType string) (*multipart.Reader, error) {
@darioleanbit's changes didn't fix the issue for me. Turns out that I'm getting inbound email requests with headers but an entirely empty body, so the crash is happening at the indexing ofsections[1]
which is beyond the end of the array.
sections := strings.SplitN(rawEmail, "\n\n", 2)
email.parseHeaders(sections[0])
raw, err := parseMultipart(strings.NewReader(sections[1]), email.Headers["Content-Type"])
if err != nil {
return err
}
In my fork, I fixed this (on top of @darioleanbit's changes), and added a testcase to reproduce the crash and make sure it's fixed.
https://github.com/sendgrid/sendgrid-go/compare/main...tadhunt:sendgrid-go:main