Skip to content

Commit 3c0b4b2

Browse files
committed
Add checking of CNAME records.
LookupMX will perform this if the CNAME result has an MX record as well, but the spec states we should "start again" so we will do just that. This allows fallback to A/AAAA records in a CNAME -> A configuration. Loops are handled by the LookupCNAME method.
1 parent 8349811 commit 3c0b4b2

File tree

4 files changed

+87
-31
lines changed

4 files changed

+87
-31
lines changed

htmltest/check-link.go

Lines changed: 53 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import (
55
"github.com/wjdp/htmltest/issues"
66
"github.com/wjdp/htmltest/output"
77
"golang.org/x/net/html"
8+
"net"
89
"net/http"
910
"net/url"
1011
"os"
1112
"path"
1213
"strings"
13-
"net"
1414
"fmt"
1515
)
1616

@@ -351,45 +351,68 @@ func (hT *HTMLTest) checkMailto(ref *htmldoc.Reference) {
351351
return
352352
}
353353

354-
// split off domain, check mx, fallback to A or AAAA if that fais
355-
domain := strings.Split(ref.URL.Opaque, "@")[1]
356-
_, err := net.LookupMX(domain)
354+
// split off domain, check mx, fallback to A or AAAA if that fails
357355
var dnserr *net.DNSError
358356
var ok bool
359-
if err != nil {
360-
if dnserr, ok = err.(*net.DNSError); ok {
361-
switch dnserr.Err {
362-
case "no such host":
363-
// current no MX records, but we should try again with A record
364-
_, err := net.LookupHost(domain)
365-
if dnserr, ok = err.(*net.DNSError); ok {
366-
break
367-
} else {
368-
hT.issueStore.AddIssue(issues.Issue{
369-
Level: issues.LevelWarning,
370-
Message: "unable to perform LookupHost, unknown error",
371-
Reference: ref,
372-
})
373-
return
374-
}
375-
}
376-
} else {
357+
358+
domain := strings.Split(ref.URL.Opaque, "@")[1]
359+
360+
for domain != "" {
361+
// if a simple MX lookup works, we are done, continue
362+
if _, err := net.LookupMX(domain); err == nil {
363+
break // success, time to exit
364+
} else if dnserr, ok = err.(*net.DNSError); !ok || dnserr.Err != "no such host" {
365+
// this isn't an error we are expecting to see here
377366
hT.issueStore.AddIssue(issues.Issue{
378367
Level: issues.LevelWarning,
379368
Message: "unable to perform LookupMX, unknown error",
380369
Reference: ref,
381370
})
382371
return
383372
}
384-
}
385373

386-
// check if we finally have a dnserr
387-
if dnserr != nil {
388-
hT.issueStore.AddIssue(issues.Issue{
389-
Level: issues.LevelError,
390-
Message: "email cannot be routed to domain, no MX/A/AAAA records",
391-
Reference: ref,
392-
})
374+
// do we have to restart because of a CNAME
375+
if cname, err := net.LookupCNAME(domain); err == nil && cname != domain {
376+
// we have a valid CNAME, try with that. Loops return NXDOMAIN by default
377+
domain = cname
378+
continue
379+
380+
} else if dnserr, ok = err.(*net.DNSError); !ok || dnserr.Err != "no such host" {
381+
// this isn't an error we are expecting to see here
382+
hT.issueStore.AddIssue(issues.Issue{
383+
Level: issues.LevelWarning,
384+
Message: "unable to perform LookupCNAME, unknown error",
385+
Reference: ref,
386+
})
387+
return
388+
}
389+
390+
// an A or AAAA record here would be valid
391+
if _, err := net.LookupHost(domain); err == nil {
392+
break // its not ideal, but a valid A/AAAA record is acceptable for email
393+
} else {
394+
dnserr, ok = err.(*net.DNSError)
395+
if !ok || dnserr.Err != "no such host" {
396+
// we shouldn't see this here
397+
hT.issueStore.AddIssue(issues.Issue{
398+
Level: issues.LevelWarning,
399+
Message: "unable to perform LookupHost, unknown error",
400+
Reference: ref,
401+
})
402+
return
403+
}
404+
405+
if dnserr.Err == "no such host" {
406+
// represents NXDOMAIN or no records
407+
hT.issueStore.AddIssue(issues.Issue{
408+
Level: issues.LevelError,
409+
Message: "email domain could not be resolved correctly",
410+
Reference: ref,
411+
})
412+
return
413+
}
414+
}
415+
393416
}
394417
}
395418

htmltest/check-link_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,11 +390,24 @@ func TestMailtoInvalidFormat(t *testing.T) {
390390
tExpectIssue(t, hT, "contains an invalid email address", 1)
391391
}
392392

393+
func TestMailtoInvalidCname(t *testing.T) {
394+
// fails for invalid mailto links
395+
hT := tTestFile("fixtures/links/invalid_mailto_cname.html")
396+
tExpectIssueCount(t, hT, 0)
397+
}
398+
399+
func TestMailtoInvalidCnameLoop(t *testing.T) {
400+
// fails for invalid mailto links
401+
hT := tTestFile("fixtures/links/invalid_mailto_cname_loop.html")
402+
tExpectIssueCount(t, hT, 1)
403+
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1)
404+
}
405+
393406
func TestMailtoInvalidNoRecords(t *testing.T) {
394407
// fails for invalid mailto links
395408
hT := tTestFile("fixtures/links/invalid_mailto_norecords.html")
396409
tExpectIssueCount(t, hT, 1)
397-
tExpectIssue(t, hT, "email cannot be routed to domain, no MX/A/AAAA records", 1)
410+
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1)
398411
}
399412

400413
func TestMailtoInvalidAFallback(t *testing.T) {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
3+
<body>
4+
5+
<!-- www.golang.org CNAME golang.org - seems appropriate -->
6+
<a href="mailto:helloworld@cname_to_a_with_no_mx.dtaylor.uk">Meow me</a>
7+
8+
</body>
9+
10+
</html>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
3+
<body>
4+
5+
<!-- www.golang.org CNAME golang.org - seems appropriate -->
6+
<a href="mailto:[email protected]">Meow me</a>
7+
8+
</body>
9+
10+
</html>

0 commit comments

Comments
 (0)