Safe.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. /* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */
  2. /* vim: set ts=2 et sw=2 tw=80: */
  3. /*************************************************************
  4. *
  5. * MathJax/extensions/Safe.js
  6. *
  7. * Implements a "Safe" mode that disables features that could be
  8. * misused in a shared environment (such as href's to javascript URL's).
  9. * See the CONFIG variable below for configuration options.
  10. *
  11. * ---------------------------------------------------------------------
  12. *
  13. * Copyright (c) 2013 The MathJax Consortium
  14. *
  15. * Licensed under the Apache License, Version 2.0 (the "License");
  16. * you may not use this file except in compliance with the License.
  17. * You may obtain a copy of the License at
  18. *
  19. * http://www.apache.org/licenses/LICENSE-2.0
  20. *
  21. * Unless required by applicable law or agreed to in writing, software
  22. * distributed under the License is distributed on an "AS IS" BASIS,
  23. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. * See the License for the specific language governing permissions and
  25. * limitations under the License.
  26. */
  27. (function (HUB,AJAX) {
  28. var VERSION = "2.2";
  29. var CONFIG = MathJax.Hub.CombineConfig("Safe",{
  30. allow: {
  31. //
  32. // Values can be "all", "safe", or "none"
  33. //
  34. URLs: "safe", // safe are in safeProtocols below
  35. classes: "safe", // safe start with MJX-
  36. cssIDs: "safe", // safe start with MJX-
  37. styles: "safe", // safe are in safeStyles below
  38. fontsize: "all", // safe are between sizeMin and sizeMax em's
  39. require: "safe" // safe are in safeRequire below
  40. },
  41. sizeMin: .7, // \scriptsize
  42. sizeMax: 1.44, // \large
  43. safeProtocols: {
  44. http: true,
  45. https: true,
  46. file: true,
  47. javascript: false
  48. },
  49. safeStyles: {
  50. color: true,
  51. backgroundColor: true,
  52. border: true,
  53. cursor: true,
  54. margin: true,
  55. padding: true,
  56. textShadow: true,
  57. fontFamily: true,
  58. fontSize: true,
  59. fontStyle: true,
  60. fontWeight: true,
  61. opacity: true,
  62. outline: true
  63. },
  64. safeRequire: {
  65. action: true,
  66. amscd: true,
  67. amsmath: true,
  68. amssymbols: true,
  69. autobold: false,
  70. "autoload-all": false,
  71. bbox: true,
  72. begingroup: true,
  73. boldsymbol: true,
  74. cancel: true,
  75. color: true,
  76. enclose: true,
  77. extpfeil: true,
  78. HTML: true,
  79. mathchoice: true,
  80. mhchem: true,
  81. newcommand: true,
  82. noErrors: false,
  83. noUndefined: false,
  84. unicode: true,
  85. verb: true
  86. }
  87. });
  88. var ALLOW = CONFIG.allow;
  89. if (ALLOW.fontsize !== "all") {CONFIG.safeStyles.fontSize = false}
  90. var SAFE = MathJax.Extension.Safe = {
  91. version: VERSION,
  92. config: CONFIG,
  93. div1: document.createElement("div"), // for CSS processing
  94. div2: document.createElement("div"),
  95. //
  96. // Methods called for MathML attribute processing
  97. //
  98. filter: {
  99. href: "filterURL",
  100. src: "filterURL",
  101. altimg: "filterURL",
  102. "class": "filterClass",
  103. style: "filterStyles",
  104. id: "filterID",
  105. fontsize: "filterFontSize",
  106. mathsize: "filterFontSize",
  107. scriptminsize: "filterFontSize",
  108. scriptsizemultiplier: "filterSizeMultiplier",
  109. scriptlevel: "filterScriptLevel"
  110. },
  111. //
  112. // Filter HREF URL's
  113. //
  114. filterURL: function (url) {
  115. var protocol = (url.match(/^\s*([a-z]+):/i)||[null,""])[1].toLowerCase();
  116. if (ALLOW.URLs === "none" ||
  117. (ALLOW.URLs !== "all" && !CONFIG.safeProtocols[protocol])) {url = null}
  118. return url;
  119. },
  120. //
  121. // Filter class names and css ID's
  122. //
  123. filterClass: function (CLASS) {
  124. if (ALLOW.classes === "none" ||
  125. (ALLOW.classes !== "all" && !CLASS.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {CLASS = null}
  126. return CLASS;
  127. },
  128. filterID: function (id) {
  129. if (ALLOW.cssIDs === "none" ||
  130. (ALLOW.cssIDs !== "all" && !id.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {id = null}
  131. return id;
  132. },
  133. //
  134. // Filter style strings
  135. //
  136. filterStyles: function (styles) {
  137. if (ALLOW.styles === "all") {return styles}
  138. if (ALLOW.styles === "none") {return null}
  139. try {
  140. //
  141. // Set the div1 styles to the given styles, and clear div2
  142. //
  143. var STYLE1 = this.div1.style, STYLE2 = this.div2.style;
  144. STYLE1.cssText = styles; STYLE2.cssText = "";
  145. //
  146. // Check each allowed style and transfer OK ones to div2
  147. //
  148. for (var name in CONFIG.safeStyles) {if (CONFIG.safeStyles.hasOwnProperty(name)) {
  149. var value = this.filterStyle(name,STYLE1[name]);
  150. if (value != null) {STYLE2[name] = value}
  151. }}
  152. //
  153. // Return the div2 style string
  154. //
  155. styles = STYLE2.cssText;
  156. } catch (e) {styles = null}
  157. return styles;
  158. },
  159. //
  160. // Filter an individual name:value style pair
  161. //
  162. filterStyle: function (name,value) {
  163. if (typeof value !== "string") {return null}
  164. if (value.match(/^\s*expression/)) {return null}
  165. if (value.match(/javascript:/)) {return null}
  166. return (CONFIG.safeStyles[name] ? value : null);
  167. },
  168. //
  169. // Filter TeX font size values (in em's)
  170. //
  171. filterSize: function (size) {
  172. if (ALLOW.fontsize === "none") {return null}
  173. if (ALLOW.fontsize !== "all")
  174. {size = Math.min(Math.max(size,CONFIG.sizeMin),CONFIG.sizeMax)}
  175. return size;
  176. },
  177. filterFontSize: function (size) {
  178. return (ALLOW.fontsize === "all" ? size: null);
  179. },
  180. //
  181. // Filter scriptsizemultiplier
  182. //
  183. filterSizeMultiplier: function (size) {
  184. if (ALLOW.fontsize === "none") {size = null}
  185. else if (ALLOW.fontsize !== "all") {size = Math.min(1,Math.max(.6,size)).toString()}
  186. return size;
  187. },
  188. //
  189. // Filter scriptLevel
  190. //
  191. filterScriptLevel: function (level) {
  192. if (ALLOW.fontsize === "none") {level = null}
  193. else if (ALLOW.fontsize !== "all") {level = Math.max(0,level).toString()}
  194. return level;
  195. },
  196. //
  197. // Filter TeX extension names
  198. //
  199. filterRequire: function (name) {
  200. if (ALLOW.require === "none" ||
  201. (ALLOW.require !== "all" && !CONFIG.safeRequire[name.toLowerCase()]))
  202. {name = null}
  203. return name;
  204. }
  205. };
  206. HUB.Register.StartupHook("TeX HTML Ready",function () {
  207. var TEX = MathJax.InputJax.TeX;
  208. TEX.Parse.Augment({
  209. //
  210. // Implements \href{url}{math} with URL filter
  211. //
  212. HREF_attribute: function (name) {
  213. var url = SAFE.filterURL(this.GetArgument(name)),
  214. arg = this.GetArgumentMML(name);
  215. if (url) {arg.With({href:url})}
  216. this.Push(arg);
  217. },
  218. //
  219. // Implements \class{name}{math} with class-name filter
  220. //
  221. CLASS_attribute: function (name) {
  222. var CLASS = SAFE.filterClass(this.GetArgument(name)),
  223. arg = this.GetArgumentMML(name);
  224. if (CLASS) {
  225. if (arg["class"] != null) {CLASS = arg["class"] + " " + CLASS}
  226. arg.With({"class":CLASS});
  227. }
  228. this.Push(arg);
  229. },
  230. //
  231. // Implements \style{style-string}{math} with style filter
  232. //
  233. STYLE_attribute: function (name) {
  234. var style = SAFE.filterStyles(this.GetArgument(name)),
  235. arg = this.GetArgumentMML(name);
  236. if (style) {
  237. if (arg.style != null) {
  238. if (style.charAt(style.length-1) !== ";") {style += ";"}
  239. style = arg.style + " " + style;
  240. }
  241. arg.With({style: style});
  242. }
  243. this.Push(arg);
  244. },
  245. //
  246. // Implements \cssId{id}{math} with ID filter
  247. //
  248. ID_attribute: function (name) {
  249. var ID = SAFE.filterID(this.GetArgument(name)),
  250. arg = this.GetArgumentMML(name);
  251. if (ID) {arg.With({id:ID})}
  252. this.Push(arg);
  253. }
  254. });
  255. });
  256. HUB.Register.StartupHook("TeX Jax Ready",function () {
  257. var TEX = MathJax.InputJax.TeX,
  258. PARSE = TEX.Parse, METHOD = SAFE.filter;
  259. PARSE.Augment({
  260. //
  261. // Implements \require{name} with filtering
  262. //
  263. Require: function (name) {
  264. var file = this.GetArgument(name).replace(/.*\//,"").replace(/[^a-z0-9_.-]/ig,"");
  265. file = SAFE.filterRequire(file);
  266. if (file) {this.Extension(null,file)}
  267. },
  268. //
  269. // Controls \mmlToken attributes
  270. //
  271. MmlFilterAttribute: function (name,value) {
  272. if (METHOD[name]) {value = SAFE[METHOD[name]](value)}
  273. return value;
  274. },
  275. //
  276. // Handles font size macros with filtering
  277. //
  278. SetSize: function (name,size) {
  279. size = SAFE.filterSize(size);
  280. if (size) {
  281. this.stack.env.size = size;
  282. this.Push(TEX.Stack.Item.style().With({styles: {mathsize: size+"em"}}));
  283. }
  284. }
  285. });
  286. });
  287. HUB.Register.StartupHook("TeX bbox Ready",function () {
  288. var TEX = MathJax.InputJax.TeX;
  289. //
  290. // Filter the styles for \bbox
  291. //
  292. TEX.Parse.Augment({
  293. BBoxStyle: function (styles) {return SAFE.filterStyles(styles)}
  294. });
  295. });
  296. HUB.Register.StartupHook("MathML Jax Ready",function () {
  297. var PARSE = MathJax.InputJax.MathML.Parse,
  298. METHOD = SAFE.filter;
  299. //
  300. // Filter MathML attributes
  301. //
  302. PARSE.Augment({
  303. filterAttribute: function (name,value) {
  304. if (METHOD[name]) {value = SAFE[METHOD[name]](value)}
  305. return value;
  306. }
  307. });
  308. });
  309. // MathML input (href, style, fontsize, class, id)
  310. HUB.Startup.signal.Post("Safe Extension Ready");
  311. AJAX.loadComplete("[MathJax]/extensions/Safe.js");
  312. })(MathJax.Hub,MathJax.Ajax);