servo-script 0.5.0

A component of the servo web-engine.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::RefCell;

use dom_struct::dom_struct;
use html5ever::{LocalName, Namespace, QualName, local_name, ns};
use js::context::JSContext;
use js::conversions::ToJSValConvertible;
use js::jsval::NullValue;
use js::rust::HandleValue;
use script_bindings::reflector::{Reflector, reflect_dom_object_with_cx};

use crate::conversions::Convert;
use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyFactoryBinding::{
    TrustedTypePolicyFactoryMethods, TrustedTypePolicyOptions,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
use crate::dom::bindings::conversions::root_from_handlevalue;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::csp::CspReporting;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
use crate::dom::trustedtypes::trustedscript::TrustedScript;
use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
use crate::dom::trustedtypes::trustedtypepolicy::{TrustedType, TrustedTypePolicy};
use crate::dom::types::WorkerGlobalScope;
use crate::dom::window::Window;

#[dom_struct]
pub struct TrustedTypePolicyFactory {
    reflector_: Reflector,

    default_policy: MutNullableDom<TrustedTypePolicy>,
    policy_names: RefCell<Vec<String>>,
}

pub(crate) static DEFAULT_SCRIPT_SINK_GROUP: &str = "'script'";

// We currently always clone the result, so keep the `clone()` in the trait
// for now to keep the caller side clean
impl Convert<DOMString> for TrustedTypeOrString {
    fn convert(self) -> DOMString {
        match self {
            TrustedTypeOrString::TrustedHTML(trusted_html) => trusted_html.data().clone(),
            TrustedTypeOrString::TrustedScript(trusted_script) => trusted_script.data().clone(),
            TrustedTypeOrString::TrustedScriptURL(trusted_script_url) => {
                trusted_script_url.data().clone()
            },
            TrustedTypeOrString::String(str_) => str_,
        }
    }
}

impl TrustedTypePolicyFactory {
    fn new_inherited() -> Self {
        Self {
            reflector_: Reflector::new(),
            default_policy: Default::default(),
            policy_names: RefCell::new(vec![]),
        }
    }

    pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Self> {
        reflect_dom_object_with_cx(Box::new(Self::new_inherited()), global, cx)
    }

    /// <https://www.w3.org/TR/trusted-types/#create-trusted-type-policy-algorithm>
    fn create_trusted_type_policy(
        &self,
        cx: &mut JSContext,
        policy_name: String,
        options: &TrustedTypePolicyOptions,
        global: &GlobalScope,
    ) -> Fallible<DomRoot<TrustedTypePolicy>> {
        // Avoid double borrow on policy_names
        {
            // Step 1: Let allowedByCSP be the result of executing Should Trusted Type policy creation be blocked by
            // Content Security Policy? algorithm with global, policyName and factory’s created policy names value.
            let policy_names = self.policy_names.borrow();
            let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
            let allowed_by_csp = global
                .get_csp_list()
                .is_trusted_type_policy_creation_allowed(cx, global, &policy_name, &policy_names);

            // Step 2: If allowedByCSP is "Blocked", throw a TypeError and abort further steps.
            if !allowed_by_csp {
                return Err(Error::Type(c"Not allowed by CSP".to_owned()));
            }
        }

        // Step 3: If policyName is default and the factory’s default policy value is not null, throw a TypeError
        // and abort further steps.
        if policy_name == "default" && self.default_policy.get().is_some() {
            return Err(Error::Type(
                c"Already set default policy for factory".to_owned(),
            ));
        }

        // Step 4: Let policy be a new TrustedTypePolicy object.
        // Step 5: Set policy’s name property value to policyName.
        // Step 6: Set policy’s options value to «[ "createHTML" ->
        // options["createHTML", "createScript" -> options["createScript",
        // "createScriptURL" -> options["createScriptURL" ]».
        let policy = TrustedTypePolicy::new(cx, policy_name.clone(), options, global);
        // Step 7: If the policyName is default, set the factory’s default policy value to policy.
        if policy_name == "default" {
            self.default_policy.set(Some(&policy))
        }
        // Step 8: Append policyName to factory’s created policy names.
        self.policy_names.borrow_mut().push(policy_name);
        // Step 9: Return policy.
        Ok(policy)
    }

    /// <https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-get-trusted-type-data-for-attribute>
    #[expect(clippy::if_same_then_else)]
    fn get_trusted_type_data_for_attribute(
        element_namespace: &Namespace,
        element_name: &LocalName,
        attribute: &str,
        attribute_namespace: Option<&Namespace>,
    ) -> Option<(TrustedType, String)> {
        // Step 1: Let data be null.
        //
        // We return the if directly
        // Step 2: If attributeNs is null, « HTML namespace, SVG namespace, MathML namespace » contains
        // element’s namespace, and attribute is the name of an event handler content attribute:
        if attribute_namespace.is_none() &&
            matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
            EventTarget::is_content_event_handler(attribute)
        {
            // Step 2.1. Return (Element, null, attribute, TrustedScript, "Element " + attribute).
            return Some((
                TrustedType::TrustedScript,
                "Element ".to_owned() + attribute,
            ));
        }
        // Step 3: Find the row in the following table, where element is in the first column,
        // attributeNs is in the second column, and attribute is in the third column.
        // If a matching row is found, set data to that row.
        // Step 4: Return data.
        if *element_namespace == ns!(html) &&
            *element_name == local_name!("iframe") &&
            attribute_namespace.is_none() &&
            attribute == "srcdoc"
        {
            Some((
                TrustedType::TrustedHTML,
                "HTMLIFrameElement srcdoc".to_owned(),
            ))
        } else if *element_namespace == ns!(html) &&
            *element_name == local_name!("script") &&
            attribute_namespace.is_none() &&
            attribute == "src"
        {
            Some((
                TrustedType::TrustedScriptURL,
                "HTMLScriptElement src".to_owned(),
            ))
        } else if *element_namespace == ns!(svg) &&
            *element_name == local_name!("script") &&
            attribute_namespace.is_none() &&
            attribute == "href"
        {
            Some((
                TrustedType::TrustedScriptURL,
                "SVGScriptElement href".to_owned(),
            ))
        } else if *element_namespace == ns!(svg) &&
            *element_name == local_name!("script") &&
            attribute_namespace == Some(&ns!(xlink)) &&
            attribute == "href"
        {
            Some((
                TrustedType::TrustedScriptURL,
                "SVGScriptElement href".to_owned(),
            ))
        } else {
            None
        }
    }

    /// <https://w3c.github.io/trusted-types/dist/spec/#validate-attribute-mutation>
    pub(crate) fn get_trusted_types_compliant_attribute_value(
        cx: &mut JSContext,
        element_namespace: &Namespace,
        element_name: &LocalName,
        attribute: &str,
        attribute_namespace: Option<&Namespace>,
        new_value: TrustedTypeOrString,
        global: &GlobalScope,
    ) -> Fallible<DOMString> {
        // Step 1. If attributeNs is the empty string, set attributeNs to null.
        let attribute_namespace =
            attribute_namespace.and_then(|a| if *a == ns!() { None } else { Some(a) });
        // Step 2. Set attributeData to the result of Get Trusted Type data for attribute algorithm,
        // with the following arguments:
        let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
            element_namespace,
            element_name,
            attribute,
            attribute_namespace,
        ) else {
            // Step 3. If attributeData is null, then:
            // Step 3.1. If newValue is a string, return newValue.
            // Step 3.2. Assert: newValue is TrustedHTML or TrustedScript or TrustedScriptURL.
            // Step 3.3. Return value’s associated data.
            return Ok(new_value.convert());
        };
        // Step 4. Let expectedType be the value of the fourth member of attributeData.
        // Step 5. Let sink be the value of the fifth member of attributeData.
        let (expected_type, sink) = attribute_data;
        let new_value = if let TrustedTypeOrString::String(str_) = new_value {
            str_
        } else {
            // If the type was already trusted, we should return immediately as
            // all callers of `get_trusted_type_compliant_string` implement this
            // check themselves. However, we should only do this if it matches
            // the expected type.
            if expected_type.matches_idl_trusted_type(&new_value) {
                return Ok(new_value.convert());
            }
            new_value.convert()
        };
        // Step 6. Return the result of executing Get Trusted Type compliant string with the following arguments:
        // If the algorithm threw an error, rethrow the error.
        Self::get_trusted_type_compliant_string(
            cx,
            expected_type,
            global,
            new_value,
            &sink,
            DEFAULT_SCRIPT_SINK_GROUP,
        )
    }

    /// <https://w3c.github.io/trusted-types/dist/spec/#process-value-with-a-default-policy-algorithm>
    pub(crate) fn process_value_with_default_policy(
        cx: &mut JSContext,
        expected_type: TrustedType,
        global: &GlobalScope,
        input: DOMString,
        sink: &str,
    ) -> Fallible<Option<DOMString>> {
        // Step 1: Let defaultPolicy be the value of global’s trusted type policy factory's default policy.
        let global_policy_factory = global.trusted_types(cx);
        let default_policy = match global_policy_factory.default_policy.get() {
            None => return Ok(None),
            Some(default_policy) => default_policy,
        };
        // Step 2: Let policyValue be the result of executing Get Trusted Type policy value,
        // with the following arguments:
        rooted!(&in(cx) let mut trusted_type_name_value = NullValue());
        expected_type
            .as_ref()
            .safe_to_jsval(cx, trusted_type_name_value.handle_mut());

        rooted!(&in(cx) let mut sink_value = NullValue());
        sink.safe_to_jsval(cx, sink_value.handle_mut());

        let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
        let policy_value = default_policy.get_trusted_type_policy_value(
            cx,
            expected_type,
            input,
            arguments,
            false,
        );
        let data_string = match policy_value {
            // Step 3: If the algorithm threw an error, rethrow the error and abort the following steps.
            Err(error) => return Err(error),
            Ok(policy_value) => match policy_value {
                // Step 4: If policyValue is null or undefined, return policyValue.
                None => return Ok(None),
                // Step 5: Let dataString be the result of stringifying policyValue.
                Some(policy_value) => policy_value,
            },
        };
        Ok(Some(data_string))
    }
    /// Step 1 is implemented by the caller
    /// <https://w3c.github.io/trusted-types/dist/spec/#get-trusted-type-compliant-string-algorithm>
    pub(crate) fn get_trusted_type_compliant_string(
        cx: &mut JSContext,
        expected_type: TrustedType,
        global: &GlobalScope,
        input: DOMString,
        sink: &str,
        sink_group: &str,
    ) -> Fallible<DOMString> {
        // Step 2: Let requireTrustedTypes be the result of executing Does sink type require trusted types?
        // algorithm, passing global, sinkGroup, and true.
        let require_trusted_types = global
            .get_csp_list()
            .does_sink_type_require_trusted_types(sink_group, true);
        // Step 3: If requireTrustedTypes is false, return stringified input and abort these steps.
        if !require_trusted_types {
            return Ok(input);
        }
        // Step 4: Let convertedInput be the result of executing Process value with a default policy
        // with the same arguments as this algorithm.
        let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
            cx,
            expected_type,
            global,
            input.clone(),
            sink,
        );
        // Step 5: If the algorithm threw an error, rethrow the error and abort the following steps.
        match converted_input? {
            // Step 6: If convertedInput is null or undefined, execute the following steps:
            None => {
                // Step 6.1: Let disposition be the result of executing Should sink type mismatch violation
                // be blocked by Content Security Policy? algorithm, passing global,
                // stringified input as source, sinkGroup and sink.
                let is_blocked = global
                    .get_csp_list()
                    .should_sink_type_mismatch_violation_be_blocked_by_csp(
                        cx,
                        global,
                        sink,
                        sink_group,
                        &input.str(),
                    );
                // Step 6.2: If disposition is “Allowed”, return stringified input and abort further steps.
                if !is_blocked {
                    Ok(input)
                } else {
                    // Step 6.3: Throw a TypeError and abort further steps.
                    Err(Error::Type(
                        c"Cannot set value, expected trusted type".to_owned(),
                    ))
                }
            },
            // Step 8: Return stringified convertedInput.
            Some(converted_input) => Ok(converted_input),
        }
        // Step 7: Assert: convertedInput is an instance of expectedType.
        // TODO(https://github.com/w3c/trusted-types/issues/566): Implement when spec is resolved
    }

    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscript>
    pub(crate) fn is_trusted_script(
        cx: &mut JSContext,
        value: HandleValue,
    ) -> Result<DomRoot<TrustedScript>, ()> {
        root_from_handlevalue::<TrustedScript>(cx, value)
    }
}

impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-createpolicy>
    fn CreatePolicy(
        &self,
        cx: &mut JSContext,
        policy_name: DOMString,
        options: &TrustedTypePolicyOptions,
    ) -> Fallible<DomRoot<TrustedTypePolicy>> {
        self.create_trusted_type_policy(cx, String::from(policy_name), options, &self.global())
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-ishtml>
    fn IsHTML(&self, cx: &mut JSContext, value: HandleValue) -> bool {
        root_from_handlevalue::<TrustedHTML>(cx, value).is_ok()
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscript>
    fn IsScript(&self, cx: &mut JSContext, value: HandleValue) -> bool {
        TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-isscripturl>
    fn IsScriptURL(&self, cx: &mut JSContext, value: HandleValue) -> bool {
        root_from_handlevalue::<TrustedScriptURL>(cx, value).is_ok()
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-emptyhtml>
    fn EmptyHTML(&self, cx: &mut JSContext) -> DomRoot<TrustedHTML> {
        TrustedHTML::new(cx, DOMString::new(), &self.global())
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-emptyscript>
    fn EmptyScript(&self, cx: &mut JSContext) -> DomRoot<TrustedScript> {
        TrustedScript::new(cx, DOMString::new(), &self.global())
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-getattributetype>
    fn GetAttributeType(
        &self,
        tag_name: DOMString,
        attribute: DOMString,
        element_namespace: Option<DOMString>,
        attribute_namespace: Option<DOMString>,
    ) -> Option<DOMString> {
        // Step 1: Set localName to tagName in ASCII lowercase.
        let local_name = tag_name.to_ascii_lowercase();
        // Step 2: Set attribute to attribute in ASCII lowercase.
        let attribute = attribute.to_ascii_lowercase();
        // Step 3: If elementNs is null or an empty string, set elementNs to HTML namespace.
        let element_namespace = match element_namespace {
            Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
            Some(_) | None => ns!(html),
        };
        // Step 4: If attrNs is an empty string, set attrNs to null.
        let attribute_namespace = match attribute_namespace {
            Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
            Some(_) | None => None,
        };
        // Step 5: Let interface be the element interface for localName and elementNs.
        // Step 6: Let expectedType be null.
        // Step 7: Set attributeData to the result of Get Trusted Type data for attribute algorithm,
        // with the following arguments: interface as element, attribute, attrNs
        // Step 8: If attributeData is not null, then set expectedType to the interface’s name of
        // the value of the fourth member of attributeData.
        // Step 9: Return expectedType.
        TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
            &element_namespace,
            &LocalName::from(local_name),
            &attribute,
            attribute_namespace.as_ref(),
        )
        .map(|tuple| DOMString::from(tuple.0.as_ref()))
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-getpropertytype>
    #[expect(clippy::if_same_then_else)]
    fn GetPropertyType(
        &self,
        tag_name: DOMString,
        property: DOMString,
        element_namespace: Option<DOMString>,
    ) -> Option<DOMString> {
        // Step 1: Set localName to tagName in ASCII lowercase.
        let local_name = tag_name.to_ascii_lowercase();
        // Step 2: If elementNs is null or an empty string, set elementNs to HTML namespace.
        let element_namespace = match element_namespace {
            Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
            Some(_) | None => ns!(html),
        };
        // Step 3: Let interface be the element interface for localName and elementNs.
        let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
        // Step 4: Let expectedType be null.
        let mut expected_type = None;
        // Step 5: Find the row in the following table, where the first column is "*" or interface’s name,
        // and property is in the second column. If a matching row is found, set expectedType to
        // the interface’s name of the value of the third column.
        let property = property.str();
        if interface.ns == ns!(html) &&
            interface.local == local_name!("iframe") &&
            property == "srcdoc"
        {
            expected_type = Some(DOMString::from("TrustedHTML"))
        } else if interface.ns == ns!(html) &&
            interface.local == local_name!("script") &&
            property == "innerText"
        {
            expected_type = Some(DOMString::from("TrustedScript"))
        } else if interface.ns == ns!(html) &&
            interface.local == local_name!("script") &&
            property == "src"
        {
            expected_type = Some(DOMString::from("TrustedScriptURL"))
        } else if interface.ns == ns!(html) &&
            interface.local == local_name!("script") &&
            property == "text"
        {
            expected_type = Some(DOMString::from("TrustedScript"))
        } else if interface.ns == ns!(html) &&
            interface.local == local_name!("script") &&
            property == "textContent"
        {
            expected_type = Some(DOMString::from("TrustedScript"))
        } else if property == "innerHTML" {
            expected_type = Some(DOMString::from("TrustedHTML"))
        } else if property == "outerHTML" {
            expected_type = Some(DOMString::from("TrustedHTML"))
        }
        // Step 6: Return expectedType.
        expected_type
    }
    /// <https://www.w3.org/TR/trusted-types/#dom-trustedtypepolicyfactory-defaultpolicy>
    fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
        self.default_policy.get()
    }
}

impl GlobalScope {
    fn trusted_types(&self, cx: &mut JSContext) -> DomRoot<TrustedTypePolicyFactory> {
        if let Some(window) = self.downcast::<Window>() {
            return window.TrustedTypes(cx);
        }
        if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
            return worker.TrustedTypes(cx);
        }
        unreachable!();
    }
}