Compare commits

..

No commits in common. "f5bbc558b20dc908899f43cb495b2d2717152598" and "53566a93cd6e1bf0f451bd3aac4f4cf699a0bf01" have entirely different histories.

7 changed files with 26 additions and 35 deletions

View file

@ -33,7 +33,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest, macos-latest, windows-latest] os: [ubuntu-latest, macos-latest, windows-latest]
go: [1.18, 1.19, '1.20', '1.21', '1.22'] go: [1.17, 1.18, 1.19, '1.20', '1.21']
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@master uses: actions/checkout@master
@ -42,14 +42,14 @@ jobs:
with: with:
go-version: ${{ matrix.go }} go-version: ${{ matrix.go }}
- name: Install sendmail - name: Install sendmail
if: matrix.go == '1.22' && matrix.os == 'ubuntu-latest' if: matrix.go == '1.21' && matrix.os == 'ubuntu-latest'
run: | run: |
sudo apt-get -y install sendmail; which sendmail sudo apt-get -y install sendmail; which sendmail
- name: Run Tests - name: Run Tests
run: | run: |
go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./... go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: success() && matrix.go == '1.22' && matrix.os == 'ubuntu-latest' if: success() && matrix.go == '1.21' && matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3 uses: codecov/codecov-action@v3
with: with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos

View file

@ -21,7 +21,7 @@ jobs:
steps: steps:
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: '1.22' go-version: '1.21'
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: golangci-lint - name: golangci-lint
uses: golangci/golangci-lint-action@v3 uses: golangci/golangci-lint-action@v3

View file

@ -29,7 +29,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3 uses: actions/setup-go@v3
with: with:
go-version: '1.22.x' go-version: '1.21.x'
- name: Run unit Tests - name: Run unit Tests
run: | run: |

33
eml.go
View file

@ -178,6 +178,9 @@ func parseEMLHeaders(mh *nm.Header, m *Msg) error {
for _, h := range commonHeaders { for _, h := range commonHeaders {
if v := mh.Get(h.String()); v != "" { if v := mh.Get(h.String()); v != "" {
m.SetGenHeader(h, v) m.SetGenHeader(h, v)
if strings.EqualFold(h.String(), "subject") {
fmt.Printf("SUBJECT: %s\n", m.GetGenHeader(HeaderSubject)[0])
}
} }
} }
@ -201,8 +204,7 @@ func parseEMLBodyParts(pm *nm.Message, bodybuf *bytes.Buffer, m *Msg) error {
if err := parseEMLBodyPlain(mediatype, pm, bodybuf, m); err != nil { if err := parseEMLBodyPlain(mediatype, pm, bodybuf, m); err != nil {
return fmt.Errorf("failed to parse plain body: %w", err) return fmt.Errorf("failed to parse plain body: %w", err)
} }
case strings.EqualFold(mediatype, TypeMultipartAlternative.String()), case strings.EqualFold(mediatype, TypeMultipartAlternative.String()):
strings.EqualFold(mediatype, "multipart/mixed"):
if err := parseEMLMultipartAlternative(params, bodybuf, m); err != nil { if err := parseEMLMultipartAlternative(params, bodybuf, m); err != nil {
return fmt.Errorf("failed to parse multipart/alternative body: %w", err) return fmt.Errorf("failed to parse multipart/alternative body: %w", err)
} }
@ -264,27 +266,19 @@ func parseEMLMultipartAlternative(params map[string]string, bodybuf *bytes.Buffe
if !ok { if !ok {
return fmt.Errorf("failed to get content-type from part") return fmt.Errorf("failed to get content-type from part")
} }
conType, charSet := parseContentType(mpContentType[0]) mpContentTypeSplit := strings.Split(mpContentType[0], "; ")
p := m.newPart(ContentType(conType)) p := m.newPart(ContentType(mpContentTypeSplit[0]))
p.SetCharset(Charset(charSet)) parseEMLMultiPartCharset(mpContentTypeSplit, p)
mpTransferEnc, ok := mpart.Header[HeaderContentTransferEnc.String()] mpTransferEnc, ok := mpart.Header[HeaderContentTransferEnc.String()]
if !ok { if !ok {
// If CTE is empty we can assume that it's a quoted-printable CTE since the return fmt.Errorf("failed to get content-transfer-encoding from part")
// GO stdlib multipart packages deletes that header
// See: https://cs.opensource.google/go/go/+/refs/tags/go1.22.0:src/mime/multipart/multipart.go;l=161
mpTransferEnc = []string{EncodingQP.String()}
} }
switch { switch {
case strings.EqualFold(mpTransferEnc[0], EncodingB64.String()): case strings.EqualFold(mpTransferEnc[0], EncodingB64.String()):
if err := handleEMLMultiPartBase64Encoding(mpdata, p); err != nil { if err := handleEMLMultiPartBase64Encoding(mpdata, p); err != nil {
return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err) return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
} }
case strings.EqualFold(mpTransferEnc[0], EncodingQP.String()):
p.SetContent(string(mpdata))
default:
return fmt.Errorf("unsupported Content-Transfer-Encoding")
} }
m.parts = append(m.parts, p) m.parts = append(m.parts, p)
@ -297,6 +291,17 @@ func parseEMLMultipartAlternative(params map[string]string, bodybuf *bytes.Buffe
return nil return nil
} }
// parseEMLMultiPartCharset parses the Charset from a ContentType header and assigns it to a Part
// TODO: This might be redundant to parseContentType
func parseEMLMultiPartCharset(mpContentTypeSplit []string, p *Part) {
if len(mpContentTypeSplit) > 1 && strings.HasPrefix(strings.ToLower(mpContentTypeSplit[1]), "charset=") {
valSplit := strings.Split(mpContentTypeSplit[1], "=")
if len(valSplit) > 1 {
p.SetCharset(Charset(valSplit[1]))
}
}
}
// handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part // handleEMLMultiPartBase64Encoding sets the content body of a base64 encoded Part
func handleEMLMultiPartBase64Encoding(mpdata []byte, p *Part) error { func handleEMLMultiPartBase64Encoding(mpdata []byte, p *Part) error {
p.SetEncoding(EncodingB64) p.SetEncoding(EncodingB64)

View file

@ -140,8 +140,6 @@ const (
const ( const (
TypeAppOctetStream ContentType = "application/octet-stream" TypeAppOctetStream ContentType = "application/octet-stream"
TypeMultipartAlternative ContentType = "multipart/alternative" TypeMultipartAlternative ContentType = "multipart/alternative"
TypeMultipartMixed ContentType = "multipart/mixed"
TypeMultipartRelated ContentType = "multipart/related"
TypePGPSignature ContentType = "application/pgp-signature" TypePGPSignature ContentType = "application/pgp-signature"
TypePGPEncrypted ContentType = "application/pgp-encrypted" TypePGPEncrypted ContentType = "application/pgp-encrypted"
TypeTextHTML ContentType = "text/html" TypeTextHTML ContentType = "text/html"

View file

@ -40,18 +40,6 @@ func TestContentType_String(t *testing.T) {
"ContentType: application/octet-stream", TypeAppOctetStream, "ContentType: application/octet-stream", TypeAppOctetStream,
"application/octet-stream", "application/octet-stream",
}, },
{
"ContentType: multipart/alternative", TypeMultipartAlternative,
"multipart/alternative",
},
{
"ContentType: multipart/mixed", TypeMultipartMixed,
"multipart/mixed",
},
{
"ContentType: multipart/related", TypeMultipartRelated,
"multipart/related",
},
{ {
"ContentType: application/pgp-signature", TypePGPSignature, "ContentType: application/pgp-signature", TypePGPSignature,
"application/pgp-signature", "application/pgp-signature",

View file

@ -89,11 +89,11 @@ func (mw *msgWriter) writeMsg(m *Msg) {
} }
if m.hasMixed() { if m.hasMixed() {
mw.startMP(MIMEMixed, m.boundary) mw.startMP("mixed", m.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasRelated() { if m.hasRelated() {
mw.startMP(MIMERelated, m.boundary) mw.startMP("related", m.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasAlt() { if m.hasAlt() {