client.go 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package client // import "miniflux.app/v2/internal/http/client"
  4. import (
  5. "context"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "net/http"
  10. "time"
  11. "miniflux.app/v2/internal/urllib"
  12. )
  13. // ErrPrivateNetwork is returned when a connection to a private network is blocked.
  14. var ErrPrivateNetwork = errors.New("client: connection to private network is blocked")
  15. // Options holds configuration for creating an HTTP client.
  16. type Options struct {
  17. Timeout time.Duration
  18. BlockPrivateNetworks bool
  19. }
  20. // NewClientWithOptions creates a new HTTP client with the specified options.
  21. func NewClientWithOptions(opts Options) *http.Client {
  22. if !opts.BlockPrivateNetworks {
  23. return &http.Client{Timeout: opts.Timeout}
  24. }
  25. dialer := &net.Dialer{
  26. Timeout: opts.Timeout,
  27. }
  28. transport := &http.Transport{
  29. // The check is performed at connect time on the actual resolved IP, which eliminates TOCTOU / DNS-rebinding vulnerabilities.
  30. DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
  31. host, port, err := net.SplitHostPort(addr)
  32. if err != nil {
  33. return nil, fmt.Errorf("client: unable to parse address %q: %w", addr, err)
  34. }
  35. ips, err := net.LookupIP(host)
  36. if err != nil {
  37. return nil, fmt.Errorf("client: unable to resolve host %q: %w", host, err)
  38. }
  39. var safeIP net.IP
  40. for _, ip := range ips {
  41. if !urllib.IsNonPublicIP(ip) {
  42. safeIP = ip
  43. break
  44. }
  45. }
  46. if safeIP == nil {
  47. return nil, fmt.Errorf("%w: host %q resolves to a non-public IP address", ErrPrivateNetwork, host)
  48. }
  49. safeAddr := net.JoinHostPort(safeIP.String(), port)
  50. return dialer.DialContext(ctx, network, safeAddr)
  51. },
  52. }
  53. return &http.Client{
  54. Timeout: opts.Timeout,
  55. Transport: transport,
  56. }
  57. }