diff options
author | danielsdeleo <dan@opscode.com> | 2013-02-11 16:15:13 -0800 |
---|---|---|
committer | danielsdeleo <dan@opscode.com> | 2013-02-12 11:01:46 -0800 |
commit | 3845a4c7517643963d9fe662eae167b1b62e87ec (patch) | |
tree | e2edc324ce0cd19efa472c1f5144db2ef156d95c | |
parent | 7b617fd3ca32592cf87b4ea7c2afdc56271bca5f (diff) | |
download | chef-3845a4c7517643963d9fe662eae167b1b62e87ec.tar.gz |
hand cherry-pick json-dos-fix to pl master
Conflicts:
chef/lib/chef/resource.rb
-rw-r--r-- | chef/lib/chef/json_compat.rb | 83 | ||||
-rw-r--r-- | chef/lib/chef/resource.rb | 18 |
2 files changed, 100 insertions, 1 deletions
diff --git a/chef/lib/chef/json_compat.rb b/chef/lib/chef/json_compat.rb index 9f59a41839..f9e669ba24 100644 --- a/chef/lib/chef/json_compat.rb +++ b/chef/lib/chef/json_compat.rb @@ -24,6 +24,19 @@ 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) @@ -38,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) @@ -48,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 bebb3a1d82..931334c5b4 100644 --- a/chef/lib/chef/resource.rb +++ b/chef/lib/chef/resource.rb @@ -119,6 +119,24 @@ F FORBIDDEN_IVARS = [:@run_context, :@node, :@not_if, :@only_if, :@enclosing_provider] HIDDEN_IVARS = [:@allowed_actions, :@resource_name, :@source_line, :@run_context, :@name, :@node, :@not_if, :@only_if, :@elapsed_time, :@enclosing_provider] + # 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 + + include Chef::Mixin::CheckHelper include Chef::Mixin::ParamsValidate include Chef::Mixin::Language |