summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2013-02-11 16:15:13 -0800
committerdanielsdeleo <dan@opscode.com>2013-02-11 16:15:13 -0800
commitad329e0687d7e2cce955e4e0c62c8e107ed2e743 (patch)
treea7ab7b9ca980bd7b384406e0548f6f7a2fb48034
parent7d1cbdbf6d0f3971780ec6b2be619ed686630999 (diff)
downloadchef-ad329e0687d7e2cce955e4e0c62c8e107ed2e743.tar.gz
hand cherry-pick json-dos-fix to pl master
-rw-r--r--chef/lib/chef/json_compat.rb84
-rw-r--r--chef/lib/chef/resource.rb17
2 files changed, 100 insertions, 1 deletions
diff --git a/chef/lib/chef/json_compat.rb b/chef/lib/chef/json_compat.rb
index 06ed7e5279..f9e669ba24 100644
--- a/chef/lib/chef/json_compat.rb
+++ b/chef/lib/chef/json_compat.rb
@@ -18,11 +18,25 @@
# Wrapper class for interacting with JSON.
require 'json'
+require 'yajl'
class Chef
class JSONCompat
JSON_MAX_NESTING = 1000
+ JSON_CLASS = "json_class".freeze
+
+ CHEF_APICLIENT = "Chef::ApiClient".freeze
+ CHEF_COOKBOOKVERSION = "Chef::CookbookVersion".freeze
+ CHEF_DATABAG = "Chef::DataBag".freeze
+ CHEF_DATABAGITEM = "Chef::DataBagItem".freeze
+ CHEF_ENVIRONMENT = "Chef::Environment".freeze
+ CHEF_NODE = "Chef::Node".freeze
+ CHEF_ROLE = "Chef::Role".freeze
+ CHEF_SANDBOX = "Chef::Sandbox".freeze
+ CHEF_RESOURCE = "Chef::Resource".freeze
+ CHEF_RESOURCECOLLECTION = "Chef::ResourceCollection".freeze
+
class <<self
# See CHEF-1292/PL-538. Increase the max nesting for JSON, which defaults
# to 19, and isn't enough for some (for example, a Node within a Node)
@@ -37,7 +51,43 @@ class Chef
# Just call the JSON gem's parse method with a modified :max_nesting field
def from_json(source, opts = {})
- ::JSON.parse(source, opts_add_max_nesting(opts))
+ obj = ::Yajl::Parser.parse(source)
+
+ # The old default in the json gem (which we are mimicing because we
+ # sadly rely on this misfeature) is to "create additions" i.e., convert
+ # JSON objects into ruby objects. Explicit :create_additions => false
+ # is required to turn it off.
+ if opts[:create_additions].nil? || opts[:create_additions]
+ map_to_rb_obj(obj)
+ else
+ obj
+ end
+ end
+
+ # Look at an object that's a basic type (from json parse) and convert it
+ # to an instance of Chef classes if desired.
+ def map_to_rb_obj(json_obj)
+ res = case json_obj
+ when Hash
+ mapped_hash = map_hash_to_rb_obj(json_obj)
+ if json_obj.has_key?(JSON_CLASS) && (class_to_inflate = class_for_json_class(json_obj[JSON_CLASS]))
+ class_to_inflate.json_create(mapped_hash)
+ else
+ mapped_hash
+ end
+ when Array
+ json_obj.map {|e| map_to_rb_obj(e) }
+ else
+ json_obj
+ end
+ res
+ end
+
+ def map_hash_to_rb_obj(json_hash)
+ json_hash.each do |key, value|
+ json_hash[key] = map_to_rb_obj(value)
+ end
+ json_hash
end
def to_json(obj, opts = nil)
@@ -47,6 +97,38 @@ class Chef
def to_json_pretty(obj, opts = nil)
::JSON.pretty_generate(obj, opts_add_max_nesting(opts))
end
+
+
+ def class_for_json_class(json_class)
+ case json_class
+ when CHEF_APICLIENT
+ Chef::ApiClient
+ when CHEF_COOKBOOKVERSION
+ Chef::CookbookVersion
+ when CHEF_DATABAG
+ Chef::DataBag
+ when CHEF_DATABAGITEM
+ Chef::DataBagItem
+ when CHEF_ENVIRONMENT
+ Chef::Environment
+ when CHEF_NODE
+ Chef::Node
+ when CHEF_ROLE
+ Chef::Role
+ when CHEF_SANDBOX
+ false
+ when CHEF_RESOURCE
+ Chef::Resource
+ when CHEF_RESOURCECOLLECTION
+ Chef::ResourceCollection
+ when /^Chef::Resource/
+ Chef::Resource.find_subclass_by_name(json_class)
+ else
+ raise ArgumentError, "Unsupported `json_class` type '#{json_class}'"
+ end
+ end
+
end
end
end
+
diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb
index 7ba59ca265..c60ca1b84a 100644
--- a/chef/lib/chef/resource.rb
+++ b/chef/lib/chef/resource.rb
@@ -70,6 +70,23 @@ F
end
+ # Track all subclasses of Resource. This is used so names can be looked up
+ # when attempting to deserialize from JSON. (See: json_compat)
+ def self.resource_classes
+ @resource_classes ||= []
+ end
+
+ # Callback when subclass is defined. Adds subclass to list of subclasses.
+ def self.inherited(subclass)
+ resource_classes << subclass
+ end
+
+ # Look up a subclass by +class_name+ which should be a string that matches
+ # `Subclass.name`
+ def self.find_subclass_by_name(class_name)
+ resource_classes.first {|c| c.name == class_name }
+ end
+
FORBIDDEN_IVARS = [:@run_context, :@node]
HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node]