| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package client // import "miniflux.app/v2/internal/http/client"
- import (
- "context"
- "errors"
- "fmt"
- "net"
- "net/http"
- "time"
- "miniflux.app/v2/internal/urllib"
- )
- // ErrPrivateNetwork is returned when a connection to a private network is blocked.
- var ErrPrivateNetwork = errors.New("client: connection to private network is blocked")
- // Options holds configuration for creating an HTTP client.
- type Options struct {
- Timeout time.Duration
- BlockPrivateNetworks bool
- }
- // NewClientWithOptions creates a new HTTP client with the specified options.
- func NewClientWithOptions(opts Options) *http.Client {
- if !opts.BlockPrivateNetworks {
- return &http.Client{Timeout: opts.Timeout}
- }
- dialer := &net.Dialer{
- Timeout: opts.Timeout,
- }
- transport := &http.Transport{
- // The check is performed at connect time on the actual resolved IP, which eliminates TOCTOU / DNS-rebinding vulnerabilities.
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
- host, port, err := net.SplitHostPort(addr)
- if err != nil {
- return nil, fmt.Errorf("client: unable to parse address %q: %w", addr, err)
- }
- ips, err := net.LookupIP(host)
- if err != nil {
- return nil, fmt.Errorf("client: unable to resolve host %q: %w", host, err)
- }
- var safeIP net.IP
- for _, ip := range ips {
- if !urllib.IsNonPublicIP(ip) {
- safeIP = ip
- break
- }
- }
- if safeIP == nil {
- return nil, fmt.Errorf("%w: host %q resolves to a non-public IP address", ErrPrivateNetwork, host)
- }
- safeAddr := net.JoinHostPort(safeIP.String(), port)
- return dialer.DialContext(ctx, network, safeAddr)
- },
- }
- return &http.Client{
- Timeout: opts.Timeout,
- Transport: transport,
- }
- }
|