/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax/extensions/Safe.js * * Implements a "Safe" mode that disables features that could be * misused in a shared environment (such as href's to javascript URL's). * See the CONFIG variable below for configuration options. * * --------------------------------------------------------------------- * * Copyright (c) 2013-2017 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. */ (function (HUB,AJAX) { var VERSION = "2.7.2"; var CONFIG = MathJax.Hub.CombineConfig("Safe",{ allow: { // // Values can be "all", "safe", or "none" // URLs: "safe", // safe are in safeProtocols below classes: "safe", // safe start with MJX- cssIDs: "safe", // safe start with MJX- styles: "safe", // safe are in safeStyles below fontsize: "all", // safe are between sizeMin and sizeMax em's require: "safe" // safe are in safeRequire below }, sizeMin: .7, // \scriptsize sizeMax: 1.44, // \large lengthMax: 3, // largest padding/border/margin, etc. in em's safeProtocols: { http: true, https: true, file: true, javascript: false }, safeStyles: { 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 }, safeRequire: { action: true, amscd: true, amsmath: true, amssymbols: true, autobold: false, "autoload-all": false, bbox: true, begingroup: true, boldsymbol: true, cancel: true, color: true, enclose: true, extpfeil: true, HTML: true, mathchoice: true, mhchem: true, newcommand: true, noErrors: false, noUndefined: false, unicode: true, verb: true }, // // CSS styles that have Top/Right/Bottom/Left versions // styleParts: { 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: { 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: [.7,1.44] } }); var ALLOW = CONFIG.allow; if (ALLOW.fontsize !== "all") {CONFIG.safeStyles.fontSize = false} var SAFE = MathJax.Extension.Safe = { version: VERSION, config: CONFIG, div1: document.createElement("div"), // for CSS processing div2: document.createElement("div"), // // Methods called for MathML attribute processing // filter: { href: "filterURL", src: "filterURL", altimg: "filterURL", "class": "filterClass", style: "filterStyles", id: "filterID", fontsize: "filterFontSize", mathsize: "filterFontSize", scriptminsize: "filterFontSize", scriptsizemultiplier: "filterSizeMultiplier", scriptlevel: "filterScriptLevel" }, // // Filter HREF URL's // filterURL: function (url) { var protocol = (url.match(/^\s*([a-z]+):/i)||[null,""])[1].toLowerCase(); if (ALLOW.URLs === "none" || (ALLOW.URLs !== "all" && !CONFIG.safeProtocols[protocol])) {url = null} return url; }, // // Filter class names and css ID's // filterClass: function (CLASS) { if (ALLOW.classes === "none" || (ALLOW.classes !== "all" && !CLASS.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {CLASS = null} return CLASS; }, filterID: function (id) { if (ALLOW.cssIDs === "none" || (ALLOW.cssIDs !== "all" && !id.match(/^MJX-[-a-zA-Z0-9_.]+$/))) {id = null} return id; }, // // Filter style strings // filterStyles: function (styles) { if (ALLOW.styles === "all") {return styles} if (ALLOW.styles === "none") {return null} try { // // Set the div1 styles to the given styles, and clear div2 // var STYLE1 = this.div1.style, STYLE2 = this.div2.style, value; STYLE1.cssText = styles; STYLE2.cssText = ""; // // Check each allowed style and transfer OK ones to div2 // If the style has Top/Right/Bottom/Left, look at all four separately // for (var name in CONFIG.safeStyles) {if (CONFIG.safeStyles.hasOwnProperty(name)) { if (CONFIG.styleParts[name]) { for (var i = 0; i < 4; i++) { var NAME = name+["Top","Right","Bottom","Left"][i] value = this.filterStyle(NAME,STYLE1); if (value) {STYLE2[NAME] = value} } } else { value = this.filterStyle(name,STYLE1); if (value) {STYLE2[name] = value} } }} // // Return the div2 style string // styles = STYLE2.cssText; } catch (e) {styles = null} return styles; }, // // Filter an individual name:value style pair // filterStyle: function (name,styles) { var value = styles[name]; if (typeof value !== "string" || value === "") {return null} if (value.match(/^\s*expression/)) {return null} if (value.match(/javascript:/)) {return null} var NAME = name.replace(/Top|Right|Left|Bottom/,""); if (!CONFIG.safeStyles[name] && !CONFIG.safeStyles[NAME]) {return null} if (!CONFIG.styleLengths[name]) {return value} return (this.filterStyleLength(name,value,styles) ? value : null); }, filterStyleLength: function (name,value,styles) { if (typeof CONFIG.styleLengths[name] === "string") value = styles[CONFIG.styleLengths[name]]; value = this.length2em(value); if (value == null) return false; var mM = [-CONFIG.lengthMax,CONFIG.lengthMax]; if (MathJax.Object.isArray(CONFIG.styleLengths[name])) mM = CONFIG.styleLengths[name]; return (value >= mM[0] && value <= mM[1]); }, // // Conversion of units to em's // unit2em: { em: 1, ex: .5, // assume 1ex = .5em ch: .5, // assume 1ch = .5em rem: 1, // assume 1rem = 1em px: 1/16, // assume 1em = 16px mm: 96/25.4/16, // 25.4mm = 96px cm: 96/2.54/16, // 2.54cm = 96px 'in': 96/16, // 1in = 96px pt: 96/72/16, // 72pt = 1in pc: 96/6/16 // 1pc = 12pt }, length2em: function (value) { var match = value.match(/(.+)(em|ex|ch|rem|px|mm|cm|in|pt|pc)/); if (!match) return null; return parseFloat(match[1])*this.unit2em[match[2]]; }, // // Filter TeX font size values (in em's) // filterSize: function (size) { if (ALLOW.fontsize === "none") {return null} if (ALLOW.fontsize !== "all") {size = Math.min(Math.max(size,CONFIG.sizeMin),CONFIG.sizeMax)} return size; }, filterFontSize: function (size) { return (ALLOW.fontsize === "all" ? size: null); }, // // Filter scriptsizemultiplier // filterSizeMultiplier: function (size) { if (ALLOW.fontsize === "none") {size = null} else if (ALLOW.fontsize !== "all") {size = Math.min(1,Math.max(.6,size)).toString()} return size; }, // // Filter scriptLevel // filterScriptLevel: function (level) { if (ALLOW.fontsize === "none") {level = null} else if (ALLOW.fontsize !== "all") {level = Math.max(0,level).toString()} return level; }, // // Filter TeX extension names // filterRequire: function (name) { if (ALLOW.require === "none" || (ALLOW.require !== "all" && !CONFIG.safeRequire[name.toLowerCase()])) {name = null} return name; } }; HUB.Register.StartupHook("TeX HTML Ready",function () { var TEX = MathJax.InputJax.TeX; TEX.Parse.Augment({ // // Implements \href{url}{math} with URL filter // HREF_attribute: function (name) { var url = SAFE.filterURL(this.GetArgument(name)), arg = this.GetArgumentMML(name); if (url) {arg.With({href:url})} this.Push(arg); }, // // Implements \class{name}{math} with class-name filter // CLASS_attribute: function (name) { var CLASS = SAFE.filterClass(this.GetArgument(name)), arg = this.GetArgumentMML(name); if (CLASS) { if (arg["class"] != null) {CLASS = arg["class"] + " " + CLASS} arg.With({"class":CLASS}); } this.Push(arg); }, // // Implements \style{style-string}{math} with style filter // STYLE_attribute: function (name) { var style = SAFE.filterStyles(this.GetArgument(name)), arg = this.GetArgumentMML(name); if (style) { if (arg.style != null) { if (style.charAt(style.length-1) !== ";") {style += ";"} style = arg.style + " " + style; } arg.With({style: style}); } this.Push(arg); }, // // Implements \cssId{id}{math} with ID filter // ID_attribute: function (name) { var ID = SAFE.filterID(this.GetArgument(name)), arg = this.GetArgumentMML(name); if (ID) {arg.With({id:ID})} this.Push(arg); } }); }); HUB.Register.StartupHook("TeX Jax Ready",function () { var TEX = MathJax.InputJax.TeX, PARSE = TEX.Parse, METHOD = SAFE.filter; PARSE.Augment({ // // Implements \require{name} with filtering // Require: function (name) { var file = this.GetArgument(name).replace(/.*\//,"").replace(/[^a-z0-9_.-]/ig,""); file = SAFE.filterRequire(file); if (file) {this.Extension(null,file)} }, // // Controls \mmlToken attributes // MmlFilterAttribute: function (name,value) { if (METHOD[name]) {value = SAFE[METHOD[name]](value)} return value; }, // // Handles font size macros with filtering // SetSize: function (name,size) { size = SAFE.filterSize(size); if (size) { this.stack.env.size = size; this.Push(TEX.Stack.Item.style().With({styles: {mathsize: size+"em"}})); } } }); }); HUB.Register.StartupHook("TeX bbox Ready",function () { var TEX = MathJax.InputJax.TeX; // // Filter the styles for \bbox // TEX.Parse.Augment({ BBoxStyle: function (styles) {return SAFE.filterStyles(styles)}, BBoxPadding: function (pad) { var styles = SAFE.filterStyles("padding: "+pad); return (styles ? pad : 0); } }); }); HUB.Register.StartupHook("MathML Jax Ready",function () { var PARSE = MathJax.InputJax.MathML.Parse, METHOD = SAFE.filter; // // Filter MathML attributes // PARSE.Augment({ filterAttribute: function (name,value) { if (METHOD[name]) {value = SAFE[METHOD[name]](value)} return value; } }); }); // MathML input (href, style, fontsize, class, id) HUB.Startup.signal.Post("Safe Extension Ready"); AJAX.loadComplete("[MathJax]/extensions/Safe.js"); })(MathJax.Hub,MathJax.Ajax);