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