github.com/jlevesy/mattermost-server@v5.3.2-0.20181003190404-7468f35cb0c8+incompatible/services/httpservice/client.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package httpservice
     5  
     6  import (
     7  	"context"
     8  	"crypto/tls"
     9  	"errors"
    10  	"net"
    11  	"net/http"
    12  	"time"
    13  
    14  	"github.com/mattermost/mattermost-server/model"
    15  )
    16  
    17  const (
    18  	connectTimeout = 3 * time.Second
    19  	requestTimeout = 30 * time.Second
    20  )
    21  
    22  var reservedIPRanges []*net.IPNet
    23  
    24  func IsReservedIP(ip net.IP) bool {
    25  	for _, ipRange := range reservedIPRanges {
    26  		if ipRange.Contains(ip) {
    27  			return true
    28  		}
    29  	}
    30  	return false
    31  }
    32  
    33  var defaultUserAgent string
    34  
    35  func init() {
    36  	for _, cidr := range []string{
    37  		// See https://tools.ietf.org/html/rfc6890
    38  		"0.0.0.0/8",      // This host on this network
    39  		"10.0.0.0/8",     // Private-Use
    40  		"127.0.0.0/8",    // Loopback
    41  		"169.254.0.0/16", // Link Local
    42  		"172.16.0.0/12",  // Private-Use Networks
    43  		"192.168.0.0/16", // Private-Use Networks
    44  		"::/128",         // Unspecified Address
    45  		"::1/128",        // Loopback Address
    46  		"fc00::/7",       // Unique-Local
    47  		"fe80::/10",      // Linked-Scoped Unicast
    48  	} {
    49  		_, parsed, err := net.ParseCIDR(cidr)
    50  		if err != nil {
    51  			panic(err)
    52  		}
    53  		reservedIPRanges = append(reservedIPRanges, parsed)
    54  	}
    55  	defaultUserAgent = "mattermost-" + model.CurrentVersion
    56  }
    57  
    58  type DialContextFunction func(ctx context.Context, network, addr string) (net.Conn, error)
    59  
    60  var AddressForbidden error = errors.New("address forbidden, you may need to set AllowedUntrustedInternalConnections to allow an integration access to your internal network")
    61  
    62  func dialContextFilter(dial DialContextFunction, allowHost func(host string) bool, allowIP func(ip net.IP) bool) DialContextFunction {
    63  	return func(ctx context.Context, network, addr string) (net.Conn, error) {
    64  		host, port, err := net.SplitHostPort(addr)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  
    69  		if allowHost != nil && allowHost(host) {
    70  			return dial(ctx, network, addr)
    71  		}
    72  
    73  		ips, err := net.LookupIP(host)
    74  		if err != nil {
    75  			return nil, err
    76  		}
    77  
    78  		var firstErr error
    79  		for _, ip := range ips {
    80  			select {
    81  			case <-ctx.Done():
    82  				return nil, ctx.Err()
    83  			default:
    84  			}
    85  
    86  			if allowIP == nil || !allowIP(ip) {
    87  				continue
    88  			}
    89  
    90  			conn, err := dial(ctx, network, net.JoinHostPort(ip.String(), port))
    91  			if err == nil {
    92  				return conn, nil
    93  			}
    94  			if firstErr == nil {
    95  				firstErr = err
    96  			}
    97  		}
    98  		if firstErr == nil {
    99  			return nil, AddressForbidden
   100  		}
   101  		return nil, firstErr
   102  	}
   103  }
   104  
   105  type Client struct {
   106  	*http.Client
   107  }
   108  
   109  func (c *Client) Do(req *http.Request) (*http.Response, error) {
   110  	req.Header.Set("User-Agent", defaultUserAgent)
   111  	return c.Client.Do(req)
   112  }
   113  
   114  // NewHTTPClient returns a variation the default implementation of Client.
   115  // It uses a Transport with the same settings as the default Transport
   116  // but with the following modifications:
   117  // - shorter timeout for dial and TLS handshake (defined as constant
   118  //   "connectTimeout")
   119  // - timeout for the end-to-end request (defined as constant
   120  //   "requestTimeout")
   121  func NewHTTPClient(enableInsecureConnections bool, allowHost func(host string) bool, allowIP func(ip net.IP) bool) *Client {
   122  	dialContext := (&net.Dialer{
   123  		Timeout:   connectTimeout,
   124  		KeepAlive: 30 * time.Second,
   125  	}).DialContext
   126  
   127  	if allowHost != nil || allowIP != nil {
   128  		dialContext = dialContextFilter(dialContext, allowHost, allowIP)
   129  	}
   130  
   131  	client := &http.Client{
   132  		Transport: &http.Transport{
   133  			Proxy:                 http.ProxyFromEnvironment,
   134  			DialContext:           dialContext,
   135  			MaxIdleConns:          100,
   136  			IdleConnTimeout:       90 * time.Second,
   137  			TLSHandshakeTimeout:   connectTimeout,
   138  			ExpectContinueTimeout: 1 * time.Second,
   139  			TLSClientConfig: &tls.Config{
   140  				InsecureSkipVerify: enableInsecureConnections,
   141  			},
   142  		},
   143  		Timeout: requestTimeout,
   144  	}
   145  
   146  	return &Client{Client: client}
   147  }