Compare commits

..

5 commits

Author SHA1 Message Date
f5bbc558b2
Merge branch 'main' into feature/145_add-eml-parser-to-generate-msg-from-eml-files 2024-02-10 15:12:25 +01:00
93611e47a5
Merge pull request #174 from wneessen/go_122
Update Go version in GitHub workflow files
2024-02-10 15:09:20 +01:00
f01047855f
Update Go version in GitHub workflow files
The Go version has been updated to '1.22' in the 'sonarqube.yml', 'golangci-lint.yml', and 'codecov.yml' GitHub action workflow files. This includes an additional modification for the Go versions matrix and condition statements in the 'codecov.yml' workflow.
2024-02-10 14:14:34 +01:00
3facbde703
Add new content types and refactor message writer
Introduced "multipart/mixed" and "multipart/related" content types in encoding.go and updated msgwriter.go to accommodate these. Adjustments made in related tests for these new types. Additionally, removed unnecessary print statements and improved multipart alternative parsing in eml.go.
2024-02-10 13:36:42 +01:00
59e85809f7
Refactor multipart encoding handling and improve content parsing
Refactored the processing of multipart encoding to be robust and easily maintainable. The changes include setting 'QP' encoding as default when the Content-Transfer-Encoding header is empty, accounting for the removal of this header by the standard Go multipart package. Also, parser functions for content type and charset are now independently handling the headers, replacing the split-string approach, thus improving efficiency and code readability.
2024-02-10 13:22:38 +01:00
7 changed files with 35 additions and 26 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.17, 1.18, 1.19, '1.20', '1.21'] go: [1.18, 1.19, '1.20', '1.21', '1.22']
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.21' && matrix.os == 'ubuntu-latest' if: matrix.go == '1.22' && 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.21' && matrix.os == 'ubuntu-latest' if: success() && matrix.go == '1.22' && 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.21' go-version: '1.22'
- 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.21.x' go-version: '1.22.x'
- name: Run unit Tests - name: Run unit Tests
run: | run: |

33
eml.go
View file

@ -178,9 +178,6 @@ 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])
}
} }
} }
@ -204,7 +201,8 @@ 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)
} }
@ -266,19 +264,27 @@ 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")
} }
mpContentTypeSplit := strings.Split(mpContentType[0], "; ") conType, charSet := parseContentType(mpContentType[0])
p := m.newPart(ContentType(mpContentTypeSplit[0])) p := m.newPart(ContentType(conType))
parseEMLMultiPartCharset(mpContentTypeSplit, p) p.SetCharset(Charset(charSet))
mpTransferEnc, ok := mpart.Header[HeaderContentTransferEnc.String()] mpTransferEnc, ok := mpart.Header[HeaderContentTransferEnc.String()]
if !ok { if !ok {
return fmt.Errorf("failed to get content-transfer-encoding from part") // If CTE is empty we can assume that it's a quoted-printable CTE since the
// 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)
@ -291,17 +297,6 @@ 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,6 +140,8 @@ 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,6 +40,18 @@ 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("mixed", m.boundary) mw.startMP(MIMEMixed, m.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasRelated() { if m.hasRelated() {
mw.startMP("related", m.boundary) mw.startMP(MIMERelated, m.boundary)
mw.writeString(DoubleNewLine) mw.writeString(DoubleNewLine)
} }
if m.hasAlt() { if m.hasAlt() {