1
0

errors.go 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. package fetch
  2. import (
  3. "crypto/tls"
  4. "crypto/x509"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "net/url"
  9. )
  10. // Explain turns a failed request's error into a message a reader can act
  11. // on, keeping the original error wrapped. Errors it doesn't recognise are
  12. // returned unchanged.
  13. func Explain(err error) error {
  14. if err == nil {
  15. return nil
  16. }
  17. var host string
  18. var ue *url.Error
  19. if errors.As(err, &ue) {
  20. if u, perr := url.Parse(ue.URL); perr == nil {
  21. host = u.Hostname()
  22. }
  23. }
  24. var blocked *BlockedError
  25. var dnsErr *net.DNSError
  26. var certErr *tls.CertificateVerificationError
  27. var unknownAuthority x509.UnknownAuthorityError
  28. var hostnameErr x509.HostnameError
  29. var invalidCert x509.CertificateInvalidError
  30. switch {
  31. case errors.As(err, &blocked):
  32. return &explained{fmt.Sprintf("%s is a local or private address (%s); rekopub connects to those only with -LOCAL, when -URL is a local address too", orServer(host), blocked.Addr), err}
  33. case errors.As(err, &dnsErr):
  34. return &explained{fmt.Sprintf("can't find the server %s; check the address and your internet connection", dnsErr.Name), err}
  35. case isRefused(err):
  36. return &explained{fmt.Sprintf("%s refused the connection", orServer(host)), err}
  37. case errors.As(err, &certErr), errors.As(err, &unknownAuthority), errors.As(err, &hostnameErr), errors.As(err, &invalidCert):
  38. return &explained{fmt.Sprintf("%s's security certificate isn't valid, so the connection isn't safe", orServer(host)), err}
  39. case isTimeout(err):
  40. return &explained{fmt.Sprintf("%s took too long to respond", orServer(host)), err}
  41. }
  42. return err
  43. }
  44. func isTimeout(err error) bool {
  45. var t interface{ Timeout() bool }
  46. return errors.As(err, &t) && t.Timeout()
  47. }
  48. func orServer(host string) string {
  49. if host == "" {
  50. return "the server"
  51. }
  52. return host
  53. }
  54. // explained is an error with a friendlier message that still unwraps to
  55. // the original, so errors.Is and errors.As keep working.
  56. type explained struct {
  57. msg string
  58. err error
  59. }
  60. func (e *explained) Error() string { return e.msg }
  61. func (e *explained) Unwrap() error { return e.err }