www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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);