diff --git a/lib/parser.js b/lib/parser.js index 08935917..07ebef98 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -1,7 +1,7 @@ // Generated by CoffeeScript 1.12.7 (function() { "use strict"; - var bom, defaults, events, isEmpty, isValidKey, processItem, processors, sax, setImmediate, + var bom, defaults, defineProperty, events, isEmpty, processItem, processors, sax, setImmediate, bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; @@ -22,10 +22,6 @@ return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0; }; - isValidKey = function(key) { - return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; - }; - processItem = function(processors, item, key) { var i, len, process; for (i = 0, len = processors.length; i < len; i++) { @@ -35,6 +31,16 @@ return item; }; + defineProperty = function(obj, key, value) { + var descriptor; + descriptor = Object.create(null); + descriptor.value = value; + descriptor.writeable = true; + descriptor.enumerable = true; + descriptor.configurable = true; + return Object.defineProperty(obj, key, descriptor); + }; + exports.Parser = (function(superClass) { extend(Parser, superClass); @@ -96,18 +102,15 @@ }; Parser.prototype.assignOrPush = function(obj, key, newValue) { - if (!isValidKey(key)) { - return; - } if (!(key in obj)) { if (!this.options.explicitArray) { - return obj[key] = newValue; + return defineProperty(obj, key, newValue); } else { - return obj[key] = [newValue]; + return defineProperty(obj, key, [newValue]); } } else { if (!(obj[key] instanceof Array)) { - obj[key] = [obj[key]]; + defineProperty(obj, key, [obj[key]]); } return obj[key].push(newValue); } @@ -159,12 +162,10 @@ } newValue = _this.options.attrValueProcessors ? processItem(_this.options.attrValueProcessors, node.attributes[key], key) : node.attributes[key]; processedKey = _this.options.attrNameProcessors ? processItem(_this.options.attrNameProcessors, key) : key; - if (isValidKey(processedKey)) { - if (_this.options.mergeAttrs) { - _this.assignOrPush(obj, processedKey, newValue); - } else { - obj[attrkey][processedKey] = newValue; - } + if (_this.options.mergeAttrs) { + _this.assignOrPush(obj, processedKey, newValue); + } else { + defineProperty(obj[attrkey], processedKey, newValue); } } } @@ -253,9 +254,7 @@ objClone = {}; for (key in obj) { if (!hasProp.call(obj, key)) continue; - if (isValidKey(key)) { - objClone[key] = obj[key]; - } + defineProperty(objClone, key, obj[key]); } s[_this.options.childkey].push(objClone); delete obj["#name"]; @@ -270,7 +269,7 @@ if (_this.options.explicitRoot) { old = obj; obj = {}; - obj[nodeName] = old; + defineProperty(obj, nodeName, old); } _this.resultObject = obj; _this.saxParser.ended = true; diff --git a/src/parser.coffee b/src/parser.coffee index a0fe9965..a478a857 100644 --- a/src/parser.coffee +++ b/src/parser.coffee @@ -11,13 +11,19 @@ defaults = require('./defaults').defaults isEmpty = (thing) -> return typeof thing is "object" && thing? && Object.keys(thing).length is 0 -isValidKey = (key) -> - return key != '__proto__' && key != 'constructor' && key != 'prototype' - processItem = (processors, item, key) -> item = process(item, key) for process in processors return item +defineProperty = (obj, key, value) -> + # make sure the descriptor hasn't been prototype polluted + descriptor = Object.create null + descriptor.value = value + descriptor.writeable = true + descriptor.enumerable = true + descriptor.configurable = true + Object.defineProperty obj, key, descriptor + class exports.Parser extends events constructor: (opts) -> # if this was called without 'new', create an instance with new and return @@ -55,14 +61,14 @@ class exports.Parser extends events @emit err assignOrPush: (obj, key, newValue) => - return if not isValidKey(key) if key not of obj if not @options.explicitArray - obj[key] = newValue + defineProperty obj, key, newValue else - obj[key] = [newValue] + defineProperty obj, key, [newValue] else - obj[key] = [obj[key]] if not (obj[key] instanceof Array) + unless obj[key] instanceof Array + defineProperty obj, key, [obj[key]] obj[key].push newValue reset: => @@ -114,11 +120,10 @@ class exports.Parser extends events obj[attrkey] = {} newValue = if @options.attrValueProcessors then processItem(@options.attrValueProcessors, node.attributes[key], key) else node.attributes[key] processedKey = if @options.attrNameProcessors then processItem(@options.attrNameProcessors, key) else key - if isValidKey(processedKey) - if @options.mergeAttrs - @assignOrPush obj, processedKey, newValue - else - obj[attrkey][processedKey] = newValue + if @options.mergeAttrs + @assignOrPush obj, processedKey, newValue + else + defineProperty obj[attrkey], processedKey, newValue # need a place to store the node name obj["#name"] = if @options.tagNameProcessors then processItem(@options.tagNameProcessors, node.name) else node.name @@ -188,7 +193,7 @@ class exports.Parser extends events # push a clone so that the node in the children array can receive the #name property while the original obj can do without it objClone = {} for own key of obj - objClone[key] = obj[key] if isValidKey(key) + defineProperty objClone, key, obj[key] s[@options.childkey].push objClone delete obj["#name"] # re-check whether we can collapse the node now to just the charkey value @@ -204,7 +209,7 @@ class exports.Parser extends events # avoid circular references old = obj obj = {} - obj[nodeName] = old + defineProperty obj, nodeName, old @resultObject = obj # parsing has ended, mark that so we won't throw exceptions from