Firefox Lockdown

Discussion in 'privacy technology' started by guest, Sep 8, 2014.

  1. Slink489

    Slink489 Registered Member

    Joined:
    Mar 28, 2015
    Posts:
    24
    Isn't that a bit harsh inka? You are basically telling a newbie to quit before he even tries to be something more. There is nothing wrong with using pre-canned lists of options that ~ Snipped as per TOS ~ up the end user experience. This is likely to drive said newbie to figure out why things broke, right? Or not. For the not's, nothing can help them. Even now, I was totally unaware of troubleshooting information. The profile directory it points to is exactly where you'd push user.js. You can further confirm this by looking at a setting in the alien user.js, starting FF, and then using about:config in the address bar. Type the name or phrase from the alien config file and see if it is highlighted in bold and states "user" somewhere. If it is, all things are good! Unless a plugin alters things.

    Nonetheless, inka has a point there at the end. Pre-canned lists of settings can only get you started. If you really don't want to work at getting to an acceptable result, then maybe you should "fuggedaboutit". The nots faction accepts members by the second.
     
    Last edited by a moderator: Aug 11, 2015
  2. inka

    inka Registered Member

    Joined:
    Oct 21, 2009
    Posts:
    426
    Yes, intentionally harsh ~~ ala "tough love".
    While drafting that I did self-censor (changed "lazy / quickfix approach" to just "quickfix") but I'll stand by what I posted.
    To someone incapable finding (self-discovery) where prefs.js file resides, replying "fuggedaboutit" seems preferable to silently watching this devolve into a "MOAR please" thread.
     
  3. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    Below is new prototype tool called Pasted UserPrefs Exporter. Input: user_pref() lines from prefs.js, user.js, or other examples. Output: A JSON string/file describing the preferences that would be set by those lines, in a format that can be imported by my Pref Diff Tool. Purpose: to make it easier to compare user_pref() sets to other sets, particularly when they are in different order and/or commented differently and/or have syntax errors. Example usage:

    Copy and save as PastedUserPrefsExporter.html or whatever. Copy example code from this earlier post, paste into Input area, hit Load Input. An error message shows that one line was mangled during posting: 8) converted to :cool:. Fix that, Load Input again, no errors, click Save Output to "download" to a file named PastedUserPrefs.json. Then copy/paste code from this other page. Load Input, a warning message shows that a pref is set twice. Fix it or leave it, then Save Output. Then, load both .json files into Pref Diff Tool, click Generate Results, and you will see the differences between those two examples. With an easy way to change how you view the information. BTW, those examples I linked to were chosen simply because they reveal tool behavior.

    The Javascript that is pasted into this tool as input is inserted into a dynamically created script element, and executed, when you click the Load Input button. There are two options meant to identify problems with the input code and reduce the possibility of unpleasant consequences. However, it is strongly advised that you look over what you paste and assure that it is safe to load/execute. Especially if the code was posted by others. Some errors will be reported in the Messages textarea, others will be reported in the browser/web/error console. Checkout the source before use, please ignore the lack of proper structure. Feedback on the tool itself is welcome (PM might keep the tangential discussion down in the thread, your choice). If a bug is discovered, I'll update this post.

    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Pasted UserPrefs Exporter</title>
    <style type="text/css">
    table {width:99%; table-layout:fixed;}
    table, td {border-collapse:collapse; border:none;}
    td {text-align:left; vertical-align:top; padding:2px; word-wrap:break-word;}
    td#controls {width:75%;}
    td#verInfoStr {text-align:right;}
    div#noscript {font-weight:bold;}
    div.topgap {margin-top:6px;}
    </style>
    <script type="text/javascript">
    const verInfoStr = "Pasted UserPrefs Exporter 0.2";
    var prefNames = [];
    var prefsSet = {};
    var inputScriptElement = null;
    var badInputLines = 0;
    var inputLineCnt = 0;
    var userPrefCalls = 0;
    var goodUserPrefCalls = 0;
    var duplicateUserPrefCalls = 0;
    var badUserPrefCalls = 0;
    var allInputLinesProcessed = false;
    var logUserPrefCalls = false;
    
    function init()
    {
      MsgBox.init(false);
      document.getElementById("inputBox").value = "";
      document.getElementById("outputBox").value = "";
      document.getElementById("saveButton").disabled = true;
      document.getElementById("checkInputBox").checked = true;
      document.getElementById("forceQuotesOnStringValuesBox").checked = true;
      document.title = verInfoStr;
      document.getElementById("verInfoStr").innerHTML = verInfoStr;
    }
    
    function prefDescToStr(pd)
    {
      var sep = ";";
      return(pd.name + sep + pd.status + sep + pd.type + sep + pd.value.toString());
    }
    
    function user_pref()
    {
      userPrefCalls++;
      var callNum = userPrefCalls + 1;
      if(arguments.length != 2)
      {
        MsgBox.append("ERROR: user_pref() called with " + arguments.length + " arguments");
        badUserPrefCalls++;
        return;
      }
      var name = arguments[0];
      var value = arguments[1];
      switch(typeof(name))
      {
        case "string":
          break;
        default:
          MsgBox.append("ERROR: user_pref() name argument is not a string type");
          badUserPrefCalls++;
          return;
          break;
      }
      var prefDesc =
      {
        name:     name,
        status:   null,
        type:     null,
        value:    value,
      }
      prefDesc.status = "user set";
      switch(typeof(value))
      {
        case "string":
          prefDesc.type = "string";
          goodUserPrefCalls++;
          break;
        case "boolean":
          prefDesc.type = "boolean";
          goodUserPrefCalls++;
          break;
        case "number":
          if(value % 1 === 0)
            prefDesc.type = "integer";
          else prefDesc.type = "string";
          goodUserPrefCalls++;
          break;
        default:
          MsgBox.append("ERROR: Unknown value type for pref " + name);
          badUserPrefCalls++;
          prefDesc.type = "UNKNOWN";
          break;
      }
      if(prefsSet.hasOwnProperty(name))
      {
        MsgBox.append("WARNING: Call " + userPrefCalls + " is overwriting existing pref with: " + prefDescToStr(prefDesc));
        duplicateUserPrefCalls++;
      }
      else prefNames.push(name);
      prefsSet[name] = prefDesc;
      if(logUserPrefCalls)
        MsgBox.append("Call " + userPrefCalls + ": " + prefDescToStr(prefDesc));
    }
    
    function appendedFuncCall()
    {
      allInputLinesProcessed = true;
    }
    
    function getJSON()
    {
      var gcsdif = {};
      gcsdif.dif = "GCSDIF";
      gcsdif.version = "0.1";
      prefNames.sort(function(a, b) {
                      if(a < b)
                        return -1;
                      if(a > b)
                        return 1;
                      return 0;
                      });
      var prefsSetSorted = [];
      for (index in prefNames)
        prefsSetSorted.push(prefsSet[prefNames[index]]);
      gcsdif.prefList = prefsSetSorted;
      return(JSON.stringify(gcsdif));
    }
    
    function checkInput(input)
    {
      // ToDo: Go back over this
      var inputLines = input.split("\n");
      var inputLinesAdjusted = [];
      var inCommentBlk = false;
      inputLineCnt = inputLines.length;
      for(var i=0; i < inputLineCnt; i++)
      {
        var line = inputLines[i];
        var lineNum = i + 1;
        var badLine = false;
       
        // Remove single line /*..*/ comment blocks
        line = line.replace(/\/\*.*?\*\//g, '');
        if((line.search(/^\s*$/) == -1) && (line.search(/^\s*\/\//) == -1))
        {
          // Not a blank line or single line comment.  Later checks can only deal
          // with // comments, so let's try to remove the multiline comment blocks too
          if(inCommentBlk)
          {
            // Continue commenting out the comment block lines until done
            if(line.search(/\*\/\s*$/) != -1)
              inCommentBlk = false;
            line = '//' + line;
          }
          else
          {
            if(line.search(/^\s*\/\*/) != -1)
            {
              // Begin commenting out the comment block lines
              inCommentBlk = true;
              line = '//' + line;
            }
            else
            {
              // A normal line that should contain user_pref(name,value);
              var matches = line.match(/^(\s*user_pref\s*\(\s*(['"][\w.-]+?['"])\s*\,\s*)(.+?)(\s*\)\;(.*))$/);
              if((matches === null) || (matches.length != 6))
                badLine = true;
              else
              {
                var semicolonTrailing = matches[5];
                if(semicolonTrailing.search(/^\s*(?:\/\/.*)?$/) == -1)
                  badLine = true
                if(document.getElementById("forceQuotesOnStringValuesBox").checked == true)
                {
                  var beforeArg2 = matches[1];
                  var arg1 = matches[2];
                  var arg2 = matches[3];
                  var afterArg2 = matches[4];
                  if((arg2 != "true") && (arg2 != "false") && (arg2.search(/^\d+$/) == -1) &&
                     (arg2.search(/^\d+\.\d+$/) == -1))
                  {
                    // arg2 looks like a string, let's make sure it is in quotes
                    if(arg2.search(/['"].*['"]/) == -1)
                      line = beforeArg2 + '"' + arg2 + '"' + afterArg2;
                  }
                }
              }
            }
          }
        }
        if(badLine)
        {
          MsgBox.append("CHECK INPUT ERROR: Line " + lineNum + " = " + line);
          badInputLines++;
        }
        inputLinesAdjusted.push(line);
      }
      if(badInputLines > 0)
        return(null);
      return(inputLinesAdjusted.join("\n"));
    }
    
    function loadInput()
    {
      MsgBox.clear();
      document.getElementById("outputBox").value = "";
      document.getElementById("saveButton").disabled = true;
      prefNames = [];
      prefsSet = {};
      badInputLines = 0;
      inputLines = 0;
      userPrefCalls = 0;
      goodUserPrefCalls = 0;
      duplicateUserPrefCalls = 0;
      badUserPrefCalls = 0;
      allInputLinesProcessed = false;
      if(inputScriptElement !== null)
      {
        inputScriptElement.remove();
        inputScriptElement = null;
      }
    
      var input = document.getElementById("inputBox").value;
      if(input.length == 0)
      {
        MsgBox.append("No input to process");
        return;
      }
      // Replace a leading # with //# so it becomes a normal comment marker
      input = input.replace(/^(\s*)(\#.*)$/mg, '$1//$2');
    
      var checkInputChecked = document.getElementById("checkInputBox").checked;
      if(checkInputChecked)
      {
        MsgBox.append("Checking input");
        input = checkInput(input);
        if(input === null)
        {
          MsgBox.append("Aborted processing");
          MsgBox.append("InputLineCnt: " + inputLineCnt);
          MsgBox.append("Bad input lines: " + badInputLines);
          return;
        }
      }
      else MsgBox.append("Skipping input check");
    
      input += "\nappendedFuncCall();\n";
      MsgBox.append("Loading input as script");
      inputScriptElement = document.createElement('script');
      inputScriptElement.type = "text/javascript";
      inputScriptElement.onerror = function() {MsgBox.append("Error creating script element");};
      inputScriptElement.text = input;
      document.head.appendChild(inputScriptElement);
      MsgBox.append("Input loaded");
      if(allInputLinesProcessed)
      {
        var output = getJSON();
        document.getElementById("outputBox").value = output;
        MsgBox.append("Output generated");
        document.getElementById("saveButton").disabled = false;
      }
      MsgBox.append("");
      if(checkInputChecked)
        MsgBox.append("InputLineCnt: " + inputLineCnt);
      MsgBox.append("UserPrefCalls: " + userPrefCalls);
      MsgBox.append("GoodUserPrefCalls: " + goodUserPrefCalls);
      MsgBox.append("DuplicateUserPrefCalls: " + duplicateUserPrefCalls);
      MsgBox.append("BadUserPrefCalls: " + badUserPrefCalls);
      MsgBox.append("AllInputLinesProcessed: " + allInputLinesProcessed);
      if(allInputLinesProcessed)
        MsgBox.append("Success!");
      else MsgBox.append("ERRORS!");
    }
    
    function saveOutput()
    {
      var filename = "PastedUserPrefs.json";
      var output = document.getElementById("outputBox").value;
      var element = document.createElement('a');
      element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(output));
      element.setAttribute('download', filename);
      element.style.display = 'none';
      document.body.appendChild(element);
      element.click();
      document.body.removeChild(element);
    }
    
    function checkInputBoxChanged(cb)
    {
      document.getElementById("forceQuotesOnStringValuesBox").disabled = !cb.checked;
    }
    
    var Utils = {
      getTimestamp: function() {
         var d = new Date();
         return(d.toISOString());
      },
    }
    
    var MsgBox = {
      addTimestamp: false,
      msgbox: null,
    
      init: function(addTimestamp) {
        if(document.getElementById('msgbox') === null)
        {
          alert("Can't find msgbox textarea, some output may be lost");
          return;
        }
        this.msgbox = document.getElementById("msgbox");
        this.addTimestamp = addTimestamp;
        this.clear();
        this.initialized = true;
      },
    
      append: function(msg) {
        if(this.msgbox !== null)
        {
          var output;
          if(this.addTimestamp)
            output = "[" + Utils.getTimestamp() + "] " + msg + "\n";
          else output = msg + "\n"; 
          this.msgbox.value += output;
          this.msgbox.scrollTop = this.msgbox.scrollHeight;
        }
      },
    
      clear: function() {
        if(this.msgbox !== null)
          this.msgbox.value = "";
      }
    }
    
    </script>
    </head>
    <body>
    <noscript><div id="noscript">Javascript must be enabled for this to work</div></noscript>
    <div>Input:</div>
    <div><textarea id="inputBox" rows="10" style="width:99%;"></textarea></div>
    <div>Output:</div>
    <div><textarea id="outputBox" readonly rows="6" style="width:99%;"></textarea></div>
    <div>Messages:</div>
    <div><textarea id="msgbox" readonly rows="12" style="width:99%;"></textarea></div>
    <div class="topgap">
    <table id="footerTbl">
      <tr>
       <td id="controls" class="footerTd">
        <button id="loadButton" value="Load" onclick="loadInput();return false;">Load Input</button>
        <button id="saveButton" value="Save" onclick="saveOutput();return false;">Save Output</button>
        Check input lines: <input id="checkInputBox" type="checkbox" onchange="checkInputBoxChanged(this);">
        Force quotes around string values: <input id="forceQuotesOnStringValuesBox" type="checkbox">
       </td>
       <td id="verInfoStr" class="footerTd"></td>
      </tr>
    </table>
    </div>
    <script type="text/javascript">
    init();
    </script>
    </body>
    </html>
    
     
    Last edited: Aug 11, 2015
  4. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    I missed the edit window, so:

    08/16/2015 update: General improvements, added support for other pref calls, changed name, double clicking on a CHECK_ERROR message that includes a line number will now highlight the problematic source line.
    08/17/2015 update: Fixed success/fail message applicable to no input checking
    08/18/2015 update: Added option to disable eval, added option to output results as plaintext, improved test vector support, linting, misc
    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Pref Call Evaluator</title>
    <style type="text/css">
    table {width:99%; table-layout:fixed;}
    table, td {border-collapse:collapse; border:none;}
    td {text-align:left; vertical-align:top; padding:2px; white-space:nowrap;}
    td#verInfoStr {text-align:right;}
    div#controls {width:99%; border:1px solid #555555; padding:3px;}
    div#noscript {font-weight:bold;}
    div.topgap {margin-top:6px;}
    .checkbox {margin-right:10px;}
    span {white-space:nowrap;}
    </style>
    <script type="text/javascript">
    
    const verInfoStr = "Pref Call Evaluator 0.4.0";
    
    var prefCallSrcChecker = {
      funcs:            {},
      msgCallback:      null,
      logCallback:      null,
      inputLines:       null,
      inCommentBlk:     false,
      origLine:         null,
      line:             null,
      lineNum:          0,
      nonCommentLines:  false,
      tvrFinal:         null,
      tvrLine:          null,
      tvrComputed:      null,
      isLineOK:         false,
      stats:            null,
    
      init: function(funcs, msgCallback, logCallback) {
        this.funcs = funcs;
        this.msgCallback = msgCallback;
        this.logCallback = logCallback;
      },
    
      check: function(input) {
        this.inCommentBlk = false;
        this.nonCommentLines = false;
        this.tvrFinal = null;
        this.stats = {count:0, good:0, errors:0, warnings:0};
        var self = this;
    
        function tvrFinalReplacer(match, p1, p2, p3, p4, offset, string) {
          self.msgCallback("Test Vector Results Enabled");
          self.tvrFinal = {
            count:         0,
            good:          0,
            mismatches:    0,
            statsCount:    p1,
            statsGood:     p2,
            statsErrors:   p3,
            statsWarnings: p4,
          };
          return('');
        }
    
        function tvrLineReplacer(match, p1, offset, string) {
          self.tvrLine = p1;
          self.tvrFinal.count++;
          return('');
        }
    
        if(input.length === 0)
          this.inputLines = [];
        else
        {
          this.inputLines = input.split("\n");
          this.stats.count = this.inputLines.length;
        }
        for(var i=0; i < this.inputLines.length; i++)
        {
          this.origLine = this.inputLines[i];
          this.line = this.inputLines[i];
          this.lineNum = i + 1;
          this.isLineOK = false;
          this.tvrComputed = "";
          if(i === 0)
          {
            this.line = this.line.replace(/^\/\/\s\$\$TVRF\:(\d+),(\d+),(\d+),(\d+)\s*/,
                                          tvrFinalReplacer);
          }
          if(this.tvrFinal !== null)
          {
            this.tvrLine = "TVRL_NOT_FOUND";
            this.line = this.line.replace(/\s*\/\/\s\$\$TVRL\:([A-Z\.]*)\s*$/,
                                          tvrLineReplacer);
          }
          if(this.logCallback)
            this.logCallback("Line: " + this.lineNum + ": " + this.origLine);
          this.line = this.processComments(this.line);
          if(this.line.length === 0)
            this.isLineOK = true;
          else
          {
            this.nonCommentLines = true;
            var trailingStr = this.checkFuncCallLine();
            if(trailingStr === null)
            {
              // NOP: This condition can throw off future processing
            }
            else
            {
              if(trailingStr.length > 0)
              {
                trailingStr = this.processComments(trailingStr);
                if(trailingStr.length > 0)
                {
                  this.tvrComputedAdd("TC");
                  // This condition can throw off future processing
                  if(this.isLineOK)
                  {
                    this.reportProblem(true, true, "Unexpected chars after function call");
                    this.isLineOK = false;
                  }
                }
              }
            }
          }
          if(this.isLineOK)
            this.stats.good++;
          if(this.tvrFinal !== null)
          {
            if(this.tvrLine !== this.tvrComputed)
            {
              this.tvrFinal.mismatches++;
              this.msgCallback("TVR Mismatch: line " + this.lineNum + ": Expected: " +
                               this.tvrLine + ", Computed: " + this.tvrComputed);
            }
            else this.tvrFinal.good++;
          }
        }
        if(this.inCommentBlk)
          this.reportProblem(true, false, "In comment block at end of input");
        if(!this.nonCommentLines)
          this.reportProblem(false, false, "Empty source or only comments");
        if(this.tvrFinal !== null)
        {
          this.msgCallback("Test Vector Results");
          this.msgCallback("  TVR count:      " + this.tvrFinal.count);
          this.msgCallback("  TVR good:       " + this.tvrFinal.good);
          this.msgCallback("  TVR mismatches: " + this.tvrFinal.mismatches);
          this.msgCallback("  TVR stats count delta: " +
                           (this.stats.count - this.tvrFinal.statsCount));
          this.msgCallback("  TVR stats good delta: " +
                           (this.stats.good - this.tvrFinal.statsGood));
          this.msgCallback("  TVR stats errors delta: " +
                           (this.stats.errors - this.tvrFinal.statsErrors));
          this.msgCallback("  TVR stats warnings delta: " +
                           (this.stats.warnings - this.tvrFinal.statsWarnings));
        }
        if(this.stats.errors > 0)
          return(-1);
        if(this.stats.warnings > 0)
          return(0);
        return(1);
      },
    
      checkFuncCallLine: function() {
        var matches = this.line.match(/^(.*?)\s*([\w]+?)\s*\(\s*(.*?)\s*\,\s*(.*)\s*\)\s*\;\s*(.*)$/);
        if((matches === null) || (matches.length !== 6))
        {
          this.reportProblem(true, true, "Bad line");
          this.tvrComputedAdd("BFC");
          return(null);
        }
        var leader     = matches[1];
        var func       = matches[2];
        var arg1       = matches[3];
        var arg2       = matches[4];
        var trailer    = matches[5];
    
        // Function name checks
        if(this.funcs.hasOwnProperty(func) || (this.funcs[func] === false))
        {
          if(this.funcs[func] === false)
          {
            this.reportProblem(true, true, "Function call is disabled");
            this.tvrComputedAdd("BFC");
            return(trailer);
          }
        }
        else
        {
          this.reportProblem(true, true, "Invalid function name");
          this.tvrComputedAdd("BFC");
          return(trailer);
        }
    
        // Argument 1 (pref name) checks
        if(arg1.search(/^(['"])[\w.\*\{\}@\s-]+\1$/) == -1)
        {
          this.reportProblem(true, true, "Bad arg1");
          this.tvrComputedAdd("BFC");
          return(trailer);
        }
    
        // Argument 2 (pref value) checks
        arg2 = arg2.trim();
        var fc = arg2.charAt(0);
        var lc = arg2.charAt(arg2.length-1);
        if(((fc == '"') != (lc == '"')) ||
          ((fc == "'") != (lc == "'")))
        {
          this.reportProblem(true, true, "Mismatched quotes arg2");
          this.tvrComputedAdd("BFC");
          return(trailer);
        }
        if((fc == '"') || (fc == "'"))
        {
          // ToDo: Needs improvement
          if(arg2.search(/^(["'])(?:(?!\1)[^\\]|\\.)*\1$/) == -1)
          {
            this.reportProblem(true, true, "Bad arg2");
            this.tvrComputedAdd("BFC");
            return(trailer);
          }
        }
        else
        {
          if((arg2 != "true") && (arg2 != "false") && (arg2.search(/^[-+]?\d+$/) == -1))
          {
            this.reportProblem(true, true, "arg2 type needs quotes");
            this.tvrComputedAdd("BFC");
            return(trailer);
          }
        }
        if(leader.length > 0)
        {
          this.reportProblem(true, true, "Bad chars before func call");
          this.tvrComputedAdd("BFC");
          return(trailer);
        }
        this.isLineOK = true;
        this.tvrComputedAdd("GFC");
        return(trailer);
      },
    
      processComments: function(line) {
        var self = this;
        var loops = 1;
        var delayBlkOpenCheck;
    
        function leadingBlkClose(match) {
          self.inCommentBlk = false;
          loops++;
          self.tvrComputedAdd("BCC");
          return('');
        }
        function leadingOneLine(match) {
          self.tvrComputedAdd("LC");
          return('');
        }
        function leadingBlkOpenClose(match) {
          loops++;
          delayBlkOpenCheck = true;
          self.tvrComputedAdd("BCOC");
          return('');
        }
        function leadingBlkOpen(match) {
          self.inCommentBlk = true;
          loops++;
          self.tvrComputedAdd("BCO");
          return('');
        }
    
        // Processes leading comments, returns remainder or empty str
        while(loops > 0)
        {
          delayBlkOpenCheck = false;
          if(this.inCommentBlk) {
            // Leading */
            line = line.replace(/^.*\*\/\s*/, leadingBlkClose);
            // Rest of line in block comment
            if(this.inCommentBlk) {
              line = '';
              this.tvrComputedAdd("BCM");
            }
          }
          if(!this.inCommentBlk) {
            // Leading //
            line = line.replace(/^\s*\/\/.*$/, leadingOneLine);
            // Leading /*..*/
            line = line.replace(/^\s*\/\*.*?\*\/\s*/, leadingBlkOpenClose);
            if(!delayBlkOpenCheck)
            {
              // Leading /*
              line = line.replace(/^\s*\/\*.*$/, leadingBlkOpen);
            }
          }
          if(line.length === 0)
            loops = 0;
          else loops--;
        }
        return(line);
      },
    
      tvrComputedAdd: function(s) {
        if(this.tvrComputed.length > 0)
          this.tvrComputed += '.';
        this.tvrComputed += s;
      },
    
      reportProblem: function(error, lineSpecific, desc) {
        var typeStr = error ? "CHECK_ERROR" : "CHECK_WARNING";
        var msg = typeStr + ": ";
        if(lineSpecific)
          msg += "Line " + this.lineNum + ": " + desc + ': ' + this.origLine;
        else msg += desc;
        this.msgCallback(msg);
        if(error)
          this.stats.errors++;
        else this.stats.warnings++;
      },
    
      outputStats: function() {
        this.msgCallback("Input Line Stats");
        this.msgCallback("  Count:    " + this.stats.count);
        this.msgCallback("  Good:     " + this.stats.good);
        this.msgCallback("  Errors:   " + this.stats.errors);
        this.msgCallback("  Warnings: " + this.stats.warnings);
      },
    };
    
    var prefCallEval = {
      funcs:               null,
      msgCallback:         null,
      logCallback:         null,
      prefNames:           [],
      prefs:               {},
      endOfScriptReached:  false,
      inputScriptElement:  null,
      stats:               null,
    
      init: function(funcs, msgCallback, logCallback) {
        this.funcs = funcs;
        this.logCallback = logCallback;
        this.msgCallback = msgCallback;
      },
    
      eval: function(src) {
        // WARNING: Not sufficient to protect against malicious code
        this.prefNames = [];
        this.prefs = {};
        this.endOfScriptReached = false;
        this.stats = {count:0, good:0, errors:0, warnings:0};
        if(src.length === 0)
          this.reportProblem(false, null, null, "Script is zero length");
        else
        {
          var evalThis = {};
          var globalObj = window;
          for(var p in globalObj)
            evalThis[p] = undefined;
          src = "\"use strict\";\n" + src;
          src += "\nendOfScriptCall();\n";
          var srcLinesAdded = 2;
          evalThis.user_pref = this.user_pref;
          evalThis.pref = this.pref;
          evalThis.lockPref = this.lockPref;
          evalThis.sticky_pref = this.sticky_pref;
          evalThis.endOfScriptCall = function() {
            this.endOfScriptReached = true;
          };
          try
          {
            (new Function("with(this) {\n" + src + "\n}")).call(evalThis);
            if(evalThis.endOfScriptReached)
              this.msgCallback("Eval successful");
            else this.reportProblem(true, null, null, "Script did not run to completion");
          }
          catch(e)
          {
            var lineNum = e.lineNumber - srcLinesAdded;
            var msg = "Line " + lineNum + ", Col " + e.columnNumber + ': ' + e.toString();
            this.reportProblem(true, null, null, msg);
          }
        }
        if(this.stats.errors > 0)
          return(-1);
        if(this.stats.warnings > 0)
          return(0);
        return(1);
      },
    
      user_pref: function() {
        prefCallEval.addPref("user_pref", arguments);
      },
    
      pref: function() {
        prefCallEval.addPref("pref", arguments);
      },
    
      lockPref: function() {
        prefCallEval.addPref("lockPref", arguments);
      },
    
      sticky_pref: function() {
        prefCallEval.addPref("sticky_pref", arguments);
      },
    
      addPref: function(origFunc, origArgs) {
        this.stats.count++;
        if(this.logCallback)
          this.logCallback("Eval: " + this.callToStr(origFunc, origArgs));
        if(origArgs.length != 2) {
          this.reportProblem(true, origFunc, origArgs, "Incorrect argument count");
          return;
        }
        var func = origFunc;
        var prefName = origArgs[0];
        var prefValue = origArgs[1];
        if(!this.funcs[func])
        {
          this.reportProblem(true, origFunc, origArgs, "Function call is disabled");
          return;
        }
        switch(typeof(prefName)) {
          case "string":
            break;
          default:
            this.reportProblem(true, origFunc, origArgs, "Pref name is not a string type");
            return;
            break;
        }
        var prefDesc = {
          name:     prefName,
          status:   null,
          type:     null,
          value:    prefValue,
        };
        switch(func) {
          case "user_pref":
            prefDesc.status = "user set";
            break;
          case "pref":
            prefDesc.status = "user set";
            break;
          case "lockPref":
            prefDesc.status = "locked";
            break;
          case "sticky_pref":
            // ToDo: Temporary, needs review
            prefDesc.status = "user set";
            break;
          default:
            this.reportProblem(true, origFunc, origArgs, "Unknown function name");
            return;
            break;
        }
        switch(typeof(prefValue)) {
          case "string":
            prefDesc.type = "string";
            break;
          case "boolean":
            prefDesc.type = "boolean";
            break;
          case "number":
            if(prefValue % 1 === 0)
              prefDesc.type = "integer";
            else prefDesc.type = "string";
            break;
          default:
            this.reportProblem(true, origFunc, origArgs, "Unknown pref value type");
            return;
            break;
        }
        if(this.prefs.hasOwnProperty(prefName))
        {
          this.reportProblem(false, origFunc, origArgs, "Overwriting existing pref with");
        }
        else
        {
          this.stats.good++;
          this.prefNames.push(prefName);
        }
        this.prefs[prefName] = prefDesc;
      },
    
      callToStr: function(origFunc, origArgs) {
        var str = origFunc + '(';
        for(var i=0; i<origArgs.length; i++)
        {
          if(i > 0)
            str += ',';
          str += origArgs[i];
        }
        str += ');';
        return(str);
      },
    
      getOutput: function(outputJSON) {
        this.prefNames.sort(function(a, b) {
                              if(a < b)
                                return -1;
                              if(a > b)
                                return 1;
                              return 0;
                              });
        var prefsSorted = [];
        for (var index in this.prefNames)
          prefsSorted.push(this.prefs[this.prefNames[index]]);
        if(outputJSON)
        {
          var gcsdif = {};
          gcsdif.dif = "GCSDIF";
          gcsdif.version = "0.1";
          gcsdif.prefList = prefsSorted;
          return(JSON.stringify(gcsdif));
        }
        else
        {
          // Begin text output options
          const exportStatusField = false;
          const exportTypeField = false;
          const sep = ";";
          const eol  = "\r\n";
          // End text output options
          var output = "";
          for(var i=0; i<prefsSorted.length; i++)
          {
            output += prefsSorted[i].name;
            if(exportStatusField)
              output += sep + prefsSorted[i].status;
            if(exportTypeField)
              output += sep + prefsSorted[i].type;
            output += sep + prefsSorted[i].value.toString() + eol;
          }
          return(output);
        }
      },
    
      reportProblem: function(error, origFunc, origArgs, desc) {
        var typeStr = error ? "EVAL_ERROR" : "EVAL_WARNING";
        var msg = typeStr + ": ";
        if((origFunc !== null) && (origArgs !== null))
        {
          msg += "Call " + this.stats.count + ": " + desc + ": " +
                 this.callToStr(origFunc, origArgs);
        }
        else msg += desc;
        this.msgCallback(msg);
        if(error)
          this.stats.errors++;
        else this.stats.warnings++;
      },
    
      outputStats: function() {
        this.msgCallback("Pref Call Stats");
        this.msgCallback("  Count:    " + this.stats.count);
        this.msgCallback("  Good:     " + this.stats.good);
        this.msgCallback("  Errors:   " + this.stats.errors);
        this.msgCallback("  Warnings: " + this.stats.warnings);
      },
    };
    
    function init()
    {
      document.title = verInfoStr;
      document.getElementById("verInfoStr").innerHTML = verInfoStr;
      MsgBox.init(false);
      var warning =
        "// This tool acts as a host for Mozilla preference setting scripts.  It\n" +
        "// performs primitive checks on the input (handles most comments, each\n" +
        "// function call must reside within one line, recognizes only the pref\n" +
        "// setting functions). If no problems are detected, it will then EXECUTE\n" +
        "// the script (with rudimentary protections).  It captures a list of the\n" +
        "// preferences that were set (no changes are made to your browser), and\n" +
        "// outputs that information in JSON or text format.\n" +
        "//\n" +
        "// The input checks and execution restrictions are NOT sufficient to\n" +
        "// protect against malicious code. YOU must assure that the scripts\n" +
        "// you input are safe.";
      document.getElementById("inputBox").value = warning;
      document.getElementById("outputBox").value = "";
      document.getElementById("loadButton").disabled = false;
      document.getElementById("saveButton").disabled = true;
      document.getElementById("allowUserPrefBox").checked = true;
      document.getElementById("allowPrefBox").checked = true;
      document.getElementById("allowLockPrefBox").checked = true;
      document.getElementById("allowStickyPrefBox").checked = true;
      document.getElementById("checkInputBox").checked = true;
      document.getElementById("performEvalBox").checked = true;
      document.getElementById("outputJSONBox").checked = true;
      document.getElementById("logMessagesBox").checked = false;
    }
    
    function loadInput()
    {
      MsgBox.clear();
      outputBox.value = "";
      document.getElementById("saveButton").disabled = true;
      var input = document.getElementById("inputBox").value;
    
      // Replace leading # with //# so that the lines will be comments
      input = input.replace(/^(\s*)\#/mg, '$1\/\/\#');
    
      var funcs = {};
      funcs.user_pref   = document.getElementById("allowUserPrefBox").checked;
      funcs.pref        = document.getElementById("allowPrefBox").checked;
      funcs.lockPref    = document.getElementById("allowLockPrefBox").checked;
      funcs.sticky_pref = document.getElementById("allowStickyPrefBox").checked;
      var checkInput    = document.getElementById("checkInputBox").checked;
      var performEval   = document.getElementById("performEvalBox").checked;
      var logMessages   = document.getElementById("logMessagesBox").checked;
    
      var checkInputResult = 1;
      if(checkInput)
      {
        MsgBox.append("Checking input");
        prefCallSrcChecker.init(funcs,
                                MsgBox.append.bind(MsgBox),
                                logMessages ? MsgBox.append.bind(MsgBox) : null);
        checkInputResult = prefCallSrcChecker.check(input);
        if(checkInputResult < 0)
        {
          prefCallSrcChecker.outputStats();
          MsgBox.append("FAILED during input check");
          return;
        }
      }
      else MsgBox.append("Skipping input check");
      var evalResult = 1;
      if(performEval)
      {
        prefCallEval.init(funcs,
                          MsgBox.append.bind(MsgBox),
                          logMessages ? MsgBox.append.bind(MsgBox) : null);
        MsgBox.append("Executing input script");
        evalResult = prefCallEval.eval(input);
        if(evalResult >= 0)
        {
          var outputJSON = document.getElementById("outputJSONBox").checked;
          var output = prefCallEval.getOutput(outputJSON);
          outputBox.value = output;
          MsgBox.append("Output generated");
          document.getElementById("saveButton").disabled = false;
        }
      }
      else MsgBox.append("Skipping eval");
      if(checkInput)
        prefCallSrcChecker.outputStats();
      if(performEval)
        prefCallEval.outputStats();
      if((checkInputResult === 1) && (evalResult === 1))
      {
        if((checkInput === false) && (performEval === false))
          MsgBox.append("SUCCESS (but we did nothing)");
        else MsgBox.append ("SUCCESS");
      }
      else if((checkInputResult === 0) || (evalResult === 0))
        MsgBox.append ("SUCCESS (with warnings)");
      else MsgBox.append("FAILED during eval");
    }
    
    function saveOutput()
    {
      var filename = "PrefCallEvaluatorOutput.";
      if(document.getElementById("outputJSONBox").checked)
        filename += ".json";
      else filename += ".txt";
      var output = outputBox.value;
      var element = document.createElement('a');
      element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(output));
      element.setAttribute('download', filename);
      element.style.display = 'none';
      document.body.appendChild(element);
      element.click();
      document.body.removeChild(element);
    }
    
    function onInputInput()
    {
      document.getElementById("outputBox").value = "";
      document.getElementById("saveButton").disabled = true;
    }
    
    function onOutputJSONChanged()
    {
      document.getElementById("outputBox").value = "";
      document.getElementById("saveButton").disabled = true;
    }
    
    function onMsgBoxDblClick()
    {
      var msgBoxLines = msgBox.value.split("\n");
      var msgBoxLineIndex = msgBox.value.substr(0, msgBox.selectionStart).split("\n").length-1;
      var msgBoxLine = msgBoxLines[msgBoxLineIndex];
      var matches = msgBoxLine.match(/^(?:CHECK_ERROR|CHECK_WARNING):\sLine\s(\d+)/);
      var inputBox = document.getElementById("inputBox");
      if(matches !== null)
        selectTextAreaLine(inputBox, matches[1]);
      else
      {
        matches = msgBoxLine.match(/^TVR\sMismatch:\sline\s(\d+)/);
        if(matches !== null)
          selectTextAreaLine(inputBox, matches[1]);
      }
    }
    
    function selectTextAreaLine(ta, lineNum) {
      lineNum--; // array starts at 0
      var lines = ta.value.split("\n");
      var startPos = 0;
      for(var x = 0; x < lines.length; x++) {
        if(x == lineNum) {
          break;
        }
        startPos += (lines[x].length+1);
      }
      var endPos = lines[lineNum].length+startPos;
      ta.focus();
      ta.scrollTop = 0;  // Try to keep top line visible
      ta.selectionStart = startPos;
      ta.selectionEnd = endPos;
    }
    
    var MsgBox = {
      addTimestamp: false,
      msgBox: null,
    
      init: function(addTimestamp) {
        if(document.getElementById('msgBox') === null) {
          alert("Can't find msgBox textarea, some output may be lost");
          return;
        }
        this.msgBox = document.getElementById("msgBox");
        this.addTimestamp = addTimestamp;
        this.clear();
        this.initialized = true;
      },
    
      append: function(msg) {
        if(this.msgBox !== null) {
          var output;
          if(this.addTimestamp)
            output = "[" + this.getTimestamp() + "] " + msg + "\n";
          else output = msg + "\n";
          this.msgBox.value += output;
          this.msgBox.scrollTop = this.msgBox.scrollHeight;
        }
      },
    
      clear: function() {
        if(this.msgBox !== null)
          this.msgBox.value = "";
      },
    
      getTimestamp: function() {
         var d = new Date();
         return(d.toISOString());
      },
    };
    
    </script>
    </head>
    <body>
    <noscript><div id="noscript">Javascript must be enabled for this to work</div></noscript>
    <div>Input:</div>
    <div><textarea id="inputBox" rows="10" style="width:99%;" oninput="onInputInput();"></textarea></div>
    <div>Output:</div>
    <div><textarea id="outputBox" readonly rows="3" style="width:99%;"></textarea></div>
    <div>Messages:</div>
    <div><textarea id="msgBox" readonly rows="15" style="width:99%;" ondblclick="onMsgBoxDblClick(); return(false);"></textarea></div>
    <div>Controls:</div>
    <div id="controls">
    <span><label>Allow user_pref: <input id="allowUserPrefBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Allow pref: <input id="allowPrefBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Allow lockPref: <input id="allowLockPrefBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Allow sticky_pref: <input id="allowStickyPrefBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Check input: <input id="checkInputBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Perform Eval: <input id="performEvalBox" class="checkbox" type="checkbox"></label></span>
    <span><label>Output JSON: <input id="outputJSONBox" class="checkbox" type="checkbox" onchange="onOutputJSONChanged();"></label></span>
    <span><label>Log messages: <input id="logMessagesBox" class="checkbox" type="checkbox"></label></span>
    </div>
    <div class="topgap">
    <table id="footerTbl">
      <tr>
       <td class="footerTd">
        <button id="loadButton" value="Load" onclick="loadInput();return false;">Load Input</button>
        <button id="saveButton" value="Save" onclick="saveOutput();return false;">Save Output</button>
       </td>
       <td id="verInfoStr" class="footerTd"></td>
      </tr>
    </table>
    </div>
    <script type="text/javascript">
    init();
    </script>
    </body>
    </html>
    
     
    Last edited: Aug 18, 2015
  5. JRViejo

    JRViejo Super Moderator

    Joined:
    Jul 9, 2008
    Posts:
    97,427
    Location:
    U.S.A.
     
  6. bjm_

    bjm_ Registered Member

    Joined:
    May 22, 2009
    Posts:
    4,453
    Location:
    .
    Thanks JRViejo .....
     
  7. JRViejo

    JRViejo Super Moderator

    Joined:
    Jul 9, 2008
    Posts:
    97,427
    Location:
    U.S.A.
    bjm_, you're welcome! Take care.
     
  8. Holysmoke

    Holysmoke Registered Member

    Joined:
    Jun 29, 2014
    Posts:
    139
  9. JRViejo

    JRViejo Super Moderator

    Joined:
    Jul 9, 2008
    Posts:
    97,427
    Location:
    U.S.A.
    FYI. For anyone thinking of using the user.js described in that article, do pay attention to this caveat that's posted in the article:
     
  10. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    ... or b) use an alternate profile via Profile Manager, or c) make a full copy of your profile folder when Firefox is closed, so that you can copy/rename it back if something goes wrong.
     
  11. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    In light of Mozilla dropping support for pref plugins.enumerable_names in FF41, I wanted to revisit this subject. I dusted off something I had previously written. Which enumerates navigator.plugins, performs some explicit plugin name checks, enumerates navigator.mimeTypes, and performs some explicit mimeType checks. In case anyone is interested:
    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Plugin Check</title>
    <style type="text/css">
    table, td, th {border:1px solid black; border-collapse:collapse;}
    th, td {padding:2px; text-align:left;}
    th {font-weight:normal; background-color:#f0f0f0;}
    .results {margin:10px;}
    </style>
    <script type="text/javascript">
    const verInfoStr = "Plugin Check 0.2";
    
    // ToDo: This is a hastily prepared starter list that needs review/improvement
    // Edit this list and/or provide PluginNameList.js
    var pluginNameList = [
      "Adobe Acrobat",
      "Adobe Flash Player",
      "DivX Web Player",
      "Google Earth Plug-in",
      "Java Applet Plug-in",
      "Microsoft Silverlight",
      "Microsoft® DRM",
      "QuickTime Plug-in",
      "Shockwave Flash",
      "Silverlight Plug-In",
      "Unity Player",
      "VLC Multimedia Plug-in",
      "Windows Media Player Plug-in Dynamic Link Library"
    ];
    
    // ToDo: This is a hastily prepared starter list that needs review/improvement
    // Edit this list and/or provide MimeTypeList.js
    var mimeTypeList = [
      "application/asx",
      "application/futuresplash",
      "application/pdf",
      "application/vnd.adobe.pdfxml",
      "application/vnd.adobe.x-mars",
      "application/vnd.adobe.xdp+xml",
      "application/vnd.adobe.xfd+xml",
      "application/vnd.adobe.xfdf",
      "application/vnd.fdf",
      "application/vnd.unity",
      "application/x-drm",
      "application/x-drm-v2",
      "application/x-mplayer2",
      "application/x-shockwave-flash",
      "application/x-vlc-plugin",
      "audio/x-flac",
      "audio/x-ms-wax",
      "audio/x-ms-wma",
      "video/divx",
      "video/ogg",
      "video/quicktime",
      "video/webm",
      "video/x-h264",
      "video/x-mpeg",
      "video/x-ms-asf",
      "video/x-ms-asf-plugin",
      "video/x-ms-wm",
      "video/x-ms-wmv",
      "video/x-ms-wvx",
      "video/x-msvideo"
    ];
    
    function init() {
      document.title = verInfoStr;
      document.getElementById("verInfoStr").innerHTML = verInfoStr;
    }
    
    function getPluginsTblHdr() {
      return('<tr><th>Name</th><th>Description</th><th>Version</th><th>Filename</th></tr>');
    }
    
    function getPluginsTblRow(plugin) {
      var html = '<tr>' +
                 '<td>' + plugin.name + '</td>' +
                 '<td>' + plugin.description + '</td>' +
                 '<td>' + plugin.version + '</td>' +
                 '<td>' + plugin.filename + '</td>' +
                 '</tr>';
      return(html);
    }
    
    function enumeratePlugins() {
      var html = '';
      var numResults = 0;
      if(navigator.plugins && (navigator.plugins.length > 0)) {
        html += '<table class="results">';
        html += getPluginsTblHdr();
        for(var i = 0; i < navigator.plugins.length; i++)
          html += getPluginsTblRow(navigator.plugins[i]);
        html += '</table>';
        numResults = navigator.plugins.length;
      }
      else html += '<div class="results">None</div>';
      html = '<div class="desc">Plugins detected through enumeration (' + numResults + '):</div>' + html;
      return(html);
    }
    
    function explicitlyCheckForPlugins() {
      var html = '';
      var numResults = 0;
      for(var i = 0; i < pluginNameList.length; i++) {
        var name = pluginNameList[i];
        if(navigator.plugins && (navigator.plugins[name] !== undefined)) {
          numResults++;
          if(numResults === 1) {
            html += '<table class="results">';
            html += getPluginsTblHdr();
          }
          html += getPluginsTblRow(navigator.plugins[name]);
        }
      }
      if(numResults > 0)
        html += '</table>';
      else html += '<div class="results">None</div>';
      html = '<div class="desc">Plugins detected through explicit name check (' + numResults + '):</div>' + html;
      return(html);
    }
    
    function getMimeTypesTblHdr() {
      return('<tr><th>Name</th><th>Description</th><th>Suffixes</th><th>EnabledPlugin</th><th>EnabledPlugin.name</th></tr>');
    }
    
    function getMimeTypesTblRow(mimeType) {
      var enabledPluginName = '';
      if(mimeType.enabledPlugin !== null)
        enabledPluginName = mimeType.enabledPlugin.name;
      var html = '<tr>' +
                 '<td>' + mimeType.type + '</td>' +
                 '<td>' + mimeType.description + '</td>' +
                 '<td>' + mimeType.suffixes + '</td>' +
                 '<td>' + mimeType.enabledPlugin + '</td>' +
                 '<td>' + enabledPluginName + '</td>' +
                 '</tr>';
      return(html);
    }
    
    function enumerateMimeTypes() {
      var html = '';
      var numResults = 0;
      if(navigator.mimeTypes && (navigator.mimeTypes.length > 0)) {
        html += '<table class="results">';
        html += getMimeTypesTblHdr();
        for(var i = 0; i < navigator.mimeTypes.length; i++)
          html += getMimeTypesTblRow(navigator.mimeTypes[i]);
        html += '</table>';
        numResults = navigator.mimeTypes.length;
      }
      else html += '<div class="results">None</div>';
      html = '<div class="desc">MimeTypes detected through enumeration (' + numResults + ') :</div>' + html;
      return(html);
    }
    
    function explicitlyCheckForMimeTypes() {
      var html = '';
      var numResults = 0;
      for(var i = 0; i < mimeTypeList.length; i++) {
        var type = mimeTypeList[i];
        if(navigator.mimeTypes[type] !== undefined) {
          numResults++;
          if(numResults === 1) {
            html += '<table class="results">';
            html += getMimeTypesTblHdr();
          }
          html += getMimeTypesTblRow(navigator.mimeTypes[type]);
        }
      }
      if(numResults > 0)
        html += '</table>';
      else html += '<div class="results">None</div>';
      html = '<div class="desc">MimeTypes detected through explicit type check (' + numResults + ') :</div>' + html;
      return(html);
    }
    
    </script>
    <script type="text/javascript" src="PluginNameList.js"></script>
    <script type="text/javascript" src="MimeTypeList.js"></script>
    </head>
    <body onload="init();">
    <noscript>This page will not function correctly without Javascript</noscript>
    <div id="output"></div>
    <div id="verInfoStr"></div>
    <script type="text/javascript">
    var html = "";
    html += enumeratePlugins();
    html += explicitlyCheckForPlugins();
    html += enumerateMimeTypes();
    html += explicitlyCheckForMimeTypes();
    document.getElementById("output").innerHTML = html;
    </script>
    </body>
    </html>
    
     
    Last edited: Sep 30, 2015
  12. inka

    inka Registered Member

    Joined:
    Oct 21, 2009
    Posts:
    426
  13. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    While improving/using the test page I most recently mentioned, I realized that navigator.mimeTypes is a greater issue than I previously thought. In case I'm not the only one:

    If a webpage checks the navigator.mimeTypes array for a mimeType name such as 'application/vnd.ms-excel' or 'application/vnd.oasis.opendocument.spreadsheet' or whatever, the browser tries to locate that mimeType in several places. The result can depend on browser version, installed extensions, installed plugins, OS, and software installed on the system. Which may assist fingerprinting code in distinguishing one browser/account/system from another. There are many mimeTypes. Some of these mimeTypes are well-known, but others are not. There are some generic ones which are handled by a wide range of software. However, there are also some vendor, application, device-type, and/or service-type related ones. The presence of one of these unique types would reveal that the user is using or has used something [more] specific. This behavior may be helpful in some scenarios, but there are other scenarios where it would not be welcome. I think if you were in a position to modify a user's system, you could even tag them via setting up a unique mimeType and later detect the tagged person if/when they visit a webpage via proxy/VPN/TOR. As long as enumeration doesn't leak the mimeType tag and each one has to be explicitly tested for, there would be a limit to its application.

    I'm not aware of there being a simple, side-effect-free, fix for this. You can disable Javascript, but that only solves the problem at sites where you do so. Other suggestions welcome. One idea I've had is: determine how the browser checks with the OS and reports mimeTypes, then find or write a program that lists all of the mimeTypes which could be detected that way. Then you would at least know what could be revealed. Plus, you'd know where the information comes from and by extension how to eliminate it if you needed to.

    I think all browsers based on Mozilla code would be affected by this. I haven't bothered to look into what other browser families might be. I did find evidence that fingerprinting code developers are aware of it. It is pretty obvious if/when you focus on navigator.mimeTypes handling. BTW, the default mimeTypeList in my test page only includes a few samples for demonstration purposes and I intend to leave it that way.
     
    Last edited: Oct 5, 2015
  14. deBoetie

    deBoetie Registered Member

    Joined:
    Aug 7, 2013
    Posts:
    1,832
    Location:
    UK
    I think it's madness that the whole capability of your machine regarding playable mime types should be exposed in the browser at all. It should be the other way round - you choose what mime types you're willing to support in the browser. Quite apart from the fingerprinting issue, there's the whole problem of exposure to malicious document types, and the surface is increased the more mime types you have.
     
  15. mirimir

    mirimir Registered Member

    Joined:
    Oct 1, 2011
    Posts:
    9,252
    I wonder whether any of the micro GUI browsers like Midori can be locked down better.

    Or maybe it's back to command line? Just do scp or wget, and render locally in a sandbox, or in a VM without Internet connectivity?
     
  16. summerheat

    summerheat Registered Member

    Joined:
    May 16, 2015
    Posts:
    2,199
    I don't know about Windows and iOS. But in Linux wouldn't it help to blacklist the various mimeapps.list locations in Firejail or AppArmor?
     
  17. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    @summerheat: Thanks for that.

    I think blocking the browser from looking up externally registered types would be one way to go. However, would there be scenarios where you want the browser itself to have access to that information (without exposing it to other things such as page script)? Why is it exposed to web pages/sites to begin with? Are there pages which test for what is supported on the user's end in order to pick something that they know the user will be able to work with? Would hiding these externally registered types from pages cause enough breakage to even worry about?

    Just a gut feeling at this point, but I suspect there may be scenarios which call for a whitelist and one that is implemented by/within the browser. There are extensions which selectively intercept and override DOM object manipulations. I'm not sure if running navigator.mimeTypes results past a whitelist before reporting would be sufficient to address info leakage. Somewhere I can't remember I stumbled across code that, instead of simply querying for supported types, tried creating content of different types and detected support that way.
     
  18. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    FWIW, I just tried something. I dug up an example of overriding, and created these NoScript preferences:
    Code:
    noscript.surrogate.TestSurrogateDELETEME.sources;@^http://site1.example/PluginCheck/PluginCheck.html
    noscript.surrogate.TestSurrogateDELETEME.replacement;alert("Test Surrogate is active!"); Object.defineProperties(navigator,{'plugins':{configurable:false,enumerable:true,get:function(){return{};},set:function(){}},'mimeTypes':{configurable:false,enumerable:true,get:function(){return{};},set:function(){}},});
    
    Which killed navigator.plugins and navigator.mimeTypes sniffing in my test page when it was loaded from http://site1.example/PluginCheck/PluginCheck.html. I also tried setting .sources to @*, which appeared to work more broadly.

    This is the first time I've tried NoScript surrogates. So I may not appreciate the surrogate types and issues with this approach. Even if surrogate utilization and override approach is sound, I think some would want to modify it so that it is more selective. However, I figured I'd mention this in case others want to explore it and/or point out a problem.
     
  19. Techwiz

    Techwiz Registered Member

    Joined:
    Jan 5, 2012
    Posts:
    541
    Location:
    United States
    @TheWindBringeth

    Thanks, I've been playing around with surrogates after reading:

    http://www.dslreports.com/forum/r26131393-NoScript-Trick-Kills-Obfuscated-Javascript-Exploits-Dead

    I've written the following NoScript surrogates (didn't do 'userAgent' due to spoofing add-on):
    Object.defineProperty(navigator,'appCodeName',{get:function() null,set: function() null});Object.defineProperty(navigator,'appName',{get:function() null,set: function() null});Object.defineProperty(navigator,'appVersion',{get:function() null,set: function() null});Object.defineProperty(navigator,'platform',{get:function() null,set: function() null});Object.defineProperty(navigator,'plugins',{get:function() null,set: function() null});

    I've been testing with this site:

    https://jeffersonscher.com/res/jstest.php

    disabling plugin enumeration by blocking network requests to:
    https://plugins.mozilla.org/en-us/plugins_list.json

    disabling font enumeration by setting:
    browser.display.use_document_fonts 0;
     
  20. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    @Techwiz: Thanks. I'm still experimenting, but I did notice one thing worth mentioning. Some sites assume (possibly based on user agent) that certain properties are available, and the code isn't designed to handle other cases. For example, I came across some sites that don't test for navigator.plugins before accessing navigator.plugins.length. When I responded to the get with an object and assured navigator.plugins.length could be read and would be 0, the behavior was slightly improved at one or two of them (Flash not detected message vs silent fail).

    FWIW:
    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>MozUserAgentStuff</title>
    <style type="text/css">
    table.data {border-collapse:collapse;}
    table.data td, table.data th {text-align:left; border:1px solid black;
            padding:3px;}
    table.data th {background-color:#7C96C3; color:white;}
    table.data tr:nth-child(odd) td {background-color:#F5F5FF;}
    #verInfoStr {margin-top:10px;}
    </style>
    <script type="text/javascript">
    const verInfoStr = "MozUserAgentStuff 0.3";
    
    function init() {
      document.title = verInfoStr;
      document.getElementById("verInfoStr").innerHTML = verInfoStr;
    
      var tbl = [['appCodeName',   ''],
                 ['appName',       'general.appname.override'],
                 ['appVersion',    'general.appversion.override'],
                 ['buildID',       'general.buildID.override'],
                 ['oscpu',         'general.oscpu.override'],
                 ['platform',      'general.platform.override'],
                 ['product',       ''],
                 ['productSub',    ''],
                 ['userAgent',     'general.useragent.override'],
                 ['vendor',        ''],
                 ['vendorSub',     '']];
      var html = '<table class="data">';
      html += '<tr><th>Property</th><th>Value</th><th>Override</th></tr>';
      for(var i=0; i < tbl.length; i++) {
        html += '<tr>';
        html += '<td>navigator.' + tbl[i][0] + '</td><td>';
        if(tbl[i][0] in navigator)
          html += navigator[tbl[i][0]];
        else html += "*Property Doesn't Exist*";
        html += '</td><td>' + tbl[i][1] + '</td>';
        html += '</tr>';
      }
      html += '</table>';
      document.getElementById("output").innerHTML=html;
    }
    </script>
    </head>
    <body onload="init();">
    <div id="output"></div>
    <div id="verInfoStr"></div>
    <noscript>This test page requires javascript</noscript>
    </body>
    </html>
    
     
  21. inka

    inka Registered Member

    Joined:
    Oct 21, 2009
    Posts:
    426
    What led you believe "whole capacity" is exposed? AFAICT, it is not.

    Why does Firefox preferences}}Applications tab exist? Because the browser cannot "see all".
    It can only see all applications which are registered as handlers for a particular mimetype when you click the preferences selectbox.
    Thanks to "freedestop standards", the browser knows how to (O/S agnostic) query the list of preferred handlers, but that's about it "sees"
    unless/until you visit the Preferences}}Applications tab.
    The lookup, peeking the xdg-mime database on the O/S, doesn't occur until that event and, even then...
    the "peeking" isn't a full scan of the db content. It's just a query for handlers matching the mimetype(s) relevant to the clicked selectbox.
    In fact, on certain O/Ses (older, not fully xdg-compiant), the browser can probably only "see" a handler application if such is registered as the "preferred" handler for that specific mimetype.

    That (above) explains why each selectbox includes an option labled "Other", so that you may browse to find/select a handler application.

    I believe that if you deselect / disassociate all handlers ( via preferences}}Applications ), there's nothing to sniff, nothing to leak.
    If you do (for the sake of convenience) leave some handler associations in place...
    ...what's the extent of the possible leakage? What details can a "content script" glean via enumeration?
    mimehandler name? mimehandler 'version'? (asking because I do not know)

    If someone is already employing chroot/sandboxing, that should be an easy task. Otherwise, an approach ala Puppy Linux "Run as Spot" should fit the bill.

    Possible to craft an extension that displays an infobar message when certain js functions are called?
    In addition to listening for native fns (e.g. plugins.enumerate() and others of interest), it would accommodate (my) user-set patterns to watch/listen for.
     
    Last edited: Oct 10, 2015
  22. deBoetie

    deBoetie Registered Member

    Joined:
    Aug 7, 2013
    Posts:
    1,832
    Location:
    UK
    I think you're right - but the thing being that, these days pretty much every application that can play or read media is joining in the bandwagon of registering itself as a playable type - that's a business imperative; and it's harder to turn off than it should be, and many ways it's the browsers that are failing in their default behaviors.
    It's fairly easy for malicious scripts to enumerate pretty much all the mime types of interest and see what is flushed out, and which have vulnerabilities.
     
  23. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    We may be discussing two different aspects of the browser, so to begin: I was/am talking about gathering information from the navigator.plugins and navigator.mimeTypes array like objects. Which you can access through enumeration and/or through checking for specific names. Since the results can be different based on which access method you use, it sometimes has to be explicitly stated.

    I take it that what you are calling preferences}}Application is what I would call Options->Applications. The browser tab/interface for controlling what Action that browser will use for different Content Types. At this point I've done some testing (basically all on Windows, just peeked at results under Ubuntu) and glanced at some of the code (rather than study it carefully to understand all behaviors). I am not certain whether the browser's Applications settings can affect the results of the sniffing I am talking about. I've tested fresh installs, and I've also tested after deleting MimeTypes.rdf. Scenarios where there are few entries setup in the browser's Applications tab. I still get very many hits when I check navigator.mimeTypes for specific names (my shotgun list tests for roughly 3200 names, but I'm sure there are more). I haven't seen Options->Applications affecting these results and I don't think it is going to be the answer so to speak. However, if someone has confirmed a relationship between the Applications settings within the browser and explicit navigator.mimeType checking results, I'd welcome a description that facilitates duplication.

    I think the question would be: what can be gleaned from enumerating navigator.mimeTypes AND what can be gleaned from checking navigator.mimeTypes for specific names. It is the latter that I am most concerned about right now, because of what I saw. Two of my Windows systems were used with a certain type of peripheral that has nothing to do with browsers. A driver and application software... specific to that type and brand of peripheral... was installed on those systems. The developer of that software created their own unique mimeType, and the installation of the software caused the related mimeType/extension/defaultApplicationHandler to be installed at the OS level. A content script can determine that I installed such software, and used said type and brand of peripheral, simply by checking to see if navigator.mimeTypes['CustomMimeTypeInQuestion'] === undefined.

    To make things more relevant here, lets assume the software in question were an encryption tool and/or some other type of security related product. Would you want content scripts to be able to detect that you've installed, and are probably still using, a specific one of those? Different versions of software can use different custom mimeTypes, and I've spotted some mimeTypes that have an embedded version number. So I think there is some potential for this type of mimeType sniffing to be used for nefarious purposes. FWIW, this is where I thought @deBoetie was going.

    I'm not going to be able to add much to the discussion where the context involves non-Windows OSs, at least for awhile. If there are important aspects, and especially differences in behavior, I hope others will [continue to] draw attention to them. Edited for clarification purposes.
     
    Last edited: Oct 11, 2015
  24. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    There are definitely some options there, but I haven't been down that path before. Currently, I'm exploring the NoScript Surrogate approach and just arrived at this point...

    Surrogate .sources: @*
    Surrogate .exceptions: .example.com .example.org
    Surrogate .replacement:
    Code:
    /* Version 0.2 */
    (function() {
      var pluginsOverrideEnabled         = true;
      var mimeTypesOverrideEnabled       = true;
      var pluginsOverrideAlertsEnabled   = true;
      var mimeTypesOverrideAlertsEnabled = true;
      var alertTimeout                   = 3000;
     
      var alertMsgs                      = [null,null];
      var alertsShown                    = [false,false];
      var domContentLoaded               = false;
     
      function setAlert(index, msg) {
        if(alertMsgs[index] === null) {
          alertMsgs[index] = msg;
          if(domContentLoaded)
            showAlert(index);
        }
      }
     
      function showAlert(index) {
        if(!alertsShown[index]) {
          if(window.frameElement) {
            /* Might not see an overlay, so we use alert() */
            alert(alertMsgs[index]);
          }
          else {
            var overlay = document.createElement("div");
            var style = "position:fixed;z-index:2147483647;top:0;width:50%;margin:0px;padding:14px 0px;background-color:red;vertical-align:middle;text-align:center;font-weight:bold;";
            if(index === 0)
              style += "left:0;";
            else style += "left:50%";
            overlay.style = style;
            overlay.innerHTML = alertMsgs[index];
            document.body.appendChild(overlay);
            timeoutId = setTimeout(function(){
              overlay.parentNode.removeChild(overlay);
            },alertTimeout);
          }
        }
      }
       
      document.addEventListener("DOMContentLoaded", function(event) {
        domContentLoaded = true;
        if(alertMsgs[0] !== null)
          showAlert(0);
        if(alertMsgs[1] !== null)
          showAlert(1);
      });
     
      if(pluginsOverrideEnabled) {
        Object.defineProperties(navigator, {
          'plugins': {
            configurable: false,
            enumerable: true,
            get: function () {
              var msg = "NoScript surrogate blocked navigator.plugins get";
              if(window.frameElement)
                msg += " in frameElement";
              console.log(msg + ": " + window.location.href);
              if(pluginsOverrideAlertsEnabled > 0) {
                setAlert(0, msg);
              }
              return {
                get length () {
                    return 0;
                },
              };
            },
            set: function () {},
          },
        });
      }
    
      if(mimeTypesOverrideEnabled) {
        Object.defineProperties(navigator, {
          'mimeTypes': {
            configurable: false,
            enumerable: true,
            get: function () {
              var msg = "NoScript surrogate blocked navigator.mimeTypes get";
              if(window.frameElement)
                msg += " in frameElement";
              console.log(msg + ": " + window.location.href);
              if(mimeTypesOverrideAlertsEnabled > 0) {
                setAlert(1, msg);
              }
              return {
                get length () {
                  return 0;
                },
              };
            },
            set: function () {},
          },
        });
      }
    })();
    
    Code can be copied and pasted into .replacement, but the newline stripping means that comments must be /* */ style. So far so good. The visual feedback and console logging do help

    Edit: Forgot to set position:absolute back to position:fixed before posting, bumped
     
    Last edited: Oct 13, 2015
  25. TheWindBringeth

    TheWindBringeth Registered Member

    Joined:
    Feb 29, 2012
    Posts:
    2,171
    A slightly improved version of my earlier test page too:
    Code:
    <!doctype html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Plugins & MimeTypes Test</title>
    <style type="text/css">
    table, td, th {border:1px solid black; border-collapse:collapse;}
    th, td {padding:2px; text-align:left;}
    th {font-weight:normal; background-color:#f0f0f0;}
    .desc {font-weight:bold;}
    .results {margin:10px;}
    #verInfoStr {margin-top:10px;}
    #savedResultsWarning {font-weight:bold; color:red !important;}
    .demoListWarning {color:red !important;}
    </style>
    <script type="text/javascript">
    const verInfoStr = "Plugins & MimeTypes Test 0.6";
    var saveFilename = "PluginsAndMimeTypesTestResults.html";
    var resultsAsHtml;
    
    // Note: This is a small list for demo purposes.  You can
    // edit this list or override it in PluginNameList.js.
    var pluginNameList = [
      "TestEntryThatIndicatesDemoList",          // Notice
      "TestEntryThatShouldNeverBeFound",         // Notice
      "Adobe Acrobat NPAPI Plug-in, Version 11.0.02",
      "Adobe Acrobat NPAPI Plug-in, Version 11.0.04",
      "Adobe Acrobat",
      "Adobe Flash Player",
      "Adobe Shockwave Player",
      "Cisco Web Communicator",
      "DivX Web Player",
      "DivX® Web Player",
      "Gnome Shell Integration",
      "Google Earth Plug-in",
      "Java Plug-in",
      "Java Applet Plug-in",
      "Java Runtime Environment",
      "Microsoft Silverlight",
      "Microsoft® DRM",
      "Picasa",
      "QuickTime Plug-in 7.7.1",
      "QuickTime Plug-in 7.7.3",
      "QuickTime Plug-in",
      "RealPlayer",
      "Shockwave Flash 2.0",
      "Shockwave Flash",
      "Silverlight Plug-In",
      "Unity Player",
      "VLC Multimedia Plug-in",
      "VLC Multimedia Plugin (compatible Videos 3.10.1)",
      "Windows Media Player Plug-in 10 (compatible; Videos)",
      "Windows Media Player Plug-in Dynamic Link Library",
      "iTunes Application Detector",
    ];
    
    // Note: This is a small list for demo purposes.  You can
    // edit this list or override it in MimeTypeList.js.
    var mimeTypeList = [
      "TestEntryThatIndicatesDemoList",          // Notice
      "TestEntryThatShouldNeverBeFound",         // Notice
      "application/asx",
      "application/futuresplash",
      "application/hta",
      "application/itunes-plugin",
      "application/pdf",
      "application/pgp-keys",
      "application/vnd.adobe.pdfxml",
      "application/vnd.adobe.x-mars",
      "application/vnd.adobe.xdp+xml",
      "application/vnd.adobe.xfd+xml",
      "application/vnd.adobe.xfdf",
      "application/vnd.amazon.ebook",
      "application/vnd.dassault-systemes.draftsight-dwg",
      "application/vnd.fdf",
      "application/vnd.haansoft-presentation",
      "application/vnd.kde.kspread",
      "application/vnd.ms-word",
      "application/vnd.ms-word.document.macroenabled.12",
      "application/vnd.oasis.opendocument.text",
      "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
      "application/vnd.wolfram.mathematica.package",
      "application/x-audacity-project",
      "application/x-basket-item",
      "application/x-bittorrent",
      "application/x-drm",
      "application/x-drm-v2",
      "application/x-etherpeek",
      "application/x-gnome-shell-integration",
      "application/x-ica",
      "application/x-java-archive",
      "application/x-kdevelop",
      "application/x-matlab-data",
      "application/x-mplayer2",
      "application/x-pcap",
      "application/x-pcapng",
      "application/x-shockwave-flash",
      "application/x-smb-server",
      "application/x-totem-plugin",
      "application/x-vlc-plugin",
      "audio/x-flac",
      "audio/x-ms-wax",
      "audio/x-ms-wma",
      "image/g3fax",
      "image/x-apple-ios-png",
      "image/x-psp",
      "video/divx",
      "video/ogg",
      "video/quicktime",
      "video/webm",
      "video/x-h264",
      "video/x-mpeg",
      "video/x-ms-asf",
      "video/x-ms-asf-plugin",
      "video/x-ms-wm",
      "video/x-ms-wmv",
      "video/x-ms-wvx",
      "video/x-msvideo",
      "x-scheme-handler/bbc-ipd",
      "x-scheme-handler/magnet",
    ];
    
    function init() {
      document.title = verInfoStr;
      document.getElementById("verInfoStr").innerHTML = verInfoStr;
      addButton("controls", "Save Results", "saveButton", null, onSaveButtonClick, true);
    }
    
    function addButton(parentId, text, buttonId, buttonClass, buttonClickListener, buttonDisabled) {
      var element = document.createElement("button");
      if(buttonId)
        element.id = buttonId;
      if(buttonClass)
        element.class = buttonClass;
      if(buttonClickListener)
        element.addEventListener("click", buttonClickListener);
      element.disabled = buttonDisabled;
      element.appendChild(document.createTextNode(text));
      document.getElementById(parentId).appendChild(element);
    }
    
    function onSaveButtonClick(event) {
      var filename = "PluginsAndMimeTypesTestResults.html";
      var description = "Results from " + verInfoStr
      var html = '<!doctype html><html><head><meta charset="utf-8">';
      html += '<title> ' + description + '</title>';
      html += '<style type="text/css">table, td, th {border:1px solid black; border-collapse:collapse;} th, td {padding:2px; text-align:left;} th {font-weight:normal; background-color:#f0f0f0;} .desc {font-weight:bold;} .results {margin:10px;} #verInfoStr {margin-top:10px;} #savedResultsWarning {font-weight:bold; color:red !important;} .demoListWarning {color:red !important;}</style>';
      html += '</head><body><h3 id="savedResultsWarning">This is a saved results file, not a live test.</h3>';
      html += resultsAsHtml;
      html += '<div id="verInfoStr"> ' + description + '</div>';
      html += '</body></html>';
      var element = document.createElement('a');
      element.setAttribute('href', 'data:text/html;charset=utf-8,' + encodeURIComponent(html));
      element.setAttribute('download', filename);
      element.style.display = 'none';
      document.body.appendChild(element);
      element.click();
      document.body.removeChild(element);
    }
    
    function getPluginsTblHdr() {
      return('<tr><th>Name</th><th>Description</th><th>Version</th><th>Filename</th></tr>');
    }
    
    function getPluginsTblRow(plugin) {
      var html = '<tr>' +
                 '<td>' + plugin.name + '</td>' +
                 '<td>' + plugin.description + '</td>' +
                 '<td>' + plugin.version + '</td>' +
                 '<td>' + plugin.filename + '</td>' +
                 '</tr>\n';
      return(html);
    }
    
    function navigatorPluginsInfo() {
      var html = '<div class="desc">1) navigator.plugins Info</div>';
      html += '<div class="results">';
      html += '<div>navigator.plugins = ' + navigator.plugins + '</div>';
      html += '<div>navigator.plugins.length = ';
      if(navigator.plugins)
        html += navigator.plugins.length;
      else html += 'Not Accessible';
      html += '</div>';
      html += '</div>';
      return(html);
    }
    
    function enumeratePlugins() {
      var html = '';
      var numResults = 0;
      if(navigator.plugins && (navigator.plugins.length > 0)) {
        html += '<table class="results">';
        html += getPluginsTblHdr();
        for(var i = 0; i < navigator.plugins.length; i++)
          html += getPluginsTblRow(navigator.plugins[i]);
        html += '</table>';
        numResults = navigator.plugins.length;
      }
      else html += '<div class="results">No results to show</div>';
      html = '<div class="desc">2) Plugins detected by enumeration: ' + numResults + '</div>' + html;
      return(html);
    }
    
    function explicitlyCheckForPlugins() {
      var html = '';
      var numResults = 0;
      for(var i = 0; i < pluginNameList.length; i++) {
        var name = pluginNameList[i];
        if(navigator.plugins && (navigator.plugins[name] !== undefined)) {
          numResults++;
          if(numResults === 1) {
            html += '<table class="results">';
            html += getPluginsTblHdr();
          }
          html += getPluginsTblRow(navigator.plugins[name]);
        }
      }
      if(numResults > 0)
        html += '</table>';
      else html += '<div class="results">No results to show</div>';
      if(pluginNameList.indexOf('TestEntryThatIndicatesDemoList') > -1) {
        html = '<div class="results demoListWarning results">Demo list detected. For more comprehensive testing you should expand pluginNameList.</div>' + html;
      }
      html = '<div class="desc">3) Plugins detected by checking ' + pluginNameList.length + ' names: ' + numResults + '</div>' + html;
      return(html);
    }
    
    function getMimeTypesTblHdr() {
      return('<tr><th>Name</th><th>Description</th><th>Suffixes</th><th>EnabledPlugin</th><th>EnabledPlugin.name</th></tr>');
    }
    
    function getMimeTypesTblRow(mimeType) {
      var enabledPluginName = '';
      if(mimeType.enabledPlugin !== null)
        enabledPluginName = mimeType.enabledPlugin.name;
      var html = '<tr>' +
                 '<td>' + mimeType.type + '</td>' +
                 '<td>' + mimeType.description + '</td>' +
                 '<td>' + mimeType.suffixes + '</td>' +
                 '<td>' + mimeType.enabledPlugin + '</td>' +
                 '<td>' + enabledPluginName + '</td>' +
                 '</tr>\n';
      return(html);
    }
    
    function navigatorMimeTypesInfo() {
      var html = '<div class="desc">4) navigator.mimeTypes Info</div>';
      html += '<div class="results">';
      html += '<div>navigator.mimeTypes = ' + navigator.mimeTypes + '</div>';
      html += '<div>navigator.mimeTypes.length = ';
      if(navigator.mimeTypes)
        html += navigator.mimeTypes.length;
      else html += 'Not Accessible';
      html += '</div>';
      html += '</div>';
      return(html);
    }
    
    function enumerateMimeTypes() {
      var html = '';
      var numResults = 0;
      if(navigator.mimeTypes && (navigator.mimeTypes.length > 0)) {
        html += '<table class="results">';
        html += getMimeTypesTblHdr();
        for(var i = 0; i < navigator.mimeTypes.length; i++)
          html += getMimeTypesTblRow(navigator.mimeTypes[i]);
        html += '</table>';
        numResults = navigator.mimeTypes.length;
      }
      else html += '<div class="results">No results to show</div>';
      html = '<div class="desc">5) MimeTypes detected by enumeration: ' + numResults + '</div>' + html;
      return(html);
    }
    
    function explicitlyCheckForMimeTypes() {
      var html = '';
      var numResults = 0;
      for(var i = 0; i < mimeTypeList.length; i++) {
        var type = mimeTypeList[i];
        if(navigator.mimeTypes[type] !== undefined) {
          numResults++;
          if(numResults === 1) {
            html += '<table class="results">';
            html += getMimeTypesTblHdr();
          }
          html += getMimeTypesTblRow(navigator.mimeTypes[type]);
        }
      }
      if(numResults > 0)
        html += '</table>';
      else html += '<div class="results">No results to show</div>';
      if(mimeTypeList.indexOf('TestEntryThatIndicatesDemoList') > -1) {
        html = '<div class="demoListWarning results">Demo list detected. For more comprehensive testing you should expand mimeTypesList.</div>' + html;
      }
      html = '<div class="desc">6) MimeTypes detected by checking ' + mimeTypeList.length + ' names: ' + numResults + '</div>' + html;
      return(html);
    }
    
    function loadResults() {
      document.getElementById("saveButton").disabled = true;
      document.getElementById("output").innerHTML = '<h3>Performing detections, this may take a moment</h3>';
      setTimeout(function(){
        resultsAsHtml = '';
        resultsAsHtml += navigatorPluginsInfo();
        resultsAsHtml += enumeratePlugins();
        resultsAsHtml += explicitlyCheckForPlugins();
        resultsAsHtml += navigatorMimeTypesInfo();
        resultsAsHtml += enumerateMimeTypes();
        resultsAsHtml += explicitlyCheckForMimeTypes();
        document.getElementById("output").innerHTML = resultsAsHtml;
        document.getElementById("saveButton").disabled = false;
      },500);
    }
    
    </script>
    <script type="text/javascript" src="PluginNameList.js"></script>
    <script type="text/javascript" src="MimeTypeList.js"></script>
    </head>
    <body onload="init();loadResults();">
    <noscript>This page will not function correctly without Javascript</noscript>
    <div id="output"></div>
    <div id="controls"></div>
    <div id="verInfoStr"></div>
    </body>
    </html>
    
    Edit: Added a Save Results feature
    Edit: Added this may take a moment msg & demo list warnings. By design, the default lists do not test enough.
     
    Last edited: Oct 16, 2015
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.