Skip to content

Commit e658b27

Browse files
committed
trusted types
1 parent eae9f9c commit e658b27

File tree

4 files changed

+158
-1
lines changed

4 files changed

+158
-1
lines changed

src/browser/html/html.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const History = @import("history.zig").History;
2525
const Location = @import("location.zig").Location;
2626
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
2727
const Performance = @import("performance.zig").Performance;
28+
const TrustedTypes = @import("trusted_types.zig");
2829

2930
pub const Interfaces = .{
3031
HTMLDocument,
@@ -38,4 +39,5 @@ pub const Interfaces = .{
3839
Location,
3940
MediaQueryList,
4041
Performance,
42+
TrustedTypes.Interfaces,
4143
};

src/browser/html/trusted_types.zig

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Copyright (C) 2023-2025 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
const Allocator = std.mem.Allocator;
21+
22+
const Env = @import("../env.zig").Env;
23+
const SessionState = @import("../env.zig").SessionState;
24+
25+
const log = std.log.scoped(.trusted_types);
26+
27+
pub const Interfaces = .{
28+
TrustedTypePolicyFactory,
29+
TrustedTypePolicy,
30+
TrustedTypePolicyOptions,
31+
TrustedHTML,
32+
TrustedScript,
33+
TrustedScriptURL,
34+
};
35+
36+
const TrustedHTML = struct {
37+
value: []const u8,
38+
39+
// TODO _toJSON
40+
pub fn _toString(self: *const TrustedHTML) []const u8 {
41+
return self.value;
42+
}
43+
};
44+
const TrustedScript = struct {
45+
value: []const u8,
46+
47+
pub fn _toString(self: *const TrustedScript) []const u8 {
48+
return self.value;
49+
}
50+
};
51+
const TrustedScriptURL = struct {
52+
value: []const u8,
53+
54+
pub fn _toString(self: *const TrustedScriptURL) []const u8 {
55+
return self.value;
56+
}
57+
};
58+
59+
// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicyFactory
60+
pub const TrustedTypePolicyFactory = struct {
61+
// TBD innerHTML if set the default createHTML should be used when `element.innerHTML = userInput;` does v8 do that for us? Prob not.
62+
default_policy: ?TrustedTypePolicy = null, // The default policy, set by creating a policy with the name "default".
63+
created_policy_names: std.ArrayListUnmanaged([]const u8) = .empty,
64+
65+
pub fn _defaultPolicy(self: *TrustedTypePolicyFactory) ?TrustedTypePolicy {
66+
return self.default_policy;
67+
}
68+
69+
// https://w3c.github.io/trusted-types/dist/spec/#dom-trustedtypepolicyfactory-createpolicy
70+
// https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-create-a-trusted-type-policy
71+
pub fn _createPolicy(self: *TrustedTypePolicyFactory, name: []const u8, options: ?TrustedTypePolicyOptions, state: *SessionState) !TrustedTypePolicy {
72+
// TODO Throw TypeError if policy names are restricted by the Content Security Policy trusted-types directive and this name is not on the allowlist.
73+
// TODO Throw TypeError if the name is a duplicate and the Content Security Policy trusted-types directive is not using allow-duplicates
74+
75+
const policy = TrustedTypePolicy{
76+
.name = name,
77+
.options = options orelse TrustedTypePolicyOptions{},
78+
};
79+
80+
if (std.mem.eql(u8, name, "default")) {
81+
// TBD what if default_policy is already set?
82+
self.default_policy = policy;
83+
}
84+
try self.created_policy_names.append(state.arena, try state.arena.dupe(u8, name));
85+
86+
return policy;
87+
}
88+
};
89+
90+
pub const TrustedTypePolicyOptions = struct {
91+
createHTML: ?Env.Function = null, // (str, ..args) -> str
92+
createScript: ?Env.Function = null, // (str, ..args) -> str
93+
createScriptURL: ?Env.Function = null, // (str, ..args) -> str
94+
};
95+
96+
// https://developer.mozilla.org/en-US/docs/Web/API/TrustedTypePolicy
97+
pub const TrustedTypePolicy = struct {
98+
name: []const u8,
99+
options: TrustedTypePolicyOptions,
100+
101+
pub fn get_name(self: *TrustedTypePolicy) []const u8 {
102+
return self.name;
103+
}
104+
105+
pub fn _createHTML(self: *TrustedTypePolicy, html: []const u8) !TrustedHTML {
106+
// TODO handle throwIfMissing
107+
const create = self.options.createHTML orelse return error.TypeError;
108+
109+
var result: Env.Function.Result = undefined;
110+
const out = try create.tryCall([]const u8, .{html}, &result); // TODO varargs
111+
return .{
112+
.value = out,
113+
};
114+
}
115+
116+
pub fn _createScript(self: *TrustedTypePolicy, script: []const u8) !TrustedScript {
117+
// TODO handle throwIfMissing
118+
const create = self.options.createScript orelse return error.TypeError;
119+
120+
var result: Env.Function.Result = undefined;
121+
return try create.tryCall(TrustedScript, .{script}, &result); // TODO varargs
122+
}
123+
124+
pub fn _createScriptURL(self: *TrustedTypePolicy, url: []const u8) !TrustedScriptURL {
125+
// TODO handle throwIfMissing
126+
const create = self.options.createScriptURL orelse return error.TypeError;
127+
128+
var result: Env.Function.Result = undefined;
129+
return try create.tryCall(TrustedScriptURL, .{url}, &result); // TODO varargs
130+
}
131+
};
132+
133+
const testing = @import("../../testing.zig");
134+
test "Browser.TrustedTypes" {
135+
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
136+
defer runner.deinit();
137+
138+
try runner.testCases(&.{
139+
.{ "trustedTypes", "[object TrustedTypePolicyFactory]" },
140+
.{
141+
\\ let escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
142+
\\ createHTML: (string) => string.replace(/</g, "&lt;"),
143+
\\ });
144+
,
145+
null,
146+
},
147+
.{ "escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');", "&lt;img src=x onerror=alert(1)>" },
148+
}, .{});
149+
}

src/browser/html/window.zig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const Console = @import("../console/console.zig").Console;
3131
const EventTarget = @import("../dom/event_target.zig").EventTarget;
3232
const MediaQueryList = @import("media_query_list.zig").MediaQueryList;
3333
const Performance = @import("performance.zig").Performance;
34+
const TrustedTypePolicyFactory = @import("trusted_types.zig").TrustedTypePolicyFactory;
3435

3536
const storage = @import("../storage/storage.zig");
3637

@@ -58,6 +59,7 @@ pub const Window = struct {
5859
console: Console = .{},
5960
navigator: Navigator = .{},
6061
performance: Performance,
62+
trusted_types: TrustedTypePolicyFactory = .{},
6163

6264
pub fn create(target: ?[]const u8, navigator: ?Navigator) !Window {
6365
var fbs = std.io.fixedBufferStream("");
@@ -154,6 +156,10 @@ pub const Window = struct {
154156
return &self.performance;
155157
}
156158

159+
pub fn get_trustedTypes(self: *Window) !TrustedTypePolicyFactory {
160+
return self.trusted_types;
161+
}
162+
157163
// Tells the browser you wish to perform an animation. It requests the browser to call a user-supplied callback function before the next repaint.
158164
// fn callback(timestamp: f64)
159165
// Returns the request ID, that uniquely identifies the entry in the callback list.

src/runtime/js.zig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1254,7 +1254,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
12541254
return self.tryCallWithThis(T, self.getThis(), args, result);
12551255
}
12561256

1257-
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !void {
1257+
pub fn tryCallWithThis(self: *const Function, comptime T: type, this: anytype, args: anytype, result: *Result) !T {
12581258
var try_catch: TryCatch = undefined;
12591259
try_catch.init(self.scope);
12601260
defer try_catch.deinit();

0 commit comments

Comments
 (0)