Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ go 1.13

require (
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d
github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da
gopkg.in/sourcemap.v1 v1.0.5 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d h1:1VUlQbCfkoSGv7qP7Y+ro3ap1P1pPZxgdGVqiTVy5C4=
github.com/robertkrimen/otto v0.0.0-20180617131154-15f95af6e78d/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf h1:7PflaKRtU4np/epFxRXlFhlzLXZzKFrH5/I4so5Ove0=
github.com/txthinking/runnergroup v0.0.0-20210608031112-152c7c4432bf/go.mod h1:CLUSJbazqETbaR+i0YAhXBICV9TrKH93pziccMhmhpM=
github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da h1:7x8pJcBTdKTBpQbRjZZc9o6CDquXBbvm9UIrR6ZSRJ4=
github.com/txthinking/socks5 v0.0.0-20210716140126-fa1f52a8f2da/go.mod h1:7NloQcrxaZYKURWph5HLxVDlIwMHJXCPkeWPtpftsIg=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe h1:gMWxZxBFRAXqoGkwkYlPX2zvyyKNWJpxOxCrjqJkm5A=
github.com/txthinking/x v0.0.0-20210326105829-476fab902fbe/go.mod h1:WgqbSEmUYSjEV3B1qmee/PpP2NYEz4bL9/+mF1ma+s4=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
14 changes: 13 additions & 1 deletion pac/otto.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type OttoEngine struct {
loader Loader
isStarted bool
vm *otto.Otto
cache map[string]Proxies
}

func (o *OttoEngine) Start() error {
Expand All @@ -70,6 +71,8 @@ func (o *OttoEngine) Start() error {
if o.isStarted {
return nil
}
// Init the cache when start, so the cache will be invalided when reload.
o.cache = make(map[string]Proxies)
log.Print("initialising OttoEngine")
vm := otto.New()

Expand Down Expand Up @@ -358,6 +361,10 @@ func (o *OttoEngine) FindProxyForURL(in *url.URL) (Proxies, error) {
o.mutex.Lock()
defer o.mutex.Unlock()

if cached, ok := o.cache[in.Hostname()]; ok {
return cached, nil
}

value, err := o.vm.Call("FindProxyForURL", nil, in.String(), in.Hostname())
if err != nil {
return Proxies{}, err
Expand All @@ -368,5 +375,10 @@ func (o *OttoEngine) FindProxyForURL(in *url.URL) (Proxies, error) {
return Proxies{}, err
}

return ParseFindProxyString(findProxyString)
r, err := ParseFindProxyString(findProxyString)
if err != nil {
return Proxies{}, err
}
o.cache[in.Hostname()] = r
return r, nil
}
9 changes: 8 additions & 1 deletion pac/pac.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,15 @@ type ProxyChecker interface {
}
*/

const (
ProxySchemeHttp = "http"
ProxySchemeHttps = "https"
ProxySchemeSocks5 = "socks5"
)

// Proxy information struct
type Proxy struct {
Scheme string
Hostname string
Port int
}
Expand All @@ -64,7 +71,7 @@ func (p Proxy) String() string {
if p == DirectProxy {
return "DIRECT"
}
return fmt.Sprintf("PROXY %s:%d", p.Hostname, p.Port)
return fmt.Sprintf("%s %s:%d", p.Scheme, p.Hostname, p.Port)
}

// DirectProxy is used to represent a "DIRECT" value
Expand Down
15 changes: 12 additions & 3 deletions pac/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ func ParseFindProxyString(s string) (Proxies, error) {
portStr string
portInt int
portErr error
scheme string
)
for _, statement := range pacStatementSplit.Split(s, 50) {
scheme = ""
if statement == "" {
continue
}
Expand All @@ -39,10 +41,18 @@ func ParseFindProxyString(s string) (Proxies, error) {
case "DIRECT":
proxies = append(proxies, DirectProxy)
case "PROXY":
scheme = ProxySchemeHttp
case "SOCKS5":
scheme = ProxySchemeSocks5
default:
return Proxies{}, fmt.Errorf("unsupported PAC command %q", part[0])
}

if scheme != "" {
if len(part) != 2 {
return Proxies{}, fmt.Errorf("unable to parse proxy details from %q", statement)
}
url, urlErr = url.Parse("http://" + part[1])
url, urlErr = url.Parse(scheme + "://" + part[1])
if urlErr != nil {
return Proxies{}, urlErr
}
Expand All @@ -56,11 +66,10 @@ func ParseFindProxyString(s string) (Proxies, error) {
return Proxies{}, portErr
}
proxies = append(proxies, Proxy{
Scheme: scheme,
Hostname: url.Hostname(),
Port: portInt,
})
default:
return Proxies{}, fmt.Errorf("unsupported PAC command %q", part[0])
}
}
return proxies, nil
Expand Down
41 changes: 35 additions & 6 deletions proxyhttphandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"sync"
"time"

"github.com/txthinking/socks5"
"github.com/williambailey/pacproxy/pac"
)

Expand Down Expand Up @@ -86,7 +87,8 @@ func (h *proxyHTTPHandler) lookupProxy(r *http.Request) (*url.URL, error) {
return nil, nil
}
proxyURL := &url.URL{
Host: fmt.Sprintf("%s:%d", proxy.Hostname, proxy.Port),
Scheme: proxy.Scheme,
Host: fmt.Sprintf("%s:%d", proxy.Hostname, proxy.Port),
}
if proxyAuth := r.Header.Get("Proxy-Authorization"); proxyAuth != "" {
if u, p, ok := parseBasicAuth(proxyAuth); ok {
Expand Down Expand Up @@ -118,7 +120,7 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request
return
}
defer serverConn.Close()
} else {
} else if proxyURL.Scheme == pac.ProxySchemeHttp {
serverConn, err = h.dialer.Dial("tcp", proxyURL.Hostname()+":"+proxyURL.Port())
if err != nil {
log.Printf("HTTP Connect Proxy %q: %d %s", r.URL, http.StatusBadGateway, err)
Expand All @@ -129,6 +131,14 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request
removeProxyHeaders(r)
//r.WriteProxy(serverConn)
r.Write(serverConn) // instead of WriteProxy as this will *hopefully* deal with CONNECT correctly.
} else if proxyURL.Scheme == pac.ProxySchemeSocks5 {
c, _ := socks5.NewClient(proxyURL.Hostname()+":"+proxyURL.Port(), "", "", 0, 0)
serverConn, err = c.Dial("tcp", r.URL.Host)
if err != nil {
log.Printf("socks5 dial fail:%s", err.Error())
http.Error(w, err.Error(), http.StatusBadGateway)
return
}
}
hj, ok := w.(http.Hijacker)
if !ok {
Expand All @@ -144,20 +154,39 @@ func (h *proxyHTTPHandler) doConnectProxy(w http.ResponseWriter, r *http.Request
return
}
defer clientConn.Close()
if proxyURL == nil {
if proxyURL == nil || proxyURL.Scheme != pac.ProxySchemeHttp {
clientConn.Write([]byte("HTTP/1.0 200 OK\r\n\r\n"))
}
var wg sync.WaitGroup
var closeOnce sync.Once
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(clientConn, serverConn)
clientConn.SetDeadline(time.Now().Add(10 * time.Millisecond))
n, err := io.Copy(clientConn, serverConn)
if err != nil {
log.Printf("%s iocopy from server to client fail:%s", r.URL.Hostname(), err.Error())
} else {
log.Printf("%s iocopy from server to client done: %d", r.URL.Hostname(), n)
}
closeOnce.Do(func() {
clientConn.Close()
serverConn.Close()
})
clientConn.SetDeadline(time.Now().Add(100 * time.Microsecond))
}()
wg.Add(1)
go func() {
defer wg.Done()
io.Copy(serverConn, clientConn)
n, err := io.Copy(serverConn, clientConn)
if err != nil {
log.Printf("%s iocopy from client to server fail: %d %s", r.URL.Hostname(), n, err.Error())
} else {
log.Printf("%s iocopy from client to server done: %d", r.URL.Hostname(), n)
}
closeOnce.Do(func() {
clientConn.Close()
serverConn.Close()
})
}()
wg.Wait()
}
Expand Down