mirror of
https://github.com/wneessen/go-mail.git
synced 2024-11-22 13:50:49 +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
|
name: FreeBSD
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
- name: FreeBSD 13.2
|
- name: FreeBSD 13.3
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-13-2
|
image_family: freebsd-13-3
|
||||||
- name: FreeBSD 14.0
|
- name: FreeBSD 14.0
|
||||||
freebsd_instance:
|
freebsd_instance:
|
||||||
image_family: freebsd-14-0
|
image_family: freebsd-14-0
|
||||||
|
|
10
.github/workflows/codecov.yml
vendored
10
.github/workflows/codecov.yml
vendored
|
@ -36,28 +36,28 @@ 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.20', '1.21', '1.22', '1.23']
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # master
|
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # master
|
||||||
- name: Setup go
|
- name: Setup go
|
||||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||||
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.23' && 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.23' && matrix.os == 'ubuntu-latest'
|
||||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
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:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@b611370bb5703a7efb587f9d136a52ea24c5c38c # v3.25.11
|
uses: github/codeql-action/init@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# 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).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- 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.
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
# 📚 https://git.io/JvXDl
|
# 📚 https://git.io/JvXDl
|
||||||
|
@ -79,4 +79,4 @@ jobs:
|
||||||
# make release
|
# make release
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: 'Checkout Repository'
|
- name: 'Checkout Repository'
|
||||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: 'Dependency Review'
|
- 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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||||
with:
|
with:
|
||||||
go-version: '1.22'
|
go-version: '1.23'
|
||||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||||
- name: golangci-lint
|
- name: golangci-lint
|
||||||
uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1
|
uses: golangci/golangci-lint-action@aaa42aa0628b4ae2578232a66b541047968fac86 # v6.1.0
|
||||||
with:
|
with:
|
||||||
# Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version
|
# 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
|
version: latest
|
||||||
|
|
2
.github/workflows/govulncheck.yml
vendored
2
.github/workflows/govulncheck.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
- name: Run govulncheck
|
- name: Run govulncheck
|
||||||
|
|
4
.github/workflows/reuse.yml
vendored
4
.github/workflows/reuse.yml
vendored
|
@ -14,10 +14,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
|
- uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
|
||||||
- name: REUSE Compliance Check
|
- 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:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Run analysis"
|
- name: "Run analysis"
|
||||||
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
|
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
|
||||||
with:
|
with:
|
||||||
results_file: results.sarif
|
results_file: results.sarif
|
||||||
results_format: 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
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
|
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
|
@ -75,6 +75,6 @@ jobs:
|
||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- 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:
|
with:
|
||||||
sarif_file: results.sarif
|
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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 # v2.8.1
|
uses: step-security/harden-runner@5c7944e73c4c2a096b17a9cb74d65b6c2bbafbde # v2.9.1
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
|
@ -36,20 +36,20 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1
|
uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
|
||||||
with:
|
with:
|
||||||
go-version: '1.22.x'
|
go-version: '1.23.x'
|
||||||
|
|
||||||
- name: Run unit Tests
|
- name: Run unit Tests
|
||||||
run: |
|
run: |
|
||||||
go test -v -race --coverprofile=./cov.out ./...
|
go test -v -race --coverprofile=./cov.out ./...
|
||||||
|
|
||||||
- uses: sonarsource/sonarqube-scan-action@540792c588b5c2740ad2bb4667db5cd46ae678f2 # master
|
- uses: sonarsource/sonarqube-scan-action@0c0f3958d90fc466625f1d1af1f47bddd4cc6bd1 # master
|
||||||
env:
|
env:
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
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
|
timeout-minutes: 5
|
||||||
env:
|
env:
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
## SPDX-License-Identifier: MIT
|
## SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
go = "1.22"
|
go = "1.23"
|
||||||
tests = true
|
tests = true
|
||||||
|
exclude-dirs = ["examples"]
|
||||||
|
|
||||||
[linters]
|
[linters]
|
||||||
enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder",
|
enable = ["stylecheck", "whitespace", "containedctx", "contextcheck", "decorder",
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ var newlineBytes = []byte(SingleNewLine)
|
||||||
// line length is reached
|
// line length is reached
|
||||||
func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
func (l *Base64LineBreaker) Write(data []byte) (numBytes int, err error) {
|
||||||
if l.out == nil {
|
if l.out == nil {
|
||||||
err = fmt.Errorf(ErrNoOutWriter)
|
err = errors.New(ErrNoOutWriter)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if l.used+len(data) < MaxBodyLength {
|
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)
|
return fmt.Errorf("send failed: %w", err)
|
||||||
}
|
}
|
||||||
if err := c.Close(); err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
2
doc.go
2
doc.go
|
@ -6,4 +6,4 @@
|
||||||
package mail
|
package mail
|
||||||
|
|
||||||
// VERSION is used in the default user agent string
|
// 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
|
// Extract the transfer encoding of the body
|
||||||
mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String()))
|
mediatype, params, err := mime.ParseMediaType(parsedMsg.Header.Get(HeaderContentType.String()))
|
||||||
if err != nil {
|
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 {
|
if value, ok := params["charset"]; ok {
|
||||||
msg.SetCharset(Charset(value))
|
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
|
// parseEMLBodyPlain parses the mail body of plain type mails
|
||||||
func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
func parseEMLBodyPlain(mediatype string, parsedMsg *netmail.Message, bodybuf *bytes.Buffer, msg *Msg) error {
|
||||||
contentTransferEnc := parsedMsg.Header.Get(HeaderContentTransferEnc.String())
|
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()) {
|
if strings.EqualFold(contentTransferEnc, NoEncoding.String()) {
|
||||||
msg.SetEncoding(NoEncoding)
|
msg.SetEncoding(NoEncoding)
|
||||||
msg.SetBodyString(ContentType(mediatype), bodybuf.String())
|
msg.SetBodyString(ContentType(mediatype), bodybuf.String())
|
||||||
|
@ -308,14 +322,22 @@ ReadNextPart:
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
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()):
|
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)
|
return fmt.Errorf("failed to handle multipart base64 transfer-encoding: %w", err)
|
||||||
}
|
}
|
||||||
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
|
case strings.EqualFold(mutliPartTransferEnc[0], EncodingQP.String()):
|
||||||
|
part.SetEncoding(EncodingQP)
|
||||||
part.SetContent(string(multiPartData))
|
part.SetContent(string(multiPartData))
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported Content-Transfer-Encoding")
|
return fmt.Errorf("unsupported Content-Transfer-Encoding: %s", mutliPartTransferEnc[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.parts = append(msg.parts, part)
|
msg.parts = append(msg.parts, part)
|
||||||
|
|
188
eml_test.go
188
eml_test.go
|
@ -14,6 +14,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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
|
exampleMailPlainNoEnc = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Message-ID: <1305604950.683004066175.AAAAAAAAaaaaaaaaB@go-mail.dev>
|
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.
|
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!
|
Thank your for your business!
|
||||||
The go-mail team
|
The go-mail team
|
||||||
|
|
||||||
|
@ -49,18 +82,6 @@ Cc: <go-mail+cc@go-mail.dev>
|
||||||
Content-Type: text/plain; charset=UTF-8
|
Content-Type: text/plain; charset=UTF-8
|
||||||
Content-Transfer-Encoding: base64
|
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.
|
This plain text body should not be parsed as Base64.
|
||||||
`
|
`
|
||||||
exampleMailPlainUnknownContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
exampleMailPlainUnknownContentType = `Date: Wed, 01 Nov 2023 00:00:00 +0000
|
||||||
|
@ -525,6 +546,72 @@ hw22iFHl7YlpOmedZvtMTfQffXeXnvI+rTKNxguyvDKvB7U4qQAAAAlwSFlzAAALEwAACxMBAJqc
|
||||||
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
|
GAAAABFJREFUCJljnMoAA0wMNGcCAEQrAKk9oHKhAAAAAElFTkSuQmCC
|
||||||
|
|
||||||
--fe785e0384e2607697cc2ecb17cce003003bb7ca9112104f3e8ce727edb5--`
|
--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) {
|
func TestEMLToMsgFromString(t *testing.T) {
|
||||||
|
@ -534,6 +621,14 @@ func TestEMLToMsgFromString(t *testing.T) {
|
||||||
enc string
|
enc string
|
||||||
sub 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",
|
"Plain text no encoding", exampleMailPlainNoEnc, "8bit",
|
||||||
"Example mail // plain text without encoding",
|
"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")
|
t.Error("EML from Reader with unknown content type was supposed to fail, but didn't")
|
||||||
}
|
}
|
||||||
mailbuf.Reset()
|
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)
|
mailbuf.WriteString(exampleMailPlainUnsupportedTransferEnc)
|
||||||
_, err = EMLToMsgFromReader(mailbuf)
|
_, err = EMLToMsgFromReader(mailbuf)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -707,17 +796,6 @@ func TestEMLToMsgFromFileFailing(t *testing.T) {
|
||||||
if err = os.RemoveAll(tempDir); err != nil {
|
if err = os.RemoveAll(tempDir); err != nil {
|
||||||
t.Error("failed to remove temp dir:", err)
|
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")
|
tempDir, tempFile, err = stringToTempFile(exampleMailPlainUnsupportedTransferEnc, "testmail")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to write EML string to temp file: %s", err)
|
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
|
// stringToTempFile is a helper method that will create a temporary file form a give data string
|
||||||
func stringToTempFile(data, name string) (string, string, error) {
|
func stringToTempFile(data, name string) (string, string, error) {
|
||||||
tempDir, err := os.MkdirTemp("", fmt.Sprintf("*-%s", name))
|
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 represents the "quoted-printable" encoding as specified in RFC 2045.
|
||||||
EncodingQP Encoding = "quoted-printable"
|
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 avoids any character encoding (except of the mail headers)
|
||||||
NoEncoding Encoding = "8bit"
|
NoEncoding Encoding = "8bit"
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ func TestEncoding_String(t *testing.T) {
|
||||||
{"Encoding: Base64", EncodingB64, "base64"},
|
{"Encoding: Base64", EncodingB64, "base64"},
|
||||||
{"Encoding: QP", EncodingQP, "quoted-printable"},
|
{"Encoding: QP", EncodingQP, "quoted-printable"},
|
||||||
{"Encoding: None/8bit", NoEncoding, "8bit"},
|
{"Encoding: None/8bit", NoEncoding, "8bit"},
|
||||||
|
{"Encoding: US-ASCII/7bit", EncodingUSASCII, "7bit"},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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
|
return m.boundary
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetAttachements sets the attachements of the message.
|
// SetAttachments sets the attachments of the message.
|
||||||
func (m *Msg) SetAttachements(files []*File) {
|
func (m *Msg) SetAttachments(files []*File) {
|
||||||
m.attachments = files
|
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.
|
// UnsetAllAttachments unset the attachments of the message.
|
||||||
func (m *Msg) UnsetAllAttachments() {
|
func (m *Msg) UnsetAllAttachments() {
|
||||||
m.attachments = nil
|
m.attachments = nil
|
||||||
|
@ -736,7 +743,7 @@ func (m *Msg) SetBodyWriter(
|
||||||
// The content type will be set to text/html automatically
|
// The content type will be set to text/html automatically
|
||||||
func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
func (m *Msg) SetBodyHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buffer, data); err != nil {
|
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
|
// The content type will be set to text/plain automatically
|
||||||
func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
func (m *Msg) SetBodyTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buf := bytes.Buffer{}
|
buf := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buf, data); err != nil {
|
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
|
// The content type will be set to text/html automatically
|
||||||
func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
func (m *Msg) AddAlternativeHTMLTemplate(tpl *ht.Template, data interface{}, opts ...PartOption) error {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buffer, data); err != nil {
|
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
|
// The content type will be set to text/plain automatically
|
||||||
func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
func (m *Msg) AddAlternativeTextTemplate(tpl *tt.Template, data interface{}, opts ...PartOption) error {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return fmt.Errorf(errTplPointerNil)
|
return errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buffer, data); err != nil {
|
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
|
// fileFromHTMLTemplate returns a File pointer form a given html/template.Template
|
||||||
func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) {
|
func fileFromHTMLTemplate(name string, tpl *ht.Template, data interface{}) (*File, error) {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return nil, fmt.Errorf(errTplPointerNil)
|
return nil, errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buffer, data); err != nil {
|
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
|
// fileFromTextTemplate returns a File pointer form a given text/template.Template
|
||||||
func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) {
|
func fileFromTextTemplate(name string, tpl *tt.Template, data interface{}) (*File, error) {
|
||||||
if tpl == nil {
|
if tpl == nil {
|
||||||
return nil, fmt.Errorf(errTplPointerNil)
|
return nil, errors.New(errTplPointerNil)
|
||||||
}
|
}
|
||||||
buffer := bytes.Buffer{}
|
buffer := bytes.Buffer{}
|
||||||
if err := tpl.Execute(&buffer, data); err != nil {
|
if err := tpl.Execute(&buffer, data); err != nil {
|
||||||
|
|
|
@ -1413,7 +1413,7 @@ func TestMsg_SetAttachments(t *testing.T) {
|
||||||
for _, f := range tt.files {
|
for _, f := range tt.files {
|
||||||
files = append(files, &File{Name: f})
|
files = append(files, &File{Name: f})
|
||||||
}
|
}
|
||||||
m.SetAttachements(files)
|
m.SetAttachments(files)
|
||||||
if len(m.attachments) != len(files) {
|
if len(m.attachments) != len(files) {
|
||||||
t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files),
|
t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", len(files),
|
||||||
len(m.attachments))
|
len(m.attachments))
|
||||||
|
@ -1448,7 +1448,7 @@ func TestMsg_UnsetAllAttachments(t *testing.T) {
|
||||||
for _, f := range tt.attachments {
|
for _, f := range tt.attachments {
|
||||||
files = append(files, &File{Name: f})
|
files = append(files, &File{Name: f})
|
||||||
}
|
}
|
||||||
m.SetAttachements(files)
|
m.SetAttachments(files)
|
||||||
|
|
||||||
if len(m.attachments) != len(files) {
|
if len(m.attachments) != len(files) {
|
||||||
t.Errorf("SetAttachements() failed. Number of attachments expected: %d, got: %d", 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 {
|
for _, f := range tt.attachments {
|
||||||
attachments = append(attachments, &File{Name: f})
|
attachments = append(attachments, &File{Name: f})
|
||||||
}
|
}
|
||||||
m.SetAttachements(attachments)
|
m.SetAttachments(attachments)
|
||||||
if len(m.attachments) != len(attachments) {
|
if len(m.attachments) != len(attachments) {
|
||||||
t.Errorf("SetAttachements() failed. Number of attachments files expected: %d, got: %d",
|
t.Errorf("SetAttachements() failed. Number of attachments files expected: %d, got: %d",
|
||||||
len(attachments), len(m.attachments))
|
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("* incorrect %q field", ea[e])
|
||||||
}
|
}
|
||||||
em += fmt.Sprintf("\n\nFull message:\n%s", ms)
|
em += fmt.Sprintf("\n\nFull message:\n%s", ms)
|
||||||
t.Errorf(em)
|
t.Error(em)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,15 @@ const (
|
||||||
// extension.
|
// extension.
|
||||||
//
|
//
|
||||||
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
|
// 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
|
// LoginXPasswordChallenge represents the Password Challenge response sent by the SMTP server per the AUTH LOGIN
|
||||||
// extension.
|
// extension.
|
||||||
//
|
//
|
||||||
// See: https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-xlogin/.
|
// 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
|
// 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
|
// 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) {
|
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||||
if more {
|
if more {
|
||||||
switch string(fromServer) {
|
switch string(fromServer) {
|
||||||
case LoginXUsernameChallenge, LoginXDraftUsernameChallenge:
|
case LoginXUsernameChallenge, LoginXUsernameLowerChallenge, LoginXDraftUsernameChallenge:
|
||||||
return []byte(a.username), nil
|
return []byte(a.username), nil
|
||||||
case LoginXPasswordChallenge, LoginXDraftPasswordChallenge:
|
case LoginXPasswordChallenge, LoginXPasswordLowerChallenge, LoginXDraftPasswordChallenge:
|
||||||
return []byte(a.password), nil
|
return []byte(a.password), nil
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
|
return nil, fmt.Errorf("unexpected server response: %s", string(fromServer))
|
||||||
|
|
Loading…
Reference in a new issue