feat(base-driver): Add the size validation of the passed settings objects
MODIFY
packages/base-driver/lib/basedriver/device-settings.js
packages/base-driver/lib/basedriver/device-settings.js
import _ from 'lodash';
import log from './logger';
+import { node, util } from '@appium/support';
+import { errors } from '../protocol/errors';
+
+const MAX_SETTINGS_SIZE = 20 * 1024 * 1024; // 20 MB
class DeviceSettings {
@@ -11,9 +15,14 @@ class DeviceSettings {
// calls updateSettings from implementing driver every time a setting is changed.
async update (newSettings) {
if (!_.isPlainObject(newSettings)) {
- throw new Error(`Settings update should be called with valid JSON. Got ` +
+ throw new errors.InvalidArgumentError(`Settings update should be called with valid JSON. Got ` +
`${JSON.stringify(newSettings)} instead`);
}
+ if (node.getObjectSize({...this._settings, ...newSettings}) >= MAX_SETTINGS_SIZE) {
+ throw new errors.InvalidArgumentError(`New settings cannot be applied, because the overall ` +
+ `object size exceeds the allowed limit of ${util.toReadableSizeString(MAX_SETTINGS_SIZE)}`);
+ }
+
for (const prop of _.keys(newSettings)) {
if (!_.isUndefined(this._settings[prop])) {
if (this._settings[prop] === newSettings[prop]) {

MODIFY
packages/support/lib/node.js
packages/support/lib/node.js
import { isWindows } from './system';
import log from './logger';
+import _ from 'lodash';
import { exec } from 'teen_process';
import path from 'path';
+const ECMA_SIZES = Object.freeze({
+ STRING: 2,
+ BOOLEAN: 4,
+ NUMBER: 8,
+});
+
/**
* Internal utility to link global package to local context
*
@@ -63,4 +70,84 @@ async function requirePackage (packageName) {
}
}
-export { requirePackage };
+function extractAllProperties (obj) {
+ const stringProperties = [];
+ for (const prop in obj) {
+ stringProperties.push(prop);
+ }
+ if (_.isFunction(Object.getOwnPropertySymbols)) {
+ stringProperties.push(...(Object.getOwnPropertySymbols(obj)));
+ }
+ return stringProperties;
+}
+
+function _getSizeOfObject (seen, object) {
+ if (_.isNil(object)) {
+ return 0;
+ }
+
+ let bytes = 0;
+ const properties = extractAllProperties(object);
+ for (const key of properties) {
+ // Do not recalculate circular references
+ if (typeof object[key] === 'object' && !_.isNil(object[key])) {
+ if (seen.has(object[key])) {
+ continue;
+ }
+ seen.add(object[key]);
+ }
+
+ bytes += getCalculator(seen)(key);
+ try {
+ bytes += getCalculator(seen)(object[key]);
+ } catch (ex) {
+ if (ex instanceof RangeError) {
+ // circular reference detected, final result might be incorrect
+ // let's be nice and not throw an exception
+ bytes = 0;
+ }
+ }
+ }
+
+ return bytes;
+}
+
+function getCalculator (seen) {
+ return function calculator (obj) {
+ if (_.isBuffer(obj)) {
+ return obj.length;
+ }
+
+ switch (typeof (obj)) {
+ case 'string':
+ return obj.length * ECMA_SIZES.STRING;
+ case 'boolean':
+ return ECMA_SIZES.BOOLEAN;
+ case 'number':
+ return ECMA_SIZES.NUMBER;
+ case 'symbol':
+ return _.isFunction(Symbol.keyFor) && Symbol.keyFor(obj)
+ ? Symbol.keyFor(obj).length * ECMA_SIZES.STRING
+ : (obj.toString().length - 8) * ECMA_SIZES.STRING;
+ case 'object':
+ return _.isArray(obj)
+ ? obj.map(getCalculator(seen)).reduce((acc, curr) => acc + curr, 0)
+ : _getSizeOfObject(seen, obj);
+ default:
+ return 0;
+ }
+ };
+}
+
+/**
+ * Calculate the in-depth size in memory of the provided object.
+ * The original implementation is borrowed from https://github.com/miktam/sizeof.
+ *
+ * @param {*} obj An object whose size should be calculated
+ * @returns {number} Object size in bytes.
+ */
+function getObjectSize (obj) {
+ return getCalculator(new WeakSet())(obj);
+}
+
+export { requirePackage, getObjectSize };

ADD
packages/support/test/node-specs.js
+import { node } from '../lib';
+
+describe('node utilities', function () {
+ describe('getObjectSize', function () {
+ it('should be able to calculate size of different object types', function () {
+ node.getObjectSize(1).should.eql(8);
+ node.getObjectSize(true).should.eql(4);
+ node.getObjectSize('yolo').should.eql(8);
+ node.getObjectSize(null).should.eql(0);
+ node.getObjectSize({}).should.eql(0);
+ node.getObjectSize(Buffer.from([1, 2, 3])).should.eql(3);
+ node.getObjectSize({
+ 'a': 1,
+ 'b': 2,
+ 'c': {
+ 'd': 4,
+ }
+ }).should.eql(32);
+ });
+ });
+});

feat(base-driver): Add the size validation of the passed settings objects (#16420)