-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/forwarder: add pac-server and pac-eval commands
- PAC files can be read from: stdin, file path, http(s) URL - PAC are validated before running - Eval supports custom DNS the same way proxy does, Server doesn't - It produces meaningful error messages You can play with some test files from chromium-libpac. ``` go run ./cmd/forwarder pac-eval -p - https://www.google.com/ https://www.google.com/ https://www.google.com/ < pac/testdata/chromium-libpac/side_effects.js PROXY sideffect_0 PROXY sideffect_1 PROXY sideffect_2 ``` You can cross-check the implementations with: ``` go run ./cmd/forwarder pac-server -p pac/testdata/chromium-libpac/side_effects.js --protocol https & go run ./cmd/forwarder pac-eval --pac https://localhost:8080 http://www.google.com ``` Fixes #87
- Loading branch information
Showing
5 changed files
with
246 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package paceval | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"net/url" | ||
"os" | ||
"strings" | ||
|
||
"github.com/saucelabs/forwarder" | ||
"github.com/saucelabs/forwarder/bind" | ||
"github.com/saucelabs/forwarder/pac" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type command struct { | ||
pac *url.URL | ||
dnsConfig *forwarder.DNSConfig | ||
httpTransportConfig *forwarder.HTTPTransportConfig | ||
} | ||
|
||
func (c *command) RunE(cmd *cobra.Command, args []string) error { | ||
var resolver *net.Resolver | ||
if len(c.dnsConfig.Servers) > 0 { | ||
r, err := forwarder.NewResolver(c.dnsConfig, forwarder.NopLogger) | ||
if err != nil { | ||
return err | ||
} | ||
resolver = r | ||
} | ||
t := forwarder.NewHTTPTransport(c.httpTransportConfig, resolver) | ||
|
||
script, err := forwarder.ReadURL(c.pac, t) | ||
if err != nil { | ||
return fmt.Errorf("read PAC file: %w", err) | ||
} | ||
cfg := pac.ProxyResolverConfig{ | ||
Script: script, | ||
AlertSink: os.Stderr, | ||
} | ||
pr, err := pac.NewProxyResolver(&cfg, resolver) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
w := cmd.OutOrStdout() | ||
for _, arg := range args { | ||
u, err := url.Parse(arg) | ||
if err != nil { | ||
return fmt.Errorf("parse URL: %w", err) | ||
} | ||
proxy, err := pr.FindProxyForURL(u) | ||
if err != nil { | ||
return err | ||
} | ||
fmt.Fprintln(w, proxy) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func Command() (cmd *cobra.Command) { | ||
c := command{ | ||
pac: &url.URL{Scheme: "file", Path: "pac.js"}, | ||
dnsConfig: forwarder.DefaultDNSConfig(), | ||
httpTransportConfig: forwarder.DefaultHTTPTransportConfig(), | ||
} | ||
|
||
defer func() { | ||
fs := cmd.Flags() | ||
|
||
bind.PAC(fs, &c.pac) | ||
bind.DNSConfig(fs, c.dnsConfig) | ||
bind.HTTPTransportConfig(fs, c.httpTransportConfig) | ||
|
||
bind.MarkFlagFilename(cmd, "pac") | ||
}() | ||
return &cobra.Command{ | ||
Use: "pac-eval --pac <file|url> [flags] <url>...", | ||
Short: "Evaluate a PAC file for given URLs", | ||
Long: long, | ||
RunE: c.RunE, | ||
Example: example + "\n" + supportedFunctions(), | ||
} | ||
} | ||
|
||
const long = `Evaluate a PAC file for given URLs. | ||
The PAC file can be specified as a file path or URL with scheme "file", "http" or "https". | ||
The URLs to evaluate are passed as arguments. The output is a list of proxy strings, one per URL. | ||
The PAC file must contain FindProxyForURL or FindProxyForURLEx and must be valid. | ||
All PAC util functions are supported (see below). | ||
Alerts are written to stderr. | ||
` | ||
|
||
const example = ` # Evaluate a PAC file for a URL | ||
forwarder pac-eval --pac pac.js https://www.google.com | ||
# Evaluate a PAC file for multiple URLs | ||
forwarder pac-eval --pac pac.js https://www.google.com https://www.facebook.com | ||
# Evaluate a PAC file for multiple URLs using a PAC file from stdin | ||
cat pac.js | forwarder pac-eval --pac - https://www.google.com https://www.facebook.com | ||
# Evaluate a PAC file for multiple URLs using a PAC file from a URL | ||
forwarder pac-eval --pac https://example.com/pac.js https://www.google.com https://www.facebook.com | ||
` | ||
|
||
func supportedFunctions() string { | ||
var sb strings.Builder | ||
sb.WriteString("Supported PAC util functions:") | ||
for _, fn := range pac.SupportedFunctions() { | ||
sb.WriteString("\n ") | ||
sb.WriteString(fn) | ||
} | ||
return sb.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package pacserver | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"os/signal" | ||
"strings" | ||
"syscall" | ||
|
||
"github.com/saucelabs/forwarder" | ||
"github.com/saucelabs/forwarder/bind" | ||
"github.com/saucelabs/forwarder/log" | ||
"github.com/saucelabs/forwarder/log/stdlog" | ||
"github.com/saucelabs/forwarder/pac" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type command struct { | ||
pac *url.URL | ||
httpTransportConfig *forwarder.HTTPTransportConfig | ||
httpServerConfig *forwarder.HTTPServerConfig | ||
logConfig *log.Config | ||
} | ||
|
||
func (c *command) RunE(cmd *cobra.Command, args []string) error { | ||
t := forwarder.NewHTTPTransport(c.httpTransportConfig, nil) | ||
|
||
script, err := forwarder.ReadURL(c.pac, t) | ||
if err != nil { | ||
return fmt.Errorf("read PAC file: %w", err) | ||
} | ||
if _, err := pac.NewProxyResolver(&pac.ProxyResolverConfig{Script: script}, nil); err != nil { | ||
return err | ||
} | ||
|
||
if f := c.logConfig.File; f != nil { | ||
defer f.Close() | ||
} | ||
logger := stdlog.New(c.logConfig) | ||
|
||
s, err := forwarder.NewHTTPServer(c.httpServerConfig, servePAC(script), logger.Named("server")) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
ctx := context.Background() | ||
ctx, _ = signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) | ||
return s.Run(ctx) | ||
} | ||
|
||
func servePAC(script string) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") | ||
w.Write([]byte(script)) //nolint:errcheck // ignore it | ||
}) | ||
} | ||
|
||
func Command() (cmd *cobra.Command) { | ||
c := command{ | ||
pac: &url.URL{Scheme: "file", Path: "pac.js"}, | ||
httpTransportConfig: forwarder.DefaultHTTPTransportConfig(), | ||
httpServerConfig: forwarder.DefaultHTTPServerConfig(), | ||
logConfig: log.DefaultConfig(), | ||
} | ||
|
||
defer func() { | ||
fs := cmd.Flags() | ||
|
||
bind.PAC(fs, &c.pac) | ||
bind.HTTPTransportConfig(fs, c.httpTransportConfig) | ||
bind.HTTPServerConfig(fs, c.httpServerConfig, "") | ||
bind.LogConfig(fs, c.logConfig) | ||
|
||
bind.MarkFlagFilename(cmd, "pac", "cert-file", "key-file", "log-file") | ||
}() | ||
return &cobra.Command{ | ||
Use: "pac-server --pac <file|url> [--protocol <http|https|h2>] [--address <host:port>] [flags]", | ||
Short: "Start HTTP(S) server that serves a PAC file", | ||
Long: long, | ||
RunE: c.RunE, | ||
Example: example + "\n" + supportedFunctions(), | ||
} | ||
} | ||
|
||
const long = `Start HTTP(S) server that serves a PAC file. | ||
The PAC file can be specified as a file path or URL with scheme "file", "http" or "https". | ||
The PAC file must contain FindProxyForURL or FindProxyForURLEx and must be valid. | ||
All PAC util functions are supported (see below). | ||
Alerts are ignored. | ||
You can start HTTP, HTTPS or H2 (HTTPS) server. | ||
The server may be protected by basic authentication. | ||
If you start an HTTPS server and you don't provide a certificate, the server will generate a self-signed certificate on startup. | ||
` | ||
|
||
const example = ` # Start a HTTP server serving a PAC file | ||
pac-server --pac pac.js --protocol http --address localhost:8080 | ||
# Start a HTTPS server serving a PAC file | ||
pac-server --pac pac.js --protocol https --address localhost:80443 | ||
# Start a HTTPS server serving a PAC file with custom certificate | ||
pac-server --pac pac.js --protocol https --address localhost:80443 --cert-file cert.pem --key-file key.pem | ||
# Start a HTTPS server serving a PAC file with basic authentication | ||
pac-server --pac pac.js --protocol https --address localhost:80443 --basic-auth user:pass | ||
` | ||
|
||
func supportedFunctions() string { | ||
var sb strings.Builder | ||
sb.WriteString("Supported PAC util functions:") | ||
for _, fn := range pac.SupportedFunctions() { | ||
sb.WriteString("\n ") | ||
sb.WriteString(fn) | ||
} | ||
return sb.String() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters