1 |
|
2 |
|
3 | const hasOwnProperty = require('./object').hasOwnProperty
|
4 |
|
5 | /**
|
6 | * Get a property of a plain object
|
7 | * Throws an error in case the object is not a plain object or the
|
8 | * property is not defined on the object itself
|
9 | * @param {Object} object
|
10 | * @param {string} prop
|
11 | * @return {*} Returns the property value when safe
|
12 | */
|
13 | function getSafeProperty (object, prop) {
|
14 | // only allow getting safe properties of a plain object
|
15 | if (isPlainObject(object) && isSafeProperty(object, prop)) {
|
16 | return object[prop]
|
17 | }
|
18 |
|
19 | if (typeof object[prop] === 'function' && isSafeMethod(object, prop)) {
|
20 | throw new Error('Cannot access method "' + prop + '" as a property')
|
21 | }
|
22 |
|
23 | throw new Error('No access to property "' + prop + '"')
|
24 | }
|
25 |
|
26 | /**
|
27 | * Set a property on a plain object.
|
28 | * Throws an error in case the object is not a plain object or the
|
29 | * property would override an inherited property like .constructor or .toString
|
30 | * @param {Object} object
|
31 | * @param {string} prop
|
32 | * @param {*} value
|
33 | * @return {*} Returns the value
|
34 | */
|
35 | // TODO: merge this function into access.js?
|
36 | function setSafeProperty (object, prop, value) {
|
37 | // only allow setting safe properties of a plain object
|
38 | if (isPlainObject(object) && isSafeProperty(object, prop)) {
|
39 | object[prop] = value
|
40 | return value
|
41 | }
|
42 |
|
43 | throw new Error('No access to property "' + prop + '"')
|
44 | }
|
45 |
|
46 | /**
|
47 | * Test whether a property is safe to use for an object.
|
48 | * For example .toString and .constructor are not safe
|
49 | * @param {string} prop
|
50 | * @return {boolean} Returns true when safe
|
51 | */
|
52 | function isSafeProperty (object, prop) {
|
53 | if (!object || typeof object !== 'object') {
|
54 | return false
|
55 | }
|
56 | // SAFE: whitelisted
|
57 | // e.g length
|
58 | if (hasOwnProperty(safeNativeProperties, prop)) {
|
59 | return true
|
60 | }
|
61 | // UNSAFE: inherited from Object prototype
|
62 | // e.g constructor
|
63 | if (prop in Object.prototype) {
|
64 | // 'in' is used instead of hasOwnProperty for nodejs v0.10
|
65 | // which is inconsistent on root prototypes. It is safe
|
66 | // here because Object.prototype is a root object
|
67 | return false
|
68 | }
|
69 | // UNSAFE: inherited from Function prototype
|
70 | // e.g call, apply
|
71 | if (prop in Function.prototype) {
|
72 | // 'in' is used instead of hasOwnProperty for nodejs v0.10
|
73 | // which is inconsistent on root prototypes. It is safe
|
74 | // here because Function.prototype is a root object
|
75 | return false
|
76 | }
|
77 | return true
|
78 | }
|
79 |
|
80 | /**
|
81 | * Validate whether a method is safe.
|
82 | * Throws an error when that's not the case.
|
83 | * @param {Object} object
|
84 | * @param {string} method
|
85 | */
|
86 | // TODO: merge this function into assign.js?
|
87 | function validateSafeMethod (object, method) {
|
88 | if (!isSafeMethod(object, method)) {
|
89 | throw new Error('No access to method "' + method + '"')
|
90 | }
|
91 | }
|
92 |
|
93 | /**
|
94 | * Check whether a method is safe.
|
95 | * Throws an error when that's not the case (for example for `constructor`).
|
96 | * @param {Object} object
|
97 | * @param {string} method
|
98 | * @return {boolean} Returns true when safe, false otherwise
|
99 | */
|
100 | function isSafeMethod (object, method) {
|
101 | if (!object || typeof object[method] !== 'function') {
|
102 | return false
|
103 | }
|
104 | // UNSAFE: ghosted
|
105 | // e.g overridden toString
|
106 | // Note that IE10 doesn't support __proto__ and we can't do this check there.
|
107 | if (hasOwnProperty(object, method) &&
|
108 | (Object.getPrototypeOf && (method in Object.getPrototypeOf(object)))) {
|
109 | return false
|
110 | }
|
111 | // SAFE: whitelisted
|
112 | // e.g toString
|
113 | if (hasOwnProperty(safeNativeMethods, method)) {
|
114 | return true
|
115 | }
|
116 | // UNSAFE: inherited from Object prototype
|
117 | // e.g constructor
|
118 | if (method in Object.prototype) {
|
119 | // 'in' is used instead of hasOwnProperty for nodejs v0.10
|
120 | // which is inconsistent on root prototypes. It is safe
|
121 | // here because Object.prototype is a root object
|
122 | return false
|
123 | }
|
124 | // UNSAFE: inherited from Function prototype
|
125 | // e.g call, apply
|
126 | if (method in Function.prototype) {
|
127 | // 'in' is used instead of hasOwnProperty for nodejs v0.10
|
128 | // which is inconsistent on root prototypes. It is safe
|
129 | // here because Function.prototype is a root object
|
130 | return false
|
131 | }
|
132 | return true
|
133 | }
|
134 |
|
135 | function isPlainObject (object) {
|
136 | return typeof object === 'object' && object && object.constructor === Object
|
137 | }
|
138 |
|
139 | const safeNativeProperties = {
|
140 | length: true,
|
141 | name: true
|
142 | }
|
143 |
|
144 | const safeNativeMethods = {
|
145 | toString: true,
|
146 | valueOf: true,
|
147 | toLocaleString: true
|
148 | }
|
149 |
|
150 | exports.getSafeProperty = getSafeProperty
|
151 | exports.setSafeProperty = setSafeProperty
|
152 | exports.isSafeProperty = isSafeProperty
|
153 | exports.validateSafeMethod = validateSafeMethod
|
154 | exports.isSafeMethod = isSafeMethod
|
155 | exports.isPlainObject = isPlainObject
|