support js URL rewriting; support post req
This commit is contained in:
@@ -87,7 +87,7 @@ func main() {
|
|||||||
app := fiber.New(
|
app := fiber.New(
|
||||||
fiber.Config{
|
fiber.Config{
|
||||||
Prefork: *prefork,
|
Prefork: *prefork,
|
||||||
GETOnly: true,
|
GETOnly: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -138,5 +138,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.Get("/*", handlers.NewProxySiteHandler(proxyOpts))
|
app.Get("/*", handlers.NewProxySiteHandler(proxyOpts))
|
||||||
|
app.Post("/*", handlers.NewProxySiteHandler(proxyOpts))
|
||||||
log.Fatal(app.Listen(":" + *port))
|
log.Fatal(app.Listen(":" + *port))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package responsemodifers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"ladder/proxychain"
|
"ladder/proxychain"
|
||||||
|
"log"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -19,7 +21,7 @@ func init() {
|
|||||||
"src": true,
|
"src": true,
|
||||||
"href": true,
|
"href": true,
|
||||||
"action": true,
|
"action": true,
|
||||||
"srcset": true, // TODO: fix
|
"srcset": true,
|
||||||
"poster": true,
|
"poster": true,
|
||||||
"data": true,
|
"data": true,
|
||||||
"cite": true,
|
"cite": true,
|
||||||
@@ -45,17 +47,19 @@ type HTMLResourceURLRewriter struct {
|
|||||||
tokenBuffer *bytes.Buffer
|
tokenBuffer *bytes.Buffer
|
||||||
currentTokenIndex int
|
currentTokenIndex int
|
||||||
currentTokenProcessed bool
|
currentTokenProcessed bool
|
||||||
|
proxyURL string // ladder URL, not proxied site URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTMLResourceURLRewriter creates a new instance of HTMLResourceURLRewriter.
|
// NewHTMLResourceURLRewriter creates a new instance of HTMLResourceURLRewriter.
|
||||||
// It initializes the tokenizer with the provided source and sets the proxy URL.
|
// It initializes the tokenizer with the provided source and sets the proxy URL.
|
||||||
func NewHTMLResourceURLRewriter(src io.ReadCloser, baseURL *url.URL) *HTMLResourceURLRewriter {
|
func NewHTMLResourceURLRewriter(src io.ReadCloser, baseURL *url.URL, proxyURL string) *HTMLResourceURLRewriter {
|
||||||
return &HTMLResourceURLRewriter{
|
return &HTMLResourceURLRewriter{
|
||||||
tokenizer: html.NewTokenizer(src),
|
tokenizer: html.NewTokenizer(src),
|
||||||
currentToken: html.Token{},
|
currentToken: html.Token{},
|
||||||
currentTokenIndex: 0,
|
currentTokenIndex: 0,
|
||||||
tokenBuffer: new(bytes.Buffer),
|
tokenBuffer: new(bytes.Buffer),
|
||||||
baseURL: baseURL,
|
baseURL: baseURL,
|
||||||
|
proxyURL: proxyURL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +94,18 @@ func (r *HTMLResourceURLRewriter) Read(p []byte) (int, error) {
|
|||||||
// patch tokens with URLs
|
// patch tokens with URLs
|
||||||
isTokenWithAttribute := r.currentToken.Type == html.StartTagToken || r.currentToken.Type == html.SelfClosingTagToken
|
isTokenWithAttribute := r.currentToken.Type == html.StartTagToken || r.currentToken.Type == html.SelfClosingTagToken
|
||||||
if isTokenWithAttribute {
|
if isTokenWithAttribute {
|
||||||
patchResourceURL(&r.currentToken, r.baseURL)
|
patchResourceURL(&r.currentToken, r.baseURL, r.proxyURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.tokenBuffer.Reset()
|
r.tokenBuffer.Reset()
|
||||||
r.tokenBuffer.WriteString(r.currentToken.String())
|
r.tokenBuffer.WriteString(r.currentToken.String())
|
||||||
|
|
||||||
|
// inject <script> right after <head>
|
||||||
|
isHeadToken := (r.currentToken.Type == html.StartTagToken || r.currentToken.Type == html.SelfClosingTagToken) && r.currentToken.Data == "head"
|
||||||
|
if isHeadToken {
|
||||||
|
injectScript(r.tokenBuffer, rewriteJSResourceUrlsScript)
|
||||||
|
}
|
||||||
|
|
||||||
r.currentTokenProcessed = false
|
r.currentTokenProcessed = false
|
||||||
r.currentTokenIndex = 0
|
r.currentTokenIndex = 0
|
||||||
}
|
}
|
||||||
@@ -107,6 +118,17 @@ func (r *HTMLResourceURLRewriter) Read(p []byte) (int, error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
|
||||||
|
//
|
||||||
|
//go:embed rewrite_js_resource_urls.js
|
||||||
|
var rewriteJSResourceUrlsScript string
|
||||||
|
|
||||||
|
func injectScript(tokenBuffer *bytes.Buffer, script string) {
|
||||||
|
tokenBuffer.WriteString(
|
||||||
|
fmt.Sprintf("\n<script>\n%s\n</script>\n", script),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Root-relative URLs: These are relative to the root path and start with a "/".
|
// Root-relative URLs: These are relative to the root path and start with a "/".
|
||||||
func handleRootRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
func handleRootRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
||||||
// doublecheck this is a valid relative URL
|
// doublecheck this is a valid relative URL
|
||||||
@@ -126,7 +148,7 @@ func handleRootRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
|||||||
attr.Val = url.QueryEscape(attr.Val)
|
attr.Val = url.QueryEscape(attr.Val)
|
||||||
attr.Val = fmt.Sprintf("/%s", attr.Val)
|
attr.Val = fmt.Sprintf("/%s", attr.Val)
|
||||||
|
|
||||||
//log.Printf("root rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
log.Printf("root rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document-relative URLs: These are relative to the current document's path and don't start with a "/".
|
// Document-relative URLs: These are relative to the current document's path and don't start with a "/".
|
||||||
@@ -140,14 +162,14 @@ func handleDocumentRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
|||||||
)
|
)
|
||||||
attr.Val = url.QueryEscape(attr.Val)
|
attr.Val = url.QueryEscape(attr.Val)
|
||||||
attr.Val = fmt.Sprintf("/%s", attr.Val)
|
attr.Val = fmt.Sprintf("/%s", attr.Val)
|
||||||
//log.Printf("doc rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
log.Printf("doc rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol-relative URLs: These start with "//" and will use the same protocol (http or https) as the current page.
|
// Protocol-relative URLs: These start with "//" and will use the same protocol (http or https) as the current page.
|
||||||
func handleProtocolRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
func handleProtocolRelativePath(attr *html.Attribute, baseURL *url.URL) {
|
||||||
attr.Val = strings.TrimPrefix(attr.Val, "/")
|
attr.Val = strings.TrimPrefix(attr.Val, "/")
|
||||||
handleRootRelativePath(attr, baseURL)
|
handleRootRelativePath(attr, baseURL)
|
||||||
//log.Printf("proto rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
log.Printf("proto rel url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAbsolutePath(attr *html.Attribute, baseURL *url.URL) {
|
func handleAbsolutePath(attr *html.Attribute, baseURL *url.URL) {
|
||||||
@@ -165,7 +187,7 @@ func handleAbsolutePath(attr *html.Attribute, baseURL *url.URL) {
|
|||||||
strings.TrimPrefix(attr.Val, "/"),
|
strings.TrimPrefix(attr.Val, "/"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
//log.Printf("abs url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
log.Printf("abs url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSrcSet(attr *html.Attribute, baseURL *url.URL) {
|
func handleSrcSet(attr *html.Attribute, baseURL *url.URL) {
|
||||||
@@ -196,18 +218,21 @@ func handleSrcSet(attr *html.Attribute, baseURL *url.URL) {
|
|||||||
attr.Val = fmt.Sprintf("%s,", attr.Val)
|
attr.Val = fmt.Sprintf("%s,", attr.Val)
|
||||||
}
|
}
|
||||||
attr.Val = strings.TrimSuffix(attr.Val, ",")
|
attr.Val = strings.TrimSuffix(attr.Val, ",")
|
||||||
|
|
||||||
|
log.Printf("srcset url rewritten-> '%s'='%s'", attr.Key, attr.Val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: figure out how to handle these
|
func patchResourceURL(token *html.Token, baseURL *url.URL, proxyURL string) {
|
||||||
// srcset
|
|
||||||
func patchResourceURL(token *html.Token, baseURL *url.URL) {
|
|
||||||
for i := range token.Attr {
|
for i := range token.Attr {
|
||||||
attr := &token.Attr[i]
|
attr := &token.Attr[i]
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
// dont touch attributes except for the ones we defined
|
// don't touch attributes except for the ones we defined
|
||||||
case !AttributesToRewrite[attr.Key]:
|
case !AttributesToRewrite[attr.Key]:
|
||||||
continue
|
continue
|
||||||
|
// don't double-overwrite the url
|
||||||
|
case strings.HasPrefix(attr.Val, proxyURL):
|
||||||
|
continue
|
||||||
case attr.Key == "srcset":
|
case attr.Key == "srcset":
|
||||||
handleSrcSet(attr, baseURL)
|
handleSrcSet(attr, baseURL)
|
||||||
continue
|
continue
|
||||||
@@ -255,8 +280,10 @@ func RewriteHTMLResourceURLs() proxychain.ResponseModification {
|
|||||||
if !strings.HasPrefix(ct, "text/html") {
|
if !strings.HasPrefix(ct, "text/html") {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
originalURI := chain.Context.Request().URI()
|
||||||
|
proxyURL := fmt.Sprintf("%s://%s", originalURI.Scheme(), originalURI.Host())
|
||||||
|
|
||||||
chain.Response.Body = NewHTMLResourceURLRewriter(chain.Response.Body, chain.Request.URL)
|
chain.Response.Body = NewHTMLResourceURLRewriter(chain.Response.Body, chain.Request.URL, proxyURL)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,21 @@
|
|||||||
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
|
// fetch("/relative_script.js") -> fetch("http://localhost:8080/relative_script.js")
|
||||||
(() => {
|
(() => {
|
||||||
function rewriteURL(url) {
|
function rewriteURL(url) {
|
||||||
|
oldUrl = url
|
||||||
if (!url) return url
|
if (!url) return url
|
||||||
if (url.startsWith(window.location.origin)) return url
|
|
||||||
|
|
||||||
|
proxyOrigin = globalThis.window.location.origin
|
||||||
|
if (url.startsWith(proxyOrigin)) return url
|
||||||
|
|
||||||
|
const origin = (new URL(decodeURI(globalThis.window.location.pathname.substring(1)))).origin
|
||||||
if (url.startsWith("//")) {
|
if (url.startsWith("//")) {
|
||||||
url = `${window.location.origin}/${encodeURIComponent(url.substring(2))}`;
|
url = `/${origin}/${encodeURIComponent(url.substring(2))}`;
|
||||||
} else if (url.startsWith("/")) {
|
} else if (url.startsWith("/")) {
|
||||||
url = `${window.location.origin}/${encodeURIComponent(url.substring(1))}`;
|
url = `/${origin}/${encodeURIComponent(url.substring(1))}`;
|
||||||
} else if (url.startsWith("http://") || url.startsWith("https://")) {
|
} else if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||||
url = `${window.location.origin}/${encodeURIComponent(url)}`;
|
url = `/${origin}/${encodeURIComponent(url)}`;
|
||||||
}
|
}
|
||||||
|
console.log(`rewrite JS URL: ${oldUrl} -> ${url}`)
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +32,16 @@
|
|||||||
XMLHttpRequest.prototype.open = function(method, url, async = true, user = null, password = null) {
|
XMLHttpRequest.prototype.open = function(method, url, async = true, user = null, password = null) {
|
||||||
return oldOpen.call(this, method, rewriteURL(url), async, user, password);
|
return oldOpen.call(this, method, rewriteURL(url), async, user, password);
|
||||||
};
|
};
|
||||||
|
const oldSend = XMLHttpRequest.prototype.send;
|
||||||
|
XMLHttpRequest.prototype.send = function(method, url) {
|
||||||
|
return oldSend.call(this, method, rewriteURL(url));
|
||||||
|
};
|
||||||
|
|
||||||
|
// monkey patch service worker registration
|
||||||
|
const oldRegister = ServiceWorkerContainer.prototype.register;
|
||||||
|
ServiceWorkerContainer.prototype.register = function(scriptURL, options) {
|
||||||
|
return oldRegister.call(this, rewriteURL(scriptURL), options)
|
||||||
|
}
|
||||||
|
|
||||||
// Monkey patch setter methods
|
// Monkey patch setter methods
|
||||||
const elements = [
|
const elements = [
|
||||||
Reference in New Issue
Block a user