summaryrefslogtreecommitdiffstats
path: root/services/httpservice/client.go
diff options
context:
space:
mode:
Diffstat (limited to 'services/httpservice/client.go')
-rw-r--r--services/httpservice/client.go133
1 files changed, 133 insertions, 0 deletions
diff --git a/services/httpservice/client.go b/services/httpservice/client.go
new file mode 100644
index 000000000..268f63b24
--- /dev/null
+++ b/services/httpservice/client.go
@@ -0,0 +1,133 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package httpservice
+
+import (
+ "context"
+ "crypto/tls"
+ "errors"
+ "net"
+ "net/http"
+ "time"
+)
+
+const (
+ connectTimeout = 3 * time.Second
+ requestTimeout = 30 * time.Second
+)
+
+var reservedIPRanges []*net.IPNet
+
+func IsReservedIP(ip net.IP) bool {
+ for _, ipRange := range reservedIPRanges {
+ if ipRange.Contains(ip) {
+ return true
+ }
+ }
+ return false
+}
+
+func init() {
+ for _, cidr := range []string{
+ // See https://tools.ietf.org/html/rfc6890
+ "0.0.0.0/8", // This host on this network
+ "10.0.0.0/8", // Private-Use
+ "127.0.0.0/8", // Loopback
+ "169.254.0.0/16", // Link Local
+ "172.16.0.0/12", // Private-Use Networks
+ "192.168.0.0/16", // Private-Use Networks
+ "::/128", // Unspecified Address
+ "::1/128", // Loopback Address
+ "fc00::/7", // Unique-Local
+ "fe80::/10", // Linked-Scoped Unicast
+ } {
+ _, parsed, err := net.ParseCIDR(cidr)
+ if err != nil {
+ panic(err)
+ }
+ reservedIPRanges = append(reservedIPRanges, parsed)
+ }
+}
+
+type DialContextFunction func(ctx context.Context, network, addr string) (net.Conn, error)
+
+var AddressForbidden error = errors.New("address forbidden, you may need to set AllowedUntrustedInternalConnections to allow an integration access to your internal network")
+
+func dialContextFilter(dial DialContextFunction, allowHost func(host string) bool, allowIP func(ip net.IP) bool) DialContextFunction {
+ return func(ctx context.Context, network, addr string) (net.Conn, error) {
+ host, port, err := net.SplitHostPort(addr)
+ if err != nil {
+ return nil, err
+ }
+
+ if allowHost != nil && allowHost(host) {
+ return dial(ctx, network, addr)
+ }
+
+ ips, err := net.LookupIP(host)
+ if err != nil {
+ return nil, err
+ }
+
+ var firstErr error
+ for _, ip := range ips {
+ select {
+ case <-ctx.Done():
+ return nil, ctx.Err()
+ default:
+ }
+
+ if allowIP == nil || !allowIP(ip) {
+ continue
+ }
+
+ conn, err := dial(ctx, network, net.JoinHostPort(ip.String(), port))
+ if err == nil {
+ return conn, nil
+ }
+ if firstErr == nil {
+ firstErr = err
+ }
+ }
+ if firstErr == nil {
+ return nil, AddressForbidden
+ }
+ return nil, firstErr
+ }
+}
+
+// NewHTTPClient returns a variation the default implementation of Client.
+// It uses a Transport with the same settings as the default Transport
+// but with the following modifications:
+// - shorter timeout for dial and TLS handshake (defined as constant
+// "connectTimeout")
+// - timeout for the end-to-end request (defined as constant
+// "requestTimeout")
+func NewHTTPClient(enableInsecureConnections bool, allowHost func(host string) bool, allowIP func(ip net.IP) bool) *http.Client {
+ dialContext := (&net.Dialer{
+ Timeout: connectTimeout,
+ KeepAlive: 30 * time.Second,
+ }).DialContext
+
+ if allowHost != nil || allowIP != nil {
+ dialContext = dialContextFilter(dialContext, allowHost, allowIP)
+ }
+
+ client := &http.Client{
+ Transport: &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: dialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: connectTimeout,
+ ExpectContinueTimeout: 1 * time.Second,
+ TLSClientConfig: &tls.Config{
+ InsecureSkipVerify: enableInsecureConnections,
+ },
+ },
+ Timeout: requestTimeout,
+ }
+
+ return client
+}