├── .gitignore ├── .travis.yml ├── Godeps ├── Godeps.json ├── Readme └── _workspace │ └── .gitignore ├── History.md ├── LICENSE ├── Makefile ├── README.md ├── domain ├── name │ ├── name.go │ └── name_test.go ├── ns │ └── publicSuffixes.go └── query │ └── query.go ├── domainerator.go ├── go.mk ├── tests └── tests.go ├── util └── updatePublicSuffixList.go └── wordlist ├── wordlist.go └── wordlist_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | src 24 | .DS_Store 25 | domainerator 26 | 27 | *.out 28 | *.tmp 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.8.x 4 | install: 5 | - go get -v -t ./... 6 | - go get -v ./... -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/hgfischer/domainerator", 3 | "GoVersion": "go1.4.2", 4 | "Packages": [ 5 | "./..." 6 | ], 7 | "Deps": [ 8 | { 9 | "ImportPath": "github.com/miekg/dns", 10 | "Rev": "32c1cd51a98ca66b93ea5d54601891d26e4dd747" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 1.8.1 / 2013-03-16 3 | ================== 4 | 5 | * Updated code to match miekg DNS pkg 6 | 7 | 1.8.0 / 2012-11-02 8 | ================== 9 | 10 | * Retries are now processed in a second channel 11 | * Refactored entire main function to be more readable 12 | 13 | 1.7.2 / 2012-10-28 14 | ================== 15 | 16 | * Added "fuse" option 17 | 18 | 1.7.1 / 2012-10-20 19 | ================== 20 | 21 | * Fixed bug with default protocol setting and added a check 22 | 23 | 1.7.0 / 2012-10-20 24 | ================== 25 | 26 | * Added support for protocol 27 | * Changed list of default public suffix and timeouts 28 | * Improved checks 29 | * removed unused import 30 | * Bug fix in retries 31 | 32 | 1.6.9 / 2012-10-17 33 | ================== 34 | 35 | * Check empty domain list 36 | 37 | 1.6.8 / 2012-10-17 38 | ================== 39 | 40 | * Add filtering of restricted domains 41 | * Added minLength size limit 42 | * Setup of Travis-CI 43 | 44 | 1.6.7 / 2012-10-14 45 | ================== 46 | 47 | * Added Travis-CI conf 48 | * Deduplicated domain list 49 | 50 | 1.6.6 / 2012-10-13 51 | ================== 52 | 53 | * Fixed bug with -avail cmd option 54 | 55 | 1.6.5 / 2012-10-13 56 | ================== 57 | 58 | * Included option to output only available domains 59 | * Changed default PS list 60 | * Fixed domain hack generation or small words 61 | 62 | 1.6.4 / 2012-10-13 63 | ================== 64 | 65 | * Refactoring to include tests 66 | * Extraced wordlist functions to another package and tested all them 67 | 68 | 1.6.3 / 2012-10-12 69 | ================== 70 | 71 | * Updated README and default list os public suffixes 72 | * Bigger default TLDs and bug fix in domain hacks 73 | 74 | 1.6.2 / 2012-10-12 75 | ================== 76 | 77 | * Fixed bug in loop logic 78 | 79 | 1.6.1 / 2012-10-11 80 | ================== 81 | 82 | * Added max domain length option 83 | 84 | 1.6.0 / 2012-10-11 85 | ================== 86 | 87 | * Fixed TLD/Public Suffix naming confusion 88 | * Bigger default DNS server list 89 | * Domain hacks support 90 | * Flag to skip UTF-8 domains including UTF-8 public suffixes 91 | * Flag to include all TLDs in the public domain suffix list into the check 92 | 93 | 1.5.1 / 2012-10-11 94 | ================== 95 | 96 | * Fixed mess/confusion with workspace/packages/github 97 | 98 | 1.5.0 / 2012-10-11 99 | ================== 100 | 101 | * Better README 102 | * Embedded TLD list 103 | * Improvements in DNS check 104 | * Output file now have domain and DNS answer status (NXDOMAIN == available) 105 | 106 | 1.4.0 / 2012-10-10 107 | ================== 108 | 109 | * Now querying for NS instead of apex A 110 | * Added DNS option 111 | * Added github.com/miekg/dns/ as DNS client 112 | 113 | 1.3.1 / 2012-10-10 114 | ================== 115 | 116 | * Added call to remove duplicated from domain list 117 | 118 | 1.3.0 / 2012-10-07 119 | ================== 120 | 121 | * Added support for separate word lists for prefixes and suffixes 122 | * Added more items to git ignore list 123 | * Added install instructions 124 | 125 | 1.2.0 / 2012-10-07 126 | ================== 127 | 128 | * Added support for checking suffixes and TLDs 129 | * Added output to file and better feedback to user 130 | * Added BSD license 131 | * Added history file 132 | 133 | 1.1.0 / 2012-10-06 134 | ================== 135 | 136 | * Added new options 137 | 138 | 1.0.1 / 2012-10-06 139 | ================== 140 | 141 | * small fixes 142 | 143 | 1.0.0 / 2012-10-06 144 | ================== 145 | 146 | * first functional version 147 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Herbert G. Fischer 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the organization nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL HERBERT G. FISCHER BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include go.mk 2 | 3 | .PHONY: build 4 | build: gomkbuild 5 | 6 | .PHONY: xbuild 7 | xbuild: gomkxbuild 8 | 9 | .PHONY: clean 10 | clean: gomkclean 11 | 12 | .PHONY: run 13 | run: build 14 | ./$(APPBIN) 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Domainerator 2 | 3 | [![Build Status](https://travis-ci.org/hgfischer/domainerator.svg?branch=master)](https://travis-ci.org/hgfischer/domainerator) 4 | [![Go Report Card](https://goreportcard.com/badge/hgfischer/domainerator)](https://goreportcard.com/report/hgfischer/domainerator) 5 | 6 | Domainerator was my first Go application. It combines two wordlists (prefixes and suffixes) and a list of TLDs to form 7 | domain names and check their DNS status. It outputs a file with each combined domain name and the respective DNS status. 8 | 9 | ## History 10 | 11 | Not a long one, but... 12 | 13 | I've developed it after getting tired of trying to find some good domain names available to be registered. 14 | In the beginning it was a slow Ruby script that still can be found as a rubygem with the same name, and now 15 | I've ported it to Go and made a lot of improvements in the method used to check for availability and speed. 16 | 17 | ## Benchmark 18 | 19 | I was able to run a check with 2,288 domains in about 13 seconds using 100 goroutines in a MacBook Pro Late 2008! 20 | 21 | ## Instalation 22 | 23 | To install domainerator you need: 24 | 25 | 1. Install Go 26 | 2. Setup environment vars for Go 27 | 3. Run `sudo go get github.com/hgfischer/domainerator` 28 | 29 | ## Updating 30 | 31 | To update domainerator you need: 32 | 33 | 1. Run `sudo go get -u github.com/hgfischer/domainerator` 34 | -------------------------------------------------------------------------------- /domain/name/name.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | "unicode/utf8" 8 | 9 | "github.com/hgfischer/domainerator/wordlist" 10 | ) 11 | 12 | // ParsePublicSuffixCSV parse a CSV string into a cleaned slice of strings 13 | func ParsePublicSuffixCSV(csv string, accepted map[string]bool, includeTLDs bool) ([]string, error) { 14 | psl := wordlist.FromCSV(csv) 15 | for _, ps := range psl { 16 | _, ok := accepted[ps] 17 | if !ok { 18 | return nil, fmt.Errorf("Public Suffix %q is unknown", ps) 19 | } 20 | } 21 | if includeTLDs { 22 | for ps := range accepted { 23 | if !strings.Contains(ps, ".") { 24 | psl = append(psl, ps) 25 | } 26 | } 27 | } 28 | psl = wordlist.RemoveDuplicates(psl) 29 | sort.Strings(psl) 30 | return psl, nil 31 | } 32 | 33 | // ParseDNSCSV parse a CSV string into a cleaned slice of strings 34 | func ParseDNSCSV(csv string) []string { 35 | psl := wordlist.FromCSV(csv) 36 | psl = wordlist.RemoveDuplicates(psl) 37 | sort.Strings(psl) 38 | return psl 39 | } 40 | 41 | // CombinePhraseAndPublicSuffixes combine phrases (combined words) with public suffixes, with out without domain hacks 42 | // and return a slice of strings 43 | func CombinePhraseAndPublicSuffixes(word string, psl []string, hacks bool) []string { 44 | var domains []string 45 | for _, ps := range psl { 46 | domains = append(domains, word+"."+ps) 47 | if hacks { 48 | if strings.HasSuffix(word, ps) { 49 | last := strings.LastIndex(word, ps) 50 | if last > 0 { 51 | domains = append(domains, word[:last]+"."+ps) 52 | } 53 | } 54 | } 55 | } 56 | return domains 57 | } 58 | 59 | // CombinePrefixAndSuffix combine two words in all possible combinations with out without hyphenation. A suffix never 60 | // comes before the prefix. 61 | func CombinePrefixAndSuffix(prefix, suffix string, itself, hyphenate, fuse bool, minLength int) []string { 62 | var output []string 63 | if prefix == suffix && !itself { 64 | return output 65 | } 66 | str := prefix + suffix 67 | if len(str) >= minLength { 68 | output = append(output, str) 69 | } 70 | if fuse { 71 | if strings.HasSuffix(prefix, suffix[0:1]) { 72 | output = append(output, prefix+suffix[1:]) 73 | } 74 | if strings.HasSuffix(prefix, suffix[0:2]) { 75 | output = append(output, prefix+suffix[2:]) 76 | } 77 | } 78 | if hyphenate { 79 | str := prefix + "-" + suffix 80 | if len(str) >= minLength { 81 | output = append(output, prefix+"-"+suffix) 82 | } 83 | } 84 | return output 85 | } 86 | 87 | // Combine words and public suffixes to make the ordered domain list 88 | func Combine(prefixes, suffixes, psl []string, single, hyphenate, itself, hacks, fuse bool, minLength int) []string { 89 | var domains []string 90 | if single { 91 | for _, prefix := range prefixes { 92 | domains = append(domains, CombinePhraseAndPublicSuffixes(prefix, psl, hacks)...) 93 | } 94 | for _, suffix := range suffixes { 95 | domains = append(domains, CombinePhraseAndPublicSuffixes(suffix, psl, hacks)...) 96 | } 97 | } 98 | for _, prefix := range prefixes { 99 | for _, suffix := range suffixes { 100 | phrases := CombinePrefixAndSuffix(prefix, suffix, itself, hyphenate, fuse, minLength) 101 | for _, phrase := range phrases { 102 | domains = append(domains, CombinePhraseAndPublicSuffixes(phrase, psl, hacks)...) 103 | } 104 | } 105 | } 106 | return domains 107 | } 108 | 109 | // FilterMaxLength filter domains surpasing the maxLengh limit. 110 | func FilterMaxLength(domains []string, maxLength int) []string { 111 | var output []string 112 | for _, domain := range domains { 113 | if utf8.RuneCountInString(domain) <= maxLength { 114 | output = append(output, domain) 115 | } 116 | } 117 | return output 118 | } 119 | 120 | // FilterStrictDomains filter out domains possibly forbidden by registrars 121 | func FilterStrictDomains(domains []string, publicSuffixes map[string]bool) []string { 122 | var output []string 123 | for _, domain := range domains { 124 | first := strings.Index(domain, ".") 125 | cleanedDomain := domain[:first] 126 | if _, ok := publicSuffixes[cleanedDomain]; !ok { 127 | output = append(output, domain) 128 | } 129 | } 130 | return output 131 | } 132 | -------------------------------------------------------------------------------- /domain/name/name_test.go: -------------------------------------------------------------------------------- 1 | package name 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/hgfischer/domainerator/tests" 9 | ) 10 | 11 | var ( 12 | accepted = map[string]bool{ 13 | "com": true, "net": true, "org": true, 14 | "us": true, "im": true, "io": true, 15 | "ca": true, "co": true, "in": true, 16 | "com.br": true, "org.br": true, "co.uk": true, 17 | } 18 | ) 19 | 20 | func TestParsePublicSuffixCSV(t *testing.T) { 21 | csv := "us, im, in, io, ,, ca,co,co ,,ca , com" 22 | expected := []string{"com", "net", "org", "us", "im", "in", "io", "ca", "co"} 23 | sort.Strings(expected) 24 | psl, err := ParsePublicSuffixCSV(csv, accepted, true) 25 | if err != nil { 26 | t.Fatalf(tests.ErrFmtExpectedGot, "ParsePublicSuffixCSV", "No Error", err) 27 | } 28 | if !reflect.DeepEqual(psl, expected) { 29 | t.Errorf(tests.ErrFmtExpectedGot, "ParsePublicSuffixCSV", expected, psl) 30 | } 31 | } 32 | 33 | func TestParsePublicSuffixCSVForUnknownSuffix(t *testing.T) { 34 | csv := "com,net,org,unk" 35 | _, err := ParsePublicSuffixCSV(csv, accepted, false) 36 | if err == nil { 37 | t.Errorf(tests.ErrFmtExpectedGot, "ParsePublicSuffixCSV", "Unknown Public Suffix Error", "No Error") 38 | } 39 | } 40 | 41 | func TestCombinePhraseAndPublicSuffixes(t *testing.T) { 42 | psl := []string{"ex", "nd", "com"} 43 | expected := []string{"index.ex", "ind.ex", "index.nd", "index.com"} 44 | domains := CombinePhraseAndPublicSuffixes("index", psl, true) 45 | if !reflect.DeepEqual(expected, domains) { 46 | t.Errorf(tests.ErrFmtExpectedGot, "CombineWordAndPublixSuffixes", expected, domains) 47 | } 48 | } 49 | 50 | func TestCombinePhraseAndPublicSuffixesWithSmallPhrase(t *testing.T) { 51 | psl := []string{"ex"} 52 | expected := []string{"ex.ex"} 53 | domains := CombinePhraseAndPublicSuffixes("ex", psl, true) 54 | if !reflect.DeepEqual(expected, domains) { 55 | t.Errorf(tests.ErrFmtExpectedGot, "CombineWordAndPublixSuffixes", expected, domains) 56 | } 57 | } 58 | 59 | var ( 60 | prefixes = []string{"go", "py"} 61 | suffixes = []string{"lang", "coder"} 62 | psl = []string{"com", "er"} 63 | ) 64 | 65 | func TestCombineFull(t *testing.T) { 66 | expected := []string{ 67 | "go.com", "go.er", "lang.com", "lang.er", 68 | "golang.com", "golang.er", "go-lang.com", "go-lang.er", 69 | "gocoder.com", "gocoder.er", "gocod.er", "go-coder.com", "go-coder.er", "go-cod.er", 70 | "py.com", "py.er", "coder.com", "coder.er", "cod.er", 71 | "pylang.com", "pylang.er", "py-lang.com", "py-lang.er", 72 | "pycoder.com", "pycoder.er", "pycod.er", "py-coder.com", "py-coder.er", "py-cod.er", 73 | } 74 | sort.Strings(expected) 75 | words := Combine(prefixes, suffixes, psl, true, true, true, true, false, 3) 76 | sort.Strings(words) 77 | if !reflect.DeepEqual(expected, words) { 78 | t.Errorf(tests.ErrFmtExpectedGot, "Combine", expected, words) 79 | } 80 | } 81 | 82 | func TestCombineSimple(t *testing.T) { 83 | expected := []string{ 84 | "golang.com", "golang.er", "gocoder.com", "gocoder.er", 85 | "pylang.com", "pylang.er", "pycoder.com", "pycoder.er", 86 | } 87 | sort.Strings(expected) 88 | words := Combine(prefixes, suffixes, psl, false, false, false, false, false, 3) 89 | sort.Strings(words) 90 | if !reflect.DeepEqual(expected, words) { 91 | t.Errorf(tests.ErrFmtExpectedGot, "Combine", expected, words) 92 | } 93 | } 94 | 95 | func TestCombineHyphenation(t *testing.T) { 96 | expected := []string{ 97 | "golang.com", "golang.er", "gocoder.com", "gocoder.er", 98 | "go-lang.com", "go-lang.er", "go-coder.com", "go-coder.er", 99 | "pylang.com", "pylang.er", "pycoder.com", "pycoder.er", 100 | "py-lang.com", "py-lang.er", "py-coder.com", "py-coder.er", 101 | } 102 | sort.Strings(expected) 103 | words := Combine(prefixes, suffixes, psl, false, true, false, false, false, 3) 104 | sort.Strings(words) 105 | if !reflect.DeepEqual(expected, words) { 106 | t.Errorf(tests.ErrFmtExpectedGot, "Combine", expected, words) 107 | } 108 | } 109 | 110 | func TestCombinePrefixAndSuffix(t *testing.T) { 111 | expected := []string{"prefixsuffix", "prefix-suffix"} 112 | sort.Strings(expected) 113 | words := CombinePrefixAndSuffix("prefix", "suffix", false, true, false, 3) 114 | sort.Strings(words) 115 | if !reflect.DeepEqual(expected, words) { 116 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 117 | } 118 | } 119 | 120 | func TestCombinePrefixAndSuffixWithoutHyphenation(t *testing.T) { 121 | expected := []string{"prefixsuffix"} 122 | words := CombinePrefixAndSuffix("prefix", "suffix", false, false, false, 3) 123 | if !reflect.DeepEqual(expected, words) { 124 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 125 | } 126 | } 127 | 128 | func TestCombinePrefixAndSuffixWithItself(t *testing.T) { 129 | expected := []string{"itselfitself", "itself-itself"} 130 | sort.Strings(expected) 131 | words := CombinePrefixAndSuffix("itself", "itself", true, true, false, 3) 132 | sort.Strings(words) 133 | if !reflect.DeepEqual(expected, words) { 134 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 135 | } 136 | } 137 | 138 | func TestCombinePrefixAndSuffixWithFusion(t *testing.T) { 139 | expected := []string{"fusionnation", "fusionation"} 140 | sort.Strings(expected) 141 | words := CombinePrefixAndSuffix("fusion", "nation", false, false, true, 3) 142 | sort.Strings(words) 143 | if !reflect.DeepEqual(expected, words) { 144 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 145 | } 146 | } 147 | 148 | func TestCombinePrefixAndSuffixWithFusionLatestTwo(t *testing.T) { 149 | expected := []string{"flamingogorilla", "flamingorilla"} 150 | sort.Strings(expected) 151 | words := CombinePrefixAndSuffix("flamingo", "gorilla", false, false, true, 3) 152 | sort.Strings(words) 153 | if !reflect.DeepEqual(expected, words) { 154 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 155 | } 156 | } 157 | 158 | func TestCombinePrefixAndSuffixWithMinLength(t *testing.T) { 159 | expected := []string{"a-b"} 160 | sort.Strings(expected) 161 | words := CombinePrefixAndSuffix("a", "b", true, true, false, 3) 162 | sort.Strings(words) 163 | if !reflect.DeepEqual(expected, words) { 164 | t.Errorf(tests.ErrFmtExpectedGot, "CombinePrefixAndSuffix", expected, words) 165 | } 166 | } 167 | 168 | func TestFilterStrictDomains(t *testing.T) { 169 | expected := []string{"lalalala.com"} 170 | domains := []string{"co.com.br", "us.com.br", "lalalala.com"} 171 | domains = FilterStrictDomains(domains, accepted) 172 | if !reflect.DeepEqual(expected, domains) { 173 | t.Errorf(tests.ErrFmtExpectedGot, "FilterStrictDomains", expected, domains) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /domain/query/query.go: -------------------------------------------------------------------------------- 1 | package query 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/miekg/dns" 9 | ) 10 | 11 | // Result represent a DNS query result 12 | type Result struct { 13 | Domain string 14 | Rcode int 15 | err error 16 | } 17 | 18 | // Format Result into string for output file 19 | func (dr Result) String(simple bool) string { 20 | if simple { 21 | return fmt.Sprintf("%s\n", dr.Domain) 22 | } 23 | return fmt.Sprintf("%s\t%s\t%q\t\n", dr.Domain, dns.RcodeToString[dr.Rcode], dr.err) 24 | } 25 | 26 | // Available return true if the domain is available (DNS NXDOMAIN) 27 | func (dr Result) Available() bool { 28 | return dr.Rcode == dns.RcodeNameError 29 | } 30 | 31 | // Returns true if domain has a Name Server associated 32 | func queryNS(domain string, dnsServers []string, proto string) (int, error) { 33 | c := new(dns.Client) 34 | c.ReadTimeout = time.Duration(2 * time.Second) 35 | c.WriteTimeout = time.Duration(2 * time.Second) 36 | c.Net = proto 37 | m := new(dns.Msg) 38 | m.RecursionDesired = true 39 | dnsServer := dnsServers[rand.Intn(len(dnsServers))] 40 | m.SetQuestion(dns.Fqdn(domain), dns.TypeNS) 41 | in, _, err := c.Exchange(m, dnsServer+":53") 42 | if err == nil { 43 | return in.Rcode, err 44 | } 45 | return dns.RcodeRefused, err 46 | } 47 | 48 | // CheckDomains check if each domain 49 | func CheckDomains(id int, in, retries chan string, out chan Result, dnsServers []string, proto string) { 50 | for { 51 | var domain string 52 | select { 53 | case domain = <-in: 54 | case domain = <-retries: 55 | } 56 | rCode, err := queryNS(domain, dnsServers, proto) 57 | if err != nil { 58 | retries <- domain 59 | } else { 60 | out <- Result{domain, rCode, err} 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /domainerator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | "strings" 10 | "time" 11 | 12 | "github.com/hgfischer/domainerator/domain/name" 13 | "github.com/hgfischer/domainerator/domain/ns" 14 | "github.com/hgfischer/domainerator/domain/query" 15 | "github.com/hgfischer/domainerator/wordlist" 16 | ) 17 | 18 | const ( 19 | defaultPublicSuffixes = "com,net,org,biz,info,mobi,name,tel,us,in,me,co,ca,de,eu,ws,it,be,at,ch,cz,li,nl,pl,re,so,wf,im,no" 20 | defaultDNSServers = "8.8.8.8,8.8.4.4,4.2.2.1,4.2.2.2,4.2.2.3,4.2.2.4,4.2.2.5,4.2.2.6,198.153.192.1,198.153.194.1,67.138.54.100,207.225.209.66" 21 | ) 22 | 23 | // Command line options 24 | var ( 25 | single = flag.Bool("single", true, "Also check single words") 26 | itself = flag.Bool("itself", false, "Include words combined with itself") 27 | hyphenate = flag.Bool("hyphen", false, "Include hyphenated combinations") 28 | hacks = flag.Bool("hacks", true, "Enable domain hacks") 29 | fuse = flag.Bool("fuse", true, "Fuse words if letters match (ex.: ab + bc = abbc => abc") 30 | includeTLDs = flag.Bool("tlds", false, "Include all TLDs in public domain suffix list") 31 | includeUTF8 = flag.Bool("utf8", false, "Include combinations with UTF-8 characters") 32 | publicCSV = flag.String("ps", defaultPublicSuffixes, "Public domain suffixes to combine with") 33 | dnsCSV = flag.String("dns", defaultDNSServers, "Comma-separated list of DNS servers to talk to") 34 | protocol = flag.String("proto", "udp", "Protocol (udp/tcp)") 35 | maxLength = flag.Int("maxlen", 64, "Maximum length of generated domains including public suffix") 36 | minLength = flag.Int("minlen", 3, "Minimum length of generated domains without public suffic") 37 | concurrency = flag.Int("c", 50, "Number of concurrent threads doing checks") 38 | available = flag.Bool("avail", true, "If true, output only available domains (NXDOMAIN) without DNS status code") 39 | strictMode = flag.Bool("strict", true, "If true, filter some possibly prohibited domains (domain == tld, etc)") 40 | ) 41 | 42 | // Prints an error message to stderr and exist with a return code 43 | func showErrorAndExit(err error, returnCode int) { 44 | fmt.Fprintf(os.Stderr, "Error: %s\n", err) 45 | os.Exit(returnCode) 46 | } 47 | 48 | // Print command line help and exit application 49 | func usage() { 50 | fmt.Fprintf(os.Stderr, 51 | "Usage: domainerator [flags] [prefixes wordlist] [suffixes wordlist] [output file]\n") 52 | fmt.Fprintf(os.Stderr, "\nFlags:\n") 53 | flag.PrintDefaults() 54 | os.Exit(1) 55 | } 56 | 57 | func loadWordList(file string) (list []string) { 58 | list, err := wordlist.Load(file) 59 | if err != nil { 60 | showErrorAndExit(err, 10) 61 | } 62 | return 63 | } 64 | 65 | func loadWordLists(prefixFile, suffixFile string) (prefixes, suffixes []string) { 66 | fmt.Print("Loading word lists.. ") 67 | prefixes = loadWordList(prefixFile) 68 | suffixes = loadWordList(suffixFile) 69 | if len(prefixes) == 0 && len(suffixes) == 0 { 70 | showErrorAndExit(errors.New("Empty wordlists"), 12) 71 | } 72 | fmt.Println("done.") 73 | return 74 | } 75 | 76 | func loadFlags() { 77 | flag.Usage = usage 78 | flag.Parse() 79 | if flag.NArg() != 3 { 80 | fmt.Fprintf(os.Stderr, "Error: Missing some word list file path and/or output file path\n") 81 | flag.Usage() 82 | } 83 | } 84 | 85 | func loadPublicSuffixList() (psl []string) { 86 | psl, err := name.ParsePublicSuffixCSV(*publicCSV, ns.PublicSuffixes, *includeTLDs) 87 | if err != nil { 88 | showErrorAndExit(err, 20) 89 | } 90 | if !*includeUTF8 { 91 | psl = wordlist.FilterUTF8(psl) 92 | } 93 | fmt.Printf("Public Suffixes: %s\n", strings.Join(psl, ", ")) 94 | return 95 | } 96 | 97 | func loadDNSServers() (dnsServers []string) { 98 | dnsServers = name.ParseDNSCSV(*dnsCSV) 99 | if len(dnsServers) == 0 { 100 | showErrorAndExit(errors.New("You need to specify a DNS server"), 30) 101 | } 102 | return 103 | } 104 | 105 | func checkProtocol() { 106 | if *protocol != "tcp" && *protocol != "udp" { 107 | errmsg := fmt.Sprintf("Unknown protocol: %q (should be \"udp\" or \"tcp\"", *protocol) 108 | showErrorAndExit(errors.New(errmsg), 35) 109 | } 110 | } 111 | 112 | func setupOutputFile(outputPath string) (outputFile *os.File) { 113 | outputFile, err := os.Create(outputPath) 114 | if err != nil { 115 | showErrorAndExit(err, 40) 116 | } 117 | return 118 | } 119 | 120 | func createDomainList(prefixes, suffixes, psl []string) (domains []string) { 121 | fmt.Print("Creating domain list... ") 122 | domains = name.Combine(prefixes, suffixes, psl, *single, *hyphenate, *itself, *hacks, *fuse, *minLength) 123 | if !*includeUTF8 { 124 | domains = wordlist.FilterUTF8(domains) 125 | } 126 | domains = name.FilterMaxLength(domains, *maxLength) 127 | if *strictMode { 128 | domains = name.FilterStrictDomains(domains, ns.PublicSuffixes) 129 | } 130 | domains = wordlist.RemoveDuplicates(domains) 131 | fmt.Println("done.") 132 | if len(domains) == 0 { 133 | showErrorAndExit(errors.New("I could not generate a single valid domain"), 50) 134 | } 135 | return 136 | } 137 | 138 | func printFeedback(startTime time.Time, processed, total int) { 139 | fmtStr := "\rChecked %d of %d domains. Elapsed %s. ETA %s. Goroutines: %d\033[K" 140 | elapsed := time.Since(startTime) 141 | etaSecs := elapsed.Seconds() * float64(total) / float64(processed) 142 | eta := time.Duration(etaSecs) * time.Second 143 | out := fmt.Sprintf(fmtStr, processed, total, elapsed, eta, runtime.NumGoroutine()) 144 | fmt.Print(out) 145 | } 146 | 147 | func saveDomainResult(outputFile *os.File, r query.Result, available bool) { 148 | if (available && r.Available()) || !available { 149 | _, err := outputFile.WriteString(r.String(available)) 150 | if err != nil { 151 | showErrorAndExit(err, 6) 152 | } 153 | } 154 | } 155 | 156 | // MAIN 157 | func main() { 158 | loadFlags() 159 | prefixes, suffixes := loadWordLists(flag.Arg(0), flag.Arg(1)) 160 | psl := loadPublicSuffixList() 161 | dnsServers := loadDNSServers() 162 | checkProtocol() 163 | outputFile := setupOutputFile(flag.Arg(2)) 164 | defer outputFile.Close() 165 | domains := createDomainList(prefixes, suffixes, psl) 166 | pending, retries, complete := make(chan string), make(chan string), make(chan query.Result) 167 | 168 | fmt.Println("Starting checks... ") 169 | startTime := time.Now() 170 | 171 | // start checks 172 | for i := 0; i < *concurrency; i++ { 173 | go query.CheckDomains(i, pending, retries, complete, dnsServers, *protocol) 174 | } 175 | 176 | // send domains 177 | go func() { 178 | for _, domain := range domains { 179 | pending <- domain 180 | } 181 | close(pending) 182 | }() 183 | 184 | // save results and print feedback 185 | wrote := 0 186 | for r := range complete { 187 | saveDomainResult(outputFile, r, *available) 188 | wrote++ 189 | printFeedback(startTime, wrote, len(domains)) 190 | if wrote == len(domains) { 191 | close(complete) 192 | } 193 | } 194 | fmt.Println("\nDone.") 195 | } 196 | -------------------------------------------------------------------------------- /go.mk: -------------------------------------------------------------------------------- 1 | # go.mk 2 | # 3 | # Copyright (c) 2015, Herbert G. Fischer 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # * Neither the name of the organization nor the 14 | # names of its contributors may be used to endorse or promote products 15 | # derived from this software without specific prior written permission. 16 | # 17 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | # DISCLAIMED. IN NO EVENT SHALL HERBERT G. FISCHER BE LIABLE FOR ANY 21 | # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | APPBIN := $(shell basename $(PWD)) 29 | GOSOURCES := $(shell find . -type f -name '*.go' ! -path '*Godeps/_workspace*') 30 | GOPKGS := $(shell go list ./... 2>/dev/null) 31 | GOPKG := $(shell go list 2>/dev/null) 32 | COVERAGEOUT := coverage.out 33 | COVERAGETMP := coverage.tmp 34 | GODEPPATH := $(PWD)/Godeps/_workspace 35 | LOCALGOPATH := $(GODEPPATH):$(GOPATH) 36 | ORIGGOPATH := $(GOPATH) 37 | GOMKVERSION := 0.8.1 38 | 39 | # Check GOPATH 40 | ifndef GOPATH 41 | $(error ERROR!! GOPATH must be declared. Check [http://golang.org/doc/code.html#GOPATH]) 42 | endif 43 | 44 | # Check GOBIN, and automatically export it 45 | ifndef GOBIN 46 | export GOBIN=$(GOPATH)/bin 47 | GOBIN := $(GOBIN) 48 | endif 49 | 50 | # Include GODEPPATH 51 | export GOPATH=$(LOCALGOPATH) 52 | 53 | # Check current path 54 | ifeq ($(shell go list ./... 2>/dev/null | grep -q '^_'; echo $$?), 0) 55 | $(error ERROR!! This directory should be at $(GOPATH)/src/$(REPO)] 56 | endif 57 | 58 | 59 | .PHONY: gomkhelp 60 | gomkhelp: 61 | $(info Available go.mk targets: ) 62 | $(info | gomkhelp ) 63 | $(info | gomkbuild ) 64 | $(info | gomkxbuild ) 65 | $(info | gomkclean ) 66 | $(info | gomkupdate ) 67 | $(info | vet ) 68 | $(info | lint ) 69 | $(info | fmt ) 70 | $(info | test ) 71 | $(info | bench ) 72 | $(info | race ) 73 | $(info | deps ) 74 | $(info | cover ) 75 | $(info | present ) 76 | $(info | savegodeps ) 77 | $(info | restoregodeps ) 78 | $(info | updategodeps ) 79 | $(info | printvars ) 80 | @exit 0 81 | 82 | 83 | ########################################################################################## 84 | ## Project targets 85 | ########################################################################################## 86 | 87 | $(APPBIN): gomkbuild 88 | 89 | ########################################################################################## 90 | ## Main targets 91 | ########################################################################################## 92 | 93 | .PHONY: gomkbuild 94 | gomkbuild: $(GOSOURCES) ; @go build 95 | 96 | .PHONY: gomkxbuild 97 | gomkxbuild: ; $(GOX) 98 | 99 | .PHONY: gomkenv 100 | gomkenv: ; @go env 101 | 102 | .PHONY: gomkclean 103 | gomkclean: 104 | @rm -vf $(APPBIN)_*_386 $(APPBIN)_*_amd64 $(APPBIN)_*_arm $(APPBIN)_*.exe 105 | @rm -vf $(COVERAGEOUT) $(COVERAGETMP) 106 | @go clean 107 | 108 | .PHONY: gomkupdate 109 | gomkupdate: 110 | @curl -o go.mk https://raw.githubusercontent.com/hgfischer/gomk/master/go.mk?$(shell date +%s) 111 | 112 | ########################################################################################## 113 | ## Go tools 114 | ########################################################################################## 115 | 116 | GOTOOLDIR := $(shell go env GOTOOLDIR) 117 | BENCHCMP := $(GOTOOLDIR)/benchcmp 118 | CALLGRAPH := $(GOTOOLDIR)/callgraph 119 | COVER := $(GOTOOLDIR)/cover 120 | DIGRAPH := $(GOTOOLDIR)/digraph 121 | EG := $(GOTOOLDIR)/eg 122 | GODEX := $(GOTOOLDIR)/godex 123 | GODOC := $(GOTOOLDIR)/godoc 124 | GOIMPORTS := $(GOTOOLDIR)/goimports 125 | GOMVPKG := $(GOTOOLDIR)/gomvpkg 126 | GOTYPE := $(GOTOOLDIR)/gotype 127 | ORACLE := $(GOTOOLDIR)/oracle 128 | SSADUMP := $(GOTOOLDIR)/ssadump 129 | STRINGER := $(GOTOOLDIR)/stringer 130 | VET := $(GOTOOLDIR)/vet 131 | GOX := $(GOBIN)/gox 132 | LINT := $(GOBIN)/lint 133 | GODEP := $(GOBIN)/godep 134 | PRESENT := $(GOBIN)/present 135 | 136 | $(BENCHCMP) : ; @go get -v golang.org/x/tools/cmd/benchcmp 137 | $(CALLGRAPH) : ; @go get -v golang.org/x/tools/cmd/callgraph 138 | $(COVER) : ; @go get -v golang.org/x/tools/cmd/cover 139 | $(DIGRAPH) : ; @go get -v golang.org/x/tools/cmd/digraph 140 | $(EG) : ; @go get -v golang.org/x/tools/cmd/eg 141 | $(GODEX) : ; @go get -v golang.org/x/tools/cmd/godex 142 | $(GODOC) : ; @go get -v golang.org/x/tools/cmd/godoc 143 | $(GOIMPORTS) : ; @go get -v golang.org/x/tools/cmd/goimports 144 | $(GOMVPKG) : ; @go get -v golang.org/x/tools/cmd/gomvpkgs 145 | $(GOTYPE) : ; @go get -v golang.org/x/tools/cmd/gotype 146 | $(ORACLE) : ; @go get -v golang.org/x/tools/cmd/oracle 147 | $(SSADUMP) : ; @go get -v golang.org/x/tools/cmd/ssadump 148 | $(STRINGER) : ; @go get -v golang.org/x/tools/cmd/stringer 149 | $(VET) : ; @go get -v golang.org/x/tools/cmd/vet 150 | $(PRESENT) : ; @go get -v golang.org/x/tools/cmd/present 151 | $(LINT) : ; @go get -v github.com/golang/lint/golint 152 | $(GOX) : ; @go get -v github.com/mitchellh/gox 153 | $(GODEP) : ; @go get -v github.com/tools/godep 154 | 155 | .PHONY: vet 156 | vet: $(VET) ; @for src in $(GOSOURCES); do GOPATH=$(ORIGGOPATH) go tool vet $$src; done 157 | 158 | .PHONY: lint 159 | lint: $(LINT) ; @for src in $(GOSOURCES); do GOPATH=$(ORIGGOPATH) golint $$src || exit 1; done 160 | 161 | .PHONY: fmt 162 | fmt: ; @GOPATH=$(ORIGGOPATH) go fmt ./... 163 | 164 | .PHONY: test 165 | test: ; @go test -v ./... 166 | 167 | .PHONY: bench 168 | bench: ; @go test -v -bench=. ./... 169 | 170 | .PHONY: race 171 | race: ; @for pkg in $(GOPKGS); do go test -v -race $$pkg || exit 1; done 172 | 173 | .PHONY: deps 174 | deps: ; @GOPATH=$(ORIGGOPATH) go get -u -v -t ./... 175 | 176 | .PHONY: cover 177 | cover: $(COVER) 178 | @echo 'mode: set' > $(COVERAGEOUT) 179 | @for pkg in $(GOPKGS); do \ 180 | go test -v -coverprofile=$(COVERAGETMP) $$pkg; \ 181 | if [ -f $(COVERAGETMP) ]; then \ 182 | grep -v 'mode: set' $(COVERAGETMP) >> $(COVERAGEOUT); \ 183 | rm $(COVERAGETMP); \ 184 | fi; \ 185 | done 186 | @go tool cover -html=$(COVERAGEOUT) 187 | 188 | .PHONY: present 189 | present: $(PRESENT) 190 | @present 191 | 192 | ########################################################################################## 193 | ## Godep support 194 | ########################################################################################## 195 | 196 | .PHONY: savegodeps 197 | savegodeps: $(GODEP) ; @GOPATH=$(ORIGGOPATH) $(GODEP) save ./... 198 | 199 | .PHONY: restoregodeps 200 | restoregodeps: $(GODEP) ; @GOPATH=$(ORIGGOPATH) $(GODEP) restore 201 | 202 | .PHONY: updategodeps 203 | updategodeps: $(GODEP) ; @GOPATH=$(ORIGGOPATH) $(GODEP) update ./... 204 | 205 | ########################################################################################## 206 | ## Make utilities 207 | ########################################################################################## 208 | 209 | .PHONY: printvars 210 | printvars: 211 | @$(foreach V, $(sort $(.VARIABLES)), $(if $(filter-out environment% default automatic, $(origin $V)), $(warning $V=$($V) ))) 212 | @exit 0 213 | -------------------------------------------------------------------------------- /tests/tests.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | // Predefined errors 4 | const ( 5 | ErrFmtExpectedGot = "%s() FAILED! Expected %q, got %q" 6 | ErrFmtStringAtString = "%s() FAILED! %q at %q" 7 | ) 8 | -------------------------------------------------------------------------------- /util/updatePublicSuffixList.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // PublicSuffixListURL is the URL where we read current TLD names 12 | const ( 13 | PublicSuffixListURL = "http://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1" 14 | ) 15 | 16 | // Load known public suffix list (http://publicsuffix.org) 17 | func loadPublicSuffixList() (suffixes []string, err error) { 18 | resp, err := http.Get(PublicSuffixListURL) 19 | if err != nil { 20 | return nil, err 21 | } 22 | defer resp.Body.Close() 23 | body, err := ioutil.ReadAll(resp.Body) 24 | lines := strings.Split(string(body), "\n") 25 | for _, line := range lines { 26 | line = strings.TrimSpace(line) 27 | if len(line) == 0 || strings.HasPrefix(line, "//") || strings.HasPrefix(line, "!") { 28 | continue 29 | } 30 | if strings.HasPrefix(line, "*.") { 31 | line = strings.Replace(line, "*.", "", 1) 32 | } 33 | suffixes = append(suffixes, line) 34 | } 35 | return 36 | } 37 | 38 | func main() { 39 | suffixes, err := loadPublicSuffixList() 40 | if err != nil { 41 | fmt.Println(err) 42 | os.Exit(1) 43 | } 44 | 45 | fmt.Fprintf(os.Stdout, "package ns\n\n") 46 | fmt.Fprintf(os.Stdout, "var PublicSuffixes = map[string]bool{\n") 47 | for _, suffix := range suffixes { 48 | fmt.Fprintf(os.Stdout, "\t%q: true,\n", suffix) 49 | } 50 | fmt.Fprintf(os.Stdout, "}\n") 51 | } 52 | -------------------------------------------------------------------------------- /wordlist/wordlist.go: -------------------------------------------------------------------------------- 1 | // Package wordlist implements some wordlists functions 2 | package wordlist 3 | 4 | import ( 5 | "io/ioutil" 6 | "strings" 7 | "unicode/utf8" 8 | ) 9 | 10 | // TrimWords spaces for each word in a wordlist 11 | func TrimWords(words []string) []string { 12 | for key, word := range words { 13 | words[key] = strings.Replace(word, " ", "", -1) 14 | } 15 | return words 16 | } 17 | 18 | // Load a word list file in a strings slice and return it 19 | func Load(filePath string) ([]string, error) { 20 | content, err := ioutil.ReadFile(filePath) 21 | if err != nil { 22 | return nil, err 23 | } 24 | words := strings.Split(string(content), "\n") 25 | words = TrimWords(words) 26 | words = FilterEmptyWords(words) 27 | return words, nil 28 | } 29 | 30 | // FilterEmptyWords remove empty words from the word list 31 | func FilterEmptyWords(words []string) []string { 32 | var filtered []string 33 | for _, word := range words { 34 | if len(word) > 0 { 35 | filtered = append(filtered, word) 36 | } 37 | } 38 | return filtered 39 | } 40 | 41 | // RemoveDuplicates remove duplicate strings from a slice 42 | func RemoveDuplicates(words []string) []string { 43 | m := map[string]bool{} 44 | cleaned := []string{} 45 | for _, str := range words { 46 | if _, seen := m[str]; !seen { 47 | cleaned = append(cleaned, str) 48 | m[str] = true 49 | } 50 | } 51 | return cleaned 52 | } 53 | 54 | // FilterUTF8 remove words with UTF8 encoded characters 55 | func FilterUTF8(words []string) []string { 56 | var filtered []string 57 | for _, word := range words { 58 | if utf8.RuneCountInString(word) == len(word) { 59 | filtered = append(filtered, word) 60 | } 61 | } 62 | return filtered 63 | } 64 | 65 | // FromCSV parse a CSV string into a cleaned slice of strings 66 | func FromCSV(csv string) []string { 67 | csv = strings.TrimSpace(csv) 68 | words := strings.Split(csv, ",") 69 | words = TrimWords(words) 70 | words = FilterEmptyWords(words) 71 | return words 72 | } 73 | -------------------------------------------------------------------------------- /wordlist/wordlist_test.go: -------------------------------------------------------------------------------- 1 | package wordlist 2 | 3 | import ( 4 | "io/ioutil" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/hgfischer/domainerator/tests" 9 | ) 10 | 11 | func TestTrimWords(t *testing.T) { 12 | words := []string{" word ", "word ", " word", "wor d", " wo rd ", " wo rd "} 13 | expected := []string{"word", "word", "word", "word", "word", "word"} 14 | words = TrimWords(words) 15 | if !reflect.DeepEqual(words, expected) { 16 | t.Errorf(tests.ErrFmtExpectedGot, "TrimWords", expected, words) 17 | } 18 | } 19 | 20 | func setupTestLoad(words []string) (string, error) { 21 | file, err := ioutil.TempFile("/tmp", "domainerator.wordlist.test.") 22 | if err != nil { 23 | return "", err 24 | } 25 | defer file.Close() 26 | for _, word := range words { 27 | file.WriteString(word + "\n") 28 | } 29 | return file.Name(), nil 30 | } 31 | 32 | func TestLoad(t *testing.T) { 33 | words := []string{" word ", "word ", " word", "wor d", " wo rd ", " wo rd ", " ", ""} 34 | expected := []string{"word", "word", "word", "word", "word", "word"} 35 | path, err := setupTestLoad(words) 36 | if err != nil { 37 | t.Fatalf(tests.ErrFmtStringAtString, "setupTestLoad", err, path) 38 | } 39 | words, err = Load(path) 40 | if err != nil { 41 | t.Fatalf(tests.ErrFmtStringAtString, "Load", err, path) 42 | } 43 | if !reflect.DeepEqual(words, expected) { 44 | t.Errorf(tests.ErrFmtExpectedGot, "Load", expected, words) 45 | } 46 | } 47 | 48 | func TestFilterEmptyWords(t *testing.T) { 49 | words := []string{"", "", "word", " ", " ", "", "", " word "} 50 | expected := []string{"word", " ", " ", " word "} 51 | words = FilterEmptyWords(words) 52 | if !reflect.DeepEqual(words, expected) { 53 | t.Errorf(tests.ErrFmtExpectedGot, "FilterEmptyWords", expected, words) 54 | } 55 | } 56 | 57 | func TestRemoveDuplicates(t *testing.T) { 58 | words := []string{"word", "word", "word", "list", "list", "word", "list"} 59 | expected := []string{"word", "list"} 60 | words = RemoveDuplicates(words) 61 | if !reflect.DeepEqual(words, expected) { 62 | t.Errorf(tests.ErrFmtExpectedGot, "RemoveDuplicates", expected, words) 63 | } 64 | } 65 | 66 | func TestFilterUTF8(t *testing.T) { 67 | words := []string{"word", "ẮẴÆƂƄḈḜ", "℥℗©ℌℹ", "∅⊆⊇∖", "▲◀►➣", "♔♛", "list"} 68 | expected := []string{"word", "list"} 69 | words = FilterUTF8(words) 70 | if !reflect.DeepEqual(words, expected) { 71 | t.Errorf(tests.ErrFmtExpectedGot, "FilterUTF8", expected, words) 72 | } 73 | } 74 | 75 | func TestFromCSV(t *testing.T) { 76 | csv := "some, Large, LIST ,, of,simple,words,,as, expec ted," 77 | expected := []string{"some", "Large", "LIST", "of", "simple", "words", "as", "expected"} 78 | words := FromCSV(csv) 79 | if !reflect.DeepEqual(words, expected) { 80 | t.Errorf(tests.ErrFmtExpectedGot, "FromCSV", expected, words) 81 | } 82 | } 83 | --------------------------------------------------------------------------------