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::wrappers2::{
30 JS_IdToValue, JS_IsExceptionPending, JS_ValueToSource, RUST_JSID_IS_VOID,
31};
32use js::rust::{
33 Handle, HandleId, HandleObject, HandleValue, IntoHandle, MutableHandle, MutableHandleObject,
34};
35use js::{jsapi, rooted};
36
37use crate::DomTypes;
38use crate::conversions::{is_dom_proxy, jsid_to_string, native_from_object};
39use crate::error::Error;
40use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
41use crate::reflector::DomObject;
42use crate::script_runtime::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
226fn id_to_source(cx: &mut js::context::JSContext, id: HandleId) -> Option<DOMString> {
227 unsafe {
228 if RUST_JSID_IS_VOID(id) {
229 return None;
230 }
231 rooted!(&in(cx) let mut value = UndefinedValue());
232 rooted!(&in(cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
233 JS_IdToValue(cx, id.get(), value.handle_mut())
234 .then(|| {
235 jsstr.set(JS_ValueToSource(cx, value.handle()));
236 jsstr.get()
237 })
238 .and_then(ptr::NonNull::new)
239 .map(|jsstr| jsstr_to_string(cx, jsstr).into())
240 }
241}
242
243pub(crate) struct CrossOriginProperties {
248 pub(crate) attributes: &'static [JSPropertySpec],
249 pub(crate) methods: &'static [JSFunctionSpec],
250}
251
252impl CrossOriginProperties {
253 fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
255 self.attributes
257 .iter()
258 .map(|spec| unsafe { spec.name.string_ })
259 .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
260 .filter(|ptr| !ptr.is_null())
261 }
262}
263
264pub(crate) fn cross_origin_own_property_keys(
268 cx: &mut js::context::JSContext,
269 _proxy: RawHandleObject,
270 cross_origin_properties: &'static CrossOriginProperties,
271 props: RawMutableHandleIdVector,
272) -> bool {
273 for key in cross_origin_properties.keys() {
276 unsafe {
277 rooted!(&in(cx) let rooted = js::rust::wrappers2::JS_AtomizeAndPinString(cx, key));
278 rooted!(&in(cx) let mut rooted_jsid: jsid);
279 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
280 cx,
281 rooted.handle().get(),
282 rooted_jsid.handle_mut(),
283 );
284 AppendToIdVector(props, rooted_jsid.handle());
285 }
286 }
287
288 append_cross_origin_allowlisted_prop_keys(cx, props);
291
292 true
293}
294
295pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
298 _: *mut JSContext,
299 _proxy: RawHandleObject,
300 is_ordinary: *mut bool,
301 _proto: RawMutableHandleObject,
302) -> bool {
303 *is_ordinary = false;
305 true
306}
307
308pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
316 cx: *mut JSContext,
317 proxy: RawHandleObject,
318 proto: RawHandleObject,
319 result: *mut ObjectOpResult,
320) -> bool {
321 rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
329 if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
330 return false;
331 }
332
333 if proto.get() == current.get() {
335 (*result).code_ = 0 ;
336 return true;
337 }
338
339 (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
341 true
342}
343
344pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
345 if d.hasGetter_() {
346 out.set(d.getter_);
347 }
348}
349
350pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
351 if d.hasSetter_() {
352 out.set(d.setter_);
353 }
354}
355
356pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
358 d.hasSetter_() || d.hasGetter_()
359}
360
361pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
363 d.hasWritable_() || d.hasValue_()
364}
365
366pub(crate) unsafe fn cross_origin_has_own(
375 cx: &mut js::realm::CurrentRealm,
376 _proxy: RawHandleObject,
377 cross_origin_properties: &'static CrossOriginProperties,
378 id: RawHandleId,
379 bp: *mut bool,
380) -> bool {
381 *bp = jsid_to_string(cx, Handle::from_raw(id)).is_some_and(|key| {
385 cross_origin_properties.keys().any(|defined_key| {
386 let defined_key = CStr::from_ptr(defined_key);
387 defined_key.to_bytes() == key.str().as_bytes()
388 })
389 });
390
391 true
392}
393
394pub(crate) fn cross_origin_get_own_property_helper(
401 cx: &mut CurrentRealm,
402 proxy: RawHandleObject,
403 cross_origin_properties: &'static CrossOriginProperties,
404 id: RawHandleId,
405 desc: RawMutableHandle<PropertyDescriptor>,
406 is_none: &mut bool,
407) -> bool {
408 rooted!(&in(cx) let mut holder = ptr::null_mut::<JSObject>());
409 let id = unsafe { Handle::from_raw(id) };
410 let proxy = unsafe { Handle::from_raw(proxy) };
411 let desc = unsafe { MutableHandle::from_raw(desc) };
412
413 ensure_cross_origin_property_holder(cx, proxy, cross_origin_properties, holder.handle_mut());
414
415 unsafe {
416 js::rust::wrappers2::JS_GetOwnPropertyDescriptorById(cx, holder.handle(), id, desc, is_none)
417 }
418}
419
420const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
421 SymbolCode::toStringTag,
422 SymbolCode::hasInstance,
423 SymbolCode::isConcatSpreadable,
424];
425
426pub(crate) fn is_cross_origin_allowlisted_prop(
427 cx: &mut js::context::JSContext,
428 id: RawHandleId,
429) -> bool {
430 unsafe {
431 if jsid_to_string(cx, Handle::from_raw(id)).is_some_and(|st| st == "then") {
432 return true;
433 }
434
435 rooted!(&in(cx) let mut allowed_id: jsid);
436 ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
437 allowed_id.set(SymbolId(GetWellKnownSymbol(cx.raw_cx(), allowed_code)));
438 allowed_id.get().asBits_ == id.asBits_
441 })
442 }
443}
444
445fn append_cross_origin_allowlisted_prop_keys(
450 cx: &mut js::context::JSContext,
451 props: RawMutableHandleIdVector,
452) {
453 unsafe {
454 rooted!(&in(cx) let mut id: jsid);
455
456 let jsstring = js::rust::wrappers2::JS_AtomizeAndPinString(cx, c"then".as_ptr());
457 rooted!(&in(cx) let rooted = jsstring);
458 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
459 cx,
460 rooted.handle().get(),
461 id.handle_mut(),
462 );
463 AppendToIdVector(props, id.handle());
464
465 for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
466 id.set(SymbolId(js::rust::wrappers2::GetWellKnownSymbol(
467 cx,
468 allowed_code,
469 )));
470 AppendToIdVector(props, id.handle());
471 }
472 }
473}
474
475fn ensure_cross_origin_property_holder(
488 cx: &mut CurrentRealm,
489 _proxy: HandleObject,
490 cross_origin_properties: &'static CrossOriginProperties,
491 mut out_holder: MutableHandleObject,
492) -> bool {
493 unsafe {
500 out_holder.set(js::rust::wrappers2::JS_NewObjectWithGivenProto(
501 cx,
502 ptr::null_mut(),
503 HandleObject::null(),
504 ));
505
506 if out_holder.get().is_null() ||
507 !js::rust::wrappers2::JS_DefineProperties(
508 cx,
509 out_holder.handle(),
510 cross_origin_properties.attributes.as_ptr(),
511 ) ||
512 !js::rust::wrappers2::JS_DefineFunctions(
513 cx,
514 out_holder.handle(),
515 cross_origin_properties.methods.as_ptr(),
516 )
517 {
518 return false;
519 }
520 }
521
522 true
525}
526
527pub(crate) fn is_cross_origin_object<D: DomTypes>(cx: SafeJSContext, obj: RawHandleObject) -> bool {
533 unsafe {
534 jsapi::IsWindowProxy(*obj) ||
535 native_from_object::<D::Location>(*obj, *cx).is_ok() ||
536 native_from_object::<D::DissimilarOriginLocation>(*obj, *cx).is_ok()
537 }
538}
539
540pub(crate) fn report_cross_origin_denial<D: DomTypes>(
547 cx: &mut CurrentRealm,
548 id: HandleId,
549 access: &str,
550) -> bool {
551 if let Some(id) = id_to_source(cx, id) {
552 debug!(
553 "permission denied to {} property {} on cross-origin object",
554 access,
555 &*id.str(),
556 );
557 } else {
558 debug!("permission denied to {} on cross-origin object", access);
559 }
560 unsafe {
561 if !JS_IsExceptionPending(cx) {
562 let global = D::GlobalScope::from_current_realm(cx);
563 <D as DomHelpers<D>>::throw_dom_exception(cx, &global, Error::Security(None));
565 }
566 }
567 false
568}
569
570pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
574 cx: *mut JSContext,
575 proxy: RawHandleObject,
576 id: RawHandleId,
577 v: RawHandleValue,
578 receiver: RawHandleValue,
579 result: *mut ObjectOpResult,
580) -> bool {
581 unsafe {
582 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
583 let mut realm = js::realm::CurrentRealm::assert(&mut cx);
584 let proxy = HandleObject::from_raw(proxy);
585 let id = Handle::from_raw(id);
586 let v = Handle::from_raw(v);
587 let receiver = Handle::from_raw(receiver);
588
589 if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy.into_handle()) {
590 return cross_origin_set::<D>(&mut realm, proxy, id, v.into_handle(), receiver, result);
591 }
592
593 let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy);
595
596 rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
599 let mut is_none = false;
600 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
601 GetProxyHandler(*proxy),
602 &mut realm,
603 proxy,
604 id,
605 own_desc.handle_mut(),
606 &mut is_none,
607 ) {
608 return false;
609 }
610
611 js::rust::wrappers2::SetPropertyIgnoringNamedGetter(
612 &mut realm,
613 proxy,
614 id,
615 v,
616 receiver,
617 if is_none {
618 None
619 } else {
620 Some(own_desc.handle())
621 },
622 result,
623 )
624 }
625}
626
627pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
631 cx: &mut CurrentRealm,
632 proxy: RawHandleObject,
633 get_proto_object: fn(
634 cx: &mut js::context::JSContext,
635 global: HandleObject,
636 rval: MutableHandleObject,
637 ),
638 proto: RawMutableHandleObject,
639) -> bool {
640 let proxy = unsafe { Handle::from_raw(proxy) };
641 if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
643 let mut realm = AutoRealm::new_from_handle(cx, proxy);
644 let mut realm = realm.current_realm();
645 let global = D::GlobalScope::from_current_realm(&realm);
646 get_proto_object(&mut realm, global.reflector().get_jsobject(), unsafe {
647 MutableHandleObject::from_raw(proto)
648 });
649 return !proto.is_null();
650 }
651
652 proto.set(ptr::null_mut());
654 true
655}
656
657pub(crate) fn cross_origin_get<D: DomTypes>(
664 cx: &mut CurrentRealm,
665 proxy: RawHandleObject,
666 receiver: RawHandleValue,
667 id: RawHandleId,
668 vp: RawMutableHandleValue,
669) -> bool {
670 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
672 let proxy = unsafe { Handle::from_raw(proxy) };
673 let receiver = unsafe { Handle::from_raw(receiver) };
674 let id = unsafe { Handle::from_raw(id) };
675 let mut vp = unsafe { MutableHandle::from_raw(vp) };
676 let mut is_none = false;
677 if !unsafe {
678 js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
679 GetProxyHandler(*proxy),
680 cx,
681 proxy,
682 id,
683 descriptor.handle_mut(),
684 &mut is_none,
685 )
686 } {
687 return false;
688 }
689
690 assert!(
692 !is_none,
693 "Callees should throw in all cases when they are not finding \
694 a property decriptor"
695 );
696
697 if is_data_descriptor(&descriptor) {
699 vp.set(descriptor.value_);
700 return true;
701 }
702
703 assert!(is_accessor_descriptor(&descriptor));
705
706 rooted!(&in(cx) let mut getter = ptr::null_mut::<JSObject>());
711 get_getter_object(&descriptor, getter.handle_mut().into());
712 if getter.get().is_null() {
713 return report_cross_origin_denial::<D>(cx, id, "get");
714 }
715
716 rooted!(&in(cx) let mut getter_jsval = UndefinedValue());
717 unsafe {
718 getter
719 .get()
720 .to_jsval(cx.raw_cx(), getter_jsval.handle_mut());
721 }
722
723 unsafe {
725 js::rust::wrappers2::Call(
726 cx,
727 receiver,
728 getter_jsval.handle(),
729 &jsapi::HandleValueArray::empty(),
730 vp,
731 )
732 }
733}
734
735pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
742 cx: &mut CurrentRealm,
743 proxy: HandleObject,
744 id: HandleId,
745 v: RawHandleValue,
746 receiver: HandleValue,
747 result: *mut ObjectOpResult,
748) -> bool {
749 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
751 let mut is_none = false;
752 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
753 GetProxyHandler(*proxy),
754 cx,
755 proxy,
756 id,
757 descriptor.handle_mut(),
758 &mut is_none,
759 ) {
760 return false;
761 }
762
763 assert!(
765 !is_none,
766 "Callees should throw in all cases when they are not finding \
767 a property decriptor"
768 );
769
770 rooted!(&in(cx) let mut setter = ptr::null_mut::<JSObject>());
773 get_setter_object(&descriptor, setter.handle_mut().into());
774 if setter.get().is_null() {
775 return report_cross_origin_denial::<D>(cx, id, "set");
777 }
778
779 rooted!(&in(cx) let mut setter_jsval = UndefinedValue());
780 setter
781 .get()
782 .to_jsval(cx.raw_cx(), setter_jsval.handle_mut());
783
784 rooted!(&in(cx) let mut ignored = UndefinedValue());
788 if !js::rust::wrappers2::Call(
789 cx,
790 receiver,
791 setter_jsval.handle(),
792 &jsapi::HandleValueArray {
795 length_: 1,
796 elements_: v.ptr,
797 },
798 ignored.handle_mut(),
799 ) {
800 return false;
801 }
802
803 (*result).code_ = 0 ;
804 true
805}
806
807pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
814 cx: &mut CurrentRealm,
815 _proxy: RawHandleObject,
816 id: RawHandleId,
817 desc: RawMutableHandle<PropertyDescriptor>,
818 is_none: &mut bool,
819) -> bool {
820 assert!(*is_none, "why are we being called?");
821
822 if is_cross_origin_allowlisted_prop(cx, id) {
827 set_property_descriptor(
828 unsafe { MutableHandle::from_raw(desc) },
829 HandleValue::undefined(),
830 jsapi::JSPROP_READONLY as u32,
831 is_none,
832 );
833 return true;
834 }
835
836 let id = unsafe { Handle::from_raw(id) };
837
838 report_cross_origin_denial::<D>(cx, id, "access")
840}