Files
3x-ui/internal/pia/http_helpers.go
T
Masterain bd6a6aba43 feat(pia): add PIA login-and-add WireGuard outbounds (#6272)
* 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.
2026-08-22 23:11:06 +02:00

101 lines
2.5 KiB
Go

package pia
import (
"bytes"
"context"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"mime"
"net"
"net/http"
"strings"
)
func readLimitedBody(body io.Reader, limit int64) ([]byte, error) {
raw, err := io.ReadAll(io.LimitReader(body, limit+1))
if err != nil {
return nil, err
}
if int64(len(raw)) > limit {
return nil, fmt.Errorf("response exceeds %d bytes", limit)
}
return raw, nil
}
func expectedContentType(header string, accepted ...string) bool {
mediaType, _, err := mime.ParseMediaType(header)
if err != nil {
return false
}
for _, candidate := range accepted {
if strings.EqualFold(mediaType, candidate) {
return true
}
}
return false
}
func noRedirect(_ *http.Request, _ []*http.Request) error {
return errors.New("redirects are disabled for this request")
}
func decodeSingleJSON(raw []byte, target any) error {
decoder := json.NewDecoder(bytes.NewReader(raw))
decoder.UseNumber()
if err := decoder.Decode(target); err != nil {
return err
}
var extra any
if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
if err == nil {
return errors.New("multiple JSON values are not allowed")
}
return err
}
return nil
}
func classifyNetworkError(ctx context.Context, fallback, message string, err error) error {
cause := redactNetErr(err)
if errors.Is(ctx.Err(), context.Canceled) || errors.Is(err, context.Canceled) {
return WrapError(CodeCancelled, "The operation was cancelled.", cause)
}
if errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
return WrapError(CodeTimeout, "The network request timed out.", cause)
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
return WrapError(CodeTimeout, "The network request timed out.", cause)
}
var unknownAuthority x509.UnknownAuthorityError
var hostnameError x509.HostnameError
var invalidCertificate x509.CertificateInvalidError
if errors.As(err, &unknownAuthority) || errors.As(err, &hostnameError) || errors.As(err, &invalidCertificate) {
return WrapError(CodeTLSValidation, "PIA's server identity could not be verified.", cause)
}
return WrapError(fallback, message, cause)
}
type redactedCause struct{ kind string }
func (e redactedCause) Error() string { return e.kind }
func redactNetErr(err error) error {
if err == nil {
return nil
}
return redactedCause{kind: "network error"}
}
func containsSecret(s string, secrets ...string) bool {
for _, secret := range secrets {
if secret != "" && strings.Contains(s, secret) {
return true
}
}
return false
}