Skip to content

Commit

Permalink
Allowlist support for mutators (#487)
Browse files Browse the repository at this point in the history
* Denylist support for mutators

* Move to allowlist

* Fix clone node for SVG and other elements with a namespace (#499)
  • Loading branch information
kristoferbaxter authored May 22, 2019
1 parent f39e31f commit 60a7161
Show file tree
Hide file tree
Showing 25 changed files with 571 additions and 210 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
{
"path": "./dist/worker/worker.mjs",
"compression": "brotli",
"maxSize": "10.3 kB"
"maxSize": "10.4 kB"
},
{
"path": "./dist/worker/worker.js",
Expand Down
48 changes: 26 additions & 22 deletions src/main-thread/commands/attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,35 @@
* limitations under the License.
*/

import { AttributeMutationIndex } from '../../transfer/TransferrableMutation';
import { CommandExecutor } from './interface';
import { Strings } from '../strings';
import { WorkerDOMConfiguration } from '../configuration';
import { AttributeMutationIndex, TransferrableMutationType } from '../../transfer/TransferrableMutation';
import { CommandExecutorInterface } from './interface';

export const AttributeProcessor: CommandExecutorInterface = (strings, nodes, workerContext, config) => {
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.ATTRIBUTES);

export function AttributeProcessor(strings: Strings, config: WorkerDOMConfiguration): CommandExecutor {
return {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number {
const attributeName = strings.get(mutations[startPosition + AttributeMutationIndex.Name]);
// Value is sent as 0 when it's the default value or removal.
// Value is sent as index + 1 when it's a valid value.
const value =
(mutations[startPosition + AttributeMutationIndex.Value] !== 0 && strings.get(mutations[startPosition + AttributeMutationIndex.Value] - 1)) ||
null;
if (allowedExecution) {
const attributeName = strings.get(mutations[startPosition + AttributeMutationIndex.Name]);
// Value is sent as 0 when it's the default value or removal.
// Value is sent as index + 1 when it's a valid value.
const value =
(mutations[startPosition + AttributeMutationIndex.Value] !== 0 &&
strings.get(mutations[startPosition + AttributeMutationIndex.Value] - 1)) ||
null;

if (attributeName != null) {
if (config.sanitizer) {
const mutated = config.sanitizer.mutateAttribute(target, attributeName, value);
if (!mutated) {
// TODO(choumx): Inform worker that sanitizer ignored unsafe attribute value change.
}
} else {
if (value == null) {
target.removeAttribute(attributeName);
if (attributeName != null) {
if (config.sanitizer) {
const mutated = config.sanitizer.mutateAttribute(target, attributeName, value);
if (!mutated) {
// TODO(choumx): Inform worker that sanitizer ignored unsafe attribute value change.
}
} else {
target.setAttribute(attributeName, value);
if (value == null) {
target.removeAttribute(attributeName);
} else {
target.setAttribute(attributeName, value);
}
}
}
}
Expand All @@ -55,10 +58,11 @@ export function AttributeProcessor(strings: Strings, config: WorkerDOMConfigurat

return {
target,
allowedExecution,
attributeName,
value,
remove: value == null,
};
},
};
}
};
45 changes: 25 additions & 20 deletions src/main-thread/commands/bounding-client-rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,33 @@

import { TransferrableKeys } from '../../transfer/TransferrableKeys';
import { MessageType } from '../../transfer/Messages';
import { WorkerContext } from '../worker';
import { CommandExecutor } from './interface';
import { CommandExecutorInterface } from './interface';
import { BoundClientRectMutationIndex } from '../../transfer/TransferrableBoundClientRect';
import { TransferrableMutationType } from '../../transfer/TransferrableMutation';

export const BoundingClientRectProcessor: CommandExecutorInterface = (strings, nodes, workerContext, config) => {
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.GET_BOUNDING_CLIENT_RECT);

export function BoundingClientRectProcessor(workerContext: WorkerContext): CommandExecutor {
return {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number {
if (target) {
const boundingRect = target.getBoundingClientRect();
workerContext.messageToWorker({
[TransferrableKeys.type]: MessageType.GET_BOUNDING_CLIENT_RECT,
[TransferrableKeys.target]: [target._index_],
[TransferrableKeys.data]: [
boundingRect.top,
boundingRect.right,
boundingRect.bottom,
boundingRect.left,
boundingRect.width,
boundingRect.height,
],
});
} else {
console.error(`getNode() yields null – ${target}`);
if (allowedExecution) {
if (target) {
const boundingRect = target.getBoundingClientRect();
workerContext.messageToWorker({
[TransferrableKeys.type]: MessageType.GET_BOUNDING_CLIENT_RECT,
[TransferrableKeys.target]: [target._index_],
[TransferrableKeys.data]: [
boundingRect.top,
boundingRect.right,
boundingRect.bottom,
boundingRect.left,
boundingRect.width,
boundingRect.height,
],
});
} else {
console.error(`getNode() yields null – ${target}`);
}
}

return startPosition + BoundClientRectMutationIndex.End;
Expand All @@ -47,7 +51,8 @@ export function BoundingClientRectProcessor(workerContext: WorkerContext): Comma
return {
type: 'GET_BOUNDING_CLIENT_RECT',
target,
allowedExecution,
};
},
};
}
};
22 changes: 13 additions & 9 deletions src/main-thread/commands/character-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,29 @@
* limitations under the License.
*/

import { CharacterDataMutationIndex } from '../../transfer/TransferrableMutation';
import { CommandExecutor } from './interface';
import { Strings } from '../strings';
import { CharacterDataMutationIndex, TransferrableMutationType } from '../../transfer/TransferrableMutation';
import { CommandExecutorInterface } from './interface';

export const CharacterDataProcessor: CommandExecutorInterface = (strings, nodes, workerContext, config) => {
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.CHARACTER_DATA);

export function CharacterDataProcessor(strings: Strings): CommandExecutor {
return {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number {
const value = mutations[startPosition + CharacterDataMutationIndex.Value];
if (value) {
// Sanitization not necessary for textContent.
target.textContent = strings.get(value);
if (allowedExecution) {
const value = mutations[startPosition + CharacterDataMutationIndex.Value];
if (value) {
// Sanitization not necessary for textContent.
target.textContent = strings.get(value);
}
}
return startPosition + CharacterDataMutationIndex.End;
},
print(mutations: Uint16Array, startPosition: number, target?: RenderableElement | null): Object {
return {
target,
allowedExecution,
value: strings.get(mutations[startPosition + CharacterDataMutationIndex.Value]),
};
},
};
}
};
67 changes: 36 additions & 31 deletions src/main-thread/commands/child-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,46 @@
* limitations under the License.
*/

import { ChildListMutationIndex } from '../../transfer/TransferrableMutation';
import { CommandExecutor } from './interface';
import { ChildListMutationIndex, TransferrableMutationType } from '../../transfer/TransferrableMutation';
import { CommandExecutorInterface } from './interface';
import { NodeContext } from '../nodes';

export function ChildListProcessor({ getNode }: NodeContext): CommandExecutor {
export const ChildListProcessor: CommandExecutorInterface = (strings, { getNode }: NodeContext, workerContext, config) => {
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.CHILD_LIST);

return {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number {
const appendNodeCount = mutations[startPosition + ChildListMutationIndex.AppendedNodeCount];
const removeNodeCount = mutations[startPosition + ChildListMutationIndex.RemovedNodeCount];
if (removeNodeCount > 0) {
mutations
.slice(
startPosition + ChildListMutationIndex.Nodes + appendNodeCount,
startPosition + ChildListMutationIndex.Nodes + appendNodeCount + removeNodeCount,
)
.forEach(removeId => {
const node = getNode(removeId);
if (!node) {
console.error(`getNode() yields null – ${removeId}`);
return;
}
node.remove();
});
}
if (appendNodeCount > 0) {
mutations
.slice(startPosition + ChildListMutationIndex.Nodes, startPosition + ChildListMutationIndex.Nodes + appendNodeCount)
.forEach(addId => {
const nextSibling = mutations[startPosition + ChildListMutationIndex.NextSibling];
const newNode = getNode(addId);
if (newNode) {
// TODO: Handle this case ---
// Transferred nodes that are not stored were previously removed by the sanitizer.
target.insertBefore(newNode, (nextSibling && getNode(nextSibling)) || null);
}
});
if (allowedExecution) {
if (removeNodeCount > 0) {
mutations
.slice(
startPosition + ChildListMutationIndex.Nodes + appendNodeCount,
startPosition + ChildListMutationIndex.Nodes + appendNodeCount + removeNodeCount,
)
.forEach(removeId => {
const node = getNode(removeId);
if (!node) {
console.error(`getNode() yields null – ${removeId}`);
return;
}
node.remove();
});
}
if (appendNodeCount > 0) {
mutations
.slice(startPosition + ChildListMutationIndex.Nodes, startPosition + ChildListMutationIndex.Nodes + appendNodeCount)
.forEach(addId => {
const nextSibling = mutations[startPosition + ChildListMutationIndex.NextSibling];
const newNode = getNode(addId);
if (newNode) {
// TODO: Handle this case ---
// Transferred nodes that are not stored were previously removed by the sanitizer.
target.insertBefore(newNode, (nextSibling && getNode(nextSibling)) || null);
}
});
}
}
return startPosition + ChildListMutationIndex.End + appendNodeCount + removeNodeCount;
},
Expand All @@ -68,11 +72,12 @@ export function ChildListProcessor({ getNode }: NodeContext): CommandExecutor {

return {
target,
allowedExecution,
nextSibling: getNode(mutations[startPosition + ChildListMutationIndex.NextSibling]) || null,
previousSibling: getNode(mutations[startPosition + ChildListMutationIndex.PreviousSibling]) || null,
addedNodes,
removedNodes,
};
},
};
}
};
23 changes: 13 additions & 10 deletions src/main-thread/commands/event-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@
*/

import { MessageType } from '../../transfer/Messages';
import { Strings } from '../strings';
import { TransferrableKeys } from '../../transfer/TransferrableKeys';
import { EVENT_SUBSCRIPTION_LENGTH, EventSubscriptionMutationIndex, TransferrableTouchList } from '../../transfer/TransferrableEvent';
import { WorkerContext } from '../worker';
import { CommandExecutor } from './interface';
import { NodeContext } from '../nodes';
import { CommandExecutorInterface } from './interface';
import { TransferrableMutationType } from '../../transfer/TransferrableMutation';

/**
* Instead of a whitelist of elements that need their value tracked, use the existence
Expand Down Expand Up @@ -81,8 +80,9 @@ const createTransferrableTouchList = (touchList: TouchList): TransferrableTouchL
(touch.target as RenderableElement)._index_,
]);

export function EventSubscriptionProcessor(strings: Strings, nodeContext: NodeContext, workerContext: WorkerContext): CommandExecutor {
export const EventSubscriptionProcessor: CommandExecutorInterface = (strings, nodeContext, workerContext, config) => {
const knownListeners: Array<(event: Event) => any> = [];
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.EVENT_SUBSCRIPTION);
let cachedWindowSize: [number, number] = [window.innerWidth, window.innerHeight];

/**
Expand Down Expand Up @@ -178,12 +178,14 @@ export function EventSubscriptionProcessor(strings: Strings, nodeContext: NodeCo
const endPosition =
startPosition + EventSubscriptionMutationIndex.Events + (addEventListenerCount + removeEventListenerCount) * EVENT_SUBSCRIPTION_LENGTH;

if (target) {
for (let iterator = startPosition + EventSubscriptionMutationIndex.Events; iterator < endPosition; iterator += EVENT_SUBSCRIPTION_LENGTH) {
processListenerChange(target, iterator <= addEventListenersPosition, strings.get(mutations[iterator]), mutations[iterator + 1]);
if (allowedExecution) {
if (target) {
for (let iterator = startPosition + EventSubscriptionMutationIndex.Events; iterator < endPosition; iterator += EVENT_SUBSCRIPTION_LENGTH) {
processListenerChange(target, iterator <= addEventListenersPosition, strings.get(mutations[iterator]), mutations[iterator + 1]);
}
} else {
console.error(`getNode() yields null – ${target}`);
}
} else {
console.error(`getNode() yields null – ${target}`);
}

return endPosition;
Expand All @@ -208,9 +210,10 @@ export function EventSubscriptionProcessor(strings: Strings, nodeContext: NodeCo

return {
target,
allowedExecution,
removedEventListeners,
addedEventListeners,
};
},
};
}
};
9 changes: 9 additions & 0 deletions src/main-thread/commands/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@
* limitations under the License.
*/

import { WorkerDOMConfiguration } from '../configuration';
import { Strings } from '../strings';
import { NodeContext } from '../nodes';
import { WorkerContext } from '../worker';

export interface CommandExecutor {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number;
print(mutations: Uint16Array, startPosition: number, target?: RenderableElement | null): Object;
}

export interface CommandExecutorInterface {
(strings: Strings, nodeContext: NodeContext, workerContext: WorkerContext, config: WorkerDOMConfiguration): CommandExecutor;
}
23 changes: 18 additions & 5 deletions src/main-thread/commands/long-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,33 @@
* limitations under the License.
*/

import { WorkerDOMConfiguration } from '../configuration';
import { CommandExecutor } from './interface';
import { CommandExecutor, CommandExecutorInterface } from './interface';
import { TransferrableMutationType, ReadableMutationType, LongTaskMutationIndex } from '../../transfer/TransferrableMutation';
import { Strings } from '../strings';
import { NodeContext } from '../nodes';
import { WorkerContext } from '../worker';
import { WorkerDOMConfiguration } from '../configuration';

export interface LongTaskCommandExecutorInterface extends CommandExecutorInterface {
(strings: Strings, nodeContext: NodeContext, workerContext: WorkerContext, config: WorkerDOMConfiguration): LongTaskCommandExecutor;
}
export interface LongTaskCommandExecutor extends CommandExecutor {
active: boolean;
}

export function LongTaskExecutor(config: WorkerDOMConfiguration): LongTaskCommandExecutor {
export const LongTaskExecutor: LongTaskCommandExecutorInterface = (
strings: Strings,
nodeContext: NodeContext,
workerContext: WorkerContext,
config: WorkerDOMConfiguration,
) => {
const allowedExecution = config.executorsAllowed.includes(TransferrableMutationType.LONG_TASK_START);
let index: number = 0;
let currentResolver: Function | null;

return {
execute(mutations: Uint16Array, startPosition: number, target: RenderableElement): number {
if (config.longTask) {
if (allowedExecution && config.longTask) {
if (mutations[startPosition] === TransferrableMutationType.LONG_TASK_START) {
index++;
if (!currentResolver) {
Expand All @@ -48,10 +60,11 @@ export function LongTaskExecutor(config: WorkerDOMConfiguration): LongTaskComman
print(mutations: Uint16Array, startPosition: number, target?: RenderableElement | null): Object {
return {
type: ReadableMutationType[mutations[startPosition]],
allowedExecution,
};
},
get active(): boolean {
return currentResolver !== null;
},
};
}
};
Loading

0 comments on commit 60a7161

Please sign in to comment.