1use std::ffi::CStr;
8use std::os::raw::c_char;
9use std::ptr;
10
11use js::conversions::{ToJSValConvertible, jsstr_to_string};
12use js::glue::{
13 GetProxyHandler, GetProxyHandlerFamily, GetProxyPrivate, InvokeGetOwnPropertyDescriptor,
14 SetProxyPrivate,
15};
16use js::jsapi::{
17 DOMProxyShadowsResult, GetStaticPrototype, GetWellKnownSymbol, Handle as RawHandle,
18 HandleId as RawHandleId, HandleObject as RawHandleObject, HandleValue as RawHandleValue,
19 JS_AtomizeAndPinString, JS_DefinePropertyById, JS_GetOwnPropertyDescriptorById,
20 JS_IsExceptionPending, JSAutoRealm, JSContext, JSErrNum, JSFunctionSpec, JSObject,
21 JSPropertySpec, MutableHandle as RawMutableHandle,
22 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::rust::wrappers::{
29 AppendToIdVector, JS_AlreadyHasOwnPropertyById, JS_NewObjectWithGivenProto,
30 RUST_INTERNED_STRING_TO_JSID, SetDataPropertyDescriptor,
31};
32use js::rust::{Handle, HandleObject, HandleValue, MutableHandle, MutableHandleObject};
33use js::{jsapi, rooted};
34
35use crate::DomTypes;
36use crate::conversions::{is_dom_proxy, jsid_to_string};
37use crate::error::Error;
38use crate::interfaces::{DomHelpers, GlobalScopeHelpers};
39use crate::realms::{AlreadyInRealm, InRealm};
40use crate::reflector::DomObject;
41use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
42use crate::str::DOMString;
43use crate::utils::delete_property_by_id;
44
45pub(crate) unsafe extern "C" fn shadow_check_callback(
50 cx: *mut JSContext,
51 object: RawHandleObject,
52 id: RawHandleId,
53) -> DOMProxyShadowsResult {
54 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
57 get_expando_object(object, expando.handle_mut());
58 if !expando.get().is_null() {
59 let mut has_own = false;
60 let raw_id = Handle::from_raw(id);
61
62 if !JS_AlreadyHasOwnPropertyById(cx, expando.handle(), raw_id, &mut has_own) {
63 return DOMProxyShadowsResult::ShadowCheckFailed;
64 }
65
66 if has_own {
67 return DOMProxyShadowsResult::ShadowsViaDirectExpando;
68 }
69 }
70
71 DOMProxyShadowsResult::DoesntShadow
73}
74
75pub fn init() {
77 unsafe {
78 SetDOMProxyInformation(
79 GetProxyHandlerFamily(),
80 Some(shadow_check_callback),
81 ptr::null(),
82 );
83 }
84}
85
86pub(crate) unsafe extern "C" fn define_property(
92 cx: *mut JSContext,
93 proxy: RawHandleObject,
94 id: RawHandleId,
95 desc: RawHandle<PropertyDescriptor>,
96 result: *mut ObjectOpResult,
97) -> bool {
98 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
99 ensure_expando_object(cx, proxy, expando.handle_mut());
100 JS_DefinePropertyById(cx, expando.handle().into(), id, desc, result)
101}
102
103pub(crate) unsafe extern "C" fn delete(
109 cx: *mut JSContext,
110 proxy: RawHandleObject,
111 id: RawHandleId,
112 bp: *mut ObjectOpResult,
113) -> bool {
114 rooted!(in(cx) let mut expando = ptr::null_mut::<JSObject>());
115 get_expando_object(proxy, expando.handle_mut());
116 if expando.is_null() {
117 (*bp).code_ = 0 ;
118 return true;
119 }
120
121 delete_property_by_id(cx, expando.handle(), Handle::from_raw(id), bp)
122}
123
124pub(crate) unsafe extern "C" fn prevent_extensions(
129 _cx: *mut JSContext,
130 _proxy: RawHandleObject,
131 result: *mut ObjectOpResult,
132) -> bool {
133 (*result).code_ = JSErrNum::JSMSG_CANT_PREVENT_EXTENSIONS as ::libc::uintptr_t;
134 true
135}
136
137pub(crate) unsafe extern "C" fn is_extensible(
142 _cx: *mut JSContext,
143 _proxy: RawHandleObject,
144 succeeded: *mut bool,
145) -> bool {
146 *succeeded = true;
147 true
148}
149
150pub(crate) unsafe extern "C" fn get_prototype_if_ordinary(
163 _: *mut JSContext,
164 proxy: RawHandleObject,
165 is_ordinary: *mut bool,
166 proto: RawMutableHandleObject,
167) -> bool {
168 *is_ordinary = true;
169 proto.set(GetStaticPrototype(proxy.get()));
170 true
171}
172
173pub(crate) fn get_expando_object(obj: RawHandleObject, mut expando: MutableHandleObject) {
175 unsafe {
176 assert!(is_dom_proxy(obj.get()));
177 let val = &mut UndefinedValue();
178 GetProxyPrivate(obj.get(), val);
179 expando.set(if val.is_undefined() {
180 ptr::null_mut()
181 } else {
182 val.to_object()
183 });
184 }
185}
186
187pub(crate) unsafe fn ensure_expando_object(
193 cx: *mut JSContext,
194 obj: RawHandleObject,
195 mut expando: MutableHandleObject,
196) {
197 assert!(is_dom_proxy(obj.get()));
198 get_expando_object(obj, expando.reborrow());
199 if expando.is_null() {
200 expando.set(JS_NewObjectWithGivenProto(
201 cx,
202 ptr::null_mut(),
203 HandleObject::null(),
204 ));
205 assert!(!expando.is_null());
206
207 SetProxyPrivate(obj.get(), &ObjectValue(expando.get()));
208 }
209}
210
211pub fn set_property_descriptor(
214 desc: MutableHandle<PropertyDescriptor>,
215 value: HandleValue,
216 attrs: u32,
217 is_none: &mut bool,
218) {
219 unsafe {
220 SetDataPropertyDescriptor(desc, value, attrs);
221 }
222 *is_none = false;
223}
224
225pub(crate) fn id_to_source(cx: SafeJSContext, id: RawHandleId) -> Option<DOMString> {
226 unsafe {
227 rooted!(in(*cx) let mut value = UndefinedValue());
228 rooted!(in(*cx) let mut jsstr = ptr::null_mut::<jsapi::JSString>());
229 jsapi::JS_IdToValue(*cx, id.get(), value.handle_mut().into())
230 .then(|| {
231 jsstr.set(jsapi::JS_ValueToSource(*cx, value.handle().into()));
232 jsstr.get()
233 })
234 .and_then(ptr::NonNull::new)
235 .map(|jsstr| DOMString::from_string(jsstr_to_string(*cx, jsstr)))
236 }
237}
238
239pub(crate) struct CrossOriginProperties {
244 pub(crate) attributes: &'static [JSPropertySpec],
245 pub(crate) methods: &'static [JSFunctionSpec],
246}
247
248impl CrossOriginProperties {
249 fn keys(&self) -> impl Iterator<Item = *const c_char> + '_ {
251 self.attributes
253 .iter()
254 .map(|spec| unsafe { spec.name.string_ })
255 .chain(self.methods.iter().map(|spec| unsafe { spec.name.string_ }))
256 .filter(|ptr| !ptr.is_null())
257 }
258}
259
260pub(crate) fn cross_origin_own_property_keys(
264 cx: SafeJSContext,
265 _proxy: RawHandleObject,
266 cross_origin_properties: &'static CrossOriginProperties,
267 props: RawMutableHandleIdVector,
268) -> bool {
269 for key in cross_origin_properties.keys() {
272 unsafe {
273 rooted!(in(*cx) let rooted = JS_AtomizeAndPinString(*cx, key));
274 rooted!(in(*cx) let mut rooted_jsid: jsid);
275 RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), rooted_jsid.handle_mut());
276 AppendToIdVector(props, rooted_jsid.handle());
277 }
278 }
279
280 append_cross_origin_allowlisted_prop_keys(cx, props);
283
284 true
285}
286
287pub(crate) unsafe extern "C" fn maybe_cross_origin_get_prototype_if_ordinary_rawcx(
290 _: *mut JSContext,
291 _proxy: RawHandleObject,
292 is_ordinary: *mut bool,
293 _proto: RawMutableHandleObject,
294) -> bool {
295 *is_ordinary = false;
297 true
298}
299
300pub(crate) unsafe extern "C" fn maybe_cross_origin_set_prototype_rawcx(
308 cx: *mut JSContext,
309 proxy: RawHandleObject,
310 proto: RawHandleObject,
311 result: *mut ObjectOpResult,
312) -> bool {
313 rooted!(in(cx) let mut current = ptr::null_mut::<JSObject>());
321 if !jsapi::GetObjectProto(cx, proxy, current.handle_mut().into()) {
322 return false;
323 }
324
325 if proto.get() == current.get() {
327 (*result).code_ = 0 ;
328 return true;
329 }
330
331 (*result).code_ = JSErrNum::JSMSG_CANT_SET_PROTO as usize;
333 true
334}
335
336pub(crate) fn get_getter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
337 if d.hasGetter_() {
338 out.set(d.getter_);
339 }
340}
341
342pub(crate) fn get_setter_object(d: &PropertyDescriptor, out: RawMutableHandleObject) {
343 if d.hasSetter_() {
344 out.set(d.setter_);
345 }
346}
347
348pub(crate) fn is_accessor_descriptor(d: &PropertyDescriptor) -> bool {
350 d.hasSetter_() || d.hasGetter_()
351}
352
353pub(crate) fn is_data_descriptor(d: &PropertyDescriptor) -> bool {
355 d.hasWritable_() || d.hasValue_()
356}
357
358pub(crate) unsafe fn cross_origin_has_own(
367 cx: SafeJSContext,
368 _proxy: RawHandleObject,
369 cross_origin_properties: &'static CrossOriginProperties,
370 id: RawHandleId,
371 bp: *mut bool,
372) -> bool {
373 *bp = jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|key| {
377 cross_origin_properties.keys().any(|defined_key| {
378 let defined_key = CStr::from_ptr(defined_key);
379 defined_key.to_bytes() == key.as_bytes()
380 })
381 });
382
383 true
384}
385
386pub(crate) fn cross_origin_get_own_property_helper(
393 cx: SafeJSContext,
394 proxy: RawHandleObject,
395 cross_origin_properties: &'static CrossOriginProperties,
396 id: RawHandleId,
397 desc: RawMutableHandle<PropertyDescriptor>,
398 is_none: &mut bool,
399) -> bool {
400 rooted!(in(*cx) let mut holder = ptr::null_mut::<JSObject>());
401
402 ensure_cross_origin_property_holder(
403 cx,
404 proxy,
405 cross_origin_properties,
406 holder.handle_mut().into(),
407 );
408
409 unsafe { JS_GetOwnPropertyDescriptorById(*cx, holder.handle().into(), id, desc, is_none) }
410}
411
412const ALLOWLISTED_SYMBOL_CODES: &[SymbolCode] = &[
413 SymbolCode::toStringTag,
414 SymbolCode::hasInstance,
415 SymbolCode::isConcatSpreadable,
416];
417
418pub(crate) fn is_cross_origin_allowlisted_prop(cx: SafeJSContext, id: RawHandleId) -> bool {
419 unsafe {
420 if jsid_to_string(*cx, Handle::from_raw(id)).is_some_and(|st| st == "then") {
421 return true;
422 }
423
424 rooted!(in(*cx) let mut allowed_id: jsid);
425 ALLOWLISTED_SYMBOL_CODES.iter().any(|&allowed_code| {
426 allowed_id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
427 allowed_id.get().asBits_ == id.asBits_
430 })
431 }
432}
433
434fn append_cross_origin_allowlisted_prop_keys(cx: SafeJSContext, props: RawMutableHandleIdVector) {
439 unsafe {
440 rooted!(in(*cx) let mut id: jsid);
441
442 let jsstring = JS_AtomizeAndPinString(*cx, c"then".as_ptr());
443 rooted!(in(*cx) let rooted = jsstring);
444 RUST_INTERNED_STRING_TO_JSID(*cx, rooted.handle().get(), id.handle_mut());
445 AppendToIdVector(props, id.handle());
446
447 for &allowed_code in ALLOWLISTED_SYMBOL_CODES.iter() {
448 id.set(SymbolId(GetWellKnownSymbol(*cx, allowed_code)));
449 AppendToIdVector(props, id.handle());
450 }
451 }
452}
453
454fn ensure_cross_origin_property_holder(
467 cx: SafeJSContext,
468 _proxy: RawHandleObject,
469 cross_origin_properties: &'static CrossOriginProperties,
470 out_holder: RawMutableHandleObject,
471) -> bool {
472 unsafe {
479 out_holder.set(jsapi::JS_NewObjectWithGivenProto(
480 *cx,
481 ptr::null_mut(),
482 RawHandleObject::null(),
483 ));
484
485 if out_holder.get().is_null() ||
486 !jsapi::JS_DefineProperties(
487 *cx,
488 out_holder.handle(),
489 cross_origin_properties.attributes.as_ptr(),
490 ) ||
491 !jsapi::JS_DefineFunctions(
492 *cx,
493 out_holder.handle(),
494 cross_origin_properties.methods.as_ptr(),
495 )
496 {
497 return false;
498 }
499 }
500
501 true
504}
505
506pub(crate) fn report_cross_origin_denial<D: DomTypes>(
513 cx: SafeJSContext,
514 id: RawHandleId,
515 access: &str,
516) -> bool {
517 debug!(
518 "permission denied to {} property {} on cross-origin object",
519 access,
520 id_to_source(cx, id).as_deref().unwrap_or("< error >"),
521 );
522 let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
523 unsafe {
524 if !JS_IsExceptionPending(*cx) {
525 let global = D::GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof));
526 <D as DomHelpers<D>>::throw_dom_exception(cx, &global, Error::Security, CanGc::note());
528 }
529 }
530 false
531}
532
533pub(crate) unsafe extern "C" fn maybe_cross_origin_set_rawcx<D: DomTypes>(
537 cx: *mut JSContext,
538 proxy: RawHandleObject,
539 id: RawHandleId,
540 v: RawHandleValue,
541 receiver: RawHandleValue,
542 result: *mut ObjectOpResult,
543) -> bool {
544 let cx = SafeJSContext::from_ptr(cx);
545
546 if !<D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
547 return cross_origin_set::<D>(cx, proxy, id, v, receiver, result);
548 }
549
550 let _ac = JSAutoRealm::new(*cx, proxy.get());
552
553 rooted!(in(*cx) let mut own_desc = PropertyDescriptor::default());
556 let mut is_none = false;
557 if !InvokeGetOwnPropertyDescriptor(
558 GetProxyHandler(*proxy),
559 *cx,
560 proxy,
561 id,
562 own_desc.handle_mut().into(),
563 &mut is_none,
564 ) {
565 return false;
566 }
567
568 let own_desc_handle = own_desc.handle().into();
569 js::jsapi::SetPropertyIgnoringNamedGetter(
570 *cx,
571 proxy,
572 id,
573 v,
574 receiver,
575 if is_none {
576 ptr::null()
577 } else {
578 &own_desc_handle
579 },
580 result,
581 )
582}
583
584pub(crate) fn maybe_cross_origin_get_prototype<D: DomTypes>(
588 cx: SafeJSContext,
589 proxy: RawHandleObject,
590 get_proto_object: fn(cx: SafeJSContext, global: HandleObject, rval: MutableHandleObject),
591 proto: RawMutableHandleObject,
592) -> bool {
593 if <D as DomHelpers<D>>::is_platform_object_same_origin(cx, proxy) {
595 let ac = JSAutoRealm::new(*cx, proxy.get());
596 let global = unsafe { D::GlobalScope::from_context(*cx, InRealm::Entered(&ac)) };
597 get_proto_object(cx, global.reflector().get_jsobject(), unsafe {
598 MutableHandleObject::from_raw(proto)
599 });
600 return !proto.is_null();
601 }
602
603 proto.set(ptr::null_mut());
605 true
606}
607
608pub(crate) fn cross_origin_get<D: DomTypes>(
615 cx: SafeJSContext,
616 proxy: RawHandleObject,
617 receiver: RawHandleValue,
618 id: RawHandleId,
619 vp: RawMutableHandleValue,
620) -> bool {
621 rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
623 let mut is_none = false;
624 if !unsafe {
625 InvokeGetOwnPropertyDescriptor(
626 GetProxyHandler(*proxy),
627 *cx,
628 proxy,
629 id,
630 descriptor.handle_mut().into(),
631 &mut is_none,
632 )
633 } {
634 return false;
635 }
636
637 assert!(
639 !is_none,
640 "Callees should throw in all cases when they are not finding \
641 a property decriptor"
642 );
643
644 if is_data_descriptor(&descriptor) {
646 vp.set(descriptor.value_);
647 return true;
648 }
649
650 assert!(is_accessor_descriptor(&descriptor));
652
653 rooted!(in(*cx) let mut getter = ptr::null_mut::<JSObject>());
658 get_getter_object(&descriptor, getter.handle_mut().into());
659 if getter.get().is_null() {
660 return report_cross_origin_denial::<D>(cx, id, "get");
661 }
662
663 rooted!(in(*cx) let mut getter_jsval = UndefinedValue());
664 unsafe {
665 getter.get().to_jsval(*cx, getter_jsval.handle_mut());
666 }
667
668 unsafe {
670 jsapi::Call(
671 *cx,
672 receiver,
673 getter_jsval.handle().into(),
674 &jsapi::HandleValueArray::empty(),
675 vp,
676 )
677 }
678}
679
680pub(crate) unsafe fn cross_origin_set<D: DomTypes>(
687 cx: SafeJSContext,
688 proxy: RawHandleObject,
689 id: RawHandleId,
690 v: RawHandleValue,
691 receiver: RawHandleValue,
692 result: *mut ObjectOpResult,
693) -> bool {
694 rooted!(in(*cx) let mut descriptor = PropertyDescriptor::default());
696 let mut is_none = false;
697 if !InvokeGetOwnPropertyDescriptor(
698 GetProxyHandler(*proxy),
699 *cx,
700 proxy,
701 id,
702 descriptor.handle_mut().into(),
703 &mut is_none,
704 ) {
705 return false;
706 }
707
708 assert!(
710 !is_none,
711 "Callees should throw in all cases when they are not finding \
712 a property decriptor"
713 );
714
715 rooted!(in(*cx) let mut setter = ptr::null_mut::<JSObject>());
718 get_setter_object(&descriptor, setter.handle_mut().into());
719 if setter.get().is_null() {
720 return report_cross_origin_denial::<D>(cx, id, "set");
722 }
723
724 rooted!(in(*cx) let mut setter_jsval = UndefinedValue());
725 setter.get().to_jsval(*cx, setter_jsval.handle_mut());
726
727 rooted!(in(*cx) let mut ignored = UndefinedValue());
731 if !jsapi::Call(
732 *cx,
733 receiver,
734 setter_jsval.handle().into(),
735 &jsapi::HandleValueArray {
738 length_: 1,
739 elements_: v.ptr,
740 },
741 ignored.handle_mut().into(),
742 ) {
743 return false;
744 }
745
746 (*result).code_ = 0 ;
747 true
748}
749
750pub(crate) fn cross_origin_property_fallback<D: DomTypes>(
757 cx: SafeJSContext,
758 _proxy: RawHandleObject,
759 id: RawHandleId,
760 desc: RawMutableHandle<PropertyDescriptor>,
761 is_none: &mut bool,
762) -> bool {
763 assert!(*is_none, "why are we being called?");
764
765 if is_cross_origin_allowlisted_prop(cx, id) {
770 set_property_descriptor(
771 unsafe { MutableHandle::from_raw(desc) },
772 HandleValue::undefined(),
773 jsapi::JSPROP_READONLY as u32,
774 is_none,
775 );
776 return true;
777 }
778
779 report_cross_origin_denial::<D>(cx, id, "access")
781}