Skip to content

Commit 2c03eef

Browse files
author
Bradley Kemp
committed
Major refactor to enable usage as a library
1 parent 5ecefae commit 2c03eef

File tree

8 files changed

+316
-217
lines changed

8 files changed

+316
-217
lines changed

abwhose.go

Lines changed: 60 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ package main
22

33
import (
44
"fmt"
5-
"net"
65
"net/url"
76
"os"
8-
"os/exec"
97
"strings"
108
"text/tabwriter"
119

12-
"golang.org/x/net/publicsuffix"
10+
"github.com/bradleyjkemp/abwhose/matchers"
1311
)
1412

1513
func main() {
@@ -23,101 +21,83 @@ func main() {
2321
}
2422
}
2523

26-
var tabWriter = tabwriter.NewWriter(os.Stdout, 12, 2, 1, ' ', tabwriter.TabIndent)
27-
28-
func run(abuseURL string) error {
29-
var abusive *url.URL
30-
var err error
31-
if strings.Contains(abuseURL, "/") {
32-
// This looks like a full URL instead of a plain domain
33-
if !strings.HasPrefix(abuseURL, "http://") && !strings.HasPrefix(abuseURL, "https://") {
34-
// Doesn't have a protocol so won't url.Parse properly
35-
abuseURL = "http://" + abuseURL
36-
}
37-
38-
abusive, err = url.Parse(abuseURL)
39-
if err != nil {
40-
return fmt.Errorf("couldn't parse URL: %w", err)
41-
}
42-
} else {
43-
// This is a plain domain name so we construct a URL directly
44-
abusive = &url.URL{
45-
Scheme: "http",
46-
Host: abuseURL,
47-
}
48-
}
49-
50-
if abusive.Hostname() == "" {
51-
return fmt.Errorf("%s doesn't look like a valid URL (hostname is empty)", abuseURL)
24+
func run(query string) error {
25+
u, err := parseURL(query)
26+
if err != nil {
27+
return err
5228
}
5329

54-
// First look up abuse details for the domain itself (this will be the registrar)
55-
rootDomain, _ := publicsuffix.EffectiveTLDPlusOne(abusive.Hostname())
56-
57-
// First check if this is a shared host
58-
var sharedHost bool
59-
for _, matcher := range sharedHostMatchers {
60-
if match, display := matcher(rootDomain); match {
61-
if !sharedHost {
62-
fmt.Println("Report abuse to shared hosting provider:")
63-
}
64-
display()
65-
sharedHost = true
66-
}
67-
}
68-
// If this is a shared host then skip the WHOIS lookup
69-
// as that information isn't useful.
70-
if sharedHost {
30+
if ok, contact := matchers.IsSharedHostingProvider(u); ok {
31+
fmt.Println("Report abuse to shared hosting provider:")
32+
printContactDetails(u, contact)
33+
// If this is a shared host then skip the rest of the lookups
34+
// as that information isn't useful.
7135
return nil
7236
}
7337

74-
err = getAbuseReportDetails("Report abuse to domain registrar:", abusive, rootDomain)
38+
contacts, err := matchers.Registrar(u)
7539
if err != nil {
76-
return fmt.Errorf("failed to get registrar abuse details: %w", err)
77-
}
78-
79-
// Now look up the IP in order to find the hosting provider
80-
ips, err := net.LookupIP(abusive.Hostname())
81-
if err != nil {
82-
return fmt.Errorf("failed to find hosting provider: %w", err)
40+
return err
8341
}
42+
fmt.Println("Report abuse to domain registrar:")
43+
printContactDetails(u, contacts...)
8444

85-
// Abuse details for the IP should be the hosting provider
86-
err = getAbuseReportDetails("Report abuse to host:", abusive, ips[0].String())
45+
contacts, err = matchers.HostingProvider(u)
8746
if err != nil {
88-
return fmt.Errorf("failed to get host abuse details: %w", err)
47+
return err
8948
}
49+
fmt.Println("Report abuse to hosting provider:")
50+
printContactDetails(u, contacts...)
9051
return nil
9152
}
9253

93-
func getAbuseReportDetails(header string, abusive *url.URL, query string) error {
94-
rawWhois, err := exec.Command("whois", query).CombinedOutput()
54+
func parseURL(input string) (*url.URL, error) {
55+
if !strings.Contains(input, "/") {
56+
// This is likely a plain domain name so we construct a URL directly
57+
return &url.URL{
58+
Scheme: "http",
59+
Host: input,
60+
}, nil
61+
}
62+
63+
// This looks like a full URL instead of a plain domain
64+
if !strings.HasPrefix(input, "http://") && !strings.HasPrefix(input, "https://") {
65+
// Doesn't have a protocol so won't url.Parse properly
66+
input = "http://" + input
67+
}
68+
69+
u, err := url.Parse(input)
9570
if err != nil {
96-
return err
71+
return nil, fmt.Errorf("couldn't parse URL: %w", err)
72+
}
73+
if u.Hostname() == "" {
74+
return nil, fmt.Errorf("%s doesn't look like a valid URL (hostname is empty)", input)
9775
}
9876

99-
gotMatch := false
100-
for _, matcher := range whoisMatchers {
101-
if match, display := matcher(string(rawWhois)); match {
102-
if !gotMatch {
103-
fmt.Println(header)
104-
gotMatch = true
77+
return u, nil
78+
}
79+
80+
var tabWriter = tabwriter.NewWriter(os.Stdout, 12, 2, 1, ' ', tabwriter.TabIndent)
81+
82+
func printContactDetails(u *url.URL, contacts ...matchers.ProviderContact) {
83+
for _, contact := range contacts {
84+
switch c := contact.(type) {
85+
case matchers.AbuseEmail:
86+
if emailTemplateConfigured() {
87+
offerToSendEmail(u, c)
88+
} else {
89+
fmt.Fprintf(tabWriter, " Email:\t%s\n", c.Email)
10590
}
106-
display()
91+
92+
case matchers.OnlineForm:
93+
fmt.Fprintf(tabWriter, " %s:\tFill out abuse form %s\n", contact.Name(), c.URL)
94+
95+
default:
96+
panic(fmt.Sprintf("unknown contact type: %T", contact))
10797
}
10898
}
109-
if gotMatch {
110-
return nil
99+
if len(contacts) == 0 {
100+
fmt.Fprintf(tabWriter, " Couldn't find any contact details\n")
111101
}
112-
113-
// None of the specific matchers hit so use a generic one
114-
found, display := fallbackEmailMatcher(header, abusive, string(rawWhois))
115-
if found {
116-
display()
117-
return nil
118-
}
119-
120-
fmt.Println(header)
121-
fmt.Println(" couldn't find any abuse contact details")
122-
return nil
102+
tabWriter.Flush()
123103
}

email_template.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"html/template"
7+
"io/ioutil"
8+
"net/url"
9+
"os"
10+
"os/exec"
11+
"strings"
12+
13+
"github.com/bradleyjkemp/abwhose/matchers"
14+
)
15+
16+
func emailTemplateConfigured() bool {
17+
_, configured := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE")
18+
return configured
19+
}
20+
21+
func offerToSendEmail(u *url.URL, contact matchers.AbuseEmail) {
22+
emailTemplateFile, _ := os.LookupEnv("ABWHOSE_MAILTO_TEMPLATE")
23+
emailTemplateContents, err := ioutil.ReadFile(emailTemplateFile)
24+
if err != nil {
25+
fmt.Printf("Failed reading email template: %v\n", err)
26+
return
27+
}
28+
mailto := &bytes.Buffer{}
29+
err = template.Must(template.New("email").Parse(string(emailTemplateContents))).Execute(mailto, map[string]interface{}{
30+
"domain": strings.Replace(u.Hostname(), ".", "[.]", -1),
31+
"url": strings.Replace(u.Hostname(), ".", "[.]", -1) + u.RawPath + u.RawQuery,
32+
"recipient": contact.Email,
33+
})
34+
if err != nil {
35+
fmt.Printf("Error templating email: %v\n", err)
36+
return
37+
}
38+
fmt.Printf(" Send email to %s? [Y/n] ", contact.Email)
39+
if userSaysYes() {
40+
exec.Command("open", mailto.String()).Run()
41+
}
42+
}
43+
44+
func userSaysYes() bool {
45+
var response string
46+
_, err := fmt.Scanln(&response)
47+
if err != nil && err.Error() != "unexpected newline" {
48+
panic(err)
49+
}
50+
okayResponses := map[string]bool{
51+
"": true,
52+
"y": true,
53+
"yes": true,
54+
}
55+
if okayResponses[strings.ToLower(response)] {
56+
return true
57+
}
58+
return false
59+
}

matchers.go

Lines changed: 0 additions & 137 deletions
This file was deleted.

0 commit comments

Comments
 (0)