Skip to content

Commit c10c559

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 7697c19 commit c10c559

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
)
1515

1616
func (hT *HTMLTest) checkLink(document *htmldoc.Document, node *html.Node) {
@@ -349,45 +349,68 @@ func (hT *HTMLTest) checkMailto(ref *htmldoc.Reference) {
349349
return
350350
}
351351

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

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

htmltest/check-link_test.go

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

379+
func TestMailtoInvalidCname(t *testing.T) {
380+
// fails for invalid mailto links
381+
hT := tTestFile("fixtures/links/invalid_mailto_cname.html")
382+
tExpectIssueCount(t, hT, 0)
383+
}
384+
385+
func TestMailtoInvalidCnameLoop(t *testing.T) {
386+
// fails for invalid mailto links
387+
hT := tTestFile("fixtures/links/invalid_mailto_cname_loop.html")
388+
tExpectIssueCount(t, hT, 1)
389+
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1)
390+
}
391+
379392
func TestMailtoInvalidNoRecords(t *testing.T) {
380393
// fails for invalid mailto links
381394
hT := tTestFile("fixtures/links/invalid_mailto_norecords.html")
382395
tExpectIssueCount(t, hT, 1)
383-
tExpectIssue(t, hT, "email cannot be routed to domain, no MX/A/AAAA records", 1)
396+
tExpectIssue(t, hT, "email domain could not be resolved correctly", 1)
384397
}
385398

386399
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)