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::{GetProxyHandler, GetProxyHandlerFamily, GetProxyPrivate, SetProxyPrivate};
14use js::jsapi::{
15 DOMProxyShadowsResult, GetStaticPrototype, GetWellKnownSymbol, Handle as RawHandle,
16 HandleId as RawHandleId, HandleObject as RawHandleObject, HandleValue as RawHandleValue,
17 JS_DefinePropertyById, JSContext, JSErrNum, JSFunctionSpec, JSObject, JSPropertySpec,
18 MutableHandle as RawMutableHandle, MutableHandleIdVector as RawMutableHandleIdVector,
19 MutableHandleObject as RawMutableHandleObject, MutableHandleValue as RawMutableHandleValue,
20 ObjectOpResult, PropertyDescriptor, SetDOMProxyInformation, SymbolCode, jsid,
21};
22use js::jsid::SymbolId;
23use js::jsval::{ObjectValue, UndefinedValue};
24use js::realm::{AutoRealm, CurrentRealm};
25use js::rust::wrappers::{
26 AppendToIdVector, JS_AlreadyHasOwnPropertyById, JS_NewObjectWithGivenProto,
27 SetDataPropertyDescriptor,
28};
29use js::rust::{
30 Handle, HandleId, HandleObject, HandleValue, IntoHandle, MutableHandle, MutableHandleObject,
31};
32use js::{jsapi, rooted};
33
34use crate::DomTypes;
35use crate::conversions::{is_dom_proxy, jsid_to_string, native_from_object};
36use crate::error::Error;
37use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
38use crate::reflector::DomObject;
39use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
40use crate::str::DOMString;
41use crate::utils::delete_property_by_id;
42
43pub(crate) unsafe extern "C" fn shadow_check_callback(
48 cx: *mut JSContext,
49 object: RawHandleObject,
50 id: RawHandleId,
51) -> DOMProxyShadowsResult {
52 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
55 get_expando_object(object, expando.handle_mut());
56 if !expando.get().is_null() {
57 let mut has_own = false;
58 let raw_id = Handle::from_raw(id);
59
60 if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) {
61 return DOMProxyShadowsResult::ShadowCheckFailed;
62 }
63
64 if has_own {
65 return DOMProxyShadowsResult::ShadowsViaDirectExpando;
66 }
67 }
68
69 DOMProxyShadowsResult::DoesntShadow
71}
72
73pub fn init() {
75 unsafe {
76 SetDOMProxyInformation(
77 GetProxyHandlerFamily(),
78 Some(shadow_check_callback),
79 ptr::null(),
80 );
81 }
82}
83
84pub(crate) unsafe extern "C" fn define_property(
90 cx: *mut JSContext,
91 proxy: RawHandleObject,
92 id: RawHandleId,
93 desc: RawHandle<PropertyDescriptor>,
94 result: *mut ObjectOpResult,
95) -> bool {
96 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
97 ensure_expando_object(cx, proxy, expando.handle_mut());
98 JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result)
99}
100
101pub(crate) unsafe extern "C" fn delete(
107 cx: *mut JSContext,
108 proxy: RawHandleObject,
109 id: RawHandleId,
110 bp: *mut ObjectOpResult,
111) -> bool {
112 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
113 get_expando_object(proxy, expando.handle_mut());
114 if expando.is_null() {
115 (*bp).code_ = 0 ;
116 return true;
117 }
118
119 delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp)
120}
121
122pub(crate) unsafe extern "C" fn prevent_extensions(
127 _cx: *mut JSContext,
128 _proxy: RawHandleObject,
129 result: *mut ObjectOpResult,
130) -> bool {
131 (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t;
132 true
133}
134
135pub(crate) unsafe extern "C" fn is_extensible(
140 _cx: *mut JSContext,
141 _proxy: RawHandleObject,
142 succeeded: *mut bool,
143) -> bool {
144 *succeeded = true;
145 true
146}
147
148pub(crate) unsafe extern "C" fn get_prototype_if_ordinary(
161 _: *mut JSContext,
162 proxy: RawHandleObject,
163 is_ordinary: *mut bool,
164 proto: RawMutableHandleObject,
165) -> bool {
166 *is_ordinary = true;
167 proto.set(GetStaticPrototype(proxy.get()));
168 true
169}
170
171pub(crate) fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) {
173 unsafe {
174 assert!(is_dom_proxy(obj.get()));
175 let val = &mut UndefinedValue();
176 GetProxyPrivate(obj.get(), val);
177 expando.set(if val.is_undefined() {
178 ptr::null_mut()
179 } else {
180 val.to_object()
181 });
182 }
183}
184
185pub(crate) unsafe fn ensure_expando_object(
191 cx: *mut JSContext,
192 obj: RawHandleObject,
193 mut expando: MutableHandleObject,
194) {
195 assert!(is_dom_proxy(obj.get()));
196 get_expando_object(obj, expando.reborrow());
197 if expando.is_null() {
198 expando.set(JS_NewObjectWithGivenProto(
199 cx,
200 ptr::null_mut(),
201 HandleObject::null(),
202 ));
203 assert!(!expando.is_null());
204
205 SetProxyPrivate(obj.get(), &ObjectValue(expando.get()));
206 }
207}
208
209pub fn set_property_descriptor(
212 desc: MutableHandle<PropertyDescriptor>,
213 value: HandleValue,
214 attrs: u32,
215 is_none: &mut bool,
216) {
217 unsafe {
218 SetDataPropertyDescriptor(desc, value, attrs);
219 }
220 *is_none = false;
221}
222
223pub(crate) fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> {
224 unsafe {
225 if js::glue::RUST_JSID_IS_VOID(id) {
226 return None;
227 }
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| jsstr_to_string(*cx, jsstr).into())
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: &mut js::context::JSContext,
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::rust::wrappers2::JS_AtomizeAndPinString(cx, key));
275 rooted!(&in(cx) let mut rooted_jsid: jsid);
276 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
277 cx,
278 rooted.handle().get(),
279 rooted_jsid.handle_mut(),
280 );
281 AppendToIdVector(props, rooted_jsid.handle());
282 }
283 }
284
285 append_cross_origin_allowlisted_prop_keys(cx, props);
288
289 true
290}
291
292pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
295 _: *mut JSContext,
296 _proxy: RawHandleObject,
297 is_ordinary: *mut bool,
298 _proto: RawMutableHandleObject,
299) -> bool {
300 *is_ordinary = false;
302 true
303}
304
305pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
313 cx: *mut JSContext,
314 proxy: RawHandleObject,
315 proto: RawHandleObject,
316 result: *mut ObjectOpResult,
317) -> bool {
318 rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
326 if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
327 return false;
328 }
329
330 if proto.get() == current.get() {
332 (*result).code_ = 0 ;
333 return true;
334 }
335
336 (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
338 true
339}
340
341pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
342 if d.hasGetter_() {
343 out.set(d.getter_);
344 }
345}
346
347pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
348 if d.hasSetter_() {
349 out.set(d.setter_);
350 }
351}
352
353pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
355 d.hasSetter_() || d.hasGetter_()
356}
357
358pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
360 d.hasWritable_() || d.hasValue_()
361}
362
363pub(crate) unsafe fn cross_origin_has_own(
372 cx: &mut js::realm::CurrentRealm,
373 _proxy: RawHandleObject,
374 cross_origin_properties: &'static CrossOriginProperties,
375 id: RawHandleId,
376 bp: *mut bool,
377) -> bool {
378 *bp = jsid_to_string(cx.raw_cx(), Handle::from_raw(id)).is_some_and(|key| {
382 cross_origin_properties.keys().any(|defined_key| {
383 let defined_key = CStr::from_ptr(defined_key);
384 defined_key.to_bytes() == key.str().as_bytes()
385 })
386 });
387
388 true
389}
390
391pub(crate) fn cross_origin_get_own_property_helper(
398 cx: &mut CurrentRealm,
399 proxy: RawHandleObject,
400 cross_origin_properties: &'static CrossOriginProperties,
401 id: RawHandleId,
402 desc: RawMutableHandle<PropertyDescriptor>,
403 is_none: &mut bool,
404) -> bool {
405 rooted!(&in(cx) let mut holder = ptr::null_mut::<JSObject>());
406 let id = unsafe { Handle::from_raw(id) };
407 let proxy = unsafe { Handle::from_raw(proxy) };
408 let desc = unsafe { MutableHandle::from_raw(desc) };
409
410 ensure_cross_origin_property_holder(cx, proxy, cross_origin_properties, holder.handle_mut());
411
412 unsafe {
413 js::rust::wrappers2::JS_GetOwnPropertyDescriptorById(cx, holder.handle(), id, desc, is_none)
414 }
415}
416
417const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
418 SymbolCode::toStringTag,
419 SymbolCode::hasInstance,
420 SymbolCode::isConcatSpreadable,
421];
422
423pub(crate) fn is_cross_origin_allowlisted_prop(
424 cx: &mut js::context::JSContext,
425 id: RawHandleId,
426) -> bool {
427 unsafe {
428 if jsid_to_string(cx.raw_cx(), Handle::from_raw(id)).is_some_and(|st| st == "then") {
429 return true;
430 }
431
432 rooted!(&in(cx) let mut allowed_id: jsid);
433 ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
434 allowed_id.set(SymbolId(GetWellKnownSymbol(cx.raw_cx(), allowed_code)));
435 allowed_id.get().asBits_ == id.asBits_
438 })
439 }
440}
441
442fn append_cross_origin_allowlisted_prop_keys(
447 cx: &mut js::context::JSContext,
448 props: RawMutableHandleIdVector,
449) {
450 unsafe {
451 rooted!(&in(cx) let mut id: jsid);
452
453 let jsstring = js::rust::wrappers2::JS_AtomizeAndPinString(cx, c"then".as_ptr());
454 rooted!(&in(cx) let rooted = jsstring);
455 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
456 cx,
457 rooted.handle().get(),
458 id.handle_mut(),
459 );
460 AppendToIdVector(props, id.handle());
461
462 for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
463 id.set(SymbolId(js::rust::wrappers2::GetWellKnownSymbol(
464 cx,
465 allowed_code,
466 )));
467 AppendToIdVector(props, id.handle());
468 }
469 }
470}
471
472fn ensure_cross_origin_property_holder(
485 cx: &mut CurrentRealm,
486 _proxy: HandleObject,
487 cross_origin_properties: &'static CrossOriginProperties,
488 mut out_holder: MutableHandleObject,
489) -> bool {
490 unsafe {
497 out_holder.set(js::rust::wrappers2::JS_NewObjectWithGivenProto(
498 cx,
499 ptr::null_mut(),
500 HandleObject::null(),
501 ));
502
503 if out_holder.get().is_null() ||
504 !js::rust::wrappers2::JS_DefineProperties(
505 cx,
506 out_holder.handle(),
507 cross_origin_properties.attributes.as_ptr(),
508 ) ||
509 !js::rust::wrappers2::JS_DefineFunctions(
510 cx,
511 out_holder.handle(),
512 cross_origin_properties.methods.as_ptr(),
513 )
514 {
515 return false;
516 }
517 }
518
519 true
522}
523
524pub(crate) fn is_cross_origin_object<D: DomTypes>(cx: SafeJSContext, obj: RawHandleObject) -> bool {
530 unsafe {
531 jsapi::IsWindowProxy(*obj) ||
532 native_from_object::<D::Location>(*obj, *cx).is_ok() ||
533 native_from_object::<D::DissimilarOriginLocation>(*obj, *cx).is_ok()
534 }
535}
536
537pub(crate) fn report_cross_origin_denial<D: DomTypes>(
544 cx: &mut CurrentRealm,
545 id: RawHandleId,
546 access: &str,
547) -> bool {
548 if let Some(id) = id_to_source(cx.into(), id) {
549 debug!(
550 "permission denied to {} property {} on cross-origin object",
551 access,
552 &*id.str(),
553 );
554 } else {
555 debug!("permission denied to {} on cross-origin object", access);
556 }
557 unsafe {
558 if !js::rust::wrappers2::JS_IsExceptionPending(cx) {
559 let global = D::GlobalScope::from_current_realm(cx);
560 <D as DomHelpers<D>>::throw_dom_exception(
562 cx.into(),
563 &global,
564 Error::Security(None),
565 CanGc::note(),
566 );
567 }
568 }
569 false
570}
571
572pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
576 cx: *mut JSContext,
577 proxy: RawHandleObject,
578 id: RawHandleId,
579 v: RawHandleValue,
580 receiver: RawHandleValue,
581 result: *mut ObjectOpResult,
582) -> bool {
583 unsafe {
584 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
585 let mut realm = js::realm::CurrentRealm::assert(&mut cx);
586 let proxy = HandleObject::from_raw(proxy);
587 let id = Handle::from_raw(id);
588 let v = Handle::from_raw(v);
589 let receiver = Handle::from_raw(receiver);
590
591 if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy.into_handle()) {
592 return cross_origin_set::<D>(&mut realm, proxy, id, v.into_handle(), receiver, result);
593 }
594
595 let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy);
597
598 rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
601 let mut is_none = false;
602 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
603 GetProxyHandler(*proxy),
604 &mut realm,
605 proxy,
606 id,
607 own_desc.handle_mut(),
608 &mut is_none,
609 ) {
610 return false;
611 }
612
613 js::rust::wrappers2::SetPropertyIgnoringNamedGetter(
614 &mut realm,
615 proxy,
616 id,
617 v,
618 receiver,
619 if is_none {
620 None
621 } else {
622 Some(own_desc.handle())
623 },
624 result,
625 )
626 }
627}
628
629pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
633 cx: &mut CurrentRealm,
634 proxy: RawHandleObject,
635 get_proto_object: fn(
636 cx: &mut js::context::JSContext,
637 global: HandleObject,
638 rval: MutableHandleObject,
639 ),
640 proto: RawMutableHandleObject,
641) -> bool {
642 let proxy = unsafe { Handle::from_raw(proxy) };
643 if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
645 let mut realm = AutoRealm::new_from_handle(cx, proxy);
646 let mut realm = realm.current_realm();
647 let global = D::GlobalScope::from_current_realm(&realm);
648 get_proto_object(&mut realm, global.reflector().get_jsobject(), unsafe {
649 MutableHandleObject::from_raw(proto)
650 });
651 return !proto.is_null();
652 }
653
654 proto.set(ptr::null_mut());
656 true
657}
658
659pub(crate) fn cross_origin_get<D: DomTypes>(
666 cx: &mut CurrentRealm,
667 proxy: RawHandleObject,
668 receiver: RawHandleValue,
669 id: RawHandleId,
670 vp: RawMutableHandleValue,
671) -> bool {
672 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
674 let proxy = unsafe { Handle::from_raw(proxy) };
675 let receiver = unsafe { Handle::from_raw(receiver) };
676 let id = unsafe { Handle::from_raw(id) };
677 let mut vp = unsafe { MutableHandle::from_raw(vp) };
678 let mut is_none = false;
679 if !unsafe {
680 js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
681 GetProxyHandler(*proxy),
682 cx,
683 proxy,
684 id,
685 descriptor.handle_mut(),
686 &mut is_none,
687 )
688 } {
689 return false;
690 }
691
692 assert!(
694 !is_none,
695 "Callees should throw in all cases when they are not finding \
696 a property decriptor"
697 );
698
699 if is_data_descriptor(&descriptor) {
701 vp.set(descriptor.value_);
702 return true;
703 }
704
705 assert!(is_accessor_descriptor(&descriptor));
707
708 rooted!(&in(cx) let mut getter = ptr::null_mut::<JSObject>());
713 get_getter_object(&descriptor, getter.handle_mut().into());
714 if getter.get().is_null() {
715 return report_cross_origin_denial::<D>(cx, id.into_handle(), "get");
716 }
717
718 rooted!(&in(cx) let mut getter_jsval = UndefinedValue());
719 unsafe {
720 getter
721 .get()
722 .to_jsval(cx.raw_cx(), getter_jsval.handle_mut());
723 }
724
725 unsafe {
727 js::rust::wrappers2::Call(
728 cx,
729 receiver,
730 getter_jsval.handle(),
731 &jsapi::HandleValueArray::empty(),
732 vp,
733 )
734 }
735}
736
737pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
744 cx: &mut CurrentRealm,
745 proxy: HandleObject,
746 id: HandleId,
747 v: RawHandleValue,
748 receiver: HandleValue,
749 result: *mut ObjectOpResult,
750) -> bool {
751 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
753 let mut is_none = false;
754 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
755 GetProxyHandler(*proxy),
756 cx,
757 proxy,
758 id,
759 descriptor.handle_mut(),
760 &mut is_none,
761 ) {
762 return false;
763 }
764
765 assert!(
767 !is_none,
768 "Callees should throw in all cases when they are not finding \
769 a property decriptor"
770 );
771
772 rooted!(&in(cx) let mut setter = ptr::null_mut::<JSObject>());
775 get_setter_object(&descriptor, setter.handle_mut().into());
776 if setter.get().is_null() {
777 return report_cross_origin_denial::<D>(cx, id.into_handle(), "set");
779 }
780
781 rooted!(&in(cx) let mut setter_jsval = UndefinedValue());
782 setter
783 .get()
784 .to_jsval(cx.raw_cx(), setter_jsval.handle_mut());
785
786 rooted!(&in(cx) let mut ignored = UndefinedValue());
790 if !js::rust::wrappers2::Call(
791 cx,
792 receiver,
793 setter_jsval.handle(),
794 &jsapi::HandleValueArray {
797 length_: 1,
798 elements_: v.ptr,
799 },
800 ignored.handle_mut(),
801 ) {
802 return false;
803 }
804
805 (*result).code_ = 0 ;
806 true
807}
808
809pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
816 cx: &mut CurrentRealm,
817 _proxy: RawHandleObject,
818 id: RawHandleId,
819 desc: RawMutableHandle<PropertyDescriptor>,
820 is_none: &mut bool,
821) -> bool {
822 assert!(*is_none, "why are we being called?");
823
824 if is_cross_origin_allowlisted_prop(cx, id) {
829 set_property_descriptor(
830 unsafe { MutableHandle::from_raw(desc) },
831 HandleValue::undefined(),
832 jsapi::JSPROP_READONLY as u32,
833 is_none,
834 );
835 return true;
836 }
837
838 report_cross_origin_denial::<D>(cx, id, "access")
840}