|
| 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, "<"), |
| 143 | + \\ }); |
| 144 | + , |
| 145 | + null, |
| 146 | + }, |
| 147 | + .{ "escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');", "<img src=x onerror=alert(1)>" }, |
| 148 | + }, .{}); |
| 149 | +} |
0 commit comments