git-lfs/lfshttp/proxy.go
brian m. carlson 6dfe9766e0
Add support for SOCKS proxies
Currently, we prepend "http://" to the proxy URL if it's not an HTTP or
HTTPS URL. Unfortunately, that breaks support for SOCKS 5 proxies, which
use URLs that start with "socks5://". Fix this by allowing URL schemes
starting with "socks" in addition to those starting with "http".

Note that this does not introduce any support for socks5h proxies, since
Go does not support them, but we will support them automatically once Go
does.
2019-06-06 13:59:27 +00:00

152 lines
3.6 KiB
Go

package lfshttp
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/git-lfs/git-lfs/config"
)
// Logic is copied, with small changes, from "net/http".ProxyFromEnvironment in the go std lib.
func proxyFromClient(c *Client) func(req *http.Request) (*url.URL, error) {
return func(req *http.Request) (*url.URL, error) {
httpsProxy, httpProxy, noProxy := getProxyServers(req.URL, c.uc, c.osEnv)
var proxy string
if req.URL.Scheme == "https" {
proxy = httpsProxy
}
if len(proxy) == 0 {
proxy = httpProxy
}
if len(proxy) == 0 {
return nil, nil
}
if !useProxy(noProxy, canonicalAddr(req.URL)) {
return nil, nil
}
proxyURL, err := url.Parse(proxy)
if err != nil || !(strings.HasPrefix(proxyURL.Scheme, "http") || strings.HasPrefix(proxyURL.Scheme, "socks")) {
// proxy was bogus. Try prepending "http://" to it and
// see if that parses correctly. If not, we fall
// through and complain about the original one.
if httpProxyURL, httpErr := url.Parse("http://" + proxy); httpErr == nil {
return httpProxyURL, nil
}
}
if err != nil {
return nil, fmt.Errorf("invalid proxy address: %q: %v", proxy, err)
}
return proxyURL, nil
}
}
func getProxyServers(u *url.URL, urlCfg *config.URLConfig, osEnv config.Environment) (httpsProxy string, httpProxy string, noProxy string) {
if osEnv == nil {
return
}
if len(httpsProxy) == 0 {
httpsProxy, _ = osEnv.Get("HTTPS_PROXY")
}
if len(httpsProxy) == 0 {
httpsProxy, _ = osEnv.Get("https_proxy")
}
if len(httpProxy) == 0 {
httpProxy, _ = osEnv.Get("HTTP_PROXY")
}
if len(httpProxy) == 0 {
httpProxy, _ = osEnv.Get("http_proxy")
}
if urlCfg != nil {
gitProxy, ok := urlCfg.Get("http", u.String(), "proxy")
if len(gitProxy) > 0 && ok {
if strings.HasPrefix(gitProxy, "https://") {
httpsProxy = gitProxy
}
httpProxy = gitProxy
}
}
noProxy, _ = osEnv.Get("NO_PROXY")
if len(noProxy) == 0 {
noProxy, _ = osEnv.Get("no_proxy")
}
return
}
// canonicalAddr returns url.Host but always with a ":port" suffix
// Copied from "net/http".ProxyFromEnvironment in the go std lib.
func canonicalAddr(url *url.URL) string {
addr := url.Host
if !hasPort(addr) {
return addr + ":" + portMap[url.Scheme]
}
return addr
}
// useProxy reports whether requests to addr should use a proxy,
// according to the noProxy or noProxy environment variable.
// addr is always a canonicalAddr with a host and port.
// Copied from "net/http".ProxyFromEnvironment in the go std lib
// and adapted to allow proxy usage even for localhost.
func useProxy(noProxy, addr string) bool {
if len(addr) == 0 {
return true
}
if noProxy == "*" {
return false
}
addr = strings.ToLower(strings.TrimSpace(addr))
if hasPort(addr) {
addr = addr[:strings.LastIndex(addr, ":")]
}
for _, p := range strings.Split(noProxy, ",") {
p = strings.ToLower(strings.TrimSpace(p))
if len(p) == 0 {
continue
}
if hasPort(p) {
p = p[:strings.LastIndex(p, ":")]
}
if addr == p {
return false
}
if p[0] == '.' && (strings.HasSuffix(addr, p) || addr == p[1:]) {
// noProxy ".foo.com" matches "bar.foo.com" or "foo.com"
return false
}
if p[0] != '.' && strings.HasSuffix(addr, p) && addr[len(addr)-len(p)-1] == '.' {
// noProxy "foo.com" matches "bar.foo.com"
return false
}
}
return true
}
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
// return true if the string includes a port.
// Copied from "net/http".ProxyFromEnvironment in the go std lib.
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
var (
portMap = map[string]string{
"http": "80",
"https": "443",
}
)