http_helpers.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package pia
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/x509"
  6. "encoding/json"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "mime"
  11. "net"
  12. "net/http"
  13. "strings"
  14. )
  15. func readLimitedBody(body io.Reader, limit int64) ([]byte, error) {
  16. raw, err := io.ReadAll(io.LimitReader(body, limit+1))
  17. if err != nil {
  18. return nil, err
  19. }
  20. if int64(len(raw)) > limit {
  21. return nil, fmt.Errorf("response exceeds %d bytes", limit)
  22. }
  23. return raw, nil
  24. }
  25. func expectedContentType(header string, accepted ...string) bool {
  26. mediaType, _, err := mime.ParseMediaType(header)
  27. if err != nil {
  28. return false
  29. }
  30. for _, candidate := range accepted {
  31. if strings.EqualFold(mediaType, candidate) {
  32. return true
  33. }
  34. }
  35. return false
  36. }
  37. func noRedirect(_ *http.Request, _ []*http.Request) error {
  38. return errors.New("redirects are disabled for this request")
  39. }
  40. func decodeSingleJSON(raw []byte, target any) error {
  41. decoder := json.NewDecoder(bytes.NewReader(raw))
  42. decoder.UseNumber()
  43. if err := decoder.Decode(target); err != nil {
  44. return err
  45. }
  46. var extra any
  47. if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
  48. if err == nil {
  49. return errors.New("multiple JSON values are not allowed")
  50. }
  51. return err
  52. }
  53. return nil
  54. }
  55. func classifyNetworkError(ctx context.Context, fallback, message string, err error) error {
  56. cause := redactNetErr(err)
  57. if errors.Is(ctx.Err(), context.Canceled) || errors.Is(err, context.Canceled) {
  58. return WrapError(CodeCancelled, "The operation was cancelled.", cause)
  59. }
  60. if errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
  61. return WrapError(CodeTimeout, "The network request timed out.", cause)
  62. }
  63. var netErr net.Error
  64. if errors.As(err, &netErr) && netErr.Timeout() {
  65. return WrapError(CodeTimeout, "The network request timed out.", cause)
  66. }
  67. var unknownAuthority x509.UnknownAuthorityError
  68. var hostnameError x509.HostnameError
  69. var invalidCertificate x509.CertificateInvalidError
  70. if errors.As(err, &unknownAuthority) || errors.As(err, &hostnameError) || errors.As(err, &invalidCertificate) {
  71. return WrapError(CodeTLSValidation, "PIA's server identity could not be verified.", cause)
  72. }
  73. return WrapError(fallback, message, cause)
  74. }
  75. type redactedCause struct{ kind string }
  76. func (e redactedCause) Error() string { return e.kind }
  77. func redactNetErr(err error) error {
  78. if err == nil {
  79. return nil
  80. }
  81. return redactedCause{kind: "network error"}
  82. }
  83. func containsSecret(s string, secrets ...string) bool {
  84. for _, secret := range secrets {
  85. if secret != "" && strings.Contains(s, secret) {
  86. return true
  87. }
  88. }
  89. return false
  90. }