add dynamic resource url patcher as standalone responsemodifier
This commit is contained in:
@@ -86,8 +86,9 @@ func main() {
|
|||||||
|
|
||||||
app := fiber.New(
|
app := fiber.New(
|
||||||
fiber.Config{
|
fiber.Config{
|
||||||
Prefork: *prefork,
|
Prefork: *prefork,
|
||||||
GETOnly: false,
|
GETOnly: false,
|
||||||
|
ReadBufferSize: 4096 * 4, // increase max header size
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -56,15 +56,16 @@ func NewProxySiteHandler(opts *ProxyOptions) fiber.Handler {
|
|||||||
SetFiberCtx(c).
|
SetFiberCtx(c).
|
||||||
SetDebugLogging(opts.Verbose).
|
SetDebugLogging(opts.Verbose).
|
||||||
SetRequestModifications(
|
SetRequestModifications(
|
||||||
//rx.DeleteOutgoingCookies(),
|
rx.DeleteOutgoingCookies(),
|
||||||
//rx.RequestArchiveIs(),
|
//rx.RequestArchiveIs(),
|
||||||
rx.MasqueradeAsGoogleBot(),
|
rx.MasqueradeAsGoogleBot(),
|
||||||
).
|
).
|
||||||
AddResponseModifications(
|
AddResponseModifications(
|
||||||
//tx.DeleteIncomingCookies(),
|
|
||||||
tx.RewriteHTMLResourceURLs(),
|
|
||||||
tx.BypassCORS(),
|
tx.BypassCORS(),
|
||||||
tx.BypassContentSecurityPolicy(),
|
tx.BypassContentSecurityPolicy(),
|
||||||
|
tx.DeleteIncomingCookies(),
|
||||||
|
tx.RewriteHTMLResourceURLs(),
|
||||||
|
tx.PatchDynamicResourceURLs(),
|
||||||
).
|
).
|
||||||
Execute()
|
Execute()
|
||||||
|
|
||||||
|
|||||||
@@ -256,8 +256,11 @@ func (chain *ProxyChain) extractUrl() (*url.URL, error) {
|
|||||||
return reconstructUrlFromReferer(referer, relativePath)
|
return reconstructUrlFromReferer(referer, relativePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddBodyRewriter adds a HTMLTokenRewriter to the chain
|
// AddBodyRewriter adds a HTMLTokenRewriter to the chain.
|
||||||
// HTMLTokenRewriters modify the body response by parsing the HTML
|
// - HTMLTokenRewriters modify the body response by parsing the HTML
|
||||||
|
// and making changes to the DOM as it streams to the client
|
||||||
|
// - In most cases, you don't need to use this method. It's usually called by
|
||||||
|
// a ResponseModifier to batch queue changes for performance reasons.
|
||||||
func (chain *ProxyChain) AddHTMLTokenRewriter(rr rr.IHTMLTokenRewriter) *ProxyChain {
|
func (chain *ProxyChain) AddHTMLTokenRewriter(rr rr.IHTMLTokenRewriter) *ProxyChain {
|
||||||
chain.htmlTokenRewriters = append(chain.htmlTokenRewriters, rr)
|
chain.htmlTokenRewriters = append(chain.htmlTokenRewriters, rr)
|
||||||
return chain
|
return chain
|
||||||
|
|||||||
55
proxychain/responsemodifers/patch_dynamic_resource_urls.go
Normal file
55
proxychain/responsemodifers/patch_dynamic_resource_urls.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package responsemodifers
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"ladder/proxychain"
|
||||||
|
"ladder/proxychain/responsemodifers/rewriters"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed patch_dynamic_resource_urls.js
|
||||||
|
var patchDynamicResourceURLsScript string
|
||||||
|
|
||||||
|
// PatchDynamicResourceURLs patches the javascript runtime to rewrite URLs client-side.
|
||||||
|
// - This function is designed to allow the proxified page
|
||||||
|
// to still be browsible by routing all resource URLs through the proxy.
|
||||||
|
// - Native APIs capable of network requests will be hooked
|
||||||
|
// and the URLs arguments modified to point to the proxy instead.
|
||||||
|
// - fetch('/relative_path') -> fetch('/https://proxiedsite.com/relative_path')
|
||||||
|
// - Element.setAttribute('src', "/assets/img.jpg") -> Element.setAttribute('src', "/https://proxiedsite.com/assets/img.jpg") -> fetch('/https://proxiedsite.com/relative_path')
|
||||||
|
func PatchDynamicResourceURLs() proxychain.ResponseModification {
|
||||||
|
return func(chain *proxychain.ProxyChain) error {
|
||||||
|
// don't add rewriter if it's not even html
|
||||||
|
ct := chain.Response.Header.Get("content-type")
|
||||||
|
if !strings.HasPrefix(ct, "text/html") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the original URL sent by client:
|
||||||
|
// http://localhost:8080/http://proxiedsite.com/foo/bar
|
||||||
|
originalURI := chain.Context.Request().URI()
|
||||||
|
|
||||||
|
// this is the extracted URL that the client requests to proxy
|
||||||
|
// http://proxiedsite.com/foo/bar
|
||||||
|
reqURL := chain.Request.URL
|
||||||
|
|
||||||
|
params := map[string]string{
|
||||||
|
// ie: http://localhost:8080
|
||||||
|
"{{PROXY_ORIGIN}}": fmt.Sprintf("%s://%s", originalURI.Scheme(), originalURI.Host()),
|
||||||
|
// ie: http://proxiedsite.com
|
||||||
|
"{{ORIGIN}}": fmt.Sprintf("%s://%s", reqURL.Scheme, reqURL.Host),
|
||||||
|
}
|
||||||
|
|
||||||
|
// the rewriting actually happens in chain.Execute() as the client is streaming the response body back
|
||||||
|
rr := rewriters.NewScriptInjectorRewriterWithParams(
|
||||||
|
patchDynamicResourceURLsScript,
|
||||||
|
rewriters.BeforeDOMContentLoaded,
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
// we just queue it up here
|
||||||
|
chain.AddHTMLTokenRewriter(rr)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,19 @@
|
|||||||
// Also overrides the attribute setter prototype to modify the request URLs
|
// Also overrides the attribute setter prototype to modify the request URLs
|
||||||
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
|
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
|
||||||
(() => {
|
(() => {
|
||||||
|
|
||||||
|
// ============== PARAMS ===========================
|
||||||
|
// if the original request was: http://localhost:8080/http://proxiedsite.com/foo/bar
|
||||||
|
// proxyOrigin is http://localhost:8080
|
||||||
|
const proxyOrigin = "{{PROXY_ORIGIN}}";
|
||||||
|
//const proxyOrigin = globalThis.window.location.origin;
|
||||||
|
|
||||||
|
// if the original request was: http://localhost:8080/http://proxiedsite.com/foo/bar
|
||||||
|
// origin is http://proxiedsite.com
|
||||||
|
const origin = "{{ORIGIN}}";
|
||||||
|
//const origin = (new URL(decodeURIComponent(globalThis.window.location.pathname.substring(1)))).origin
|
||||||
|
// ============== END PARAMS ======================
|
||||||
|
|
||||||
const blacklistedSchemes = [
|
const blacklistedSchemes = [
|
||||||
"ftp:",
|
"ftp:",
|
||||||
"mailto:",
|
"mailto:",
|
||||||
@@ -24,11 +37,6 @@
|
|||||||
// don't rewrite special URIs
|
// don't rewrite special URIs
|
||||||
if (blacklistedSchemes.includes(url)) return url;
|
if (blacklistedSchemes.includes(url)) return url;
|
||||||
|
|
||||||
//const proxyOrigin = globalThis.window.location.origin;
|
|
||||||
const proxyOrigin = "R_PROXYURL";
|
|
||||||
//const origin = (new URL(decodeURIComponent(globalThis.window.location.pathname.substring(1)))).origin
|
|
||||||
const origin = "R_BASEURL";
|
|
||||||
|
|
||||||
// don't rewrite invalid URIs
|
// don't rewrite invalid URIs
|
||||||
try { new URL(url, origin) } catch { return url }
|
try { new URL(url, origin) } catch { return url }
|
||||||
|
|
||||||
@@ -314,4 +322,4 @@ const originalSetters = {};
|
|||||||
console.log('DOM is now idle. Executing...');
|
console.log('DOM is now idle. Executing...');
|
||||||
}
|
}
|
||||||
|
|
||||||
})();
|
})();
|
||||||
@@ -3,6 +3,7 @@ package rewriters
|
|||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
@@ -50,6 +51,22 @@ func (r *ScriptInjectorRewriter) ModifyToken(token *html.Token) (string, string)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applies parameters by string replacement of the template script
|
||||||
|
func (r *ScriptInjectorRewriter) applyParams(params map[string]string) {
|
||||||
|
// Sort the keys by length in descending order
|
||||||
|
keys := make([]string, 0, len(params))
|
||||||
|
for key := range params {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Slice(keys, func(i, j int) bool {
|
||||||
|
return len(keys[i]) > len(keys[j])
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
r.script = strings.ReplaceAll(r.script, key, params[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewScriptInjectorRewriter implements a HtmlTokenRewriter
|
// NewScriptInjectorRewriter implements a HtmlTokenRewriter
|
||||||
// and injects JS into the page for execution at a particular time
|
// and injects JS into the page for execution at a particular time
|
||||||
func NewScriptInjectorRewriter(script string, execTime ScriptExecTime) *ScriptInjectorRewriter {
|
func NewScriptInjectorRewriter(script string, execTime ScriptExecTime) *ScriptInjectorRewriter {
|
||||||
@@ -58,3 +75,17 @@ func NewScriptInjectorRewriter(script string, execTime ScriptExecTime) *ScriptIn
|
|||||||
script: script,
|
script: script,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewScriptInjectorRewriterWith implements a HtmlTokenRewriter
|
||||||
|
// and injects JS into the page for execution at a particular time
|
||||||
|
// accepting arguments into the script, which will be added via a string replace
|
||||||
|
// the params map represents the key-value pair of the params.
|
||||||
|
// the key will be string replaced with the value
|
||||||
|
func NewScriptInjectorRewriterWithParams(script string, execTime ScriptExecTime, params map[string]string) *ScriptInjectorRewriter {
|
||||||
|
rr := &ScriptInjectorRewriter{
|
||||||
|
execTime: execTime,
|
||||||
|
script: script,
|
||||||
|
}
|
||||||
|
rr.applyParams(params)
|
||||||
|
return rr
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user