-
Notifications
You must be signed in to change notification settings - Fork 54
Add basic checking of mx records for mailto links #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
c545c7c
8349811
3c0b4b2
75fff69
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ import ( | |
"github.com/wjdp/htmltest/issues" | ||
"github.com/wjdp/htmltest/output" | ||
"golang.org/x/net/html" | ||
"net" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
|
@@ -349,6 +350,71 @@ func (hT *HTMLTest) checkMailto(ref *htmldoc.Reference) { | |
}) | ||
return | ||
} | ||
|
||
// split off domain, check mx, fallback to A or AAAA if that fails | ||
var dnserr *net.DNSError | ||
var ok bool | ||
|
||
domain := strings.Split(ref.URL.Opaque, "@")[1] | ||
|
||
// loop over the current domain until we have a valid result or have exhausted all possibilities | ||
for domain != "" { | ||
// if a simple MX lookup works, we are done, continue | ||
if _, err := net.LookupMX(domain); err == nil { | ||
break // success, time to exit | ||
} else if dnserr, ok = err.(*net.DNSError); !ok || dnserr.Err != "no such host" { | ||
// this isn't an error we are expecting to see here | ||
hT.issueStore.AddIssue(issues.Issue{ | ||
Level: issues.LevelWarning, | ||
Message: "unable to perform LookupMX, unknown error", | ||
Reference: ref, | ||
}) | ||
return | ||
} | ||
|
||
// do we have to restart because of a CNAME | ||
if cname, err := net.LookupCNAME(domain); err == nil && cname != domain { | ||
// we have a valid CNAME, try with that. Loops return NXDOMAIN by default | ||
domain = cname | ||
continue | ||
|
||
} else if dnserr, ok = err.(*net.DNSError); !ok || dnserr.Err != "no such host" { | ||
// this isn't an error we are expecting to see here | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The exact error on Linux for the loop CNAME test is
Likely an error discrepancy between Linux and OSX. You may need to restructure a bit to handle this cleanly. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is infact an issue, no such host is an isolated case where we want to proceed to checking A records. Should a CNAME loop not produce an warning of some variety? |
||
hT.issueStore.AddIssue(issues.Issue{ | ||
Level: issues.LevelWarning, | ||
Message: "unable to perform LookupCNAME, unknown error", | ||
Reference: ref, | ||
}) | ||
return | ||
} | ||
|
||
// an A or AAAA record here would be valid | ||
if _, err := net.LookupHost(domain); err == nil { | ||
break // its not ideal, but a valid A/AAAA record is acceptable for email | ||
} else { | ||
dnserr, ok = err.(*net.DNSError) | ||
if !ok || dnserr.Err != "no such host" { | ||
// we shouldn't see this here | ||
hT.issueStore.AddIssue(issues.Issue{ | ||
Level: issues.LevelWarning, | ||
Message: "unable to perform LookupHost, unknown error", | ||
Reference: ref, | ||
}) | ||
return | ||
} | ||
|
||
if dnserr.Err == "no such host" { | ||
// represents NXDOMAIN or no records | ||
hT.issueStore.AddIssue(issues.Issue{ | ||
Level: issues.LevelError, | ||
Message: "email domain could not be resolved correctly", | ||
Reference: ref, | ||
}) | ||
return | ||
} | ||
} | ||
|
||
} | ||
} | ||
|
||
func (hT *HTMLTest) checkTel(ref *htmldoc.Reference) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -383,13 +383,44 @@ func TestMailtoBlank(t *testing.T) { | |
tExpectIssue(t, hT, "mailto is empty", 1) | ||
} | ||
|
||
func TestMailtoInvalid(t *testing.T) { | ||
func TestMailtoInvalidFormat(t *testing.T) { | ||
// fails for invalid mailto links | ||
hT := tTestFile("fixtures/links/invalid_mailto_link.html") | ||
tExpectIssueCount(t, hT, 1) | ||
tExpectIssue(t, hT, "contains an invalid email address", 1) | ||
} | ||
|
||
func TestMailtoInvalidCname(t *testing.T) { | ||
// rubdomain with just a CNAME record without MX pointers | ||
// email is still deliverable so should be accepted | ||
// the records have been carefully created to ensure this is valid and will later fall under one of the other | ||
// pass conditions | ||
hT := tTestFile("fixtures/links/invalid_mailto_cname.html") | ||
tExpectIssueCount(t, hT, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guessing this isn't done yet. In general write the tests with expected result and add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The test was complete, it was checking that just a CNAME pointing to another domain that was valid would work as expected, comments should hopefully clear that up. |
||
} | ||
|
||
func TestMailtoInvalidCnameLoop(t *testing.T) { | ||
// subdomain with just a CNAME record without MX records in a loop | ||
// should be detected and fail as we can never resolve this | ||
hT := tTestFile("fixtures/links/invalid_mailto_cname_loop.html") | ||
tExpectIssueCount(t, hT, 1) | ||
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1) | ||
} | ||
|
||
func TestMailtoInvalidNoRecords(t *testing.T) { | ||
// domain with no records associated at all, must fail as completely unroutable | ||
hT := tTestFile("fixtures/links/invalid_mailto_norecords.html") | ||
tExpectIssueCount(t, hT, 1) | ||
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1) | ||
} | ||
|
||
func TestMailtoInvalidAFallback(t *testing.T) { | ||
// domain without MX records but with a valid A record | ||
// email can be routed to the A record so should be accepted | ||
hT := tTestFile("fixtures/links/invalid_mailto_fallback.html") | ||
tExpectIssueCount(t, hT, 0) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above |
||
} | ||
|
||
func TestMailtoIgnore(t *testing.T) { | ||
// ignores mailto links when told to | ||
hT := tTestFileOpts("fixtures/links/blank_mailto_link.html", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<html> | ||
|
||
<body> | ||
|
||
<!-- www.golang.org CNAME golang.org - seems appropriate --> | ||
<a href="mailto:helloworld@cname_to_a_with_no_mx.dtaylor.uk">Meow me</a> | ||
|
||
</body> | ||
|
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<html> | ||
|
||
<body> | ||
|
||
<!-- www.golang.org CNAME golang.org - seems appropriate --> | ||
<a href="mailto:[email protected]">Meow me</a> | ||
|
||
</body> | ||
|
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<html> | ||
|
||
<body> | ||
|
||
<a href="mailto:[email protected]">Meow me</a> | ||
|
||
</body> | ||
|
||
</html> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<html> | ||
|
||
<body> | ||
|
||
<a href="mailto:[email protected]">Meow me</a> | ||
|
||
</body> | ||
|
||
</html> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here would be nice: we're going to loop until we find a valid record or error