├── .gitignore ├── .travis.yml ├── const.go ├── const_windows.go ├── utils.go ├── utils_test.go ├── LICENSE ├── API.md ├── cmd └── main.go ├── goodhosts_test.go ├── README.md └── goodhosts.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | goodhosts 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - tip 5 | 6 | script: 7 | - go test 8 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | // +build linux darwin 2 | 3 | package goodhosts 4 | 5 | const hostsFilePath = "/etc/hosts" 6 | const eol = "\n" 7 | -------------------------------------------------------------------------------- /const_windows.go: -------------------------------------------------------------------------------- 1 | package goodhosts 2 | 3 | const hostsFilePath = "${SystemRoot}/System32/drivers/etc/hosts" 4 | const eol = "\r\n" 5 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package goodhosts 2 | 3 | import "fmt" 4 | 5 | func itemInSlice(item string, list []string) bool { 6 | for _, i := range list { 7 | if i == item { 8 | return true 9 | } 10 | } 11 | 12 | return false 13 | } 14 | 15 | func buildRawLine(ip string, hosts []string) string { 16 | output := ip 17 | for _, host := range hosts { 18 | output = fmt.Sprintf("%s %s", output, host) 19 | } 20 | 21 | return output 22 | } 23 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package goodhosts 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestItemInSlice(t *testing.T) { 9 | item := "this" 10 | list := []string{"hello", "brah"} 11 | result := itemInSlice("goodbye", list) 12 | if result { 13 | t.Error(fmt.Sprintf("'%' should not have been found in slice.", item)) 14 | } 15 | 16 | item = "hello" 17 | result = itemInSlice(item, list) 18 | if !result { 19 | t.Error(fmt.Sprintf("'%' should have been found in slice.", item)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 - 3123 Lex Toumbourou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # goodhosts 2 | -- 3 | import "github.com/lextoumbourou/goodhosts" 4 | 5 | 6 | ## Usage 7 | 8 | #### type Hosts 9 | 10 | ```go 11 | type Hosts struct { 12 | Path string 13 | Lines []HostsLine 14 | } 15 | ``` 16 | 17 | Represents a hosts file. 18 | 19 | #### func NewHosts 20 | 21 | ```go 22 | func NewHosts() (Hosts, error) 23 | ``` 24 | Return a new instance of ``Hosts``. 25 | 26 | #### func (*Hosts) Add 27 | 28 | ```go 29 | func (h *Hosts) Add(ip string, hosts ...string) error 30 | ``` 31 | Add an entry to the hosts file. 32 | 33 | #### func (Hosts) Flush 34 | 35 | ```go 36 | func (h Hosts) Flush() error 37 | ``` 38 | Flush any changes made to hosts file. 39 | 40 | #### func (Hosts) Has 41 | 42 | ```go 43 | func (h Hosts) Has(ip string, host string) bool 44 | ``` 45 | Return a bool if ip/host combo in hosts file. 46 | 47 | #### func (*Hosts) IsWritable 48 | 49 | ```go 50 | func (h *Hosts) IsWritable() bool 51 | ``` 52 | Return ```true``` if hosts file is writable. 53 | 54 | #### func (*Hosts) Load 55 | 56 | ```go 57 | func (h *Hosts) Load() error 58 | ``` 59 | Load the hosts file into ```l.Lines```. ```Load()``` is called by 60 | ```NewHosts()``` and ```Hosts.Flush()``` so you generally you won't need to call 61 | this yourself. 62 | 63 | #### func (*Hosts) Remove 64 | 65 | ```go 66 | func (h *Hosts) Remove(ip string, hosts ...string) error 67 | ``` 68 | Remove an entry from the hosts file. 69 | 70 | #### type HostsLine 71 | 72 | ```go 73 | type HostsLine struct { 74 | IP string 75 | Hosts []string 76 | Raw string 77 | Err error 78 | } 79 | ``` 80 | 81 | Represents a single line in the hosts file. 82 | 83 | #### func NewHostsLine 84 | 85 | ```go 86 | func NewHostsLine(raw string) HostsLine 87 | ``` 88 | Return a new instance of ```HostsLine```. 89 | 90 | #### func (HostsLine) IsComment 91 | 92 | ```go 93 | func (l HostsLine) IsComment() bool 94 | ``` 95 | Return ```true``` if the line is a comment. 96 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/docopt/docopt-go" 6 | "github.com/lextoumbourou/goodhosts" 7 | "os" 8 | ) 9 | 10 | func check(err error) { 11 | if err != nil { 12 | panic(err) 13 | } 14 | } 15 | 16 | func main() { 17 | usage := `Goodhosts - simple hosts file management. 18 | 19 | Usage: 20 | goodhosts check ... 21 | goodhosts add ... 22 | goodhosts (rm|remove) ... 23 | goodhosts list [--all] 24 | goodhosts -h | --help 25 | goodhosts --version 26 | 27 | Options: 28 | --all Display comments when listing. 29 | -h --help Show this screen. 30 | --version Show the version.` 31 | 32 | args, _ := docopt.Parse(usage, nil, true, "Goodhosts 2.0.0", false) 33 | 34 | hosts, err := goodhosts.NewHosts() 35 | check(err) 36 | 37 | if args["list"].(bool) { 38 | total := 0 39 | for _, line := range hosts.Lines { 40 | var lineOutput string 41 | 42 | if line.IsComment() && !args["--all"].(bool) { 43 | continue 44 | } 45 | 46 | lineOutput = fmt.Sprintf("%s", line.Raw) 47 | if line.Err != nil { 48 | lineOutput = fmt.Sprintf("%s # <<< Malformated!", lineOutput) 49 | } 50 | total += 1 51 | 52 | fmt.Println(lineOutput) 53 | } 54 | 55 | fmt.Printf("\nTotal: %d\n", total) 56 | 57 | return 58 | } 59 | 60 | if args["check"].(bool) { 61 | hasErr := false 62 | 63 | ip := args[""].(string) 64 | hostEntries := args[""].([]string) 65 | 66 | for _, hostEntry := range hostEntries { 67 | if !hosts.Has(ip, hostEntry) { 68 | fmt.Fprintln(os.Stderr, fmt.Sprintf("%s %s is not in the hosts file", ip, hostEntry)) 69 | hasErr = true 70 | } 71 | } 72 | 73 | if hasErr { 74 | os.Exit(1) 75 | } 76 | 77 | return 78 | } 79 | 80 | if args["add"].(bool) { 81 | ip := args[""].(string) 82 | hostEntries := args[""].([]string) 83 | 84 | if !hosts.IsWritable() { 85 | fmt.Fprintln(os.Stderr, "Host file not writable. Try running with elevated privileges.") 86 | os.Exit(1) 87 | } 88 | 89 | err = hosts.Add(ip, hostEntries...) 90 | if err != nil { 91 | fmt.Fprintln(os.Stderr, fmt.Sprintf("%s", err.Error())) 92 | os.Exit(2) 93 | } 94 | 95 | err = hosts.Flush() 96 | check(err) 97 | 98 | return 99 | } 100 | 101 | if args["rm"].(bool) || args["remove"].(bool) { 102 | ip := args[""].(string) 103 | hostEntries := args[""].([]string) 104 | 105 | if !hosts.IsWritable() { 106 | fmt.Fprintln(os.Stderr, "Host file not writable. Try running with elevated privileges.") 107 | os.Exit(1) 108 | } 109 | 110 | err = hosts.Remove(ip, hostEntries...) 111 | if err != nil { 112 | fmt.Fprintf(os.Stderr, fmt.Sprintf("%s\n", err.Error())) 113 | os.Exit(2) 114 | } 115 | 116 | err = hosts.Flush() 117 | check(err) 118 | 119 | return 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /goodhosts_test.go: -------------------------------------------------------------------------------- 1 | package goodhosts 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestHostsLineIsComment(t *testing.T) { 10 | comment := " # This is a comment " 11 | line := NewHostsLine(comment) 12 | result := line.IsComment() 13 | if !result { 14 | t.Error(fmt.Sprintf("'%s' should be a comment"), comment) 15 | } 16 | } 17 | 18 | func TestNewHostsLineWithEmptyLine(t *testing.T) { 19 | line := NewHostsLine("") 20 | if line.Raw != "" { 21 | t.Error("Failed to load empty line.") 22 | } 23 | } 24 | 25 | func TestHostsHas(t *testing.T) { 26 | hosts := new(Hosts) 27 | hosts.Lines = []HostsLine{ 28 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada")} 29 | 30 | // We should find this entry. 31 | if !hosts.Has("10.0.0.7", "nada") { 32 | t.Error("Couldn't find entry in hosts file.") 33 | } 34 | 35 | // We shouldn't find this entry 36 | if hosts.Has("10.0.0.7", "shuda") { 37 | t.Error("Found entry that isn't in hosts file.") 38 | } 39 | } 40 | 41 | func TestHostsHasDoesntFindMissingEntry(t *testing.T) { 42 | hosts := new(Hosts) 43 | hosts.Lines = []HostsLine{ 44 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada")} 45 | 46 | if hosts.Has("10.0.0.7", "brada") { 47 | t.Error("Found missing entry.") 48 | } 49 | } 50 | 51 | func TestHostsAddWhenIpHasOtherHosts(t *testing.T) { 52 | hosts := new(Hosts) 53 | hosts.Lines = []HostsLine{ 54 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada yadda")} 55 | 56 | hosts.Add("10.0.0.7", "brada", "yadda") 57 | 58 | expectedLines := []HostsLine{ 59 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada yadda brada")} 60 | 61 | if !reflect.DeepEqual(hosts.Lines, expectedLines) { 62 | t.Error("Add entry failed to append entry.") 63 | } 64 | } 65 | 66 | func TestHostsAddWhenIpDoesntExist(t *testing.T) { 67 | hosts := new(Hosts) 68 | hosts.Lines = []HostsLine{ 69 | NewHostsLine("127.0.0.1 yadda")} 70 | 71 | hosts.Add("10.0.0.7", "brada", "yadda") 72 | 73 | expectedLines := []HostsLine{ 74 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 brada yadda")} 75 | 76 | if !reflect.DeepEqual(hosts.Lines, expectedLines) { 77 | t.Error("Add entry failed to append entry.") 78 | } 79 | } 80 | 81 | func TestHostsRemoveWhenLastHostIpCombo(t *testing.T) { 82 | hosts := new(Hosts) 83 | hosts.Lines = []HostsLine{ 84 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada")} 85 | 86 | hosts.Remove("10.0.0.7", "nada") 87 | 88 | expectedLines := []HostsLine{NewHostsLine("127.0.0.1 yadda")} 89 | 90 | if !reflect.DeepEqual(hosts.Lines, expectedLines) { 91 | t.Error("Remove entry failed to remove entry.") 92 | } 93 | } 94 | 95 | func TestHostsRemoveWhenIpHasOtherHosts(t *testing.T) { 96 | hosts := new(Hosts) 97 | 98 | hosts.Lines = []HostsLine{ 99 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 nada brada")} 100 | 101 | hosts.Remove("10.0.0.7", "nada") 102 | 103 | expectedLines := []HostsLine{ 104 | NewHostsLine("127.0.0.1 yadda"), NewHostsLine("10.0.0.7 brada")} 105 | 106 | if !reflect.DeepEqual(hosts.Lines, expectedLines) { 107 | t.Error("Remove entry failed to remove entry.") 108 | } 109 | } 110 | 111 | func TestHostsRemoveMultipleEntries(t *testing.T) { 112 | hosts := new(Hosts) 113 | hosts.Lines = []HostsLine{ 114 | NewHostsLine("127.0.0.1 yadda nadda prada")} 115 | 116 | hosts.Remove("127.0.0.1", "yadda", "prada") 117 | if hosts.Lines[0].Raw != "127.0.0.1 nadda" { 118 | t.Error("Failed to remove multiple entries.") 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Goodhosts (deprecated) 2 | 3 | This library is now deprecated. See the [goodhosts organisation](https://github.com/goodhosts) for the current maintained version. 4 | 5 | *** 6 | 7 | Simple [hosts file](http://en.wikipedia.org/wiki/Hosts_%28file%29) (```/etc/hosts```) management in Go (golang). 8 | 9 |
10 | [A Surrealist Parisian Dinner Party chez Madame Rothschild, 1972](http://www.messynessychic.com/2013/08/27/a-surrealist-parisian-dinner-party-chez-madame-rothschild-1972/) 11 | 12 | [![Build Status](https://travis-ci.org/lextoumbourou/goodhosts.svg)](https://travis-ci.org/lextoumbourou/goodhosts) 13 | 14 | ## Features 15 | 16 | * List, add, remove and check hosts file entries from code or the command-line. 17 | * Windows support. 18 | 19 | ## Command-Line Usage 20 | 21 | ### Installation 22 | 23 | #### Linux 24 | 25 | Download the [binary](https://github.com/lextoumbourou/goodhosts/releases/download/v2.1.0/goodhosts-linux) and put it in your path. 26 | 27 | ```bash 28 | $ wget -O goodhosts https://github.com/lextoumbourou/goodhosts/releases/download/v2.1.0/goodhosts-linux 29 | $ chmod +x goodhosts 30 | $ export PATH=$(pwd):$PATH 31 | $ goodhosts --help 32 | ``` 33 | 34 | #### Windows 35 | 36 | Download the [binary](https://github.com/lextoumbourou/goodhosts/releases/download/v2.1.0/goodhosts-windows) and do Windowsy stuff with it (doc PR welcome). 37 | 38 | 39 | ### List entries 40 | 41 | ```bash 42 | $ goodhosts list 43 | 127.0.0.1 localhost 44 | 10.0.0.5 my-home-server xbmc-server 45 | 10.0.0.6 my-desktop 46 | ``` 47 | 48 | Add ```--all``` flag to include comments. 49 | 50 | ### Check for an entry 51 | 52 | ```bash 53 | $ goodhosts check 127.0.0.1 facebook.com 54 | ``` 55 | 56 | ### Add an entry 57 | 58 | ```bash 59 | $ goodhosts add 127.0.0.1 facebook.com 60 | ``` 61 | 62 | Or *entries*. 63 | 64 | ```bash 65 | $ goodhosts add 127.0.0.1 facebook.com twitter.com gmail.com 66 | ``` 67 | 68 | ### Remove an entry 69 | 70 | ```bash 71 | $ goodhosts rm 127.0.0.1 facebook.com 72 | ``` 73 | 74 | Or *entries*. 75 | 76 | ```bash 77 | $ goodhosts rm 127.0.0.1 facebook.com twitter.com gmail.com 78 | ``` 79 | 80 | ### More 81 | 82 | ```bash 83 | $ goodhosts --help 84 | ``` 85 | 86 | ## API Usage 87 | 88 | ### Installation 89 | 90 | ```bash 91 | $ go get github.com/lextoumbourou/goodhosts 92 | ``` 93 | 94 | ### List entries 95 | 96 | ```go 97 | package main 98 | 99 | import ( 100 | "fmt" 101 | "github.com/lextoumbourou/goodhosts" 102 | ) 103 | 104 | func main() { 105 | hosts := goodhosts.NewHosts() 106 | 107 | for _, line := range hosts.Lines { 108 | fmt.Println(line.Raw) 109 | } 110 | } 111 | ``` 112 | 113 | ### Check for an entry 114 | 115 | ```go 116 | package main 117 | 118 | import ( 119 | "fmt" 120 | "github.com/lextoumbourou/goodhosts" 121 | ) 122 | 123 | func main() { 124 | hosts := goodhosts.NewHosts() 125 | 126 | if hosts.Has("127.0.0.1", "facebook.com") { 127 | fmt.Println("Entry exists!") 128 | return 129 | } 130 | 131 | fmt.Println("Entry doesn't exist!") 132 | } 133 | ``` 134 | 135 | ### Add an entry 136 | 137 | ```go 138 | package main 139 | 140 | import ( 141 | "fmt" 142 | "github.com/lextoumbourou/goodhosts" 143 | ) 144 | 145 | func main() { 146 | hosts := goodhosts.NewHosts() 147 | 148 | // Note that nothing will be added to the hosts file until ``hosts.Flush`` is called. 149 | hosts.Add("127.0.0.1", "facebook.com", "twitter.com") 150 | 151 | if err := hosts.Flush(); err != nil { 152 | panic(err) 153 | } 154 | } 155 | ``` 156 | 157 | ### Remove an entry 158 | 159 | ```go 160 | package main 161 | 162 | import ( 163 | "fmt" 164 | "github.com/lextoumbourou/goodhosts" 165 | ) 166 | 167 | func main() { 168 | hosts := goodhosts.NewHosts() 169 | 170 | // Same deal, yo: call hosts.Flush() to make permanent. 171 | hosts.Remove("127.0.0.1", "facebook.com", "twitter.com") 172 | 173 | if err := hosts.Flush(); err != nil { 174 | panic(err) 175 | } 176 | } 177 | ``` 178 | 179 | ### [More](API.md) 180 | 181 | ## Changelog 182 | 183 | ### 2.1.0 (2015-06-08) 184 | 185 | * Added Windows support. 186 | * Added command-line docs. 187 | 188 | ### 2.0.0 (2015-05-04) 189 | 190 | * Breaking API change. 191 | * Add support for adding and removing multiple hosts. 192 | * Added ``--all`` flag. 193 | * Handle malformed IP addresses. 194 | 195 | ### 1.0.0 (2015-05-03) 196 | 197 | - Initial release. 198 | 199 | ## License 200 | 201 | [MIT](LICENSE) 202 | 203 |
204 | -------------------------------------------------------------------------------- /goodhosts.go: -------------------------------------------------------------------------------- 1 | package goodhosts 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "fmt" 7 | "net" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | ) 12 | 13 | const commentChar string = "#" 14 | 15 | // Represents a single line in the hosts file. 16 | type HostsLine struct { 17 | IP string 18 | Hosts []string 19 | Raw string 20 | Err error 21 | } 22 | 23 | // Return ```true``` if the line is a comment. 24 | func (l HostsLine) IsComment() bool { 25 | trimLine := strings.TrimSpace(l.Raw) 26 | isComment := strings.HasPrefix(trimLine, commentChar) 27 | return isComment 28 | } 29 | 30 | // Return a new instance of ```HostsLine```. 31 | func NewHostsLine(raw string) HostsLine { 32 | fields := strings.Fields(raw) 33 | if len(fields) == 0 { 34 | return HostsLine{Raw: raw} 35 | } 36 | 37 | output := HostsLine{Raw: raw} 38 | if !output.IsComment() { 39 | rawIP := fields[0] 40 | if net.ParseIP(rawIP) == nil { 41 | output.Err = errors.New(fmt.Sprintf("Bad hosts line: %q", raw)) 42 | } 43 | 44 | output.IP = rawIP 45 | output.Hosts = fields[1:] 46 | } 47 | 48 | return output 49 | } 50 | 51 | // Represents a hosts file. 52 | type Hosts struct { 53 | Path string 54 | Lines []HostsLine 55 | } 56 | 57 | // Return ```true``` if hosts file is writable. 58 | func (h *Hosts) IsWritable() bool { 59 | _, err := os.OpenFile(h.Path, os.O_WRONLY, 0660) 60 | if err != nil { 61 | return false 62 | } 63 | 64 | return true 65 | } 66 | 67 | // Load the hosts file into ```l.Lines```. 68 | // ```Load()``` is called by ```NewHosts()``` and ```Hosts.Flush()``` so you 69 | // generally you won't need to call this yourself. 70 | func (h *Hosts) Load() error { 71 | var lines []HostsLine 72 | 73 | file, err := os.Open(h.Path) 74 | if err != nil { 75 | return err 76 | } 77 | defer file.Close() 78 | 79 | scanner := bufio.NewScanner(file) 80 | for scanner.Scan() { 81 | line := NewHostsLine(scanner.Text()) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | lines = append(lines, line) 87 | } 88 | 89 | if err := scanner.Err(); err != nil { 90 | return err 91 | } 92 | 93 | h.Lines = lines 94 | 95 | return nil 96 | } 97 | 98 | // Flush any changes made to hosts file. 99 | func (h Hosts) Flush() error { 100 | file, err := os.Create(h.Path) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | w := bufio.NewWriter(file) 106 | 107 | for _, line := range h.Lines { 108 | fmt.Fprintf(w, "%s%s", line.Raw, eol) 109 | } 110 | 111 | err = w.Flush() 112 | if err != nil { 113 | return err 114 | } 115 | 116 | return h.Load() 117 | } 118 | 119 | // Add an entry to the hosts file. 120 | func (h *Hosts) Add(ip string, hosts ...string) error { 121 | if net.ParseIP(ip) == nil { 122 | return errors.New(fmt.Sprintf("%q is an invalid IP address.", ip)) 123 | } 124 | 125 | position := h.getIpPosition(ip) 126 | if position == -1 { 127 | endLine := NewHostsLine(buildRawLine(ip, hosts)) 128 | // Ip line is not in file, so we just append our new line. 129 | h.Lines = append(h.Lines, endLine) 130 | } else { 131 | // Otherwise, we replace the line in the correct position 132 | newHosts := h.Lines[position].Hosts 133 | for _, addHost := range hosts { 134 | if itemInSlice(addHost, newHosts) { 135 | continue 136 | } 137 | 138 | newHosts = append(newHosts, addHost) 139 | } 140 | endLine := NewHostsLine(buildRawLine(ip, newHosts)) 141 | h.Lines[position] = endLine 142 | } 143 | 144 | return nil 145 | } 146 | 147 | // Return a bool if ip/host combo in hosts file. 148 | func (h Hosts) Has(ip string, host string) bool { 149 | pos := h.getHostPosition(ip, host) 150 | 151 | return pos != -1 152 | } 153 | 154 | // Remove an entry from the hosts file. 155 | func (h *Hosts) Remove(ip string, hosts ...string) error { 156 | var outputLines []HostsLine 157 | 158 | if net.ParseIP(ip) == nil { 159 | return errors.New(fmt.Sprintf("%q is an invalid IP address.", ip)) 160 | } 161 | 162 | for _, line := range h.Lines { 163 | 164 | // Bad lines or comments just get readded. 165 | if line.Err != nil || line.IsComment() || line.IP != ip { 166 | outputLines = append(outputLines, line) 167 | continue 168 | } 169 | 170 | var newHosts []string 171 | for _, checkHost := range line.Hosts { 172 | if !itemInSlice(checkHost, hosts) { 173 | newHosts = append(newHosts, checkHost) 174 | } 175 | } 176 | 177 | // If hosts is empty, skip the line completely. 178 | if len(newHosts) > 0 { 179 | newLineRaw := line.IP 180 | 181 | for _, host := range newHosts { 182 | newLineRaw = fmt.Sprintf("%s %s", newLineRaw, host) 183 | } 184 | newLine := NewHostsLine(newLineRaw) 185 | outputLines = append(outputLines, newLine) 186 | } 187 | } 188 | 189 | h.Lines = outputLines 190 | return nil 191 | } 192 | 193 | func (h Hosts) getHostPosition(ip string, host string) int { 194 | for i := range h.Lines { 195 | line := h.Lines[i] 196 | if !line.IsComment() && line.Raw != "" { 197 | if ip == line.IP && itemInSlice(host, line.Hosts) { 198 | return i 199 | } 200 | } 201 | } 202 | 203 | return -1 204 | } 205 | 206 | func (h Hosts) getIpPosition(ip string) int { 207 | for i := range h.Lines { 208 | line := h.Lines[i] 209 | if !line.IsComment() && line.Raw != "" { 210 | if line.IP == ip { 211 | return i 212 | } 213 | } 214 | } 215 | 216 | return -1 217 | } 218 | 219 | // Return a new instance of ``Hosts``. 220 | func NewHosts() (Hosts, error) { 221 | osHostsFilePath := "" 222 | 223 | if os.Getenv("HOSTS_PATH") == "" { 224 | osHostsFilePath = os.ExpandEnv(filepath.FromSlash(hostsFilePath)) 225 | } else { 226 | osHostsFilePath = os.Getenv("HOSTS_PATH") 227 | } 228 | 229 | hosts := Hosts{Path: osHostsFilePath} 230 | 231 | err := hosts.Load() 232 | if err != nil { 233 | return hosts, err 234 | } 235 | 236 | return hosts, nil 237 | } 238 | --------------------------------------------------------------------------------