Bug fix: PureVPN updater from ZIP files

- Fix #317
- Refers to #320
This commit is contained in:
Quentin McGaw
2020-12-31 21:07:30 +00:00
parent a56471fe73
commit b8cb181070
4 changed files with 97 additions and 61 deletions

View File

@@ -62,7 +62,7 @@ func Test_versions(t *testing.T) {
"Purevpn": { "Purevpn": {
model: models.PurevpnServer{}, model: models.PurevpnServer{},
version: allServers.Purevpn.Version, version: allServers.Purevpn.Version,
digest: "cc1a2219", digest: "ada45379",
}, },
"Surfshark": { "Surfshark": {
model: models.SurfsharkServer{}, model: models.SurfsharkServer{},

View File

@@ -96,15 +96,15 @@ func (s *NordvpnServer) String() string {
} }
type PurevpnServer struct { type PurevpnServer struct {
Region string `json:"region"`
Country string `json:"country"` Country string `json:"country"`
Region string `json:"region"`
City string `json:"city"` City string `json:"city"`
IPs []net.IP `json:"ips"` IPs []net.IP `json:"ips"`
} }
func (s *PurevpnServer) String() string { func (s *PurevpnServer) String() string {
return fmt.Sprintf("{Region: %q, Country: %q, City: %q, IPs: %s}", return fmt.Sprintf("{Country: %q, Region: %q, City: %q, IPs: %s}",
s.Region, s.Country, s.City, goStringifyIPs(s.IPs)) s.Country, s.Region, s.City, goStringifyIPs(s.IPs))
} }
type PrivadoServer struct { type PrivadoServer struct {

View File

@@ -2,8 +2,16 @@ package updater
import ( import (
"bytes" "bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net" "net"
"net/http"
"sort" "sort"
"strings"
"github.com/qdm12/golibs/network"
) )
func uniqueSortedIPs(ips []net.IP) []net.IP { func uniqueSortedIPs(ips []net.IP) []net.IP {
@@ -25,3 +33,35 @@ func uniqueSortedIPs(ips []net.IP) []net.IP {
}) })
return ips return ips
} }
var errBadHTTPStatus = errors.New("bad HTTP status received")
type ipInfoData struct {
Region string `json:"region"`
Country string `json:"country"`
City string `json:"city"`
}
func getIPInfo(ctx context.Context, client network.Client, ip net.IP) (country, region, city string, err error) {
const baseURL = "https://ipinfo.io/"
url := baseURL + ip.String()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return "", "", "", err
}
b, status, err := client.Do(request)
if err != nil {
return "", "", "", err
} else if status != http.StatusOK {
return "", "", "", fmt.Errorf("%w: %d", errBadHTTPStatus, status)
}
var data ipInfoData
if err := json.Unmarshal(b, &data); err != nil {
return "", "", "", err
}
country, ok := getCountryCodes()[strings.ToLower(data.Country)]
if !ok {
country = data.Country
}
return country, data.Region, data.City, nil
}

View File

@@ -2,9 +2,7 @@ package updater
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"net/http"
"sort" "sort"
"strings" "strings"
@@ -32,74 +30,72 @@ func (u *updater) updatePurevpn(ctx context.Context) (err error) {
func findPurevpnServers(ctx context.Context, client network.Client, lookupIP lookupIPFunc) ( func findPurevpnServers(ctx context.Context, client network.Client, lookupIP lookupIPFunc) (
servers []models.PurevpnServer, warnings []string, err error) { servers []models.PurevpnServer, warnings []string, err error) {
const url = "https://support.purevpn.com/vpn-servers" const zipURL = "https://s3-us-west-1.amazonaws.com/heartbleed/windows/New+OVPN+Files.zip"
bytes, status, err := client.Get(ctx, url) contents, err := fetchAndExtractFiles(ctx, client, zipURL)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if status != http.StatusOK { uniqueServers := map[string]models.PurevpnServer{}
return nil, nil, fmt.Errorf("HTTP status code %d", status) for fileName, content := range contents {
}
const jsonPrefix = "<script>var servers = "
const jsonSuffix = "</script>"
s := string(bytes)
jsonPrefixIndex := strings.Index(s, jsonPrefix)
if jsonPrefixIndex == -1 {
return nil, nil, fmt.Errorf("cannot find %q in html", jsonPrefix)
}
s = s[jsonPrefixIndex+len(jsonPrefix):]
endIndex := strings.Index(s, jsonSuffix)
if endIndex == -1 {
return nil, nil, fmt.Errorf("cannot find %q after %q in html", jsonSuffix, jsonPrefix)
}
s = s[:endIndex]
var data []struct {
Region string `json:"region_name"`
Country string `json:"country_name"`
City string `json:"city_name"`
TCP string `json:"tcp"`
UDP string `json:"udp"`
}
if err := json.Unmarshal([]byte(s), &data); err != nil {
return nil, nil, err
}
sort.Slice(data, func(i, j int) bool {
if data[i].Region == data[j].Region {
if data[i].Country == data[j].Country {
return data[i].City < data[j].City
}
return data[i].Country < data[j].Country
}
return data[i].Region < data[j].Region
})
for _, jsonServer := range data {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return nil, warnings, err return nil, warnings, err
} }
if jsonServer.UDP == "" && jsonServer.TCP == "" { if strings.HasSuffix(fileName, "-tcp.ovpn") {
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP and UDP for openvpn", continue // only parse UDP files
jsonServer.Region, jsonServer.Country, jsonServer.City))
continue
} }
if jsonServer.UDP == "" || jsonServer.TCP == "" { host, warning, err := extractHostFromOVPN(content)
warnings = append(warnings, fmt.Sprintf("server %s %s %s does not support TCP or UDP for openvpn", if len(warning) > 0 {
jsonServer.Region, jsonServer.Country, jsonServer.City)) warnings = append(warnings, warning)
continue }
if err != nil {
return nil, warnings, fmt.Errorf("%w in %q", err, fileName)
} }
host := jsonServer.UDP
const repetition = 5 const repetition = 5
IPs, err := resolveRepeat(ctx, lookupIP, host, repetition) IPs, err := resolveRepeat(ctx, lookupIP, host, repetition)
if err != nil { switch {
warnings = append(warnings, err.Error()) case err != nil:
return nil, warnings, err
case len(IPs) == 0:
warning := fmt.Sprintf("no IP address found for host %q", host)
warnings = append(warnings, warning)
continue continue
} }
servers = append(servers, models.PurevpnServer{ country, region, city, err := getIPInfo(ctx, client, IPs[0])
Region: jsonServer.Region, if err != nil {
Country: jsonServer.Country, return nil, warnings, err
City: jsonServer.City,
IPs: IPs,
})
} }
key := country + region + city
server, ok := uniqueServers[key]
if ok {
server.IPs = append(server.IPs, IPs...)
} else {
server = models.PurevpnServer{
Country: country,
Region: region,
City: city,
IPs: IPs,
}
}
uniqueServers[key] = server
}
servers = make([]models.PurevpnServer, len(uniqueServers))
i := 0
for _, server := range uniqueServers {
servers[i] = server
i++
}
sort.Slice(servers, func(i, j int) bool {
if servers[i].Country == servers[j].Country {
if servers[i].Region == servers[j].Region {
return servers[i].City < servers[j].City
}
return servers[i].Region < servers[j].Region
}
return servers[i].Country < servers[j].Country
})
return servers, warnings, nil return servers, warnings, nil
} }