Skip to content

Commit

Permalink
1. Allow multiple disposable domain URL sources and unify them
Browse files Browse the repository at this point in the history
2. Also mark subdomains as disposable if any parent domain is disposable
  • Loading branch information
guysmoilov committed Jul 7, 2024
1 parent 541f404 commit fb534cd
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 14 deletions.
15 changes: 13 additions & 2 deletions disposable.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,19 @@ func ParseEmail(email string, caseSensitive ...bool) (ParsedEmail, error) {
// Normalize local part
p.Normalized, p.Preferred, p.Extra = normalize(localPart, domain, cs)

// Check if domain is disposable
_, p.Disposable = GetDisposableList()[domain]
// Check if domain or any parent domain is disposable
for len(domain) > 0 {
_, p.Disposable = GetDisposableList()[domain]
if p.Disposable {
break
}

var found bool
_, domain, found = strings.Cut(domain, ".")
if !found {
break
}
}

return p, nil

Expand Down
46 changes: 46 additions & 0 deletions disposable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,49 @@ func Test_ParseEmail_Disposable_domain(t *testing.T) {
t.Errorf("expected disposable to be true but got false")
}
}

func Test_IsDisposable_Subdomain(t *testing.T) {
listAtomic.Store(map[string]struct{}{
"somewhere.eu.org": {},
})

p, err := ParseEmail("[email protected]")
if err != nil {
t.Fatal(err)
}
if !p.Disposable {
t.Errorf("expected disposable to be true but got false")
}

p, err = ParseEmail("[email protected]")
if err != nil {
t.Fatal(err)
}
if !p.Disposable {
t.Errorf("expected disposable to be true but got false")
}

p, err = ParseEmail("[email protected]")
if err != nil {
t.Fatal(err)
}
if p.Disposable {
t.Errorf("expected disposable to be false but got true")
}

p, err = ParseEmail("[email protected]")
if err != nil {
t.Fatal(err)
}
if p.Disposable {
t.Errorf("expected disposable to be false but got true")
}

p, err = ParseEmail("[email protected]")
if err != nil {
t.Fatal(err)
}
if p.Disposable {
t.Errorf("expected disposable to be false but got true")
}
}
11 changes: 7 additions & 4 deletions list.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ func init() {
// The interval is how often the list will be updated. The timeout is how long the update is allowed to take.
// url is optional to override the default one.
// If an error occurs during the update, the onError function will be called. If onError returns true, the update process will stop.
func ScheduleUpdates(interval time.Duration, timeout time.Duration, url string, onError func(error) (shouldStop bool)) {
if url == "" {
url = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf"
func ScheduleUpdates(interval time.Duration, timeout time.Duration, urls []string, onError func(error) (shouldStop bool)) {
if len(urls) == 0 {
urls = []string{
"https://raw.githubusercontent.com/dagshub/disposable-email-domains/main/disposable_email_blocklist.conf",
"https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf",
}
}
go func() {
for {
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
newList, err := update.Update(ctx, url)
newList, err := update.Update(ctx, urls)
if err != nil && onError != nil && onError(err) {
cancelFunc()
return
Expand Down
45 changes: 37 additions & 8 deletions update/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,57 @@ import (

// Update can be used to update the list of disposable email domains.
// It uses the regularly updated list found here: https://github.com/martenson/disposable-email-domains.
func Update(ctx context.Context, url string) (map[string]struct{}, error) {
func Update(ctx context.Context, urls []string) (map[string]struct{}, error) {
newList := make(map[string]struct{}, 3500)
for _, url := range urls {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return nil, err
}

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
_ = resp.Body.Close()
return nil, errors.New("unable to fetch disposable email domains: " + resp.Status)
}

scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
newList[scanner.Text()] = struct{}{}
}

_ = resp.Body.Close()
err = scanner.Err()
if err != nil {
return nil, err
}
}

return newList, nil
}

func fetchList(ctx context.Context, url string, domains chan<- string, errs chan<- error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
resp, err := http.DefaultTransport.RoundTrip(req)
if err != nil {
return nil, err
errs <- err
return
}
defer resp.Body.Close()

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, errors.New("unable to fetch disposable email domains: " + resp.Status)
errs <- errors.New("unable to fetch disposable email domains: " + resp.Status)
return
}

newList := make(map[string]struct{}, 3500)
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
newList[scanner.Text()] = struct{}{}
domains <- scanner.Text()
}

err = scanner.Err()
if err != nil {
return nil, err
errs <- err
}

return newList, nil
return
}

0 comments on commit fb534cd

Please sign in to comment.