subaddressing support, closes #61
This commit is contained in:
@@ -18,6 +18,7 @@ so you can use it to send emails from your apps and scripts as well.
|
|||||||
- [x] Configuration in room's account data
|
- [x] Configuration in room's account data
|
||||||
- [x] Receive emails to matrix rooms
|
- [x] Receive emails to matrix rooms
|
||||||
- [x] Receive attachments
|
- [x] Receive attachments
|
||||||
|
- [x] Subaddressing support
|
||||||
- [x] Catch-all mailbox
|
- [x] Catch-all mailbox
|
||||||
- [x] Map email threads to matrix threads
|
- [x] Map email threads to matrix threads
|
||||||
- [x] Multi-domain support
|
- [x] Multi-domain support
|
||||||
|
|||||||
2
e2e/send
2
e2e/send
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
ssmtp -v test@localhost < $1
|
ssmtp -v test+sub@localhost < $1
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Content-Type: multipart/alternative; boundary="Apple-Mail=_E091454E-BCFA-43B4-99
|
|||||||
Subject: MIME test 1
|
Subject: MIME test 1
|
||||||
Date: Sat, 13 Oct 2012 15:33:07 -0700
|
Date: Sat, 13 Oct 2012 15:33:07 -0700
|
||||||
Message-Id: <4E2E5A48-1A2C-4450-8663-D41B451DA93A@makita.skynet>
|
Message-Id: <4E2E5A48-1A2C-4450-8663-D41B451DA93A@makita.skynet>
|
||||||
To: test@localhost
|
To: test+sub@localhost
|
||||||
Mime-Version: 1.0 (Apple Message framework v1283)
|
Mime-Version: 1.0 (Apple Message framework v1283)
|
||||||
X-Mailer: Apple Mail (2.1283)
|
X-Mailer: Apple Mail (2.1283)
|
||||||
|
|
||||||
|
|||||||
@@ -107,15 +107,21 @@ func (e *Email) Mailbox(incoming bool) string {
|
|||||||
return utils.Mailbox(e.From)
|
return utils.Mailbox(e.From)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content converts the email object to a Matrix event content
|
func (e *Email) contentHeader(threadID id.EventID, text *strings.Builder, options *ContentOptions) {
|
||||||
func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Content {
|
|
||||||
var text strings.Builder
|
|
||||||
if options.Sender {
|
if options.Sender {
|
||||||
text.WriteString(e.From)
|
text.WriteString(e.From)
|
||||||
}
|
}
|
||||||
if options.Recipient {
|
if options.Recipient {
|
||||||
|
mailbox, sub, host := utils.EmailParts(e.To)
|
||||||
text.WriteString(" ➡️ ")
|
text.WriteString(" ➡️ ")
|
||||||
text.WriteString(e.To)
|
text.WriteString(mailbox)
|
||||||
|
text.WriteString("@")
|
||||||
|
text.WriteString(host)
|
||||||
|
if sub != "" {
|
||||||
|
text.WriteString(" (")
|
||||||
|
text.WriteString(sub)
|
||||||
|
text.WriteString(")")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if options.CC && len(e.CC) > 0 {
|
if options.CC && len(e.CC) > 0 {
|
||||||
text.WriteString("\ncc: ")
|
text.WriteString("\ncc: ")
|
||||||
@@ -129,6 +135,14 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
|
|||||||
text.WriteString(e.Subject)
|
text.WriteString(e.Subject)
|
||||||
text.WriteString("\n\n")
|
text.WriteString("\n\n")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content converts the email object to a Matrix event content
|
||||||
|
func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Content {
|
||||||
|
var text strings.Builder
|
||||||
|
|
||||||
|
e.contentHeader(threadID, &text, options)
|
||||||
|
|
||||||
if e.HTML != "" && options.HTML {
|
if e.HTML != "" && options.HTML {
|
||||||
text.WriteString(format.HTMLToMarkdown(e.HTML))
|
text.WriteString(format.HTMLToMarkdown(e.HTML))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -15,6 +15,7 @@ require (
|
|||||||
github.com/jhillyerd/enmime v0.10.0
|
github.com/jhillyerd/enmime v0.10.0
|
||||||
github.com/lib/pq v1.10.9
|
github.com/lib/pq v1.10.9
|
||||||
github.com/mattn/go-sqlite3 v1.14.17
|
github.com/mattn/go-sqlite3 v1.14.17
|
||||||
|
github.com/mcnijman/go-emailaddress v1.1.0
|
||||||
github.com/mileusna/crontab v1.2.0
|
github.com/mileusna/crontab v1.2.0
|
||||||
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
github.com/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
||||||
github.com/rs/zerolog v1.30.0
|
github.com/rs/zerolog v1.30.0
|
||||||
|
|||||||
3
go.sum
3
go.sum
@@ -58,6 +58,8 @@ github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxm
|
|||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mcnijman/go-emailaddress v1.1.0 h1:7/Uxgn9pXwXmvXsFSgORo6XoRTrttj7AGmmB2yFArAg=
|
||||||
|
github.com/mcnijman/go-emailaddress v1.1.0/go.mod h1:m+aauxGmv31sB5zZ1I8ICcMoa9ZHOA9RiurCijfvkhI=
|
||||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a h1:eU8j/ClY2Ty3qdHnn0TyW3ivFoPC/0F1gQZz8yTxbbE=
|
||||||
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||||
github.com/mileusna/crontab v1.2.0 h1:x9ZmE2A4p6CDqMEGQ+GbqsNtnmbdmWMQYShdQu8LvrU=
|
github.com/mileusna/crontab v1.2.0 h1:x9ZmE2A4p6CDqMEGQ+GbqsNtnmbdmWMQYShdQu8LvrU=
|
||||||
@@ -118,6 +120,7 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
|
|||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
golang.org/x/net v0.0.0-20210501142056-aec3718b3fa0/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
|||||||
@@ -1,14 +1,56 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mcnijman/go-emailaddress"
|
||||||
|
)
|
||||||
|
|
||||||
// Mailbox returns mailbox part from email address
|
// Mailbox returns mailbox part from email address
|
||||||
func Mailbox(email string) string {
|
func Mailbox(email string) string {
|
||||||
index := strings.LastIndex(email, "@")
|
mailbox, _, _ := EmailParts(email)
|
||||||
if index == -1 {
|
return mailbox
|
||||||
return email
|
}
|
||||||
|
|
||||||
|
// Subaddress returns sub address part form email address
|
||||||
|
func Subaddress(email string) string {
|
||||||
|
_, sub, _ := EmailParts(email)
|
||||||
|
return sub
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hostname returns hostname part from email address
|
||||||
|
func Hostname(email string) string {
|
||||||
|
_, _, hostname := EmailParts(email)
|
||||||
|
return hostname
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmailParts parses email address into mailbox, subaddress, and hostname
|
||||||
|
func EmailParts(email string) (string, string, string) {
|
||||||
|
var mailbox, hostname string
|
||||||
|
address, err := emailaddress.Parse(email)
|
||||||
|
if err == nil {
|
||||||
|
mailbox = address.LocalPart
|
||||||
|
hostname = address.Domain
|
||||||
|
} else {
|
||||||
|
mailbox = email
|
||||||
|
hostname = email
|
||||||
|
mIdx := strings.Index(email, "@")
|
||||||
|
hIdx := strings.LastIndex(email, "@")
|
||||||
|
if mIdx != -1 {
|
||||||
|
mailbox = email[:mIdx]
|
||||||
}
|
}
|
||||||
return email[:index]
|
if hIdx != -1 {
|
||||||
|
hostname = email[hIdx+1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sub string
|
||||||
|
idx := strings.Index(mailbox, "+")
|
||||||
|
if idx != -1 {
|
||||||
|
sub = strings.ReplaceAll(mailbox[idx:], "+", "")
|
||||||
|
mailbox = strings.ReplaceAll(mailbox[:idx], "+", "")
|
||||||
|
}
|
||||||
|
return mailbox, sub, hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmailsList returns human-readable list of mailbox's emails for all available domains
|
// EmailsList returns human-readable list of mailbox's emails for all available domains
|
||||||
@@ -34,8 +76,3 @@ func EmailsList(mailbox string, domain string) string {
|
|||||||
|
|
||||||
return msg.String()
|
return msg.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hostname returns hostname part from email address
|
|
||||||
func Hostname(email string) string {
|
|
||||||
return email[strings.LastIndex(email, "@")+1:]
|
|
||||||
}
|
|
||||||
|
|||||||
70
utils/mail_test.go
Normal file
70
utils/mail_test.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMailbox(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"mailbox@example.com": "mailbox",
|
||||||
|
"mail-box@example.com": "mail-box",
|
||||||
|
"mailbox": "mailbox",
|
||||||
|
"mail@box@example.com": "mail",
|
||||||
|
"mailbox+@example.com": "mailbox",
|
||||||
|
"mailbox+sub@example.com": "mailbox",
|
||||||
|
"mailbox+++sub@example.com": "mailbox",
|
||||||
|
}
|
||||||
|
|
||||||
|
for in, expected := range tests {
|
||||||
|
t.Run(in, func(t *testing.T) {
|
||||||
|
output := Mailbox(in)
|
||||||
|
if output != expected {
|
||||||
|
t.Error(expected, "!=", output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubaddress(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"mailbox@example@example.com": "",
|
||||||
|
"mail-box@example.com": "",
|
||||||
|
"mailbox+": "",
|
||||||
|
"mailbox+sub@example.com": "sub",
|
||||||
|
"mailbox+++sub@example.com": "sub",
|
||||||
|
}
|
||||||
|
|
||||||
|
for in, expected := range tests {
|
||||||
|
t.Run(in, func(t *testing.T) {
|
||||||
|
output := Subaddress(in)
|
||||||
|
if output != expected {
|
||||||
|
t.Error(expected, "!=", output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHostname(t *testing.T) {
|
||||||
|
tests := map[string]string{
|
||||||
|
"mailbox@example.com": "example.com",
|
||||||
|
"mailbox": "mailbox",
|
||||||
|
"mail@box@example.com": "example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
for in, expected := range tests {
|
||||||
|
t.Run(in, func(t *testing.T) {
|
||||||
|
output := Hostname(in)
|
||||||
|
if output != expected {
|
||||||
|
t.Error(expected, "!=", output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmailList(t *testing.T) {
|
||||||
|
domains = []string{"example.com", "example.org"}
|
||||||
|
expected := "test@example.org, test@example.com"
|
||||||
|
|
||||||
|
actual := EmailsList("test", "example.org")
|
||||||
|
if actual != expected {
|
||||||
|
t.Error(expected, "!=", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
2
vendor/github.com/mcnijman/go-emailaddress/.gitignore
generated
vendored
Normal file
2
vendor/github.com/mcnijman/go-emailaddress/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
emailaddress
|
||||||
|
coverage.*
|
||||||
25
vendor/github.com/mcnijman/go-emailaddress/.travis.yml
generated
vendored
Normal file
25
vendor/github.com/mcnijman/go-emailaddress/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- "1.11.x"
|
||||||
|
- "1.10.x"
|
||||||
|
- "1.9.x"
|
||||||
|
- tip
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- GO111MODULE=on
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
fast_finish: true
|
||||||
|
install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- GO111MODULE=off go get -u github.com/securego/gosec/cmd/gosec/...
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||||
|
- go tool vet .
|
||||||
|
- $GOPATH/bin/gosec ./...
|
||||||
|
- go test -race -covermode=atomic -coverprofile=coverage.txt ./...
|
||||||
|
- $GOPATH/bin/goveralls -coverprofile=coverage.txt -service=travis-ci
|
||||||
21
vendor/github.com/mcnijman/go-emailaddress/LICENCE
generated
vendored
Normal file
21
vendor/github.com/mcnijman/go-emailaddress/LICENCE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 Thijs Nijman
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
102
vendor/github.com/mcnijman/go-emailaddress/README.md
generated
vendored
Normal file
102
vendor/github.com/mcnijman/go-emailaddress/README.md
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# go-emailaddress #
|
||||||
|
|
||||||
|
[](https://godoc.org/github.com/mcnijman/go-emailaddress) [](https://travis-ci.org/mcnijman/go-emailaddress) [](https://coveralls.io/github/mcnijman/go-emailaddress?branch=master) [](https://goreportcard.com/report/github.com/mcnijman/go-emailaddress)
|
||||||
|
|
||||||
|
go-emailaddress is a tiny Go library for finding, parsing and validating email addresses. This
|
||||||
|
library is tested for Go v1.9 and above.
|
||||||
|
|
||||||
|
Note that there is no such thing as perfect email address validation other than sending an actual
|
||||||
|
email (ie. with a confirmation token). This library however checks if the email format conforms to
|
||||||
|
the spec and if the host (domain) is actually able to receive emails. You can also use this library
|
||||||
|
to find emails in a byte array. This package was created as similar packages don't seem to be
|
||||||
|
maintained anymore (ie contain bugs with pull requests still open), and/or use wrong local
|
||||||
|
validation.
|
||||||
|
|
||||||
|
## Usage ##
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get -u github.com/mcnijman/go-emailaddress
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parsing and local validation ###
|
||||||
|
|
||||||
|
Parse and validate the email locally using RFC 5322 regex, note that when `err == nil` it doesn't
|
||||||
|
necessarily mean the email address actually exists.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
email, err := emailaddress.Parse("foo@bar.com")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid email")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(email.LocalPart) // foo
|
||||||
|
fmt.Println(email.Domain) // bar.com
|
||||||
|
fmt.Println(email) // foo@bar.com
|
||||||
|
fmt.Println(email.String()) // foo@bar.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validating the host ###
|
||||||
|
|
||||||
|
Host validation will first attempt to resolve the domain and then verify if we can start a mail
|
||||||
|
transaction with the host. This is relatively slow as it will contact the host several times.
|
||||||
|
Note that when `err == nil` it doesn't necessarily mean the email address actually exists.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
email, err := emailaddress.Parse("foo@bar.com")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid email")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := email.ValidateHost()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid host")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Finding emails ###
|
||||||
|
|
||||||
|
This will look for emails in a byte array (ie text or an html response).
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
text := []byte(`Send me an email at foo@bar.com.`)
|
||||||
|
validateHost := false
|
||||||
|
|
||||||
|
emails := emailaddress.Find(text, validateHost)
|
||||||
|
|
||||||
|
for _, e := range emails {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
// foo@bar.com
|
||||||
|
```
|
||||||
|
|
||||||
|
As RFC 5322 is really broad this method will likely match images and urls that contain
|
||||||
|
the '@' character (ie. !--logo@2x.png). For more reliable results, you can use the following method.
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
text := []byte(`Send me an email at foo@bar.com or fake@domain.foobar.`)
|
||||||
|
validateHost := false
|
||||||
|
|
||||||
|
emails := emailaddress.FindWithIcannSuffix(text, validateHost)
|
||||||
|
|
||||||
|
for _, e := range emails {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
// foo@bar.com
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versioning ##
|
||||||
|
|
||||||
|
This library uses [semantic versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## License ##
|
||||||
|
|
||||||
|
This library is distributed under the MIT license found in the [LICENSE](./LICENSE)
|
||||||
|
file.
|
||||||
224
vendor/github.com/mcnijman/go-emailaddress/emailaddress.go
generated
vendored
Normal file
224
vendor/github.com/mcnijman/go-emailaddress/emailaddress.go
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
// Copyright 2018 The go-emailaddress AUTHORS. All rights reserved.
|
||||||
|
//
|
||||||
|
// Use of this source code is governed by a MIT
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package emailaddress provides a tiny library for finding, parsing and validation of email
|
||||||
|
addresses. This library is tested for Go v1.9 and above.
|
||||||
|
|
||||||
|
go get -u github.com/mcnijman/go-emailaddress
|
||||||
|
|
||||||
|
Local validation
|
||||||
|
|
||||||
|
Parse and validate the email locally using RFC 5322 regex, note that when err == nil it doesn't
|
||||||
|
necessarily mean the email address actually exists.
|
||||||
|
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
email, err := emailaddress.Parse("foo@bar.com")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid email")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(email.LocalPart) // foo
|
||||||
|
fmt.Println(email.Domain) // bar.com
|
||||||
|
fmt.Println(email) // foo@bar.com
|
||||||
|
fmt.Println(email.String()) // foo@bar.com
|
||||||
|
|
||||||
|
Host validation
|
||||||
|
|
||||||
|
Host validation will first attempt to resolve the domain and then verify if we can start a mail
|
||||||
|
transaction with the host. This is relatively slow as it will contact the host several times.
|
||||||
|
Note that when err == nil it doesn't necessarily mean the email address actually exists.
|
||||||
|
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
email, err := emailaddress.Parse("foo@bar.com")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid email")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := email.ValidateHost()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("invalid host")
|
||||||
|
}
|
||||||
|
|
||||||
|
Finding emails
|
||||||
|
|
||||||
|
This will look for emails in a byte array (ie text or an html response).
|
||||||
|
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
text := []byte(`Send me an email at foo@bar.com.`)
|
||||||
|
validateHost := false
|
||||||
|
|
||||||
|
emails := emailaddress.Find(text, validateHost)
|
||||||
|
|
||||||
|
for _, e := range emails {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
// foo@bar.com
|
||||||
|
|
||||||
|
As RFC 5322 is really broad this method will likely match images and urls that contain
|
||||||
|
the '@' character (ie. !--logo@2x.png). For more reliable results, you can use the following method.
|
||||||
|
|
||||||
|
import "github.com/mcnijman/go-emailaddress"
|
||||||
|
|
||||||
|
text := []byte(`Send me an email at foo@bar.com or fake@domain.foobar.`)
|
||||||
|
validateHost := false
|
||||||
|
|
||||||
|
emails := emailaddress.FindWithIcannSuffix(text, validateHost)
|
||||||
|
|
||||||
|
for _, e := range emails {
|
||||||
|
fmt.Println(e)
|
||||||
|
}
|
||||||
|
// foo@bar.com
|
||||||
|
|
||||||
|
*/
|
||||||
|
package emailaddress
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/smtp"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// rfc5322 is a RFC 5322 regex, as per: https://stackoverflow.com/a/201378/5405453.
|
||||||
|
// Note that this can't verify that the address is an actual working email address.
|
||||||
|
// Use ValidateHost as a starter and/or send them one :-).
|
||||||
|
rfc5322 = "(?i)(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
|
||||||
|
validEmailRegexp = regexp.MustCompile(fmt.Sprintf("^%s*$", rfc5322))
|
||||||
|
findEmailRegexp = regexp.MustCompile(rfc5322)
|
||||||
|
)
|
||||||
|
|
||||||
|
// EmailAddress is a structure that stores the address local-part@domain parts.
|
||||||
|
type EmailAddress struct {
|
||||||
|
// LocalPart usually the username of an email address.
|
||||||
|
LocalPart string
|
||||||
|
|
||||||
|
// Domain is the part of the email address after the last @.
|
||||||
|
// This should be DNS resolvable to an email server.
|
||||||
|
Domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e EmailAddress) String() string {
|
||||||
|
if e.LocalPart == "" || e.Domain == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s@%s", e.LocalPart, e.Domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateHost will test if the email address is actually reachable. It will first try to resolve
|
||||||
|
// the host and then start a mail transaction.
|
||||||
|
func (e EmailAddress) ValidateHost() error {
|
||||||
|
host, err := lookupHost(e.Domain)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tryHost(host, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateIcanSuffix will test if the public suffix of the domain is managed by ICANN using
|
||||||
|
// the golang.org/x/net/publicsuffix package. If not it will return an error. Note that if this
|
||||||
|
// method returns an error it does not necessarily mean that the email address is invalid. Also the
|
||||||
|
// suffix list in the standard package is embedded and thereby not up to date.
|
||||||
|
func (e EmailAddress) ValidateIcanSuffix() error {
|
||||||
|
d := strings.ToLower(e.Domain)
|
||||||
|
if s, icann := publicsuffix.PublicSuffix(d); !icann {
|
||||||
|
return fmt.Errorf("public suffix is not managed by ICANN, got %s", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find uses the RFC 5322 regex to match, parse and validate any email addresses found in a string.
|
||||||
|
// If the validateHost boolean is true it will call the validate host for every email address
|
||||||
|
// encounterd. As RFC 5322 is really broad this method will likely match images and urls that
|
||||||
|
// contain the '@' character.
|
||||||
|
func Find(haystack []byte, validateHost bool) (emails []*EmailAddress) {
|
||||||
|
results := findEmailRegexp.FindAll(haystack, -1)
|
||||||
|
for _, r := range results {
|
||||||
|
if e, err := Parse(string(r)); err == nil {
|
||||||
|
if validateHost {
|
||||||
|
if err := e.ValidateHost(); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emails = append(emails, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emails
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindWithIcannSuffix uses the RFC 5322 regex to match, parse and validate any email addresses
|
||||||
|
// found in a string. It will return emails if its eTLD is managed by the ICANN organization.
|
||||||
|
// If the validateHost boolean is true it will call the validate host for every email address
|
||||||
|
// encounterd. As RFC 5322 is really broad this method will likely match images and urls that
|
||||||
|
// contain the '@' character.
|
||||||
|
func FindWithIcannSuffix(haystack []byte, validateHost bool) (emails []*EmailAddress) {
|
||||||
|
results := Find(haystack, false)
|
||||||
|
for _, e := range results {
|
||||||
|
if err := e.ValidateIcanSuffix(); err == nil {
|
||||||
|
if validateHost {
|
||||||
|
if err := e.ValidateHost(); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emails = append(emails, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return emails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse will parse the input and validate the email locally. If you want to validate the host of
|
||||||
|
// this email address remotely call the ValidateHost method.
|
||||||
|
func Parse(email string) (*EmailAddress, error) {
|
||||||
|
if !validEmailRegexp.MatchString(email) {
|
||||||
|
return nil, fmt.Errorf("format is incorrect for %s", email)
|
||||||
|
}
|
||||||
|
|
||||||
|
i := strings.LastIndexByte(email, '@')
|
||||||
|
e := &EmailAddress{
|
||||||
|
LocalPart: email[:i],
|
||||||
|
Domain: email[i+1:],
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupHost first checks if any MX records are available and if not, it will check
|
||||||
|
// if A records are available as they can resolve email server hosts. An error indicates
|
||||||
|
// that non of the A or MX records are available.
|
||||||
|
func lookupHost(domain string) (string, error) {
|
||||||
|
if mx, err := net.LookupMX(domain); err == nil {
|
||||||
|
return mx[0].Host, nil
|
||||||
|
}
|
||||||
|
if ips, err := net.LookupIP(domain); err == nil {
|
||||||
|
return ips[0].String(), nil // randomly returns IPv4 or IPv6 (when available)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("failed finding MX and A records for domain %s", domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryHost will verify if we can start a mail transaction with the host.
|
||||||
|
func tryHost(host string, e EmailAddress) error {
|
||||||
|
client, err := smtp.Dial(fmt.Sprintf("%s:%d", host, 25))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
if err = client.Hello(e.Domain); err == nil {
|
||||||
|
if err = client.Mail(fmt.Sprintf("hello@%s", e.Domain)); err == nil {
|
||||||
|
if err = client.Rcpt(e.String()); err == nil {
|
||||||
|
client.Reset() // #nosec
|
||||||
|
client.Quit() // #nosec
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -71,6 +71,9 @@ github.com/mattn/go-runewidth
|
|||||||
# github.com/mattn/go-sqlite3 v1.14.17
|
# github.com/mattn/go-sqlite3 v1.14.17
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/mattn/go-sqlite3
|
github.com/mattn/go-sqlite3
|
||||||
|
# github.com/mcnijman/go-emailaddress v1.1.0
|
||||||
|
## explicit
|
||||||
|
github.com/mcnijman/go-emailaddress
|
||||||
# github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
|
# github.com/mikesmitty/edkey v0.0.0-20170222072505-3356ea4e686a
|
||||||
## explicit
|
## explicit
|
||||||
github.com/mikesmitty/edkey
|
github.com/mikesmitty/edkey
|
||||||
|
|||||||
Reference in New Issue
Block a user