Files
2024-03-05 02:32:38 -08:00

181 lines
3.9 KiB
Go

// Copyright (c) 2016-present Cloud <cloud@txthinking.com>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of version 3 of the GNU General Public
// License as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package brook
import (
"bytes"
"context"
"io"
"net/http"
"os"
"text/template"
"time"
"strings"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/js"
)
type PAC struct {
Addr string
File string
Proxy string
DomainURL string
DomainData []byte
HTTPServer *http.Server
Body []byte
}
func NewPAC(addr, file, proxy, domainURL string) *PAC {
p := &PAC{
Addr: addr,
File: file,
Proxy: proxy,
DomainURL: domainURL,
}
mux := http.NewServeMux()
mux.Handle("/", p)
p.HTTPServer = &http.Server{
Addr: p.Addr,
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20,
Handler: mux,
}
return p
}
func (p *PAC) MakeBody() (io.Reader, error) {
var err error
l := make([]string, 0)
if p.DomainURL != "" {
l, err = ReadList(p.DomainURL)
if err != nil {
return nil, err
}
}
if p.DomainData != nil {
b := bytes.TrimSpace(p.DomainData)
b = bytes.Replace(b, []byte{0x20}, []byte{}, -1)
b = bytes.Replace(b, []byte{0x0d, 0x0a}, []byte{0x0a}, -1)
l = strings.Split(string(b), "\n")
}
t := template.New("pac")
t, err = t.Parse(tpl)
if err != nil {
return nil, err
}
b := &bytes.Buffer{}
if err := t.Execute(b, map[string]interface{}{
"proxy": p.Proxy,
"domains": l,
}); err != nil {
return nil, err
}
b1 := &bytes.Buffer{}
m := minify.New()
m.AddFunc("application/javascript", js.Minify)
if err := m.Minify("application/javascript", b1, b); err != nil {
return nil, err
}
return b1, nil
}
func (p *PAC) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig")
w.Write(p.Body)
}
func (p *PAC) ListenAndServe() error {
r, err := p.MakeBody()
if err != nil {
return err
}
p.Body, err = io.ReadAll(r)
if err != nil {
return err
}
return p.HTTPServer.ListenAndServe()
}
func (p *PAC) Shutdown() error {
return p.HTTPServer.Shutdown(context.Background())
}
func (p *PAC) WriteToFile() error {
r, err := p.MakeBody()
if err != nil {
return err
}
b, err := io.ReadAll(r)
if err != nil {
return err
}
if err := os.WriteFile(p.File, b, 0644); err != nil {
return err
}
return nil
}
func (p *PAC) WriteToStdout() error {
r, err := p.MakeBody()
if err != nil {
return err
}
if _, err := io.Copy(os.Stdout, r); err != nil {
return err
}
return nil
}
var tpl = `
var proxy="{{.proxy}}";
var domains = {
{{range .domains}}
"{{.}}": 1,
{{end}}
};
function ip4todecimal(ip) {
var d = ip.split('.');
return ((((((+d[0])*256)+(+d[1]))*256)+(+d[2]))*256)+(+d[3]);
}
function FindProxyForURL(url, host){
if(/\d+\.\d+\.\d+\.\d+/.test(host)){
if (isInNet(dnsResolve(host), "10.0.0.0", "255.0.0.0") ||
isInNet(dnsResolve(host), "172.16.0.0", "255.240.0.0") ||
isInNet(dnsResolve(host), "192.168.0.0", "255.255.0.0") ||
isInNet(dnsResolve(host), "127.0.0.0", "255.255.255.0")){
return "DIRECT";
}
return "DIRECT";
}
if (isPlainHostName(host)){
return "DIRECT";
}
var a = host.split(".");
for(var i=a.length-1; i>=0; i--){
if (domains.hasOwnProperty(a.slice(i).join("."))){
return "DIRECT";
}
}
return proxy;
}
`