Skip to content

Commit afd040c

Browse files
committed
feat: add support for module worker bootstrapping and expand script/URL rewriting context with tab and runtime token tracking
1 parent dcb87da commit afd040c

17 files changed

Lines changed: 1336 additions & 435 deletions

cmd/wasm-kernel/main.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@ func transformDocumentResponse(req *http.Request, resp *http.Response, tab *zpht
209209
DynamicCompileAllowed: dynamicCompileAllowed,
210210
ReferrerPolicy: referrerPolicy,
211211
ScriptRewriter: rewriteScriptFromJS,
212+
ScriptURLRewriter: rewriteScriptURLFromJS,
213+
FetchURLRewriter: rewriteFetchURLFromJS,
212214
CSSRewriter: rewriteCSSFromJS,
213215
ImportMapRewriter: rewriteImportMapFromJS,
214216
})
@@ -299,7 +301,7 @@ func cookieRecordsForJS(records []cookiejar.SnapshotRecord) []any {
299301
return out
300302
}
301303

302-
func rewriteScriptFromJS(source, kind, targetURL, controlPrefix string) (string, error) {
304+
func rewriteScriptFromJS(source, kind, targetURL, controlPrefix, tabID, runtimeToken string) (string, error) {
303305
rewriter := js.Global().Get("ZPRewriter")
304306
if !rewriter.Truthy() || rewriter.Get("rewriteScript").Type() != js.TypeFunction {
305307
return "", fmt.Errorf("REALM_INJECTION_FAILURE")
@@ -308,6 +310,8 @@ func rewriteScriptFromJS(source, kind, targetURL, controlPrefix string) (string,
308310
"kind": kind,
309311
"targetUrl": targetURL,
310312
"controlPrefix": controlPrefix,
313+
"tabId": tabID,
314+
"runtimeToken": runtimeToken,
311315
"strict": true,
312316
})
313317
if out.Truthy() && out.Get("ok").Bool() {
@@ -316,10 +320,43 @@ func rewriteScriptFromJS(source, kind, targetURL, controlPrefix string) (string,
316320
return "", fmt.Errorf("REWRITE_FAILED")
317321
}
318322

323+
func rewriteScriptURLFromJS(raw, kind, targetURL, controlPrefix, tabID, runtimeToken string) (string, string, error) {
324+
rewriter := js.Global().Get("ZPRewriter")
325+
if !rewriter.Truthy() || rewriter.Get("rewriteScriptURL").Type() != js.TypeFunction {
326+
return "", "", fmt.Errorf("REALM_INJECTION_FAILURE")
327+
}
328+
out := rewriter.Call("rewriteScriptURL", raw, map[string]any{
329+
"kind": kind,
330+
"targetUrl": targetURL,
331+
"controlPrefix": controlPrefix,
332+
"tabId": tabID,
333+
"runtimeToken": runtimeToken,
334+
})
335+
if out.Truthy() && out.Get("ok").Bool() {
336+
return out.Get("url").String(), out.Get("target").String(), nil
337+
}
338+
return "", "", fmt.Errorf("REWRITE_FAILED")
339+
}
340+
341+
func rewriteFetchURLFromJS(raw, targetURL, controlPrefix string) (string, string, error) {
342+
rewriter := js.Global().Get("ZPRewriter")
343+
if !rewriter.Truthy() || rewriter.Get("rewriteFetchURL").Type() != js.TypeFunction {
344+
return "", "", fmt.Errorf("REALM_INJECTION_FAILURE")
345+
}
346+
out := rewriter.Call("rewriteFetchURL", raw, map[string]any{
347+
"targetUrl": targetURL,
348+
"controlPrefix": controlPrefix,
349+
})
350+
if out.Truthy() && out.Get("ok").Bool() {
351+
return out.Get("url").String(), out.Get("target").String(), nil
352+
}
353+
return "", "", fmt.Errorf("REWRITE_FAILED")
354+
}
355+
319356
func rewriteCSSFromJS(source, baseURL string) (string, error) {
320357
rewriter := js.Global().Get("ZPRewriter")
321358
if !rewriter.Truthy() || rewriter.Get("rewriteCSS").Type() != js.TypeFunction {
322-
return source, nil
359+
return "", fmt.Errorf("CSS_REWRITE_UNAVAILABLE")
323360
}
324361
out := rewriter.Call("rewriteCSS", source, map[string]any{
325362
"baseUrl": baseURL,

internal/htmltx/injection_inventory_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ func TestTransformInjectionInventorySnapshot(t *testing.T) {
4141
TargetURL: target,
4242
RuntimeToken: "rt",
4343
Servers: []string{"wss://relay.example/ws"},
44+
ScriptURLRewriter: func(raw, kind, targetURL, controlPrefix, tabID, runtimeToken string) (string, string, error) {
45+
return scriptURLRewriterForTest(raw, kind, targetURL, controlPrefix, tabID, runtimeToken)
46+
},
47+
FetchURLRewriter: func(raw, targetURL, controlPrefix string) (string, string, error) {
48+
return fetchURLRewriterForTest(raw, targetURL, controlPrefix)
49+
},
4450
})
4551
if err != nil {
4652
t.Fatal(err)

internal/htmltx/transform.go

Lines changed: 34 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ type Options struct {
2626
Servers []string
2727
DynamicCompileAllowed bool
2828
ReferrerPolicy string
29-
ScriptRewriter func(source, kind, targetURL, controlPrefix string) (string, error)
29+
ScriptRewriter func(source, kind, targetURL, controlPrefix, tabID, runtimeToken string) (string, error)
30+
ScriptURLRewriter func(raw, kind, targetURL, controlPrefix, tabID, runtimeToken string) (wrapped, target string, err error)
31+
FetchURLRewriter func(raw, targetURL, controlPrefix string) (wrapped, target string, err error)
3032
CSSRewriter func(source, baseURL string) (string, error)
3133
ImportMapRewriter func(source, baseURL, tabID, runtimeToken, controlPrefix string) (string, error)
3234
}
@@ -668,7 +670,11 @@ func rewriteSrcset(raw string, opt Options) (rewritten, visible string, changed
668670
continue
669671
}
670672
out = append(out, joinSrcsetCandidate(wrapped, c.descriptor))
671-
vis = append(vis, joinSrcsetCandidate(target, c.descriptor))
673+
if target != "" {
674+
vis = append(vis, joinSrcsetCandidate(target, c.descriptor))
675+
} else {
676+
vis = append(vis, c.raw)
677+
}
672678
changed = true
673679
}
674680
if !changed {
@@ -842,45 +848,19 @@ func wrapAttrURL(raw string, opt Options, nav bool) (wrapped, target string, ok
842848
}
843849

844850
func wrapScriptURL(raw string, opt Options, kind string) (wrapped, target string, ok bool) {
845-
s := strings.TrimSpace(raw)
846-
blocked := shareurl.ControlPrefix + "error/POLICY_BLOCKED"
847-
if s == "" || strings.HasPrefix(s, "#") || hasExecutableURLScheme(s) {
848-
return blocked, "", false
849-
}
850-
u, err := url.Parse(s)
851-
if err != nil {
852-
return blocked, "", false
853-
}
854-
abs := opt.TargetURL.ResolveReference(u)
855-
if abs.Scheme != "http" && abs.Scheme != "https" {
856-
return blocked, "", false
857-
}
858-
q := url.Values{}
859-
q.Set("u", abs.String())
860-
q.Set("kind", kind)
861-
if kind != "module" {
862-
q.Set("tab", opt.TabID)
863-
q.Set("rt", opt.RuntimeToken)
851+
if opt.ScriptURLRewriter == nil {
852+
return shareurl.ControlPrefix + "error/POLICY_BLOCKED", "", false
864853
}
865-
return shareurl.ControlPrefix + "api/script?" + q.Encode(), abs.String(), true
854+
wrapped, target, err := opt.ScriptURLRewriter(raw, kind, opt.TargetURL.String(), shareurl.ControlPrefix, opt.TabID, opt.RuntimeToken)
855+
return wrapped, target, err == nil && wrapped != "" && target != ""
866856
}
867857

868858
func wrapFetchURL(raw string, opt Options) (wrapped, target string, ok bool) {
869-
target, ok = resolveTargetURL(raw, opt)
870-
if !ok {
871-
return shareurl.ControlPrefix + "error/POLICY_BLOCKED", "", false
872-
}
873-
networkTarget := target
874-
fragment := ""
875-
if u, err := url.Parse(target); err == nil && u.Fragment != "" {
876-
fragment = "#" + u.EscapedFragment()
877-
u.Fragment = ""
878-
u.RawFragment = ""
879-
networkTarget = u.String()
859+
if opt.FetchURLRewriter == nil {
860+
return shareurl.ControlPrefix + "error/POLICY_BLOCKED", "", true
880861
}
881-
q := url.Values{}
882-
q.Set("url", networkTarget)
883-
return shareurl.ControlPrefix + "api/fetch?" + q.Encode() + fragment, target, true
862+
wrapped, target, err := opt.FetchURLRewriter(raw, opt.TargetURL.String(), shareurl.ControlPrefix)
863+
return wrapped, target, err == nil && wrapped != "" && target != ""
884864
}
885865

886866
func isStylesheetLinkRel(rel string) bool {
@@ -915,10 +895,10 @@ func executableScriptKind(tok xhtml.Token) string {
915895

916896
func rewriteInlineScript(source, kind string, opt Options) string {
917897
if strings.TrimSpace(source) == "" {
918-
return source
898+
return ""
919899
}
920900
if opt.ScriptRewriter != nil {
921-
if code, err := opt.ScriptRewriter(source, kind, opt.TargetURL.String(), shareurl.ControlPrefix); err == nil {
901+
if code, err := opt.ScriptRewriter(source, kind, opt.TargetURL.String(), shareurl.ControlPrefix, opt.TabID, opt.RuntimeToken); err == nil {
922902
return code
923903
}
924904
}
@@ -930,106 +910,44 @@ func rewriteEventHandler(source string, opt Options) string {
930910
return source
931911
}
932912
if opt.ScriptRewriter != nil {
933-
if code, err := opt.ScriptRewriter(source, "event-handler", opt.TargetURL.String(), shareurl.ControlPrefix); err == nil {
913+
if code, err := opt.ScriptRewriter(source, "event-handler", opt.TargetURL.String(), shareurl.ControlPrefix, opt.TabID, opt.RuntimeToken); err == nil {
934914
return code
935915
}
936916
}
937917
return `throw new DOMException('Blocked by ZeroProxy rewrite policy','NotSupportedError')`
938918
}
939919

940920
func rewriteInlineStyle(source string, opt Options) string {
921+
if strings.TrimSpace(source) == "" {
922+
return ""
923+
}
941924
if opt.CSSRewriter != nil {
942925
if code, err := opt.CSSRewriter(source, opt.TargetURL.String()); err == nil {
943926
return code
944927
}
945928
}
946-
return source
929+
return ""
947930
}
948931

949932
func rewriteInlineImportMap(source string, opt Options) string {
950-
if opt.ImportMapRewriter != nil {
951-
code, err := opt.ImportMapRewriter(
952-
source,
953-
opt.TargetURL.String(),
954-
opt.TabID,
955-
opt.RuntimeToken,
956-
shareurl.ControlPrefix,
957-
)
958-
if err != nil {
959-
return `{}`
960-
}
961-
return code
962-
}
963-
return rewriteImportMap(source, opt)
964-
}
965-
966-
func blockScriptSource() string {
967-
return `throw new DOMException('Blocked by ZeroProxy rewrite policy','NotSupportedError');`
968-
}
969-
970-
func rewriteImportMap(source string, opt Options) string {
971-
var doc map[string]any
972-
if err := json.Unmarshal([]byte(source), &doc); err != nil {
933+
if opt.ImportMapRewriter == nil {
973934
return `{}`
974935
}
975-
if imports, ok := doc["imports"].(map[string]any); ok {
976-
rewriteImportMapAddresses(imports, opt)
977-
}
978-
if scopes, ok := doc["scopes"].(map[string]any); ok {
979-
doc["scopes"] = rewriteImportMapScopes(scopes, opt)
980-
}
981-
b, err := json.Marshal(doc)
936+
code, err := opt.ImportMapRewriter(
937+
source,
938+
opt.TargetURL.String(),
939+
opt.TabID,
940+
opt.RuntimeToken,
941+
shareurl.ControlPrefix,
942+
)
982943
if err != nil {
983944
return `{}`
984945
}
985-
return string(b)
946+
return code
986947
}
987948

988-
// rewriteImportMapAddress maps a single import-map specifier address to its
989-
// proxied module script URL, blocking non-http(s) targets.
990-
func rewriteImportMapAddress(raw string, opt Options) string {
991-
u, err := url.Parse(strings.TrimSpace(raw))
992-
if err != nil {
993-
return shareurl.ControlPrefix + "error/POLICY_BLOCKED"
994-
}
995-
abs := opt.TargetURL.ResolveReference(u)
996-
if abs.Scheme != "http" && abs.Scheme != "https" {
997-
return shareurl.ControlPrefix + "error/POLICY_BLOCKED"
998-
}
999-
q := url.Values{}
1000-
q.Set("kind", "module")
1001-
q.Set("u", abs.String())
1002-
q.Set("tab", opt.TabID)
1003-
q.Set("rt", opt.RuntimeToken)
1004-
return shareurl.ControlPrefix + "api/script?" + q.Encode()
1005-
}
1006-
1007-
// rewriteImportMapAddresses rewrites every string-valued address in a specifier
1008-
// map in place.
1009-
func rewriteImportMapAddresses(addresses map[string]any, opt Options) {
1010-
for k, v := range addresses {
1011-
if s, ok := v.(string); ok {
1012-
addresses[k] = rewriteImportMapAddress(s, opt)
1013-
}
1014-
}
1015-
}
1016-
1017-
// rewriteImportMapScopes rewrites both the scope keys and their nested specifier
1018-
// maps, returning a fresh map keyed by the rewritten scope addresses.
1019-
func rewriteImportMapScopes(scopes map[string]any, opt Options) map[string]any {
1020-
next := make(map[string]any, len(scopes))
1021-
for scope, rawEntries := range scopes {
1022-
scopeKey := rewriteImportMapAddress(scope, opt)
1023-
entries, _ := rawEntries.(map[string]any)
1024-
out := make(map[string]any, len(entries))
1025-
for k, v := range entries {
1026-
if s, ok := v.(string); ok {
1027-
out[k] = rewriteImportMapAddress(s, opt)
1028-
}
1029-
}
1030-
next[scopeKey] = out
1031-
}
1032-
return next
949+
func blockScriptSource() string {
950+
return `throw new DOMException('Blocked by ZeroProxy rewrite policy','NotSupportedError');`
1033951
}
1034952

1035953
func injectSrcdoc(src string, opt Options) string {

internal/htmltx/transform_performance_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@ func TestTransformLatencyStaysWithinCoarseBudgets(t *testing.T) {
4343
TabID: "tab",
4444
EntryID: "entry",
4545
TargetURL: target,
46-
ScriptRewriter: func(source, kind, targetURL, controlPrefix string) (string, error) {
46+
ScriptRewriter: func(source, kind, targetURL, controlPrefix, tabID, runtimeToken string) (string, error) {
4747
return source, nil
4848
},
49+
ScriptURLRewriter: scriptURLRewriterForTest,
50+
FetchURLRewriter: fetchURLRewriterForTest,
4951
CSSRewriter: func(source, baseURL string) (string, error) {
5052
return source, nil
5153
},

0 commit comments

Comments
 (0)