mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-24 03:47: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.
101 lines
2.5 KiB
Go
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
|
|
}
|