2017-05-31 16:34:05 +02:00
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
2018-09-26 12:42:51 -04:00
package httpservice
2017-05-31 16:34:05 +02:00
import (
2017-08-09 15:49:07 -05:00
"context"
2017-05-31 16:34:05 +02:00
"crypto/tls"
2017-08-09 15:49:07 -05:00
"errors"
2017-05-31 16:34:05 +02:00
"net"
"net/http"
"time"
)
const (
connectTimeout = 3 * time . Second
2017-06-15 11:05:43 -04:00
requestTimeout = 30 * time . Second
2017-05-31 16:34:05 +02:00
)
2017-08-09 15:49:07 -05:00
var reservedIPRanges [ ] * net . IPNet
2017-11-22 09:15:03 -06:00
func IsReservedIP ( ip net . IP ) bool {
2017-08-09 15:49:07 -05:00
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 )
2018-09-02 00:30:10 -07:00
var AddressForbidden error = errors . New ( "address forbidden, you may need to set AllowedUntrustedInternalConnections to allow an integration access to your internal network" )
2017-08-09 15:49:07 -05:00
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
}
}
2017-11-22 09:15:03 -06:00
// 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 {
2017-08-09 15:49:07 -05:00
dialContext := ( & net . Dialer {
Timeout : connectTimeout ,
KeepAlive : 30 * time . Second ,
} ) . DialContext
if allowHost != nil || allowIP != nil {
dialContext = dialContextFilter ( dialContext , allowHost , allowIP )
}
2017-05-31 16:34:05 +02:00
client := & http . Client {
Transport : & http . Transport {
2017-08-09 15:49:07 -05:00
Proxy : http . ProxyFromEnvironment ,
DialContext : dialContext ,
2017-05-31 16:34:05 +02:00
MaxIdleConns : 100 ,
IdleConnTimeout : 90 * time . Second ,
TLSHandshakeTimeout : connectTimeout ,
ExpectContinueTimeout : 1 * time . Second ,
TLSClientConfig : & tls . Config {
InsecureSkipVerify : enableInsecureConnections ,
} ,
} ,
Timeout : requestTimeout ,
}
return client
}