safe.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*************************************************************
  2. *
  3. * Copyright (c) 2020-2022 The MathJax Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /**
  18. * @fileoverview Support for the safe extension
  19. *
  20. * @author dpvc@mathjax.org (Davide Cervone)
  21. */
  22. import {Property} from '../../core/Tree/Node.js';
  23. import {MmlNode} from '../../core/MmlTree/MmlNode.js';
  24. import {MathItem} from '../../core/MathItem.js';
  25. import {MathDocument} from '../../core/MathDocument.js';
  26. import {OptionList, expandable} from '../../util/Options.js';
  27. import {DOMAdaptor} from '../../core/DOMAdaptor.js';
  28. import {SafeMethods} from './SafeMethods.js';
  29. /**
  30. * Function type for filtering attributes
  31. *
  32. * @template N The HTMLElement node class
  33. * @template T The Text node class
  34. * @template D The Document class
  35. */
  36. export type FilterFunction<N, T, D> = (safe: Safe<N, T, D>, value: Property, ...args: any[]) => Property;
  37. /**
  38. * The Safe object for sanitizing the internal MathML representation of an expression
  39. *
  40. * @template N The HTMLElement node class
  41. * @template T The Text node class
  42. * @template D The Document class
  43. */
  44. export class Safe<N, T, D> {
  45. /**
  46. * The options controlling the handling of the safe extension
  47. */
  48. public static OPTIONS: OptionList = {
  49. allow: {
  50. //
  51. // Values can be "all", "safe", or "none"
  52. //
  53. URLs: 'safe', // safe are in safeProtocols below
  54. classes: 'safe', // safe start with mjx- (can be set by pattern below)
  55. cssIDs: 'safe', // safe start with mjx- (can be set by pattern below)
  56. styles: 'safe' // safe are in safeStyles below
  57. },
  58. //
  59. // Largest padding/border/margin, etc. in em's
  60. //
  61. lengthMax: 3,
  62. //
  63. // Valid range for scriptsizemultiplier
  64. //
  65. scriptsizemultiplierRange: [.6, 1],
  66. //
  67. // Valid range for scriptlevel
  68. //
  69. scriptlevelRange: [-2, 2],
  70. //
  71. // Pattern for allowed class names
  72. //
  73. classPattern: /^mjx-[-a-zA-Z0-9_.]+$/,
  74. //
  75. // Pattern for allowed ids
  76. //
  77. idPattern: /^mjx-[-a-zA-Z0-9_.]+$/,
  78. //
  79. // Pattern for data attributes
  80. //
  81. dataPattern: /^data-mjx-/,
  82. //
  83. // Which URL protocols are allowed
  84. //
  85. safeProtocols: expandable({
  86. http: true,
  87. https: true,
  88. file: true,
  89. javascript: false,
  90. data: false
  91. }),
  92. //
  93. // Which styles are allowed
  94. //
  95. safeStyles: expandable({
  96. color: true,
  97. backgroundColor: true,
  98. border: true,
  99. cursor: true,
  100. margin: true,
  101. padding: true,
  102. textShadow: true,
  103. fontFamily: true,
  104. fontSize: true,
  105. fontStyle: true,
  106. fontWeight: true,
  107. opacity: true,
  108. outline: true
  109. }),
  110. //
  111. // CSS styles that have Top/Right/Bottom/Left versions
  112. //
  113. styleParts: expandable({
  114. border: true,
  115. padding: true,
  116. margin: true,
  117. outline: true
  118. }),
  119. //
  120. // CSS styles that are lengths needing max/min testing
  121. // A string value means test that style value;
  122. // An array gives [min,max] in em's
  123. // Otherwise use [-lengthMax,lengthMax] from above
  124. //
  125. styleLengths: expandable({
  126. borderTop: 'borderTopWidth',
  127. borderRight: 'borderRightWidth',
  128. borderBottom: 'borderBottomWidth',
  129. borderLeft: 'borderLeftWidth',
  130. paddingTop: true,
  131. paddingRight: true,
  132. paddingBottom: true,
  133. paddingLeft: true,
  134. marginTop: true,
  135. marginRight: true,
  136. marginBottom: true,
  137. marginLeft: true,
  138. outlineTop: true,
  139. outlineRight: true,
  140. outlineBottom: true,
  141. outlineLeft: true,
  142. fontSize: [.707, 1.44]
  143. })
  144. };
  145. /**
  146. * The attribute-to-filter-method mapping
  147. */
  148. public filterAttributes: Map<string, string> = new Map([
  149. //
  150. // Methods called for MathML attribute processing
  151. //
  152. ['href', 'filterURL'],
  153. ['src', 'filterURL'],
  154. ['altimg', 'filterURL'],
  155. ['class', 'filterClassList'],
  156. ['style', 'filterStyles'],
  157. ['id', 'filterID'],
  158. ['fontsize', 'filterFontSize'],
  159. ['mathsize', 'filterFontSize'],
  160. ['scriptminsize', 'filterFontSize'],
  161. ['scriptsizemultiplier', 'filterSizeMultiplier'],
  162. ['scriptlevel', 'filterScriptLevel'],
  163. ['data-', 'filterData']
  164. ]);
  165. /**
  166. * The safe options from the document option list
  167. */
  168. public options: OptionList;
  169. /**
  170. * Shorthand for options.allow
  171. */
  172. public allow: OptionList;
  173. /**
  174. * The DOM adaptor from the document
  175. */
  176. public adaptor: DOMAdaptor<N, T, D>;
  177. /**
  178. * The methods for filtering the MathML attributes
  179. */
  180. public filterMethods: {[name: string]: FilterFunction<N, T, D>} = {
  181. ...SafeMethods
  182. };
  183. /**
  184. * @param {MathDocument<N,T,D>} document The MathDocument we are sanitizing
  185. * @param {OptionList} options The safeOptions from the document
  186. */
  187. constructor(document: MathDocument<N, T, D>, options: OptionList) {
  188. this.adaptor = document.adaptor;
  189. this.options = options;
  190. this.allow = this.options.allow;
  191. }
  192. /**
  193. * Sanitize a MathItem's root MathML tree
  194. *
  195. * @param {MathItem<N,T,D>} math The MathItem to sanitize
  196. * @param {MathDocument<N,T,D>} document The MathDocument in which it lives
  197. */
  198. public sanitize(math: MathItem<N, T, D>, document: MathDocument<N, T, D>) {
  199. try {
  200. math.root.walkTree(this.sanitizeNode.bind(this));
  201. } catch (err) {
  202. document.options.compileError(document, math, err);
  203. }
  204. }
  205. /**
  206. * Sanitize a node's attributes
  207. *
  208. * @param {MmlNode} node The node to sanitize
  209. */
  210. protected sanitizeNode(node: MmlNode) {
  211. const attributes = node.attributes.getAllAttributes();
  212. for (const id of Object.keys(attributes)) {
  213. const method = this.filterAttributes.get(id);
  214. if (method) {
  215. const value = this.filterMethods[method](this, attributes[id]);
  216. if (value) {
  217. if (value !== (typeof value === 'number' ? parseFloat(attributes[id] as string) : attributes[id])) {
  218. attributes[id] = value;
  219. }
  220. } else {
  221. delete attributes[id];
  222. }
  223. }
  224. }
  225. }
  226. /**
  227. * Sanitize a MathML input attribute
  228. *
  229. * @param {string} id The name of the attribute
  230. * @param {string} value The value of the attribute
  231. * @return {string|null} The sanitized value
  232. */
  233. public mmlAttribute(id: string, value: string): string | null {
  234. if (id === 'class') return null;
  235. const method = this.filterAttributes.get(id);
  236. const filter = (method || (id.substr(0, 5) === 'data-' ? this.filterAttributes.get('data-') : null));
  237. if (!filter) {
  238. return value;
  239. }
  240. const result = this.filterMethods[filter](this, value, id);
  241. return (typeof result === 'number' || typeof result === 'boolean' ? String(result) : result);
  242. }
  243. /**
  244. * Sanitize a list of class names
  245. *
  246. * @param {string[]} list The list of class names
  247. * @return {string[]} The sanitized list
  248. */
  249. public mmlClassList(list: string[]): string[] {
  250. return list.map((name) => this.filterMethods.filterClass(this, name) as string)
  251. .filter((value) => value !== null);
  252. }
  253. }