123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- /*************************************************************
- *
- * Copyright (c) 2020-2022 The MathJax Consortium
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- /**
- * @fileoverview Support for the safe extension
- *
- * @author dpvc@mathjax.org (Davide Cervone)
- */
- import {Property} from '../../core/Tree/Node.js';
- import {MmlNode} from '../../core/MmlTree/MmlNode.js';
- import {MathItem} from '../../core/MathItem.js';
- import {MathDocument} from '../../core/MathDocument.js';
- import {OptionList, expandable} from '../../util/Options.js';
- import {DOMAdaptor} from '../../core/DOMAdaptor.js';
- import {SafeMethods} from './SafeMethods.js';
- /**
- * Function type for filtering attributes
- *
- * @template N The HTMLElement node class
- * @template T The Text node class
- * @template D The Document class
- */
- export type FilterFunction<N, T, D> = (safe: Safe<N, T, D>, value: Property, ...args: any[]) => Property;
- /**
- * The Safe object for sanitizing the internal MathML representation of an expression
- *
- * @template N The HTMLElement node class
- * @template T The Text node class
- * @template D The Document class
- */
- export class Safe<N, T, D> {
- /**
- * The options controlling the handling of the safe extension
- */
- public static OPTIONS: OptionList = {
- allow: {
- //
- // Values can be "all", "safe", or "none"
- //
- URLs: 'safe', // safe are in safeProtocols below
- classes: 'safe', // safe start with mjx- (can be set by pattern below)
- cssIDs: 'safe', // safe start with mjx- (can be set by pattern below)
- styles: 'safe' // safe are in safeStyles below
- },
- //
- // Largest padding/border/margin, etc. in em's
- //
- lengthMax: 3,
- //
- // Valid range for scriptsizemultiplier
- //
- scriptsizemultiplierRange: [.6, 1],
- //
- // Valid range for scriptlevel
- //
- scriptlevelRange: [-2, 2],
- //
- // Pattern for allowed class names
- //
- classPattern: /^mjx-[-a-zA-Z0-9_.]+$/,
- //
- // Pattern for allowed ids
- //
- idPattern: /^mjx-[-a-zA-Z0-9_.]+$/,
- //
- // Pattern for data attributes
- //
- dataPattern: /^data-mjx-/,
- //
- // Which URL protocols are allowed
- //
- safeProtocols: expandable({
- http: true,
- https: true,
- file: true,
- javascript: false,
- data: false
- }),
- //
- // Which styles are allowed
- //
- safeStyles: expandable({
- color: true,
- backgroundColor: true,
- border: true,
- cursor: true,
- margin: true,
- padding: true,
- textShadow: true,
- fontFamily: true,
- fontSize: true,
- fontStyle: true,
- fontWeight: true,
- opacity: true,
- outline: true
- }),
- //
- // CSS styles that have Top/Right/Bottom/Left versions
- //
- styleParts: expandable({
- border: true,
- padding: true,
- margin: true,
- outline: true
- }),
- //
- // CSS styles that are lengths needing max/min testing
- // A string value means test that style value;
- // An array gives [min,max] in em's
- // Otherwise use [-lengthMax,lengthMax] from above
- //
- styleLengths: expandable({
- borderTop: 'borderTopWidth',
- borderRight: 'borderRightWidth',
- borderBottom: 'borderBottomWidth',
- borderLeft: 'borderLeftWidth',
- paddingTop: true,
- paddingRight: true,
- paddingBottom: true,
- paddingLeft: true,
- marginTop: true,
- marginRight: true,
- marginBottom: true,
- marginLeft: true,
- outlineTop: true,
- outlineRight: true,
- outlineBottom: true,
- outlineLeft: true,
- fontSize: [.707, 1.44]
- })
- };
- /**
- * The attribute-to-filter-method mapping
- */
- public filterAttributes: Map<string, string> = new Map([
- //
- // Methods called for MathML attribute processing
- //
- ['href', 'filterURL'],
- ['src', 'filterURL'],
- ['altimg', 'filterURL'],
- ['class', 'filterClassList'],
- ['style', 'filterStyles'],
- ['id', 'filterID'],
- ['fontsize', 'filterFontSize'],
- ['mathsize', 'filterFontSize'],
- ['scriptminsize', 'filterFontSize'],
- ['scriptsizemultiplier', 'filterSizeMultiplier'],
- ['scriptlevel', 'filterScriptLevel'],
- ['data-', 'filterData']
- ]);
- /**
- * The safe options from the document option list
- */
- public options: OptionList;
- /**
- * Shorthand for options.allow
- */
- public allow: OptionList;
- /**
- * The DOM adaptor from the document
- */
- public adaptor: DOMAdaptor<N, T, D>;
- /**
- * The methods for filtering the MathML attributes
- */
- public filterMethods: {[name: string]: FilterFunction<N, T, D>} = {
- ...SafeMethods
- };
- /**
- * @param {MathDocument<N,T,D>} document The MathDocument we are sanitizing
- * @param {OptionList} options The safeOptions from the document
- */
- constructor(document: MathDocument<N, T, D>, options: OptionList) {
- this.adaptor = document.adaptor;
- this.options = options;
- this.allow = this.options.allow;
- }
- /**
- * Sanitize a MathItem's root MathML tree
- *
- * @param {MathItem<N,T,D>} math The MathItem to sanitize
- * @param {MathDocument<N,T,D>} document The MathDocument in which it lives
- */
- public sanitize(math: MathItem<N, T, D>, document: MathDocument<N, T, D>) {
- try {
- math.root.walkTree(this.sanitizeNode.bind(this));
- } catch (err) {
- document.options.compileError(document, math, err);
- }
- }
- /**
- * Sanitize a node's attributes
- *
- * @param {MmlNode} node The node to sanitize
- */
- protected sanitizeNode(node: MmlNode) {
- const attributes = node.attributes.getAllAttributes();
- for (const id of Object.keys(attributes)) {
- const method = this.filterAttributes.get(id);
- if (method) {
- const value = this.filterMethods[method](this, attributes[id]);
- if (value) {
- if (value !== (typeof value === 'number' ? parseFloat(attributes[id] as string) : attributes[id])) {
- attributes[id] = value;
- }
- } else {
- delete attributes[id];
- }
- }
- }
- }
- /**
- * Sanitize a MathML input attribute
- *
- * @param {string} id The name of the attribute
- * @param {string} value The value of the attribute
- * @return {string|null} The sanitized value
- */
- public mmlAttribute(id: string, value: string): string | null {
- if (id === 'class') return null;
- const method = this.filterAttributes.get(id);
- const filter = (method || (id.substr(0, 5) === 'data-' ? this.filterAttributes.get('data-') : null));
- if (!filter) {
- return value;
- }
- const result = this.filterMethods[filter](this, value, id);
- return (typeof result === 'number' || typeof result === 'boolean' ? String(result) : result);
- }
- /**
- * Sanitize a list of class names
- *
- * @param {string[]} list The list of class names
- * @return {string[]} The sanitized list
- */
- public mmlClassList(list: string[]): string[] {
- return list.map((name) => this.filterMethods.filterClass(this, name) as string)
- .filter((value) => value !== null);
- }
- }
|