mirror of
https://github.com/stashapp/stash.git
synced 2025-12-18 04:44:37 +03:00
Allow use of Proxy (#3284)
* Proxy config * Disable proxy for localhost & local LAN * No_proxy is now configurable
This commit is contained in:
@@ -109,8 +109,12 @@ type githubTagResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func makeGithubRequest(ctx context.Context, url string, output interface{}) error {
|
func makeGithubRequest(ctx context.Context, url string, output interface{}) error {
|
||||||
|
|
||||||
|
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
||||||
|
|
||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 3 * time.Second,
|
Timeout: 3 * time.Second,
|
||||||
|
Transport: transport,
|
||||||
}
|
}
|
||||||
|
|
||||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
|||||||
@@ -97,6 +97,13 @@ const (
|
|||||||
|
|
||||||
ExternalHost = "external_host"
|
ExternalHost = "external_host"
|
||||||
|
|
||||||
|
// http proxy url if required
|
||||||
|
Proxy = "proxy"
|
||||||
|
|
||||||
|
// urls or IPs that should not use the proxy
|
||||||
|
NoProxy = "no_proxy"
|
||||||
|
noProxyDefault = "localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
|
||||||
|
|
||||||
// key used to sign JWT tokens
|
// key used to sign JWT tokens
|
||||||
JWTSignKey = "jwt_secret_key"
|
JWTSignKey = "jwt_secret_key"
|
||||||
|
|
||||||
@@ -1365,6 +1372,27 @@ func (i *Instance) GetMaxUploadSize() int64 {
|
|||||||
return ret << 20
|
return ret << 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProxy returns the url of a http proxy to be used for all outgoing http calls.
|
||||||
|
func (i *Instance) GetProxy() string {
|
||||||
|
// Validate format
|
||||||
|
reg := regexp.MustCompile(`^((?:socks5h?|https?):\/\/)(([\P{Cc}]+):([\P{Cc}]+)@)?(([a-zA-Z0-9][a-zA-Z0-9.-]*)(:[0-9]{1,5})?)`)
|
||||||
|
proxy := i.getString(Proxy)
|
||||||
|
if proxy != "" && reg.MatchString(proxy) {
|
||||||
|
logger.Debug("Proxy is valid, using it")
|
||||||
|
return proxy
|
||||||
|
} else if proxy != "" {
|
||||||
|
logger.Error("Proxy is invalid, please review your configuration")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProxy returns the url of a http proxy to be used for all outgoing http calls.
|
||||||
|
func (i *Instance) GetNoProxy() string {
|
||||||
|
// NoProxy does not require validation, it is validated by the native Go library sufficiently
|
||||||
|
return i.getString(NoProxy)
|
||||||
|
}
|
||||||
|
|
||||||
// ActivatePublicAccessTripwire sets the security_tripwire_accessed_from_public_internet
|
// ActivatePublicAccessTripwire sets the security_tripwire_accessed_from_public_internet
|
||||||
// config field to the provided IP address to indicate that stash has been accessed
|
// config field to the provided IP address to indicate that stash has been accessed
|
||||||
// from this public IP without authentication.
|
// from this public IP without authentication.
|
||||||
@@ -1440,6 +1468,9 @@ func (i *Instance) setDefaultValues(write bool) error {
|
|||||||
i.main.SetDefault(ScrapersPath, defaultScrapersPath)
|
i.main.SetDefault(ScrapersPath, defaultScrapersPath)
|
||||||
i.main.SetDefault(PluginsPath, defaultPluginsPath)
|
i.main.SetDefault(PluginsPath, defaultPluginsPath)
|
||||||
|
|
||||||
|
// Set NoProxy default
|
||||||
|
i.main.SetDefault(NoProxy, noProxyDefault)
|
||||||
|
|
||||||
if write {
|
if write {
|
||||||
return i.main.WriteConfig()
|
return i.main.WriteConfig()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -492,6 +492,14 @@ func (s *Manager) PostInit(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the proxy if defined in config
|
||||||
|
if s.Config.GetProxy() != "" {
|
||||||
|
os.Setenv("HTTP_PROXY", s.Config.GetProxy())
|
||||||
|
os.Setenv("HTTPS_PROXY", s.Config.GetProxy())
|
||||||
|
os.Setenv("NO_PROXY", s.Config.GetNoProxy())
|
||||||
|
logger.Info("Using HTTP Proxy")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,13 @@ func downloadSingle(ctx context.Context, configDirectory, url string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
transport := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
||||||
|
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ type GlobalConfig interface {
|
|||||||
GetScraperCDPPath() string
|
GetScraperCDPPath() string
|
||||||
GetScraperCertCheck() bool
|
GetScraperCertCheck() bool
|
||||||
GetPythonPath() string
|
GetPythonPath() string
|
||||||
|
GetProxy() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func isCDPPathHTTP(c GlobalConfig) bool {
|
func isCDPPathHTTP(c GlobalConfig) bool {
|
||||||
@@ -96,6 +97,7 @@ func newClient(gc GlobalConfig) *http.Client {
|
|||||||
Transport: &http.Transport{ // ignore insecure certificates
|
Transport: &http.Transport{ // ignore insecure certificates
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: !gc.GetScraperCertCheck()},
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: !gc.GetScraperCertCheck()},
|
||||||
MaxIdleConnsPerHost: maxIdleConnsPerHost,
|
MaxIdleConnsPerHost: maxIdleConnsPerHost,
|
||||||
|
Proxy: http.ProxyFromEnvironment,
|
||||||
},
|
},
|
||||||
Timeout: scrapeGetTimeout,
|
Timeout: scrapeGetTimeout,
|
||||||
// defaultCheckRedirect code with max changed from 10 to maxRedirects
|
// defaultCheckRedirect code with max changed from 10 to maxRedirects
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/cdp"
|
"github.com/chromedp/cdproto/cdp"
|
||||||
|
"github.com/chromedp/cdproto/fetch"
|
||||||
"github.com/chromedp/cdproto/network"
|
"github.com/chromedp/cdproto/network"
|
||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
@@ -157,6 +159,11 @@ func urlFromCDP(ctx context.Context, urlCDP string, driverOptions scraperDriverO
|
|||||||
chromedp.UserDataDir(dir),
|
chromedp.UserDataDir(dir),
|
||||||
chromedp.ExecPath(cdpPath),
|
chromedp.ExecPath(cdpPath),
|
||||||
)
|
)
|
||||||
|
if globalConfig.GetProxy() != "" {
|
||||||
|
url, _, _ := splitProxyAuth(globalConfig.GetProxy())
|
||||||
|
opts = append(opts, chromedp.ProxyServer(url))
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancelAct = chromedp.NewExecAllocator(ctx, opts...)
|
ctx, cancelAct = chromedp.NewExecAllocator(ctx, opts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +180,39 @@ func urlFromCDP(ctx context.Context, urlCDP string, driverOptions scraperDriverO
|
|||||||
var res string
|
var res string
|
||||||
headers := cdpHeaders(driverOptions)
|
headers := cdpHeaders(driverOptions)
|
||||||
|
|
||||||
|
if proxyUsesAuth(globalConfig.GetProxy()) {
|
||||||
|
_, user, pass := splitProxyAuth(globalConfig.GetProxy())
|
||||||
|
|
||||||
|
// Based on https://github.com/chromedp/examples/blob/master/proxy/main.go
|
||||||
|
lctx, lcancel := context.WithCancel(ctx)
|
||||||
|
chromedp.ListenTarget(lctx, func(ev interface{}) {
|
||||||
|
switch ev := ev.(type) {
|
||||||
|
case *fetch.EventRequestPaused:
|
||||||
|
go func() {
|
||||||
|
_ = chromedp.Run(ctx, fetch.ContinueRequest(ev.RequestID))
|
||||||
|
}()
|
||||||
|
case *fetch.EventAuthRequired:
|
||||||
|
if ev.AuthChallenge.Source == fetch.AuthChallengeSourceProxy {
|
||||||
|
go func() {
|
||||||
|
_ = chromedp.Run(ctx,
|
||||||
|
fetch.ContinueWithAuth(ev.RequestID, &fetch.AuthChallengeResponse{
|
||||||
|
Response: fetch.AuthChallengeResponseResponseProvideCredentials,
|
||||||
|
Username: user,
|
||||||
|
Password: pass,
|
||||||
|
}),
|
||||||
|
// Chrome will remember the credential for the current instance,
|
||||||
|
// so we can disable the fetch domain once credential is provided.
|
||||||
|
// Please file an issue if Chrome does not work in this way.
|
||||||
|
fetch.Disable(),
|
||||||
|
)
|
||||||
|
// and cancel the event handler too.
|
||||||
|
lcancel()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
err := chromedp.Run(ctx,
|
err := chromedp.Run(ctx,
|
||||||
network.Enable(),
|
network.Enable(),
|
||||||
setCDPCookies(driverOptions),
|
setCDPCookies(driverOptions),
|
||||||
@@ -260,3 +300,32 @@ func cdpHeaders(driverOptions scraperDriverOptions) map[string]interface{} {
|
|||||||
}
|
}
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func proxyUsesAuth(proxyUrl string) bool {
|
||||||
|
if proxyUrl == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reg := regexp.MustCompile(`^(https?:\/\/)(([\P{Cc}]+):([\P{Cc}]+)@)?(([a-zA-Z0-9][a-zA-Z0-9.-]*)(:[0-9]{1,5})?)`)
|
||||||
|
matches := reg.FindAllStringSubmatch(proxyUrl, -1)
|
||||||
|
if matches != nil {
|
||||||
|
split := matches[0]
|
||||||
|
return len(split) == 0 || (len(split) > 5 && split[3] != "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitProxyAuth(proxyUrl string) (string, string, string) {
|
||||||
|
if proxyUrl == "" {
|
||||||
|
return "", "", ""
|
||||||
|
}
|
||||||
|
reg := regexp.MustCompile(`^(https?:\/\/)(([\P{Cc}]+):([\P{Cc}]+)@)?(([a-zA-Z0-9][a-zA-Z0-9.-]*)(:[0-9]{1,5})?)`)
|
||||||
|
matches := reg.FindAllStringSubmatch(proxyUrl, -1)
|
||||||
|
|
||||||
|
if matches != nil && len(matches[0]) > 5 {
|
||||||
|
split := matches[0]
|
||||||
|
return split[1] + split[5], split[3], split[4]
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyUrl, "", ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -834,6 +834,10 @@ func (mockGlobalConfig) GetPythonPath() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mockGlobalConfig) GetProxy() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func TestSubScrape(t *testing.T) {
|
func TestSubScrape(t *testing.T) {
|
||||||
retHTML := `
|
retHTML := `
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Performer autotagging does not currently match on performer aliases. This will be addressed when finer control over the matching is implemented.
|
* Performer autotagging does not currently match on performer aliases. This will be addressed when finer control over the matching is implemented.
|
||||||
|
|
||||||
### ✨ New Features
|
### ✨ New Features
|
||||||
|
* Added support for specifying the use of a proxy for network requests. ([#3284](https://github.com/stashapp/stash/pull/3284))
|
||||||
* Added support for injecting arguments into `ffmpeg` during generation and live-transcoding. ([#3216](https://github.com/stashapp/stash/pull/3216))
|
* Added support for injecting arguments into `ffmpeg` during generation and live-transcoding. ([#3216](https://github.com/stashapp/stash/pull/3216))
|
||||||
* Added URL and Date fields to Images. ([#3015](https://github.com/stashapp/stash/pull/3015))
|
* Added URL and Date fields to Images. ([#3015](https://github.com/stashapp/stash/pull/3015))
|
||||||
* Added support for plugins to add injected CSS and Javascript to the UI. ([#3195](https://github.com/stashapp/stash/pull/3195))
|
* Added support for plugins to add injected CSS and Javascript to the UI. ([#3195](https://github.com/stashapp/stash/pull/3195))
|
||||||
|
|||||||
@@ -129,6 +129,8 @@ These options are typically not exposed in the UI and must be changed manually i
|
|||||||
| `custom_ui_location` | The file system folder where the UI files will be served from, instead of using the embedded UI. Empty to disable. Stash must be restarted to take effect. |
|
| `custom_ui_location` | The file system folder where the UI files will be served from, instead of using the embedded UI. Empty to disable. Stash must be restarted to take effect. |
|
||||||
| `max_upload_size` | Maximum file upload size for import files. Defaults to 1GB. |
|
| `max_upload_size` | Maximum file upload size for import files. Defaults to 1GB. |
|
||||||
| `theme_color` | Sets the `theme-color` property in the UI. |
|
| `theme_color` | Sets the `theme-color` property in the UI. |
|
||||||
|
| `proxy` | The url of a HTTP(S) proxy to be used when stash makes calls to online services Example: https://user:password@my.proxy:8080 |
|
||||||
|
| `no_proxy` | A list of domains for which the proxy must not be used. Default is all local LAN: localhost,127.0.0.1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12 |
|
||||||
|
|
||||||
### Custom served folders
|
### Custom served folders
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user