diff --git a/internal/updater/errors.go b/internal/updater/errors.go new file mode 100644 index 00000000..761e5c94 --- /dev/null +++ b/internal/updater/errors.go @@ -0,0 +1,8 @@ +package updater + +import "errors" + +var ( + ErrHTTPStatusCodeNotOK = errors.New("HTTP status code not OK") + ErrUnmarshalResponseBody = errors.New("cannot unmarshal response body") +) diff --git a/internal/updater/mullvad.go b/internal/updater/mullvad.go index 3023e389..77dfb408 100644 --- a/internal/updater/mullvad.go +++ b/internal/updater/mullvad.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateMullvad(ctx context.Context) (err error) { @@ -26,15 +25,25 @@ func (u *updater) updateMullvad(ctx context.Context) (err error) { return nil } -func findMullvadServers(ctx context.Context, client network.Client) (servers []models.MullvadServer, err error) { +func findMullvadServers(ctx context.Context, client *http.Client) (servers []models.MullvadServer, err error) { const url = "https://api.mullvad.net/www/relays/openvpn/" - bytes, status, err := client.Get(ctx, url) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err } - if status != http.StatusOK { - return nil, fmt.Errorf("HTTP status code %d", status) + + response, err := client.Do(request) + if err != nil { + return nil, err } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %s for %s", ErrHTTPStatusCodeNotOK, response.Status, url) + } + + decoder := json.NewDecoder(response.Body) var data []struct { Country string `json:"country_name"` City string `json:"city_name"` @@ -44,9 +53,14 @@ func findMullvadServers(ctx context.Context, client network.Client) (servers []m IPv4 string `json:"ipv4_addr_in"` IPv6 string `json:"ipv6_addr_in"` } - if err := json.Unmarshal(bytes, &data); err != nil { + if err := decoder.Decode(&data); err != nil { return nil, err } + + if err := response.Body.Close(); err != nil { + return nil, err + } + serversByKey := map[string]models.MullvadServer{} for _, jsonServer := range data { if !jsonServer.Active { diff --git a/internal/updater/nordvpn.go b/internal/updater/nordvpn.go index 29c013b7..b82e8c81 100644 --- a/internal/updater/nordvpn.go +++ b/internal/updater/nordvpn.go @@ -12,7 +12,6 @@ import ( "strings" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateNordvpn(ctx context.Context) (err error) { @@ -38,16 +37,26 @@ var ( ErrInvalidIDInServerName = errors.New("invalid ID in server name") ) -func findNordvpnServers(ctx context.Context, client network.Client) ( +func findNordvpnServers(ctx context.Context, client *http.Client) ( servers []models.NordvpnServer, warnings []string, err error) { const url = "https://nordvpn.com/api/server" - bytes, status, err := client.Get(ctx, url) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err } - if status != http.StatusOK { - return nil, nil, fmt.Errorf("HTTP status code %d", status) + + response, err := client.Do(request) + if err != nil { + return nil, nil, err } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("%w: %s for %s", ErrHTTPStatusCodeNotOK, response.Status, url) + } + + decoder := json.NewDecoder(response.Body) var data []struct { IPAddress string `json:"ip_address"` Name string `json:"name"` @@ -57,9 +66,14 @@ func findNordvpnServers(ctx context.Context, client network.Client) ( TCP bool `json:"openvpn_tcp"` } `json:"features"` } - if err := json.Unmarshal(bytes, &data); err != nil { + if err := decoder.Decode(&data); err != nil { return nil, nil, err } + + if err := response.Body.Close(); err != nil { + return nil, nil, err + } + sort.Slice(data, func(i, j int) bool { if data[i].Country == data[j].Country { return data[i].Name < data[j].Name diff --git a/internal/updater/pia.go b/internal/updater/pia.go index 354231f8..278d36df 100644 --- a/internal/updater/pia.go +++ b/internal/updater/pia.go @@ -5,22 +5,39 @@ import ( "context" "encoding/json" "fmt" + "io/ioutil" "net" "net/http" "sort" - "strings" "github.com/qdm12/gluetun/internal/models" ) func (u *updater) updatePIA(ctx context.Context) (err error) { const url = "https://serverlist.piaservers.net/vpninfo/servers/v5" - b, status, err := u.client.Get(ctx, url) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return err } - if status != http.StatusOK { - return fmt.Errorf("HTTP status code %d: %s", status, strings.ReplaceAll(string(b), "\n", "")) + + response, err := u.client.Do(request) + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("%w: %s for %s", ErrHTTPStatusCodeNotOK, response.Status, url) + } + + b, err := ioutil.ReadAll(response.Body) + if err != nil { + return err + } + + if err := response.Body.Close(); err != nil { + return err } // remove key/signature at the bottom diff --git a/internal/updater/privado.go b/internal/updater/privado.go index a4113727..b83a70d6 100644 --- a/internal/updater/privado.go +++ b/internal/updater/privado.go @@ -3,10 +3,10 @@ package updater import ( "context" "fmt" + "net/http" "sort" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updatePrivado(ctx context.Context) (err error) { @@ -27,7 +27,7 @@ func (u *updater) updatePrivado(ctx context.Context) (err error) { return nil } -func findPrivadoServersFromZip(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( +func findPrivadoServersFromZip(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) ( servers []models.PrivadoServer, warnings []string, err error) { const zipURL = "https://privado.io/apps/ovpn_configs.zip" contents, err := fetchAndExtractFiles(ctx, client, zipURL) diff --git a/internal/updater/purevpn.go b/internal/updater/purevpn.go index 3b4b8732..7125ca8a 100644 --- a/internal/updater/purevpn.go +++ b/internal/updater/purevpn.go @@ -10,7 +10,6 @@ import ( "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/gluetun/internal/publicip" - "github.com/qdm12/golibs/network" ) func (u *updater) updatePurevpn(ctx context.Context) (err error) { @@ -31,7 +30,7 @@ func (u *updater) updatePurevpn(ctx context.Context) (err error) { return nil } -func findPurevpnServers(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( +func findPurevpnServers(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) ( servers []models.PurevpnServer, warnings []string, err error) { const zipURL = "https://s3-us-west-1.amazonaws.com/heartbleed/windows/New+OVPN+Files.zip" contents, err := fetchAndExtractFiles(ctx, client, zipURL) @@ -70,10 +69,7 @@ func findPurevpnServers(ctx context.Context, client network.Client, lookupIP loo continue } - // TODO remove once we move away from network.Client - const httpTimeout = 3 * time.Second - httpClient := &http.Client{Timeout: httpTimeout} - country, region, city, err := publicip.Info(ctx, httpClient, IPs[0]) + country, region, city, err := publicip.Info(ctx, client, IPs[0]) if err != nil { return nil, warnings, err } diff --git a/internal/updater/surfshark.go b/internal/updater/surfshark.go index 02c46084..e620304f 100644 --- a/internal/updater/surfshark.go +++ b/internal/updater/surfshark.go @@ -10,7 +10,6 @@ import ( "time" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateSurfshark(ctx context.Context) (err error) { @@ -32,21 +31,36 @@ func (u *updater) updateSurfshark(ctx context.Context) (err error) { } //nolint:deadcode,unused -func findSurfsharkServersFromAPI(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( +func findSurfsharkServersFromAPI(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) ( servers []models.SurfsharkServer, warnings []string, err error) { const url = "https://my.surfshark.com/vpn/api/v4/server/clusters" - b, status, err := client.Get(ctx, url) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, nil, err - } else if status != http.StatusOK { - return nil, nil, fmt.Errorf("HTTP status code %d", status) } + + response, err := client.Do(request) + if err != nil { + return nil, nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, nil, fmt.Errorf("%w: %s for %s", ErrHTTPStatusCodeNotOK, response.Status, url) + } + + decoder := json.NewDecoder(response.Body) var jsonServers []struct { Host string `json:"connectionName"` Country string `json:"country"` Location string `json:"location"` } - if err := json.Unmarshal(b, &jsonServers); err != nil { + if err := decoder.Decode(&jsonServers); err != nil { + return nil, nil, err + } + + if err := response.Body.Close(); err != nil { return nil, nil, err } @@ -80,7 +94,7 @@ func findSurfsharkServersFromAPI(ctx context.Context, client network.Client, loo return servers, warnings, nil } -func findSurfsharkServersFromZip(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( +func findSurfsharkServersFromZip(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) ( servers []models.SurfsharkServer, warnings []string, err error) { const zipURL = "https://my.surfshark.com/vpn/api/v1/server/configurations" contents, err := fetchAndExtractFiles(ctx, client, zipURL) diff --git a/internal/updater/torguard.go b/internal/updater/torguard.go index dac8b023..889b1536 100644 --- a/internal/updater/torguard.go +++ b/internal/updater/torguard.go @@ -4,12 +4,12 @@ import ( "context" "fmt" "net" + "net/http" "sort" "strconv" "strings" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateTorguard(ctx context.Context) (err error) { @@ -30,7 +30,7 @@ func (u *updater) updateTorguard(ctx context.Context) (err error) { return nil } -func findTorguardServersFromZip(ctx context.Context, client network.Client) ( +func findTorguardServersFromZip(ctx context.Context, client *http.Client) ( servers []models.TorguardServer, warnings []string, err error) { // Note: all servers do both TCP and UDP const zipURL = "https://torguard.net/downloads/OpenVPN-TCP-Linux.zip" diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 394bc3c2..c078792c 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -10,7 +10,6 @@ import ( "github.com/qdm12/gluetun/internal/configuration" "github.com/qdm12/gluetun/internal/models" "github.com/qdm12/golibs/logging" - "github.com/qdm12/golibs/network" ) type Updater interface { @@ -29,7 +28,7 @@ type updater struct { timeNow func() time.Time println func(s string) lookupIP lookupIPFunc - client network.Client + client *http.Client } func New(settings configuration.Updater, httpClient *http.Client, @@ -38,13 +37,12 @@ func New(settings configuration.Updater, httpClient *http.Client, settings.DNSAddress = "1.1.1.1" } resolver := newResolver(settings.DNSAddress) - const clientTimeout = 10 * time.Second return &updater{ logger: logger, timeNow: time.Now, println: func(s string) { fmt.Println(s) }, lookupIP: newLookupIP(resolver), - client: network.NewClient(clientTimeout), + client: httpClient, options: settings, servers: currentServers, } diff --git a/internal/updater/vyprvpn.go b/internal/updater/vyprvpn.go index d72832a8..288ce59c 100644 --- a/internal/updater/vyprvpn.go +++ b/internal/updater/vyprvpn.go @@ -3,11 +3,11 @@ package updater import ( "context" "fmt" + "net/http" "sort" "strings" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateVyprvpn(ctx context.Context) (err error) { @@ -28,7 +28,7 @@ func (u *updater) updateVyprvpn(ctx context.Context) (err error) { return nil } -func findVyprvpnServers(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( +func findVyprvpnServers(ctx context.Context, client *http.Client, lookupIP lookupIPFunc) ( servers []models.VyprvpnServer, warnings []string, err error) { const zipURL = "https://support.vyprvpn.com/hc/article_attachments/360052617332/Vypr_OpenVPN_20200320.zip" contents, err := fetchAndExtractFiles(ctx, client, zipURL) diff --git a/internal/updater/windscribe.go b/internal/updater/windscribe.go index b7dba9bd..f0a052ab 100644 --- a/internal/updater/windscribe.go +++ b/internal/updater/windscribe.go @@ -10,7 +10,6 @@ import ( "time" "github.com/qdm12/gluetun/internal/models" - "github.com/qdm12/golibs/network" ) func (u *updater) updateWindscribe(ctx context.Context) (err error) { @@ -26,16 +25,27 @@ func (u *updater) updateWindscribe(ctx context.Context) (err error) { return nil } -func findWindscribeServers(ctx context.Context, client network.Client) (servers []models.WindscribeServer, err error) { +func findWindscribeServers(ctx context.Context, client *http.Client) (servers []models.WindscribeServer, err error) { const baseURL = "https://assets.windscribe.com/serverlist/mob-v2/1/" cacheBreaker := time.Now().Unix() url := fmt.Sprintf("%s%d", baseURL, cacheBreaker) - content, status, err := client.Get(ctx, url) + + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err - } else if status != http.StatusOK { - return nil, fmt.Errorf(http.StatusText(status)) } + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %s", ErrHTTPStatusCodeNotOK, response.Status) + } + + decoder := json.NewDecoder(response.Body) var jsonData struct { Data []struct { Region string `json:"name"` @@ -48,9 +58,14 @@ func findWindscribeServers(ctx context.Context, client network.Client) (servers } `json:"groups"` } `json:"data"` } - if err := json.Unmarshal(content, &jsonData); err != nil { + if err := decoder.Decode(&jsonData); err != nil { + return nil, fmt.Errorf("%w: %s", ErrUnmarshalResponseBody, err) + } + + if err := response.Body.Close(); err != nil { return nil, err } + for _, regionBlock := range jsonData.Data { region := regionBlock.Region for _, group := range regionBlock.Groups { diff --git a/internal/updater/zip.go b/internal/updater/zip.go index 056353dd..c3fb4fd2 100644 --- a/internal/updater/zip.go +++ b/internal/updater/zip.go @@ -4,31 +4,42 @@ import ( "archive/zip" "bytes" "context" - "errors" "fmt" "io/ioutil" "net/http" "path/filepath" "strings" - - "github.com/qdm12/golibs/network" ) -var ( - ErrBadStatusCode = errors.New("bad HTTP status code") -) - -func fetchAndExtractFiles(ctx context.Context, client network.Client, urls ...string) ( +func fetchAndExtractFiles(ctx context.Context, client *http.Client, urls ...string) ( contents map[string][]byte, err error) { contents = make(map[string][]byte) for _, url := range urls { - zipBytes, status, err := client.Get(ctx, url) + request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return nil, err - } else if status != http.StatusOK { - return nil, fmt.Errorf("%w: fetching url %s: %d", ErrBadStatusCode, url, status) } - newContents, err := zipExtractAll(zipBytes) + + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return nil, fmt.Errorf("%w: %s for %s", ErrHTTPStatusCodeNotOK, response.Status, url) + } + + b, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + if err := response.Body.Close(); err != nil { + return nil, err + } + + newContents, err := zipExtractAll(b) if err != nil { return nil, err }