diff options
author | danielsdeleo <dan@opscode.com> | 2013-02-11 16:15:13 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2013-02-11 16:15:13 -0800 |
commit | ad329e0687d7e2cce955e4e0c62c8e107ed2e743 (patch) | |
tree | a7ab7b9ca980bd7b384406e0548f6f7a2fb48034 | |
parent | 7d1cbdbf6d0f3971780ec6b2be619ed686630999 (diff) | |
download | chef-ad329e0687d7e2cce955e4e0c62c8e107ed2e743.tar.gz |
hand cherry-pick json-dos-fix to pl master
-rw-r--r-- | chef/lib/chef/json_compat.rb | 84 | ||||
-rw-r--r-- | chef/lib/chef/resource.rb | 17 |
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] |