mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 11:57:15 +00:00
bd6a6aba43
* feat(pia): add login-and-add WireGuard outbounds (#2) * fix(pia): keep PIA outbounds identifiable after the editor strips hostname The outbound editor drops piaHostname, so last-segment matching failed for hyphenated servers. Identify rows by the computed tag, re-encrypt stored tokens onto the active key, skip unusable catalog rows, and always release the catalog refresh latch.
80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
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 ""
|
|
}
|