Browse Source

first commit

gcmalloc 7 years ago
commit
efec773b8a
2 changed files with 262 additions and 0 deletions
  1. 238 0
      json_sans_eval.js
  2. 24 0
      page.html

+ 238 - 0
json_sans_eval.js

@@ -0,0 +1,238 @@
+// This source code is free for use in the public domain.
+// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+// http://code.google.com/p/json-sans-eval/
+
+/**
+ * Parses a string of well-formed JSON text.
+ *
+ * If the input is not well-formed, then behavior is undefined, but it is
+ * deterministic and is guaranteed not to modify any object other than its
+ * return value.
+ *
+ * This does not use `eval` so is less likely to have obscure security bugs than
+ * json2.js.
+ * It is optimized for speed, so is much faster than json_parse.js.
+ *
+ * This library should be used whenever security is a concern (when JSON may
+ * come from an untrusted source), speed is a concern, and erroring on malformed
+ * JSON is *not* a concern.
+ *
+ *                      Pros                   Cons
+ *                    +-----------------------+-----------------------+
+ * json_sans_eval.js  | Fast, secure          | Not validating        |
+ *                    +-----------------------+-----------------------+
+ * json_parse.js      | Validating, secure    | Slow                  |
+ *                    +-----------------------+-----------------------+
+ * json2.js           | Fast, some validation | Potentially insecure  |
+ *                    +-----------------------+-----------------------+
+ *
+ * json2.js is very fast, but potentially insecure since it calls `eval` to
+ * parse JSON data, so an attacker might be able to supply strange JS that
+ * looks like JSON, but that executes arbitrary javascript.
+ * If you do have to use json2.js with untrusted data, make sure you keep
+ * your version of json2.js up to date so that you get patches as they're
+ * released.
+ *
+ * @param {string} json per RFC 4627
+ * @param {function (this:Object, string, *):*} opt_reviver optional function
+ *     that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1.
+ *     If supplied, the function is called with a string key, and a value.
+ *     The value is the property of 'this'.  The reviver should return
+ *     the value to use in its place.  So if dates were serialized as
+ *     {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
+ *     {@code
+ *     function (key, value) {
+ *       if (value && typeof value === 'object' && 'Date' === value.type) {
+ *         return new Date(value.time);
+ *       } else {
+ *         return value;
+ *       }
+ *     }}.
+ *     If the reviver returns {@code undefined} then the property named by key
+ *     will be deleted from its container.
+ *     {@code this} is bound to the object containing the specified property.
+ * @return {Object|Array}
+ * @author Mike Samuel <mikesamuel@gmail.com>
+ */
+var jsonParse = (function () {
+  var number
+      = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
+  var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
+      + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
+  var string = '(?:\"' + oneChar + '*\")';
+
+  // Will match a value in a well-formed JSON file.
+  // If the input is not well-formed, may match strangely, but not in an unsafe
+  // way.
+  // Since this only matches value tokens, it does not match whitespace, colons,
+  // or commas.
+  var jsonToken = new RegExp(
+      '(?:false|true|null|[\\{\\}\\[\\]]'
+      + '|' + number
+      + '|' + string
+      + ')', 'g');
+
+  // Matches escape sequences in a string literal
+  var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');
+
+  // Decodes escape sequences in object literals
+  var escapes = {
+    '"': '"',
+    '/': '/',
+    '\\': '\\',
+    'b': '\b',
+    'f': '\f',
+    'n': '\n',
+    'r': '\r',
+    't': '\t'
+  };
+  function unescapeOne(_, ch, hex) {
+    return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
+  }
+
+  // A non-falsy value that coerces to the empty string when used as a key.
+  var EMPTY_STRING = new String('');
+  var SLASH = '\\';
+
+  // Constructor to use based on an open token.
+  var firstTokenCtors = { '{': Object, '[': Array };
+
+  var hop = Object.hasOwnProperty;
+
+  return function (json, opt_reviver) {
+    // Split into tokens
+    var toks = json.match(jsonToken);
+    // Construct the object to return
+    var result;
+    var tok = toks[0];
+    var topLevelPrimitive = false;
+    if ('{' === tok) {
+      result = {};
+    } else if ('[' === tok) {
+      result = [];
+    } else {
+      // The RFC only allows arrays or objects at the top level, but the JSON.parse
+      // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
+      // at the top level.
+      result = [];
+      topLevelPrimitive = true;
+    }
+
+    // If undefined, the key in an object key/value record to use for the next
+    // value parsed.
+    var key;
+    // Loop over remaining tokens maintaining a stack of uncompleted objects and
+    // arrays.
+    var stack = [result];
+    for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
+      tok = toks[i];
+
+      var cont;
+      switch (tok.charCodeAt(0)) {
+        default:  // sign or digit
+          cont = stack[0];
+          cont[key || cont.length] = +(tok);
+          key = void 0;
+          break;
+        case 0x22:  // '"'
+          tok = tok.substring(1, tok.length - 1);
+          if (tok.indexOf(SLASH) !== -1) {
+            tok = tok.replace(escapeSequence, unescapeOne);
+          }
+          cont = stack[0];
+          if (!key) {
+            if (cont instanceof Array) {
+              key = cont.length;
+            } else {
+              key = tok || EMPTY_STRING;  // Use as key for next value seen.
+              break;
+            }
+          }
+          cont[key] = tok;
+          key = void 0;
+          break;
+        case 0x5b:  // '['
+          cont = stack[0];
+          stack.unshift(cont[key || cont.length] = []);
+          key = void 0;
+          break;
+        case 0x5d:  // ']'
+          stack.shift();
+          break;
+        case 0x66:  // 'f'
+          cont = stack[0];
+          cont[key || cont.length] = false;
+          key = void 0;
+          break;
+        case 0x6e:  // 'n'
+          cont = stack[0];
+          cont[key || cont.length] = null;
+          key = void 0;
+          break;
+        case 0x74:  // 't'
+          cont = stack[0];
+          cont[key || cont.length] = true;
+          key = void 0;
+          break;
+        case 0x7b:  // '{'
+          cont = stack[0];
+          stack.unshift(cont[key || cont.length] = {});
+          key = void 0;
+          break;
+        case 0x7d:  // '}'
+          stack.shift();
+          break;
+      }
+    }
+    // Fail if we've got an uncompleted object.
+    if (topLevelPrimitive) {
+      if (stack.length !== 1) { throw new Error(); }
+      result = result[0];
+    } else {
+      if (stack.length) { throw new Error(); }
+    }
+
+    if (opt_reviver) {
+      // Based on walk as implemented in http://www.json.org/json2.js
+      var walk = function (holder, key) {
+        var value = holder[key];
+        if (value && typeof value === 'object') {
+          var toDelete = null;
+          for (var k in value) {
+            if (hop.call(value, k) && value !== holder) {
+              // Recurse to properties first.  This has the effect of causing
+              // the reviver to be called on the object graph depth-first.
+
+              // Since 'this' is bound to the holder of the property, the
+              // reviver can access sibling properties of k including ones
+              // that have not yet been revived.
+
+              // The value returned by the reviver is used in place of the
+              // current value of property k.
+              // If it returns undefined then the property is deleted.
+              var v = walk(value, k);
+              if (v !== void 0) {
+                value[k] = v;
+              } else {
+                // Deleting properties inside the loop has vaguely defined
+                // semantics in ES3 and ES3.1.
+                if (!toDelete) { toDelete = []; }
+                toDelete.push(k);
+              }
+            }
+          }
+          if (toDelete) {
+            for (var i = toDelete.length; --i >= 0;) {
+              delete value[toDelete[i]];
+            }
+          }
+        }
+        return opt_reviver.call(holder, key, value);
+      };
+      result = walk({ '': result }, '');
+    }
+
+    return result;
+  };
+})();

+ 24 - 0
page.html

@@ -0,0 +1,24 @@
+<html>
+  <head>
+    <script src="json_sans_eval.js">
+    </script>
+
+    <script language="javascript">
+    var xhr = new XMLHttpRequest();
+    xhr.open("GET", "https://fixme.ch/cgi-bin/spaceapi.py", true);
+    xhr.onreadystatechange = function(){
+      if ( xhr.readyState == 4 ) {
+        parse_text = jsonParse(xhr.responseText);
+        if ( xhr.status == 200 ) {
+          document.body.innerHTML = parse_text.status;
+        } else {
+          document.body.innerHTML = "ERROR";
+        }
+      }
+    };
+    xhr.send(null);
+    </script>
+
+  </head>
+  <body>
+  </body>