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] Receive emails to matrix rooms
|
||||
- [x] Receive attachments
|
||||
- [x] Subaddressing support
|
||||
- [x] Catch-all mailbox
|
||||
- [x] Map email threads to matrix threads
|
||||
- [x] Multi-domain support
|
||||
|
||||
2
e2e/send
2
e2e/send
@@ -1,3 +1,3 @@
|
||||
#!/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
|
||||
Date: Sat, 13 Oct 2012 15:33:07 -0700
|
||||
Message-Id: <4E2E5A48-1A2C-4450-8663-D41B451DA93A@makita.skynet>
|
||||
To: test@localhost
|
||||
To: test+sub@localhost
|
||||
Mime-Version: 1.0 (Apple Message framework v1283)
|
||||
X-Mailer: Apple Mail (2.1283)
|
||||
|
||||
|
||||
@@ -107,15 +107,21 @@ func (e *Email) Mailbox(incoming bool) string {
|
||||
return utils.Mailbox(e.From)
|
||||
}
|
||||
|
||||
// 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
|
||||
func (e *Email) contentHeader(threadID id.EventID, text *strings.Builder, options *ContentOptions) {
|
||||
if options.Sender {
|
||||
text.WriteString(e.From)
|
||||
}
|
||||
if options.Recipient {
|
||||
mailbox, sub, host := utils.EmailParts(e.To)
|
||||
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 {
|
||||
text.WriteString("\ncc: ")
|
||||
@@ -129,6 +135,14 @@ func (e *Email) Content(threadID id.EventID, options *ContentOptions) *event.Con
|
||||
text.WriteString(e.Subject)
|
||||
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 {
|
||||
text.WriteString(format.HTMLToMarkdown(e.HTML))
|
||||
} else {
|
||||
|
||||
1
go.mod
1
go.mod
@@ -15,6 +15,7 @@ require (
|
||||
github.com/jhillyerd/enmime v0.10.0
|
||||
github.com/lib/pq v1.10.9
|
||||
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/raja/argon2pw v1.0.2-0.20210910183755-a391af63bd39
|
||||
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-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
|
||||
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/go.mod h1:v8eSC2SMp9/7FTKUncp7fH9IwPfw+ysMObcEz5FWheQ=
|
||||
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/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/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-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
|
||||
@@ -1,14 +1,56 @@
|
||||
package utils
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/mcnijman/go-emailaddress"
|
||||
)
|
||||
|
||||
// Mailbox returns mailbox part from email address
|
||||
func Mailbox(email string) string {
|
||||
index := strings.LastIndex(email, "@")
|
||||
if index == -1 {
|
||||
return email
|
||||
mailbox, _, _ := EmailParts(email)
|
||||
return mailbox
|
||||
}
|
||||
|
||||
// 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]
|
||||
}
|
||||
if hIdx != -1 {
|
||||
hostname = email[hIdx+1:]
|
||||
}
|
||||
}
|
||||
return email[:index]
|
||||
|
||||
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
|
||||
@@ -34,8 +76,3 @@ func EmailsList(mailbox string, domain string) 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
|
||||
## explicit; go 1.16
|
||||
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
|
||||
## explicit
|
||||
github.com/mikesmitty/edkey
|
||||
|
||||
Reference in New Issue
Block a user