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};
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 rooted!(in(*cx) let mut value = UndefinedValue());
226 rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
227 jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into())
228 .then(|| {
229 jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into()));
230 jsstr.get()
231 })
232 .and_then(ptr::NonNull::new)
233 .map(|jsstr| DOMString::from_string(jsstr_to_string(*cx, jsstr)))
234 }
235}
236
237pub(crate) struct CrossOriginProperties {
242 pub(crate) attributes: &'static [JSPropertySpec],
243 pub(crate) methods: &'static [JSFunctionSpec],
244}
245
246impl CrossOriginProperties {
247 fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
249 self.attributes
251 .iter()
252 .map(|spec| unsafe { spec.name.string_ })
253 .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
254 .filter(|ptr| !ptr.is_null())
255 }
256}
257
258pub(crate) fn cross_origin_own_property_keys(
262 cx: &mut js::context::JSContext,
263 _proxy: RawHandleObject,
264 cross_origin_properties: &'static CrossOriginProperties,
265 props: RawMutableHandleIdVector,
266) -> bool {
267 for key in cross_origin_properties.keys() {
270 unsafe {
271 rooted!(&in(cx) let rooted = js::rust::wrappers2::JS_AtomizeAndPinString(cx, key));
272 rooted!(&in(cx) let mut rooted_jsid: jsid);
273 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
274 cx,
275 rooted.handle().get(),
276 rooted_jsid.handle_mut(),
277 );
278 AppendToIdVector(props, rooted_jsid.handle());
279 }
280 }
281
282 append_cross_origin_allowlisted_prop_keys(cx, props);
285
286 true
287}
288
289pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
292 _: *mut JSContext,
293 _proxy: RawHandleObject,
294 is_ordinary: *mut bool,
295 _proto: RawMutableHandleObject,
296) -> bool {
297 *is_ordinary = false;
299 true
300}
301
302pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
310 cx: *mut JSContext,
311 proxy: RawHandleObject,
312 proto: RawHandleObject,
313 result: *mut ObjectOpResult,
314) -> bool {
315 rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
323 if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
324 return false;
325 }
326
327 if proto.get() == current.get() {
329 (*result).code_ = 0 ;
330 return true;
331 }
332
333 (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
335 true
336}
337
338pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
339 if d.hasGetter_() {
340 out.set(d.getter_);
341 }
342}
343
344pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
345 if d.hasSetter_() {
346 out.set(d.setter_);
347 }
348}
349
350pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
352 d.hasSetter_() || d.hasGetter_()
353}
354
355pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
357 d.hasWritable_() || d.hasValue_()
358}
359
360pub(crate) unsafe fn cross_origin_has_own(
369 cx: &mut js::realm::CurrentRealm,
370 _proxy: RawHandleObject,
371 cross_origin_properties: &'static CrossOriginProperties,
372 id: RawHandleId,
373 bp: *mut bool,
374) -> bool {
375 *bp = jsid_to_string(cx.raw_cx(), Handle::from_raw(id)).is_some_and(|key| {
379 cross_origin_properties.keys().any(|defined_key| {
380 let defined_key = CStr::from_ptr(defined_key);
381 defined_key.to_bytes() == key.str().as_bytes()
382 })
383 });
384
385 true
386}
387
388pub(crate) fn cross_origin_get_own_property_helper(
395 cx: &mut CurrentRealm,
396 proxy: RawHandleObject,
397 cross_origin_properties: &'static CrossOriginProperties,
398 id: RawHandleId,
399 desc: RawMutableHandle<PropertyDescriptor>,
400 is_none: &mut bool,
401) -> bool {
402 rooted!(&in(cx) let mut holder = ptr::null_mut::<JSObject>());
403 let id = unsafe { Handle::from_raw(id) };
404 let proxy = unsafe { Handle::from_raw(proxy) };
405 let desc = unsafe { MutableHandle::from_raw(desc) };
406
407 ensure_cross_origin_property_holder(cx, proxy, cross_origin_properties, holder.handle_mut());
408
409 unsafe {
410 js::rust::wrappers2::JS_GetOwnPropertyDescriptorById(cx, holder.handle(), id, desc, is_none)
411 }
412}
413
414const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
415 SymbolCode::toStringTag,
416 SymbolCode::hasInstance,
417 SymbolCode::isConcatSpreadable,
418];
419
420pub(crate) fn is_cross_origin_allowlisted_prop(
421 cx: &mut js::context::JSContext,
422 id: RawHandleId,
423) -> bool {
424 unsafe {
425 if jsid_to_string(cx.raw_cx(), Handle::from_raw(id)).is_some_and(|st| st == "then") {
426 return true;
427 }
428
429 rooted!(&in(cx) let mut allowed_id: jsid);
430 ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
431 allowed_id.set(SymbolId(GetWellKnownSymbol(cx.raw_cx(), allowed_code)));
432 allowed_id.get().asBits_ == id.asBits_
435 })
436 }
437}
438
439fn append_cross_origin_allowlisted_prop_keys(
444 cx: &mut js::context::JSContext,
445 props: RawMutableHandleIdVector,
446) {
447 unsafe {
448 rooted!(&in(cx) let mut id: jsid);
449
450 let jsstring = js::rust::wrappers2::JS_AtomizeAndPinString(cx, c"then".as_ptr());
451 rooted!(&in(cx) let rooted = jsstring);
452 js::rust::wrappers2::RUST_INTERNED_STRING_TO_JSID(
453 cx,
454 rooted.handle().get(),
455 id.handle_mut(),
456 );
457 AppendToIdVector(props, id.handle());
458
459 for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
460 id.set(SymbolId(js::rust::wrappers2::GetWellKnownSymbol(
461 cx,
462 allowed_code,
463 )));
464 AppendToIdVector(props, id.handle());
465 }
466 }
467}
468
469fn ensure_cross_origin_property_holder(
482 cx: &mut CurrentRealm,
483 _proxy: HandleObject,
484 cross_origin_properties: &'static CrossOriginProperties,
485 mut out_holder: MutableHandleObject,
486) -> bool {
487 unsafe {
494 out_holder.set(js::rust::wrappers2::JS_NewObjectWithGivenProto(
495 cx,
496 ptr::null_mut(),
497 HandleObject::null(),
498 ));
499
500 if out_holder.get().is_null() ||
501 !js::rust::wrappers2::JS_DefineProperties(
502 cx,
503 out_holder.handle(),
504 cross_origin_properties.attributes.as_ptr(),
505 ) ||
506 !js::rust::wrappers2::JS_DefineFunctions(
507 cx,
508 out_holder.handle(),
509 cross_origin_properties.methods.as_ptr(),
510 )
511 {
512 return false;
513 }
514 }
515
516 true
519}
520
521pub(crate) fn report_cross_origin_denial<D: DomTypes>(
528 cx: &mut CurrentRealm,
529 id: RawHandleId,
530 access: &str,
531) -> bool {
532 debug!(
533 "permission denied to {} property {} on cross-origin object",
534 access,
535 id_to_source(cx.into(), id)
536 .as_ref()
537 .map(|source| source.str())
538 .as_deref()
539 .unwrap_or("< error >"),
540 );
541 unsafe {
542 if !js::rust::wrappers2::JS_IsExceptionPending(cx) {
543 let global = D::GlobalScope::from_current_realm(cx);
544 <D as DomHelpers<D>>::throw_dom_exception(
546 cx.into(),
547 &global,
548 Error::Security(None),
549 CanGc::note(),
550 );
551 }
552 }
553 false
554}
555
556pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
560 cx: *mut JSContext,
561 proxy: RawHandleObject,
562 id: RawHandleId,
563 v: RawHandleValue,
564 receiver: RawHandleValue,
565 result: *mut ObjectOpResult,
566) -> bool {
567 unsafe {
568 let mut cx = js::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
569 let mut realm = js::realm::CurrentRealm::assert(&mut cx);
570 let proxy = HandleObject::from_raw(proxy);
571 let id = Handle::from_raw(id);
572 let v = Handle::from_raw(v);
573 let receiver = Handle::from_raw(receiver);
574
575 if !<D as DomHelpers<D>>::is_platform_object_same_origin(&realm, proxy.into_handle()) {
576 return cross_origin_set::<D>(&mut realm, proxy, id, v.into_handle(), receiver, result);
577 }
578
579 let mut realm = js::realm::AutoRealm::new_from_handle(&mut realm, proxy);
581
582 rooted!(&in(&mut realm) let mut own_desc = PropertyDescriptor::default());
585 let mut is_none = false;
586 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
587 GetProxyHandler(*proxy),
588 &mut realm,
589 proxy,
590 id,
591 own_desc.handle_mut(),
592 &mut is_none,
593 ) {
594 return false;
595 }
596
597 js::rust::wrappers2::SetPropertyIgnoringNamedGetter(
598 &mut realm,
599 proxy,
600 id,
601 v,
602 receiver,
603 if is_none {
604 None
605 } else {
606 Some(own_desc.handle())
607 },
608 result,
609 )
610 }
611}
612
613pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
617 cx: &mut CurrentRealm,
618 proxy: RawHandleObject,
619 get_proto_object: fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
620 proto: RawMutableHandleObject,
621) -> bool {
622 let proxy = unsafe { Handle::from_raw(proxy) };
623 if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy.into_handle()) {
625 let mut realm = AutoRealm::new_from_handle(cx, proxy);
626 let mut realm = realm.current_realm();
627 let global = D::GlobalScope::from_current_realm(&realm);
628 get_proto_object(
629 unsafe { SafeJSContext::from_ptr(realm.raw_cx()) },
630 global.reflector().get_jsobject(),
631 unsafe { MutableHandleObject::from_raw(proto) },
632 );
633 return !proto.is_null();
634 }
635
636 proto.set(ptr::null_mut());
638 true
639}
640
641pub(crate) fn cross_origin_get<D: DomTypes>(
648 cx: &mut CurrentRealm,
649 proxy: RawHandleObject,
650 receiver: RawHandleValue,
651 id: RawHandleId,
652 vp: RawMutableHandleValue,
653) -> bool {
654 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
656 let proxy = unsafe { Handle::from_raw(proxy) };
657 let receiver = unsafe { Handle::from_raw(receiver) };
658 let id = unsafe { Handle::from_raw(id) };
659 let mut vp = unsafe { MutableHandle::from_raw(vp) };
660 let mut is_none = false;
661 if !unsafe {
662 js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
663 GetProxyHandler(*proxy),
664 cx,
665 proxy,
666 id,
667 descriptor.handle_mut(),
668 &mut is_none,
669 )
670 } {
671 return false;
672 }
673
674 assert!(
676 !is_none,
677 "Callees should throw in all cases when they are not finding \
678 a property decriptor"
679 );
680
681 if is_data_descriptor(&descriptor) {
683 vp.set(descriptor.value_);
684 return true;
685 }
686
687 assert!(is_accessor_descriptor(&descriptor));
689
690 rooted!(&in(cx) let mut getter = ptr::null_mut::<JSObject>());
695 get_getter_object(&descriptor, getter.handle_mut().into());
696 if getter.get().is_null() {
697 return report_cross_origin_denial::<D>(cx, id.into_handle(), "get");
698 }
699
700 rooted!(&in(cx) let mut getter_jsval = UndefinedValue());
701 unsafe {
702 getter
703 .get()
704 .to_jsval(cx.raw_cx(), getter_jsval.handle_mut());
705 }
706
707 unsafe {
709 js::rust::wrappers2::Call(
710 cx,
711 receiver,
712 getter_jsval.handle(),
713 &jsapi::HandleValueArray::empty(),
714 vp,
715 )
716 }
717}
718
719pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
726 cx: &mut CurrentRealm,
727 proxy: HandleObject,
728 id: HandleId,
729 v: RawHandleValue,
730 receiver: HandleValue,
731 result: *mut ObjectOpResult,
732) -> bool {
733 rooted!(&in(cx) let mut descriptor = PropertyDescriptor::default());
735 let mut is_none = false;
736 if !js::rust::wrappers2::InvokeGetOwnPropertyDescriptor(
737 GetProxyHandler(*proxy),
738 cx,
739 proxy,
740 id,
741 descriptor.handle_mut(),
742 &mut is_none,
743 ) {
744 return false;
745 }
746
747 assert!(
749 !is_none,
750 "Callees should throw in all cases when they are not finding \
751 a property decriptor"
752 );
753
754 rooted!(&in(cx) let mut setter = ptr::null_mut::<JSObject>());
757 get_setter_object(&descriptor, setter.handle_mut().into());
758 if setter.get().is_null() {
759 return report_cross_origin_denial::<D>(cx, id.into_handle(), "set");
761 }
762
763 rooted!(&in(cx) let mut setter_jsval = UndefinedValue());
764 setter
765 .get()
766 .to_jsval(cx.raw_cx(), setter_jsval.handle_mut());
767
768 rooted!(&in(cx) let mut ignored = UndefinedValue());
772 if !js::rust::wrappers2::Call(
773 cx,
774 receiver,
775 setter_jsval.handle(),
776 &jsapi::HandleValueArray {
779 length_: 1,
780 elements_: v.ptr,
781 },
782 ignored.handle_mut(),
783 ) {
784 return false;
785 }
786
787 (*result).code_ = 0 ;
788 true
789}
790
791pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
798 cx: &mut CurrentRealm,
799 _proxy: RawHandleObject,
800 id: RawHandleId,
801 desc: RawMutableHandle<PropertyDescriptor>,
802 is_none: &mut bool,
803) -> bool {
804 assert!(*is_none, "why are we being called?");
805
806 if is_cross_origin_allowlisted_prop(cx, id) {
811 set_property_descriptor(
812 unsafe { MutableHandle::from_raw(desc) },
813 HandleValue::undefined(),
814 jsapi::JSPROP_READONLY as u32,
815 is_none,
816 );
817 return true;
818 }
819
820 report_cross_origin_denial::<D>(cx, id, "access")
822}