mirror of
https://github.com/zhigang1992/cow.git
synced 2026-01-12 08:54:17 +08:00
215 lines
4.8 KiB
Go
215 lines
4.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
)
|
|
|
|
var pac struct {
|
|
template *template.Template
|
|
topLevelDomain string
|
|
directList string
|
|
// Assignments and reads to directList are in different goroutines. Go
|
|
// does not guarantee atomic assignment, so we should protect these racing
|
|
// access.
|
|
dLRWMutex sync.RWMutex
|
|
}
|
|
|
|
func getDirectList() string {
|
|
pac.dLRWMutex.RLock()
|
|
dl := pac.directList
|
|
pac.dLRWMutex.RUnlock()
|
|
return dl
|
|
}
|
|
|
|
func updateDirectList() {
|
|
dl := strings.Join(siteStat.GetDirectList(), "\",\n\"")
|
|
pac.dLRWMutex.Lock()
|
|
pac.directList = dl
|
|
pac.dLRWMutex.Unlock()
|
|
}
|
|
|
|
func init() {
|
|
const pacRawTmpl = `var direct = 'DIRECT';
|
|
var httpProxy = 'PROXY {{.ProxyAddr}}; DIRECT';
|
|
|
|
var directList = [
|
|
"",
|
|
"{{.DirectDomains}}"
|
|
];
|
|
|
|
var directAcc = {};
|
|
for (var i = 0; i < directList.length; i += 1) {
|
|
directAcc[directList[i]] = true;
|
|
}
|
|
|
|
var topLevel = {
|
|
{{.TopLevel}}
|
|
};
|
|
|
|
// hostIsIP determines whether a host address is an IP address and whether
|
|
// it is private. Currenly only handles IPv4 addresses.
|
|
function hostIsIP(host) {
|
|
var part = host.split('.');
|
|
if (part.length != 4) {
|
|
return [false, false];
|
|
}
|
|
var n;
|
|
for (var i = 3; i >= 0; i--) {
|
|
if (part[i].length === 0 || part[i].length > 3) {
|
|
return [false, false];
|
|
}
|
|
n = Number(part[i]);
|
|
if (isNaN(n) || n < 0 || n > 255) {
|
|
return [false, false];
|
|
}
|
|
}
|
|
if (part[0] == '127' || part[0] == '10' || (part[0] == '192' && part[1] == '168')) {
|
|
return [true, true];
|
|
}
|
|
if (part[0] == '172') {
|
|
n = Number(part[1]);
|
|
if (16 <= n && n <= 31) {
|
|
return [true, true];
|
|
}
|
|
}
|
|
return [true, false];
|
|
}
|
|
|
|
function host2Domain(host) {
|
|
var arr, isIP, isPrivate;
|
|
arr = hostIsIP(host);
|
|
isIP = arr[0];
|
|
isPrivate = arr[1];
|
|
if (isPrivate) {
|
|
return "";
|
|
}
|
|
if (isIP) {
|
|
return host;
|
|
}
|
|
|
|
var lastDot = host.lastIndexOf('.');
|
|
if (lastDot === -1) {
|
|
return ""; // simple host name has no domain
|
|
}
|
|
// Find the second last dot
|
|
dot2ndLast = host.lastIndexOf(".", lastDot-1);
|
|
if (dot2ndLast === -1)
|
|
return host;
|
|
|
|
var part = host.substring(dot2ndLast+1, lastDot);
|
|
if (topLevel[part]) {
|
|
var dot3rdLast = host.lastIndexOf(".", dot2ndLast-1);
|
|
if (dot3rdLast === -1) {
|
|
return host;
|
|
}
|
|
return host.substring(dot3rdLast+1);
|
|
}
|
|
return host.substring(dot2ndLast+1);
|
|
}
|
|
|
|
function FindProxyForURL(url, host) {
|
|
if (url.substring(0,4) == "ftp:")
|
|
return direct;
|
|
if (host.indexOf(".local", host.length - 6) !== -1) {
|
|
return direct;
|
|
}
|
|
var domain = host2Domain(host);
|
|
if (host.length == domain.length) {
|
|
return directAcc[host] ? direct : httpProxy;
|
|
}
|
|
return (directAcc[host] || directAcc[domain]) ? direct : httpProxy;
|
|
}
|
|
`
|
|
var err error
|
|
pac.template, err = template.New("pac").Parse(pacRawTmpl)
|
|
if err != nil {
|
|
Fatal("Internal error on generating pac file template:", err)
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
for k, _ := range topLevelDomain {
|
|
buf.WriteString(fmt.Sprintf("\t\"%s\": true,\n", k))
|
|
}
|
|
pac.topLevelDomain = buf.String()[:buf.Len()-2] // remove the final comma
|
|
}
|
|
|
|
// No need for content-length as we are closing connection
|
|
var pacHeader = []byte("HTTP/1.1 200 OK\r\nServer: cow-proxy\r\n" +
|
|
"Content-Type: application/x-ns-proxy-autoconfig\r\nConnection: close\r\n\r\n")
|
|
|
|
// Different client will have different proxy URL, so generate it upon each request.
|
|
func genPAC(c *clientConn) []byte {
|
|
buf := new(bytes.Buffer)
|
|
|
|
hproxy, ok := c.proxy.(*httpProxy)
|
|
if !ok {
|
|
panic("sendPAC should only be called for http proxy")
|
|
}
|
|
|
|
proxyAddr := hproxy.addrInPAC
|
|
if proxyAddr == "" {
|
|
host, _, err := net.SplitHostPort(c.LocalAddr().String())
|
|
// This is the only check to split host port on tcp addr's string
|
|
// representation in COW. Keep it so we will notice if there's any
|
|
// problem in the future.
|
|
if err != nil {
|
|
panic("split host port on local address error")
|
|
}
|
|
proxyAddr = net.JoinHostPort(host, hproxy.port)
|
|
}
|
|
|
|
dl := getDirectList()
|
|
|
|
if dl == "" {
|
|
// Empty direct domain list
|
|
buf.Write(pacHeader)
|
|
pacproxy := fmt.Sprintf("function FindProxyForURL(url, host) { return 'PROXY %s; DIRECT'; };",
|
|
proxyAddr)
|
|
buf.Write([]byte(pacproxy))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
data := struct {
|
|
ProxyAddr string
|
|
DirectDomains string
|
|
TopLevel string
|
|
}{
|
|
proxyAddr,
|
|
dl,
|
|
pac.topLevelDomain,
|
|
}
|
|
|
|
buf.Write(pacHeader)
|
|
if err := pac.template.Execute(buf, data); err != nil {
|
|
errl.Println("Error generating pac file:", err)
|
|
panic("Error generating pac file")
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func initPAC() {
|
|
// we can't control goroutine scheduling, make sure when
|
|
// initPAC is done, direct list is updated
|
|
updateDirectList()
|
|
go func() {
|
|
for {
|
|
time.Sleep(time.Minute)
|
|
updateDirectList()
|
|
}
|
|
}()
|
|
}
|
|
|
|
func sendPAC(c *clientConn) error {
|
|
_, err := c.Write(genPAC(c))
|
|
if err != nil {
|
|
debug.Printf("cli(%s) error sending PAC: %s", c.RemoteAddr(), err)
|
|
}
|
|
return err
|
|
}
|