mirror of
https://github.com/wneessen/go-mail.git
synced 2024-12-23 02:50:39 +01:00
Merge branch 'main' into feature/168_improve-error-handling
This commit is contained in:
commit
a747f5f74c
21 changed files with 248 additions and 82 deletions
|
@ -6,9 +6,9 @@ freebsd_task:
|
|||
name: FreeBSD
|
||||
|
||||
matrix:
|
||||
- name: FreeBSD 13.2
|
||||
- name: FreeBSD 13.3
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-2
|
||||
image_family: freebsd-13-3
|
||||
- name: FreeBSD 14.0
|
||||
freebsd_instance:
|
||||
image_family: freebsd-14-0
|
||||
|
|
10
.github/workflows/codecov.yml
vendored
10
.github/workflows/codecov.yml
vendored
|
@ -36,28 +36,28 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
go: [1.18, 1.19, '1.20', '1.21', '1.22']
|
||||
go: ['1.20', '1.21', '1.22', '1.23']
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # master
|
||||
- name: Setup go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Install sendmail
|
||||
if: matrix.go == '1.22' && matrix.os == 'ubuntu-latest'
|
||||
if: matrix.go == '1.23' && matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo apt-get -y install sendmail; which sendmail
|
||||
- name: Run Tests
|
||||
run: |
|
||||
go test -v -race --coverprofile=coverage.coverprofile --covermode=atomic ./...
|
||||
- name: Upload coverage to Codecov
|
||||
if: success() && matrix.go == '1.22' && matrix.os == 'ubuntu-latest'
|
||||
if: success() && matrix.go == '1.23' && matrix.os == 'ubuntu-latest'
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||
|
|
8
.github/workflows/codeql-analysis.yml
vendored
8
.github/workflows/codeql-analysis.yml
vendored
|
@ -45,7 +45,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
@ -54,7 +54,7 @@ jobs:
|
|||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
|
@ -65,7 +65,7 @@ jobs:
|
|||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
uses: github/codeql-action/autobuild@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
@ -79,4 +79,4 @@ jobs:
|
|||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
uses: github/codeql-action/analyze@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
|
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
|
@ -21,11 +21,11 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
|
||||
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||
|
|
8
.github/workflows/golangci-lint.yml
vendored
8
.github/workflows/golangci-lint.yml
vendored
|
@ -20,16 +20,16 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
|
||||
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
|
||||
with:
|
||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
||||
version: latest
|
||||
|
|
2
.github/workflows/govulncheck.yml
vendored
2
.github/workflows/govulncheck.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Run govulncheck
|
||||
|
|
4
.github/workflows/reuse.yml
vendored
4
.github/workflows/reuse.yml
vendored
|
@ -14,10 +14,10 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@a46482ca367aef4454a87620aa37c2be4b2f8106 # v3.0.0
|
||||
uses: fsfe/reuse-action@3ae3c6bdf1257ab19397fab11fd3312144692083 # v4.0.0
|
||||
|
|
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
@ -45,7 +45,7 @@ jobs:
|
|||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
@ -67,7 +67,7 @@ jobs:
|
|||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
||||
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
@ -75,6 +75,6 @@ jobs:
|
|||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
||||
uses: github/codeql-action/upload-sarif@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
|
10
.github/workflows/sonarqube.yml
vendored
10
.github/workflows/sonarqube.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
||||
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
@ -36,20 +36,20 @@ jobs:
|
|||
fetch-depth: 0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
||||
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||
with:
|
||||
go-version: '1.22.x'
|
||||
go-version: '1.23.x'
|
||||
|
||||
- name: Run unit Tests
|
||||
run: |
|
||||
go test -v -race --coverprofile=./cov.out ./...
|
||||
|
||||
- uses: sonarsource/sonarqube-scan-action@540792c588b5c2740ad2bb4667db5cd46ae678f2 # master
|
||||
- uses: sonarsource/sonarqube-scan-action@0c0f3958d90fc466625f1d1af1f47bddd4cc6bd1 # master
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||
|
||||
- uses: sonarsource/sonarqube-quality-gate-action@72f24ebf1f81eda168a979ce14b8203273b7c3ad # master
|
||||
- uses: sonarsource/sonarqube-quality-gate-action@dc2f7b0dd95544cd550de3028f89193576e958b9 # master
|
||||
timeout-minutes: 5
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
## SPDX-License-Identifier: MIT
|
||||
|
||||
[run]
|
||||
go = "1.22"
|
||||
go = "1.23"
|
||||
tests = true
|
||||
exclude-dirs = ["examples"]
|
||||
|
||||
[linters]
|
||||
enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder",
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
|
@ -26,7 +26,7 @@ var newlineBytes = []byte(SingleNewLine)
|
|||
// line length is reached
|
||||
func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
||||
if l.out == nil {
|
||||
err = fmt.Errorf(ErrNoOutWriter)
|
||||
err = errors.New(ErrNoOutWriter)
|
||||
return
|
||||
}
|
||||
if l.used+len(data) < MaxBodyLength {
|
||||
|
|
|
@ -683,7 +683,7 @@ func (c *Client) DialAndSendWithContext(ctx context.Context, messages ...*Msg) e
|
|||
return fmt.Errorf("send failed: %w", err)
|
||||
}
|
||||
if err := c.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close connction: %w", err)
|
||||
return fmt.Errorf("failed to close connection: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
|||
package mail
|
||||
|
||||
// VERSION is used in the default user agent string
|
||||
const VERSION = "0.4.2"
|
||||
const VERSION = "0.4.4"
|
||||
|
|
28
eml.go
28
eml.go
|
@ -180,7 +180,15 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M
|
|||
// Extract the transfer encoding of the body
|
||||
mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to extract content type: %w", err)
|
||||
switch {
|
||||
// If no Content-Type header is found, we assume that this is a plain text, 7bit, US-ASCII mail
|
||||
case strings.EqualFold(err.Error(), "mime: no media type"):
|
||||
mediatype = TypeTextPlain.String()
|
||||
params = make(map[string]string)
|
||||
params["charset"] = CharsetASCII.String()
|
||||
default:
|
||||
return fmt.Errorf("failed to extract content type: %w", err)
|
||||
}
|
||||
}
|
||||
if value, ok := params["charset"]; ok {
|
||||
msg.SetCharset(Charset(value))
|
||||
|
@ -207,6 +215,12 @@ func parseEMLBodyParts(parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *M
|
|||
// parseEMLBodyPlain parses the mail body of plain type mails
|
||||
func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||
contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String())
|
||||
// According to RFC2045, if no Content-Transfer-Encoding is set, we can imply 7bit US-ASCII encoding
|
||||
if contentTransferEnc == "" || strings.EqualFold(contentTransferEnc, EncodingUSASCII.String()) {
|
||||
msg.SetEncoding(EncodingUSASCII)
|
||||
msg.SetBodyString(ContentType(mediatype), bodybuf.String())
|
||||
return nil
|
||||
}
|
||||
if strings.EqualFold(contentTransferEnc, NoEncoding.String()) {
|
||||
msg.SetEncoding(NoEncoding)
|
||||
msg.SetBodyString(ContentType(mediatype), bodybuf.String())
|
||||
|
@ -308,14 +322,22 @@ ReadNextPart:
|
|||
}
|
||||
|
||||
switch {
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingUSASCII.String()):
|
||||
part.SetEncoding(EncodingUSASCII)
|
||||
part.SetContent(string(multiPartData))
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], NoEncoding.String()):
|
||||
part.SetEncoding(NoEncoding)
|
||||
part.SetContent(string(multiPartData))
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingB64.String()):
|
||||
if err := handleEMLMultiPartBase64Encoding(multiPartData, part); err != nil {
|
||||
part.SetEncoding(EncodingB64)
|
||||
if err = handleEMLMultiPartBase64Encoding(multiPartData, part); err != nil {
|
||||
return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
|
||||
}
|
||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
|
||||
part.SetEncoding(EncodingQP)
|
||||
part.SetContent(string(multiPartData))
|
||||
default:
|
||||
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
||||
return fmt.Errorf("unsupported Content-Transfer-Encoding: %s", mutliPartTransferEnc[0])
|
||||
}
|
||||
|
||||
msg.parts = append(msg.parts, part)
|
||||
|
|
188
eml_test.go
188
eml_test.go
|
@ -14,6 +14,16 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// RFC 5322 example mail
|
||||
// See: https://datatracker.ietf.org/doc/html/rfc5322#appendix-A.1.1
|
||||
exampleMailRFC5322A11 = `From: John Doe <jdoe@machine.example>
|
||||
To: Mary Smith <mary@example.net>
|
||||
Subject: Saying Hello
|
||||
Date: Fri, 21 Nov 1997 09:55:06 -0600
|
||||
Message-ID: <1234@local.machine.example>
|
||||
|
||||
This is a message just to say hello.
|
||||
So, "Hello".`
|
||||
exampleMailPlainNoEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||
|
@ -32,6 +42,29 @@ This is a test mail. Please do not reply to this. Also this line is very long so
|
|||
should be wrapped.
|
||||
|
||||
|
||||
Thank your for your business!
|
||||
The go-mail team
|
||||
|
||||
--
|
||||
This is a signature`
|
||||
exampleMailPlain7Bit = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||
Subject: Example mail // plain text without encoding
|
||||
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||
To: <go-mail+test@go-mail.dev>
|
||||
Cc: <go-mail+cc@go-mail.dev>
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Dear Customer,
|
||||
|
||||
This is a test mail. Please do not reply to this. Also this line is very long so it
|
||||
should be wrapped.
|
||||
|
||||
|
||||
Thank your for your business!
|
||||
The go-mail team
|
||||
|
||||
|
@ -49,18 +82,6 @@ Cc: <go-mail+cc@go-mail.dev>
|
|||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: base64
|
||||
|
||||
This plain text body should not be parsed as Base64.
|
||||
`
|
||||
exampleMailPlainNoContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||
Subject: Example mail // plain text without encoding
|
||||
User-Agent: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||
X-Mailer: go-mail v0.4.0 // https://github.com/wneessen/go-mail
|
||||
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||
To: <go-mail+test@go-mail.dev>
|
||||
Cc: <go-mail+cc@go-mail.dev>
|
||||
|
||||
This plain text body should not be parsed as Base64.
|
||||
`
|
||||
exampleMailPlainUnknownContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
|
@ -525,6 +546,72 @@ hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
|
|||
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
|
||||
|
||||
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--`
|
||||
exampleMultiPart7BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||
Subject: Example mail // 7bit with base64 attachment
|
||||
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||
To: <go-mail+test@go-mail.dev>
|
||||
Cc: <go-mail+cc@go-mail.dev>
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------26A45336F6C6196BD8BBA2A2"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------26A45336F6C6196BD8BBA2A2
|
||||
Content-Type: text/plain; charset=US-ASCII; format=flowed
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
|
||||
--------------26A45336F6C6196BD8BBA2A2
|
||||
Content-Type: text/plain; charset=UTF-8;
|
||||
name="testfile.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment;
|
||||
filename="testfile.txt"
|
||||
|
||||
VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0
|
||||
--------------26A45336F6C6196BD8BBA2A2--`
|
||||
exampleMultiPart8BitBase64 = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||
MIME-Version: 1.0
|
||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
||||
Subject: Example mail // 8bit with base64 attachment
|
||||
User-Agent: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||
X-Mailer: go-mail v0.4.1 // https://github.com/wneessen/go-mail
|
||||
From: "Toni Tester" <go-mail@go-mail.dev>
|
||||
To: <go-mail+test@go-mail.dev>
|
||||
Cc: <go-mail+cc@go-mail.dev>
|
||||
Content-Type: multipart/mixed;
|
||||
boundary="------------26A45336F6C6196BD8BBA2A2"
|
||||
|
||||
This is a multi-part message in MIME format.
|
||||
--------------26A45336F6C6196BD8BBA2A2
|
||||
Content-Type: text/plain; charset=US-ASCII; format=flowed
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
testtest
|
||||
|
||||
--------------26A45336F6C6196BD8BBA2A2
|
||||
Content-Type: text/plain; charset=UTF-8;
|
||||
name="testfile.txt"
|
||||
Content-Transfer-Encoding: base64
|
||||
Content-Disposition: attachment;
|
||||
filename="testfile.txt"
|
||||
|
||||
VGhpcyBpcyBhIHRlc3QgaW4gQmFzZTY0
|
||||
--------------26A45336F6C6196BD8BBA2A2--`
|
||||
)
|
||||
|
||||
func TestEMLToMsgFromString(t *testing.T) {
|
||||
|
@ -534,6 +621,14 @@ func TestEMLToMsgFromString(t *testing.T) {
|
|||
enc string
|
||||
sub string
|
||||
}{
|
||||
{
|
||||
"RFC5322 A1.1", exampleMailRFC5322A11, "7bit",
|
||||
"Saying Hello",
|
||||
},
|
||||
{
|
||||
"Plain text no encoding (7bit)", exampleMailPlain7Bit, "7bit",
|
||||
"Example mail // plain text without encoding",
|
||||
},
|
||||
{
|
||||
"Plain text no encoding", exampleMailPlainNoEnc, "8bit",
|
||||
"Example mail // plain text without encoding",
|
||||
|
@ -638,12 +733,6 @@ func TestEMLToMsgFromReaderFailing(t *testing.T) {
|
|||
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
|
||||
}
|
||||
mailbuf.Reset()
|
||||
mailbuf.WriteString(exampleMailPlainNoContentType)
|
||||
_, err = EMLToMsgFromReader(mailbuf)
|
||||
if err == nil {
|
||||
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
|
||||
}
|
||||
mailbuf.Reset()
|
||||
mailbuf.WriteString(exampleMailPlainUnsupportedTransferEnc)
|
||||
_, err = EMLToMsgFromReader(mailbuf)
|
||||
if err == nil {
|
||||
|
@ -707,17 +796,6 @@ func TestEMLToMsgFromFileFailing(t *testing.T) {
|
|||
if err = os.RemoveAll(tempDir); err != nil {
|
||||
t.Error("failed to remove temp dir:", err)
|
||||
}
|
||||
tempDir, tempFile, err = stringToTempFile(exampleMailPlainNoContentType, "testmail")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||
}
|
||||
_, err = EMLToMsgFromFile(tempFile)
|
||||
if err == nil {
|
||||
t.Error("EML from Reader with no content type was supposed to fail, but didn't")
|
||||
}
|
||||
if err = os.RemoveAll(tempDir); err != nil {
|
||||
t.Error("failed to remove temp dir:", err)
|
||||
}
|
||||
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnsupportedTransferEnc, "testmail")
|
||||
if err != nil {
|
||||
t.Errorf("failed to write EML string to temp file: %s", err)
|
||||
|
@ -866,6 +944,58 @@ func TestEMLToMsgFromStringMultipartMixedAlternativeRelated(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEMLToMsgFromStringMultipartMixedWith7Bit(t *testing.T) {
|
||||
wantSubject := "Example mail // 7bit with base64 attachment"
|
||||
msg, err := EMLToMsgFromString(exampleMultiPart7BitBase64)
|
||||
if err != nil {
|
||||
t.Errorf("EML multipart mixed with 7bit: %s", err)
|
||||
}
|
||||
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit: expected subject: %s,"+
|
||||
" but got: %s", wantSubject, subject[0])
|
||||
}
|
||||
if len(msg.parts) != 1 {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected 1 part, got: %d",
|
||||
len(msg.parts))
|
||||
return
|
||||
}
|
||||
if !strings.EqualFold(msg.parts[0].GetEncoding().String(), EncodingUSASCII.String()) {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected encoding: %s, got %s",
|
||||
EncodingUSASCII.String(), msg.parts[0].GetEncoding().String())
|
||||
}
|
||||
if len(msg.attachments) != 1 {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 7bit failed: expected 1 attachment, got: %d",
|
||||
len(msg.attachments))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestEMLToMsgFromStringMultipartMixedWith8Bit(t *testing.T) {
|
||||
wantSubject := "Example mail // 8bit with base64 attachment"
|
||||
msg, err := EMLToMsgFromString(exampleMultiPart8BitBase64)
|
||||
if err != nil {
|
||||
t.Errorf("EML multipart mixed with 8bit: %s", err)
|
||||
}
|
||||
if subject := msg.GetGenHeader(HeaderSubject); len(subject) > 0 && !strings.EqualFold(subject[0], wantSubject) {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit: expected subject: %s,"+
|
||||
" but got: %s", wantSubject, subject[0])
|
||||
}
|
||||
if len(msg.parts) != 1 {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected 1 part, got: %d",
|
||||
len(msg.parts))
|
||||
return
|
||||
}
|
||||
if !strings.EqualFold(msg.parts[0].GetEncoding().String(), NoEncoding.String()) {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected encoding: %s, got %s",
|
||||
NoEncoding.String(), msg.parts[0].GetEncoding().String())
|
||||
}
|
||||
if len(msg.attachments) != 1 {
|
||||
t.Errorf("EMLToMsgFromString of EML multipart mixed with 8bit failed: expected 1 attachment, got: %d",
|
||||
len(msg.attachments))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// stringToTempFile is a helper method that will create a temporary file form a give data string
|
||||
func stringToTempFile(data, name string) (string, string, error) {
|
||||
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name))
|
||||
|
|
|
@ -27,6 +27,9 @@ const (
|
|||
// EncodingQP represents the "quoted-printable" encoding as specified in RFC 2045.
|
||||
EncodingQP Encoding = "quoted-printable"
|
||||
|
||||
// EncodingUSASCII represents encoding with only US-ASCII characters (aka 7Bit)
|
||||
EncodingUSASCII Encoding = "7bit"
|
||||
|
||||
// NoEncoding avoids any character encoding (except of the mail headers)
|
||||
NoEncoding Encoding = "8bit"
|
||||
)
|
||||
|
|
|
@ -16,6 +16,7 @@ func TestEncoding_String(t *testing.T) {
|
|||
{"Encoding: Base64", EncodingB64, "base64"},
|
||||
{"Encoding: QP", EncodingQP, "quoted-printable"},
|
||||
{"Encoding: None/8bit", NoEncoding, "8bit"},
|
||||
{"Encoding: US-ASCII/7bit", EncodingUSASCII, "7bit"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
23
msg.go
23
msg.go
|
@ -684,11 +684,18 @@ func (m *Msg) GetBoundary() string {
|
|||
return m.boundary
|
||||
}
|
||||
|
||||
// SetAttachements sets the attachements of the message.
|
||||
func (m *Msg) SetAttachements(files []*File) {
|
||||
// SetAttachments sets the attachments of the message.
|
||||
func (m *Msg) SetAttachments(files []*File) {
|
||||
m.attachments = files
|
||||
}
|
||||
|
||||
// SetAttachements sets the attachments of the message.
|
||||
//
|
||||
// Deprecated: use SetAttachments instead.
|
||||
func (m *Msg) SetAttachements(files []*File) {
|
||||
m.SetAttachments(files)
|
||||
}
|
||||
|
||||
// UnsetAllAttachments unset the attachments of the message.
|
||||
func (m *Msg) UnsetAllAttachments() {
|
||||
m.attachments = nil
|
||||
|
@ -736,7 +743,7 @@ func (m *Msg) SetBodyWriter(
|
|||
// The content type will be set to text/html automatically
|
||||
func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
return errors.New(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
|
@ -751,7 +758,7 @@ func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...Pa
|
|||
// The content type will be set to text/plain automatically
|
||||
func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
return errors.New(errTplPointerNil)
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buf, data); err != nil {
|
||||
|
@ -783,7 +790,7 @@ func (m *Msg) AddAlternativeWriter(
|
|||
// The content type will be set to text/html automatically
|
||||
func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
return errors.New(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
|
@ -798,7 +805,7 @@ func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opt
|
|||
// The content type will be set to text/plain automatically
|
||||
func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||
if tpl == nil {
|
||||
return fmt.Errorf(errTplPointerNil)
|
||||
return errors.New(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
|
@ -1307,7 +1314,7 @@ func fileFromReadSeeker(name string, reader io.ReadSeeker) *File {
|
|||
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
|
||||
func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) {
|
||||
if tpl == nil {
|
||||
return nil, fmt.Errorf(errTplPointerNil)
|
||||
return nil, errors.New(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
|
@ -1319,7 +1326,7 @@ func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*Fil
|
|||
// fileFromTextTemplate returns a File pointer form a given text/template.Template
|
||||
func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) {
|
||||
if tpl == nil {
|
||||
return nil, fmt.Errorf(errTplPointerNil)
|
||||
return nil, errors.New(errTplPointerNil)
|
||||
}
|
||||
buffer := bytes.Buffer{}
|
||||
if err := tpl.Execute(&buffer, data); err != nil {
|
||||
|
|
|
@ -1413,7 +1413,7 @@ func TestMsg_SetAttachments(t *testing.T) {
|
|||
for _, f := range tt.files {
|
||||
files = append(files, &File{Name: f})
|
||||
}
|
||||
m.SetAttachements(files)
|
||||
m.SetAttachments(files)
|
||||
if len(m.attachments) != len(files) {
|
||||
t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files),
|
||||
len(m.attachments))
|
||||
|
@ -1448,7 +1448,7 @@ func TestMsg_UnsetAllAttachments(t *testing.T) {
|
|||
for _, f := range tt.attachments {
|
||||
files = append(files, &File{Name: f})
|
||||
}
|
||||
m.SetAttachements(files)
|
||||
m.SetAttachments(files)
|
||||
|
||||
if len(m.attachments) != len(files) {
|
||||
t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files),
|
||||
|
@ -1610,7 +1610,7 @@ func TestMsg_UnsetAllParts(t *testing.T) {
|
|||
for _, f := range tt.attachments {
|
||||
attachments = append(attachments, &File{Name: f})
|
||||
}
|
||||
m.SetAttachements(attachments)
|
||||
m.SetAttachments(attachments)
|
||||
if len(m.attachments) != len(attachments) {
|
||||
t.Errorf("SetAttachements() failed. Number of attachments files expected: %d, got: %d",
|
||||
len(attachments), len(m.attachments))
|
||||
|
|
|
@ -122,7 +122,7 @@ func TestMsgWriter_writeMsg(t *testing.T) {
|
|||
em += fmt.Sprintf("* incorrect %q field", ea[e])
|
||||
}
|
||||
em += fmt.Sprintf("\n\nFull message:\n%s", ms)
|
||||
t.Errorf(em)
|
||||
t.Error(em)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,13 +20,15 @@ const (
|
|||
// extension.
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
|
||||
LoginXUsernameChallenge = "Username:"
|
||||
LoginXUsernameChallenge = "Username:"
|
||||
LoginXUsernameLowerChallenge = "username:"
|
||||
|
||||
// LoginXPasswordChallenge represents the Password Challenge response sent by the SMTP server per the AUTH LOGIN
|
||||
// extension.
|
||||
//
|
||||
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
|
||||
LoginXPasswordChallenge = "Password:"
|
||||
LoginXPasswordChallenge = "Password:"
|
||||
LoginXPasswordLowerChallenge = "password:"
|
||||
|
||||
// LoginXDraftUsernameChallenge represents the Username Challenge response sent by the SMTP server per the IETF
|
||||
// draft AUTH LOGIN extension. It should be noted this extension is an expired draft which was never formally
|
||||
|
@ -76,9 +78,9 @@ func (a *loginAuth) Start(server *ServerInfo) (string, []byte, error) {
|
|||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case LoginXUsernameChallenge, LoginXDraftUsernameChallenge:
|
||||
case LoginXUsernameChallenge, LoginXUsernameLowerChallenge, LoginXDraftUsernameChallenge:
|
||||
return []byte(a.username), nil
|
||||
case LoginXPasswordChallenge, LoginXDraftPasswordChallenge:
|
||||
case LoginXPasswordChallenge, LoginXPasswordLowerChallenge, LoginXDraftPasswordChallenge:
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
|
||||
|
|
Loading…
Reference in a new issue