11import { hasOwnProperty } from './object.js'
22
33/**
4- * Get a property of a plain object
4+ * Get a property of a plain object or array
55 * Throws an error in case the object is not a plain object or the
66 * property is not defined on the object itself
77 * @param {Object } object
88 * @param {string } prop
99 * @return {* } Returns the property value when safe
1010 */
11- function getSafeProperty ( object , prop ) {
12- // only allow getting safe properties of a plain object
13- if ( isSafeProperty ( object , prop ) ) {
11+ export function getSafeProperty ( object , prop ) {
12+ if ( isSafeObjectProperty ( object , prop ) || isSafeArrayProperty ( object , prop ) ) {
1413 return object [ prop ]
1514 }
1615
17- if ( typeof object [ prop ] === 'function' && isSafeMethod ( object , prop ) ) {
18- throw new Error ( 'Cannot access method "' + prop + '" as a property' )
16+ if ( isSafeMethod ( object , prop ) ) {
17+ throw new Error ( `Cannot access method "${ prop } " as a property` )
18+ }
19+
20+ if ( object === null || object === undefined ) {
21+ throw new TypeError ( `Cannot access property "${ prop } ": object is ${ object } ` )
1922 }
2023
2124 throw new Error ( 'No access to property "' + prop + '"' )
2225}
2326
2427/**
25- * Set a property on a plain object.
28+ * Set a property on a plain object or array .
2629 * Throws an error in case the object is not a plain object or the
2730 * property would override an inherited property like .constructor or .toString
2831 * @param {Object } object
2932 * @param {string } prop
3033 * @param {* } value
3134 * @return {* } Returns the value
3235 */
33- // TODO: merge this function into access.js?
34- function setSafeProperty ( object , prop , value ) {
35- // only allow setting safe properties of a plain object
36- if ( isSafeProperty ( object , prop ) ) {
36+ export function setSafeProperty ( object , prop , value ) {
37+ if ( isSafeObjectProperty ( object , prop ) || isSafeArrayProperty ( object , prop ) ) {
3738 object [ prop ] = value
3839 return value
3940 }
4041
41- throw new Error ( ' No access to property "' + prop + '"' )
42+ throw new Error ( ` No access to property "${ prop } "` )
4243}
4344
4445/**
45- * Test whether a property is safe to use on an object or Array.
46- * For example .toString and .constructor are not safe
47- * @param {Object | Array } object
46+ * Test whether a property is safe for reading and writing on an object
47+ * For example .constructor and .__proto__ are not safe
48+ * @param {Object } object
4849 * @param {string } prop
4950 * @return {boolean } Returns true when safe
5051 */
51- function isSafeProperty ( object , prop ) {
52- if ( ! isPlainObject ( object ) && ! Array . isArray ( object ) ) {
52+ export function isSafeObjectProperty ( object , prop ) {
53+ if ( ! isPlainObject ( object ) ) {
5354 return false
5455 }
55- // SAFE: whitelisted
56- // e.g length
57- if ( hasOwnProperty ( safeNativeProperties , prop ) ) {
58- return true
59- }
60- // UNSAFE: inherited from Object prototype
61- // e.g constructor
62- if ( prop in Object . prototype ) {
63- // 'in' is used instead of hasOwnProperty for nodejs v0.10
64- // which is inconsistent on root prototypes. It is safe
65- // here because Object.prototype is a root object
66- return false
67- }
68- // UNSAFE: inherited from Function prototype
69- // e.g call, apply
70- if ( prop in Function . prototype ) {
71- // 'in' is used instead of hasOwnProperty for nodejs v0.10
72- // which is inconsistent on root prototypes. It is safe
73- // here because Function.prototype is a root object
56+
57+ return ! ( prop in Object . prototype )
58+ }
59+
60+ /**
61+ * Test whether a property is safe for reading and writing on an Array
62+ * For example .__proto__ and .constructor are not safe
63+ * @param {unknown } array
64+ * @param {string | number } prop
65+ * @return {boolean } Returns true when safe
66+ */
67+ export function isSafeArrayProperty ( array , prop ) {
68+ if ( ! Array . isArray ( array ) ) {
7469 return false
7570 }
76- return true
71+
72+ return (
73+ typeof prop === 'number' ||
74+ ( typeof prop === 'string' && isInteger ( prop ) ) ||
75+ prop === 'length'
76+ )
77+ }
78+
79+ function isInteger ( prop ) {
80+ return / ^ \d + $ / . test ( prop )
7781}
7882
7983/**
@@ -83,7 +87,7 @@ function isSafeProperty (object, prop) {
8387 * @param {string } method
8488 * @return {function } Returns the method when valid
8589 */
86- function getSafeMethod ( object , method ) {
90+ export function getSafeMethod ( object , method ) {
8791 if ( ! isSafeMethod ( object , method ) ) {
8892 throw new Error ( 'No access to method "' + method + '"' )
8993 }
@@ -98,22 +102,32 @@ function getSafeMethod (object, method) {
98102 * @param {string } method
99103 * @return {boolean } Returns true when safe, false otherwise
100104 */
101- function isSafeMethod ( object , method ) {
102- if ( object === null || object === undefined || typeof object [ method ] !== 'function' ) {
105+ export function isSafeMethod ( object , method ) {
106+ if (
107+ object === null ||
108+ object === undefined ||
109+ typeof object [ method ] !== 'function'
110+ ) {
103111 return false
104112 }
113+
105114 // UNSAFE: ghosted
106115 // e.g overridden toString
107116 // Note that IE10 doesn't support __proto__ and we can't do this check there.
108- if ( hasOwnProperty ( object , method ) &&
109- ( Object . getPrototypeOf && ( method in Object . getPrototypeOf ( object ) ) ) ) {
117+ if (
118+ hasOwnProperty ( object , method ) &&
119+ Object . getPrototypeOf &&
120+ method in Object . getPrototypeOf ( object )
121+ ) {
110122 return false
111123 }
124+
112125 // SAFE: whitelisted
113126 // e.g toString
114- if ( hasOwnProperty ( safeNativeMethods , method ) ) {
127+ if ( safeNativeMethods . has ( method ) ) {
115128 return true
116129 }
130+
117131 // UNSAFE: inherited from Object prototype
118132 // e.g constructor
119133 if ( method in Object . prototype ) {
@@ -122,6 +136,7 @@ function isSafeMethod (object, method) {
122136 // here because Object.prototype is a root object
123137 return false
124138 }
139+
125140 // UNSAFE: inherited from Function prototype
126141 // e.g call, apply
127142 if ( method in Function . prototype ) {
@@ -130,27 +145,12 @@ function isSafeMethod (object, method) {
130145 // here because Function.prototype is a root object
131146 return false
132147 }
148+
133149 return true
134150}
135151
136- function isPlainObject ( object ) {
152+ export function isPlainObject ( object ) {
137153 return typeof object === 'object' && object && object . constructor === Object
138154}
139155
140- const safeNativeProperties = {
141- length : true ,
142- name : true
143- }
144-
145- const safeNativeMethods = {
146- toString : true ,
147- valueOf : true ,
148- toLocaleString : true
149- }
150-
151- export { getSafeProperty }
152- export { setSafeProperty }
153- export { isSafeProperty }
154- export { getSafeMethod }
155- export { isSafeMethod }
156- export { isPlainObject }
156+ const safeNativeMethods = new Set ( [ 'toString' , 'valueOf' , 'toLocaleString' ] )
0 commit comments