1use std::ffi::CStr;
8use std::os::raw::c_char;
9use std::ptr;
10use std::ptr::NonNull;
11
12use js::conversions::{ToJSValConvertible, jsstr_to_string};
13use js::glue::{
14 GetProxyHandler, GetProxyHandlerFamily, GetProxyPrivate, InvokeGetOwnPropertyDescriptor,
15 SetProxyPrivate,
16};
17use js::jsapi::{
18 DOMProxyShadowsResult, GetStaticPrototype, GetWellKnownSymbol, Handle as RawHandle,
19 HandleId as RawHandleId, HandleObject as RawHandleObject, HandleValue as RawHandleValue,
20 JS_AtomizeAndPinString, JS_DefinePropertyById, JS_GetOwnPropertyDescriptorById,
21 JS_IsExceptionPending, JSContext, JSErrNum, JSFunctionSpec, JSObject, JSPropertySpec,
22 MutableHandle as RawMutableHandle, MutableHandleIdVector as RawMutableHandleIdVector,
23 MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
24 ObjectOpResult, PropertyDescriptor, SetDOMProxyInformation, SymbolCode, jsid,
25};
26use js::jsid::SymbolId;
27use js::jsval::{ObjectValue, UndefinedValue};
28use js::realm::{AutoRealm, CurrentRealm};
29use js::rust::wrappers::{
30 AppendToIdVector, JS_AlreadyHasOwnPropertyById, JS_NewObjectWithGivenProto,
31 RUST_INTERNED_STRING_TO_JSID, SetDataPropertyDescriptor,
32};
33use js::rust::{Handle, HandleObject, HandleValue, IntoHandle, MutableHandle, MutableHandleObject};
34use js::{jsapi, rooted};
35
36use crate::DomTypes;
37use crate::conversions::{is_dom_proxy, jsid_to_string};
38use crate::error::Error;
39use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
40use crate::realms::{AlreadyInRealm, InRealm};
41use crate::reflector::DomObject;
42use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
43use crate::str::DOMString;
44use crate::utils::delete_property_by_id;
45
46pub(crate) unsafe extern "C" fn shadow_check_callback(
51 cx: *mut JSContext,
52 object: RawHandleObject,
53 id: RawHandleId,
54) -> DOMProxyShadowsResult {
55 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
58 get_expando_object(object, expando.handle_mut());
59 if !expando.get().is_null() {
60 let mut has_own = false;
61 let raw_id = Handle::from_raw(id);
62
63 if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) {
64 return DOMProxyShadowsResult::ShadowCheckFailed;
65 }
66
67 if has_own {
68 return DOMProxyShadowsResult::ShadowsViaDirectExpando;
69 }
70 }
71
72 DOMProxyShadowsResult::DoesntShadow
74}
75
76pub fn init() {
78 unsafe {
79 SetDOMProxyInformation(
80 GetProxyHandlerFamily(),
81 Some(shadow_check_callback),
82 ptr::null(),
83 );
84 }
85}
86
87pub(crate) unsafe extern "C" fn define_property(
93 cx: *mut JSContext,
94 proxy: RawHandleObject,
95 id: RawHandleId,
96 desc: RawHandle<PropertyDescriptor>,
97 result: *mut ObjectOpResult,
98) -> bool {
99 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
100 ensure_expando_object(cx, proxy, expando.handle_mut());
101 JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result)
102}
103
104pub(crate) unsafe extern "C" fn delete(
110 cx: *mut JSContext,
111 proxy: RawHandleObject,
112 id: RawHandleId,
113 bp: *mut ObjectOpResult,
114) -> bool {
115 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
116 get_expando_object(proxy, expando.handle_mut());
117 if expando.is_null() {
118 (*bp).code_ = 0 ;
119 return true;
120 }
121
122 delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp)
123}
124
125pub(crate) unsafe extern "C" fn prevent_extensions(
130 _cx: *mut JSContext,
131 _proxy: RawHandleObject,
132 result: *mut ObjectOpResult,
133) -> bool {
134 (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t;
135 true
136}
137
138pub(crate) unsafe extern "C" fn is_extensible(
143 _cx: *mut JSContext,
144 _proxy: RawHandleObject,
145 succeeded: *mut bool,
146) -> bool {
147 *succeeded = true;
148 true
149}
150
151pub(crate) unsafe extern "C" fn get_prototype_if_ordinary(
164 _: *mut JSContext,
165 proxy: RawHandleObject,
166 is_ordinary: *mut bool,
167 proto: RawMutableHandleObject,
168) -> bool {
169 *is_ordinary = true;
170 proto.set(GetStaticPrototype(proxy.get()));
171 true
172}
173
174pub(crate) fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) {
176 unsafe {
177 assert!(is_dom_proxy(obj.get()));
178 let val = &mut UndefinedValue();
179 GetProxyPrivate(obj.get(), val);
180 expando.set(if val.is_undefined() {
181 ptr::null_mut()
182 } else {
183 val.to_object()
184 });
185 }
186}
187
188pub(crate) unsafe fn ensure_expando_object(
194 cx: *mut JSContext,
195 obj: RawHandleObject,
196 mut expando: MutableHandleObject,
197) {
198 assert!(is_dom_proxy(obj.get()));
199 get_expando_object(obj, expando.reborrow());
200 if expando.is_null() {
201 expando.set(JS_NewObjectWithGivenProto(
202 cx,
203 ptr::null_mut(),
204 HandleObject::null(),
205 ));
206 assert!(!expando.is_null());
207
208 SetProxyPrivate(obj.get(), &ObjectValue(expando.get()));
209 }
210}
211
212pub fn set_property_descriptor(
215 desc: MutableHandle<PropertyDescriptor>,
216 value: HandleValue,
217 attrs: u32,
218 is_none: &mut bool,
219) {
220 unsafe {
221 SetDataPropertyDescriptor(desc, value, attrs);
222 }
223 *is_none = false;
224}
225
226pub(crate) fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> {
227 unsafe {
228 rooted!(in(*cx) let mut value = UndefinedValue());
229 rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
230 jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into())
231 .then(|| {
232 jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into()));
233 jsstr.get()
234 })
235 .and_then(ptr::NonNull::new)
236 .map(|jsstr| DOMString::from_string(jsstr_to_string(*cx, jsstr)))
237 }
238}
239
240pub(crate) struct CrossOriginProperties {
245 pub(crate) attributes: &'static [JSPropertySpec],
246 pub(crate) methods: &'static [JSFunctionSpec],
247}
248
249impl CrossOriginProperties {
250 fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
252 self.attributes
254 .iter()
255 .map(|spec| unsafe { spec.name.string_ })
256 .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
257 .filter(|ptr| !ptr.is_null())
258 }
259}
260
261pub(crate) fn cross_origin_own_property_keys(
265 cx: SafeJSContext,
266 _proxy: RawHandleObject,
267 cross_origin_properties: &'static CrossOriginProperties,
268 props: RawMutableHandleIdVector,
269) -> bool {
270 for key in cross_origin_properties.keys() {
273 unsafe {
274 rooted!(in(*cx) let rooted = JS_AtomizeAndPinString(*cx, key));
275 rooted!(in(*cx) let mut rooted_jsid: jsid);
276 RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut());
277 AppendToIdVector(props, rooted_jsid.handle());
278 }
279 }
280
281 append_cross_origin_allowlisted_prop_keys(cx, props);
284
285 true
286}
287
288pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
291 _: *mut JSContext,
292 _proxy: RawHandleObject,
293 is_ordinary: *mut bool,
294 _proto: RawMutableHandleObject,
295) -> bool {
296 *is_ordinary = false;
298 true
299}
300
301pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
309 cx: *mut JSContext,
310 proxy: RawHandleObject,
311 proto: RawHandleObject,
312 result: *mut ObjectOpResult,
313) -> bool {
314 rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
322 if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
323 return false;
324 }
325
326 if proto.get() == current.get() {
328 (*result).code_ = 0 ;
329 return true;
330 }
331
332 (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
334 true
335}
336
337pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
338 if d.hasGetter_() {
339 out.set(d.getter_);
340 }
341}
342
343pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
344 if d.hasSetter_() {
345 out.set(d.setter_);
346 }
347}
348
349pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
351 d.hasSetter_() || d.hasGetter_()
352}
353
354pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
356 d.hasWritable_() || d.hasValue_()
357}
358
359pub(crate) unsafe fn cross_origin_has_own(
368 cx: SafeJSContext,
369 _proxy: RawHandleObject,
370 cross_origin_properties: &'static CrossOriginProperties,
371 id: RawHandleId,
372 bp: *mut bool,
373) -> bool {
374 *bp = jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|key| {
378 cross_origin_properties.keys().any(|defined_key| {
379 let defined_key = CStr::from_ptr(defined_key);
380 defined_key.to_bytes() == key.str().as_bytes()
381 })
382 });
383
384 true
385}
386
387pub(crate) fn cross_origin_get_own_property_helper(
394 cx: SafeJSContext,
395 proxy: RawHandleObject,
396 cross_origin_properties: &'static CrossOriginProperties,
397 id: RawHandleId,
398 desc: RawMutableHandle<PropertyDescriptor>,
399 is_none: &mut bool,
400) -> bool {
401 rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>());
402
403 ensure_cross_origin_property_holder(
404 cx,
405 proxy,
406 cross_origin_properties,
407 holder.handle_mut().into(),
408 );
409
410 unsafe { JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc, is_none) }
411}
412
413const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
414 SymbolCode::toStringTag,
415 SymbolCode::hasInstance,
416 SymbolCode::isConcatSpreadable,
417];
418
419pub(crate) fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool {
420 unsafe {
421 if jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|st| st == "then") {
422 return true;
423 }
424
425 rooted!(in(*cx) let mut allowed_id: jsid);
426 ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
427 allowed_id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
428 allowed_id.get().asBits_ == id.asBits_
431 })
432 }
433}
434
435fn append_cross_origin_allowlisted_prop_keys(cx: SafeJSContext, props: RawMutableHandleIdVector) {
440 unsafe {
441 rooted!(in(*cx) let mut id: jsid);
442
443 let jsstring = JS_AtomizeAndPinString(*cx, c"then".as_ptr());
444 rooted!(in(*cx) let rooted = jsstring);
445 RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), id.handle_mut());
446 AppendToIdVector(props, id.handle());
447
448 for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
449 id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
450 AppendToIdVector(props, id.handle());
451 }
452 }
453}
454
455fn ensure_cross_origin_property_holder(
468 cx: SafeJSContext,
469 _proxy: RawHandleObject,
470 cross_origin_properties: &'static CrossOriginProperties,
471 out_holder: RawMutableHandleObject,
472) -> bool {
473 unsafe {
480 out_holder.set(jsapi::JS_NewObjectWithGivenProto(
481 *cx,
482 ptr::null_mut(),
483 RawHandleObject::null(),
484 ));
485
486 if out_holder.get().is_null() ||
487 !jsapi::JS_DefineProperties(
488 *cx,
489 out_holder.handle(),
490 cross_origin_properties.attributes.as_ptr(),
491 ) ||
492 !jsapi::JS_DefineFunctions(
493 *cx,
494 out_holder.handle(),
495 cross_origin_properties.methods.as_ptr(),
496 )
497 {
498 return false;
499 }
500 }
501
502 true
505}
506
507pub(crate) fn report_cross_origin_denial<D: DomTypes>(
514 cx: SafeJSContext,
515 id: RawHandleId,
516 access: &str,
517) -> bool {
518 debug!(
519 "permission denied to {} property {} on cross-origin object",
520 access,
521 id_to_source(cx, id)
522 .as_ref()
523 .map(|source| source.str())
524 .as_deref()
525 .unwrap_or("< error >"),
526 );
527 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
528 unsafe {
529 if !JS_IsExceptionPending(*cx) {
530 let global = D::GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
531 <D as DomHelpers<D>>::throw_dom_exception(
533 cx,
534 &global,
535 Error::Security(None),
536 CanGc::note(),
537 );
538 }
539 }
540 false
541}
542
543pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
547 cx: *mut JSContext,
548 proxy: RawHandleObject,
549 id: RawHandleId,
550 v: RawHandleValue,
551 receiver: RawHandleValue,
552 result: *mut ObjectOpResult,
553) -> bool {
554 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
555 let mut realm = js::realm::CurrentRealm::assert(&mut cx);
556 let proxy_handle = unsafe { HandleObject::from_raw(proxy) };
557
558 if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy) {
559 return cross_origin_set::<D>(
560 SafeJSContext::from_ptr(realm.raw_cx()),
561 proxy,
562 id,
563 v,
564 receiver,
565 result,
566 );
567 }
568
569 let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy_handle);
571
572 rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
575 let mut is_none = false;
576 if !js::glue::InvokeGetOwnPropertyDescriptor(
577 GetProxyHandler(*proxy),
578 realm.raw_cx(),
579 proxy,
580 id,
581 own_desc.handle_mut().into(),
582 &mut is_none,
583 ) {
584 return false;
585 }
586
587 let own_desc_handle = own_desc.handle().into();
588 js::jsapi::SetPropertyIgnoringNamedGetter(
589 realm.raw_cx(),
590 proxy,
591 id,
592 v,
593 receiver,
594 if is_none {
595 ptr::null()
596 } else {
597 &own_desc_handle
598 },
599 result,
600 )
601}
602
603pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
607 cx: &mut CurrentRealm,
608 proxy: RawHandleObject,
609 get_proto_object: fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
610 proto: RawMutableHandleObject,
611) -> bool {
612 let proxy = unsafe { Handle::from_raw(proxy) };
613 if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
615 let mut realm = AutoRealm::new_from_handle(cx, proxy);
616 let mut realm = realm.current_realm();
617 let global = D::GlobalScope::from_current_realm(&realm);
618 get_proto_object(
619 unsafe { SafeJSContext::from_ptr(realm.raw_cx()) },
620 global.reflector().get_jsobject(),
621 unsafe { MutableHandleObject::from_raw(proto) },
622 );
623 return !proto.is_null();
624 }
625
626 proto.set(ptr::null_mut());
628 true
629}
630
631pub(crate) fn cross_origin_get<D: DomTypes>(
638 cx: SafeJSContext,
639 proxy: RawHandleObject,
640 receiver: RawHandleValue,
641 id: RawHandleId,
642 vp: RawMutableHandleValue,
643) -> bool {
644 rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
646 let mut is_none = false;
647 if !unsafe {
648 InvokeGetOwnPropertyDescriptor(
649 GetProxyHandler(*proxy),
650 *cx,
651 proxy,
652 id,
653 descriptor.handle_mut().into(),
654 &mut is_none,
655 )
656 } {
657 return false;
658 }
659
660 assert!(
662 !is_none,
663 "Callees should throw in all cases when they are not finding \
664 a property decriptor"
665 );
666
667 if is_data_descriptor(&descriptor) {
669 vp.set(descriptor.value_);
670 return true;
671 }
672
673 assert!(is_accessor_descriptor(&descriptor));
675
676 rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>());
681 get_getter_object(&descriptor, getter.handle_mut().into());
682 if getter.get().is_null() {
683 return report_cross_origin_denial::<D>(cx, id, "get");
684 }
685
686 rooted!(in(*cx) let mut getter_jsval = UndefinedValue());
687 unsafe {
688 getter.get().to_jsval(*cx, getter_jsval.handle_mut());
689 }
690
691 unsafe {
693 jsapi::Call(
694 *cx,
695 receiver,
696 getter_jsval.handle().into(),
697 &jsapi::HandleValueArray::empty(),
698 vp,
699 )
700 }
701}
702
703pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
710 cx: SafeJSContext,
711 proxy: RawHandleObject,
712 id: RawHandleId,
713 v: RawHandleValue,
714 receiver: RawHandleValue,
715 result: *mut ObjectOpResult,
716) -> bool {
717 rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
719 let mut is_none = false;
720 if !InvokeGetOwnPropertyDescriptor(
721 GetProxyHandler(*proxy),
722 *cx,
723 proxy,
724 id,
725 descriptor.handle_mut().into(),
726 &mut is_none,
727 ) {
728 return false;
729 }
730
731 assert!(
733 !is_none,
734 "Callees should throw in all cases when they are not finding \
735 a property decriptor"
736 );
737
738 rooted!(in(*cx) let mut setter = ptr::null_mut::<JSObject>());
741 get_setter_object(&descriptor, setter.handle_mut().into());
742 if setter.get().is_null() {
743 return report_cross_origin_denial::<D>(cx, id, "set");
745 }
746
747 rooted!(in(*cx) let mut setter_jsval = UndefinedValue());
748 setter.get().to_jsval(*cx, setter_jsval.handle_mut());
749
750 rooted!(in(*cx) let mut ignored = UndefinedValue());
754 if !jsapi::Call(
755 *cx,
756 receiver,
757 setter_jsval.handle().into(),
758 &jsapi::HandleValueArray {
761 length_: 1,
762 elements_: v.ptr,
763 },
764 ignored.handle_mut().into(),
765 ) {
766 return false;
767 }
768
769 (*result).code_ = 0 ;
770 true
771}
772
773pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
780 cx: SafeJSContext,
781 _proxy: RawHandleObject,
782 id: RawHandleId,
783 desc: RawMutableHandle<PropertyDescriptor>,
784 is_none: &mut bool,
785) -> bool {
786 assert!(*is_none, "why are we being called?");
787
788 if is_cross_origin_allowlisted_prop(cx, id) {
793 set_property_descriptor(
794 unsafe { MutableHandle::from_raw(desc) },
795 HandleValue::undefined(),
796 jsapi::JSPROP_READONLY as u32,
797 is_none,
798 );
799 return true;
800 }
801
802 report_cross_origin_denial::<D>(cx, id, "access")
804}