From 35fd8af3b0d2f3fad573dbb34e8dea5542de53ca Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 13 Dec 2019 23:21:37 +0000 Subject: [PATCH 1/4] REMOVED TODO comment on email validator --- rule.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/rule.go b/rule.go index e7dfd7a..4f339d6 100644 --- a/rule.go +++ b/rule.go @@ -204,8 +204,6 @@ var Email CheckFunc = func(r *http.Request, param string, _ Options) error { return fmt.Errorf("%s is not a valid email address", param) } - // TODO: This is a little basic, but will probably correctly - // verify a large number of emails. Maybe improve it. if pass, _ := regexp.MatchString(`^[^@\s]+@[^@\s]+$`, value); pass { return nil } From 673e3454ae58351a70b0d8cb1db7a8f4cf190910 Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 13 Dec 2019 23:22:06 +0000 Subject: [PATCH 2/4] ADDED helper methods for email validators --- rule.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rule.go b/rule.go index 4f339d6..0de3e20 100644 --- a/rule.go +++ b/rule.go @@ -297,3 +297,15 @@ var Date CheckFunc = func(r *http.Request, param string, o Options) error { return fmt.Errorf("%s does not satisfy and date format", param) } + +func getMXRecords(ctx context.Context, domain string, timeout int) ([]*net.MX, error) { + rsv := net.Resolver{} + ctx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second) + defer cancel() + return rsv.LookupMX(ctx, domain) +} + +func getDomain(email string) string { + parts := strings.Split(email, "@") + return parts[len(parts)-1] +} From 188d0de5f6e0fc3edb147049bde10a6da0fc5e9d Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 13 Dec 2019 23:22:30 +0000 Subject: [PATCH 3/4] CHANGED MXEmail to use new helper methods --- rule.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/rule.go b/rule.go index 0de3e20..5c2b4d5 100644 --- a/rule.go +++ b/rule.go @@ -173,15 +173,10 @@ var MXEmail CheckFunc = func(r *http.Request, param string, o Options) error { timeout = 5 } - parts := strings.Split(r.Form.Get(param), "@") - domain := parts[len(parts)-1] - - rsv := net.Resolver{} - ctx, cancel := context.WithTimeout(r.Context(), time.Duration(timeout)*time.Second) - defer cancel() - records, err := rsv.LookupMX(ctx, domain) + domain := getDomain(r.Form.Get(param)) + records, err := getMXRecords(r.Context(), domain, timeout) if err != nil { - return fmt.Errorf("failed to look up MX records for %s", param) + return fmt.Errorf("the host %s is not a valid email provider", domain) } if len(records) == 0 { From 6fe405442804f83b1dfee5c441262faeb2914daa Mon Sep 17 00:00:00 2001 From: Tom Smith Date: Fri, 13 Dec 2019 23:22:49 +0000 Subject: [PATCH 4/4] ADDED TelnetEmail validator --- rule.go | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ rule_test.go | 10 +++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/rule.go b/rule.go index 5c2b4d5..8480f6e 100644 --- a/rule.go +++ b/rule.go @@ -1,6 +1,7 @@ package validate import ( + "bufio" "context" "fmt" "net" @@ -186,6 +187,54 @@ var MXEmail CheckFunc = func(r *http.Request, param string, o Options) error { return nil } +// TelnetEmail uses a telnet connection to dial into the mail provider +// for the given email address and uses this connection to verify if +// an email address is valid. +// +// TODO: Mixed results on Outlook/Hotmail. +var TelnetEmail CheckFunc = func(r *http.Request, param string, _ Options) error { + if err := Email(r, param, nil); err != nil { + return err + } + + address := r.Form.Get(param) + + domain := getDomain(address) + records, err := getMXRecords(r.Context(), domain, 5) + if err != nil || len(records) == 0 { + return fmt.Errorf("no MX records exist for %s", param) + } + + conn, err := net.Dial("tcp", records[0].Host+":25") + if err != nil { + return fmt.Errorf("unable to connect to %s to validate email", domain) + } + defer conn.Close() + + responder := bufio.NewReader(conn) + // Discards the first line of the telnet connection + // so we are ready to send some commands to it. + responder.ReadString('\n') + + commands := []string{ + "helo hi", + "mail from: ", + fmt.Sprintf("rcpt to: <%s>", address), + } + + var msg string + for _, cmd := range commands { + fmt.Fprintf(conn, cmd+"\n") + msg, _ = responder.ReadString('\n') + } + + if msg[0:3] != "250" { + return fmt.Errorf("%s is not a valid email address", address) + } + + return nil +} + // Email returns an error if the parameter value is not a valid // email address. var Email CheckFunc = func(r *http.Request, param string, _ Options) error { diff --git a/rule_test.go b/rule_test.go index ee69fc2..2022b22 100644 --- a/rule_test.go +++ b/rule_test.go @@ -72,9 +72,17 @@ func TestRules(t *testing.T) { nil, }, { + // TODO: Test is a little flaky. + TelnetEmail, + []string{"me@tomm.us", "oliver@leaversmith.com"}, + []string{"m123123e@invalid.domain", "notanemail@tomm.us"}, + nil, + }, + { + // TODO: Test is a little flaky. MXEmail, []string{"me@tomm.us", "lucyduggleby@hotmail.co.uk"}, - []string{"me@something@addasadsdn2343567hgbf.com", "juststring", "me@space@tomm.us"}, + []string{"me@invalid.domain", "juststring", "me@space@tomm.us"}, Options{"timeout": 10}, }, {