package updater import ( "bytes" "context" "errors" "fmt" "io" "net/http" "net/url" "strings" "github.com/qdm12/gluetun/internal/provider/common" ) type apiServer struct { country string city string hostname string } var ( ErrDataMalformed = errors.New("data is malformed") ) const apiURL = "https://support.fastestvpn.com/wp-admin/admin-ajax.php" // The API URL and requests are shamelessly taken from network operations // done on the page https://support.fastestvpn.com/vpn-servers/ func fetchAPIServers(ctx context.Context, client *http.Client, protocol string) ( servers []apiServer, err error) { form := url.Values{ "action": []string{"vpn_servers"}, "protocol": []string{protocol}, } body := strings.NewReader(form.Encode()) request, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL, body) if err != nil { return nil, fmt.Errorf("creating request: %w", err) } request.Header.Set("Content-Type", "application/x-www-form-urlencoded") // request.Header.Set("User-Agent", "curl/8.9.0") // request.Header.Set("Accept", "*/*") response, err := client.Do(request) if err != nil { return nil, fmt.Errorf("sending request: %w", err) } if response.StatusCode != http.StatusOK { _ = response.Body.Close() return nil, fmt.Errorf("%w: %d", common.ErrHTTPStatusCodeNotOK, response.StatusCode) } data, err := io.ReadAll(response.Body) if err != nil { _ = response.Body.Close() return nil, fmt.Errorf("reading response body: %w", err) } err = response.Body.Close() if err != nil { return nil, fmt.Errorf("closing response body: %w", err) } const usualMaxNumber = 100 servers = make([]apiServer, 0, usualMaxNumber) for { trBlock := getNextTRBlock(data) if trBlock == nil { break } data = data[len(trBlock):] var server apiServer const numberOfTDBlocks = 3 for i := range numberOfTDBlocks { tdBlock := getNextTDBlock(trBlock) if tdBlock == nil { return nil, fmt.Errorf("%w: expected 3