| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- package pia
- import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "path"
- "strings"
- )
- type ServerListSource interface {
- Fetch(ctx context.Context) (ServerListSnapshot, error)
- }
- type ServerListSnapshot struct {
- Payload []byte
- SchemaHint string
- SignatureVerified bool
- }
- type CatalogClient struct {
- Endpoint string
- PublicKeyPEM []byte
- HTTPClient *http.Client
- MaxBody int64
- UserAgent string
- }
- func NewCatalogClient(endpoint string, publicKey []byte) *CatalogClient {
- return &CatalogClient{
- Endpoint: endpoint,
- PublicKeyPEM: publicKey,
- MaxBody: DefaultMaxServerListBody,
- UserAgent: DefaultUserAgent,
- HTTPClient: &http.Client{Timeout: DefaultRequestTimeout, CheckRedirect: noRedirect},
- }
- }
- func (c *CatalogClient) Fetch(ctx context.Context) (ServerListSnapshot, error) {
- request, err := http.NewRequestWithContext(ctx, http.MethodGet, c.Endpoint, nil)
- if err != nil {
- return ServerListSnapshot{}, WrapError(CodeCatalogUnavailable, "The PIA region-list endpoint is invalid.", err)
- }
- request.Header.Set("Accept", "application/json, text/plain;q=0.9")
- request.Header.Set("User-Agent", c.UserAgent)
- response, err := c.HTTPClient.Do(request)
- if err != nil {
- return ServerListSnapshot{}, classifyNetworkError(ctx, CodeCatalogUnavailable, "The PIA region list could not be downloaded.", err)
- }
- defer response.Body.Close()
- if response.StatusCode != http.StatusOK {
- return ServerListSnapshot{}, NewError(CodeCatalogUnavailable, fmt.Sprintf("PIA returned HTTP %d for the region list.", response.StatusCode))
- }
- if !expectedContentType(response.Header.Get("Content-Type"), "application/json", "text/plain", "application/octet-stream") {
- return ServerListSnapshot{}, NewError(CodeCatalogSchemaUnsupported, "PIA returned an unexpected region-list content type.")
- }
- raw, err := readLimitedBody(response.Body, c.MaxBody)
- if err != nil {
- return ServerListSnapshot{}, WrapError(CodeCatalogUnavailable, "The PIA region-list response is too large or incomplete.", err)
- }
- verified, err := VerifySignedServerList(raw, c.PublicKeyPEM)
- if err != nil {
- return ServerListSnapshot{}, err
- }
- return ServerListSnapshot{Payload: verified, SchemaHint: schemaHint(c.Endpoint), SignatureVerified: true}, nil
- }
- func schemaHint(endpoint string) string {
- parsed, err := url.Parse(endpoint)
- if err != nil {
- return ""
- }
- base := strings.ToLower(path.Base(parsed.Path))
- if base == "v6" || base == "v7" {
- return strings.TrimPrefix(base, "v")
- }
- return ""
- }
|