safe_yaml 0.8.6 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +56 -44
- data/lib/safe_yaml.rb +130 -61
- data/lib/safe_yaml/deep.rb +34 -0
- data/lib/safe_yaml/psych_handler.rb +92 -0
- data/lib/safe_yaml/psych_resolver.rb +2 -2
- data/lib/safe_yaml/resolver.rb +16 -15
- data/lib/safe_yaml/safe_to_ruby_visitor.rb +1 -1
- data/lib/safe_yaml/syck_node_monkeypatch.rb +28 -6
- data/lib/safe_yaml/syck_resolver.rb +7 -4
- data/lib/safe_yaml/transform.rb +7 -4
- data/lib/safe_yaml/transform/to_symbol.rb +3 -2
- data/lib/safe_yaml/version.rb +1 -1
- data/spec/resolver_specs.rb +13 -19
- data/spec/safe_yaml_spec.rb +188 -48
- metadata +11 -7
- checksums.yaml +0 -7
    
        data/README.md
    CHANGED
    
    | @@ -3,7 +3,9 @@ SafeYAML | |
| 3 3 |  | 
| 4 4 | 
             
            [](http://travis-ci.org/dtao/safe_yaml)
         | 
| 5 5 |  | 
| 6 | 
            -
            The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the ones  | 
| 6 | 
            +
            The **SafeYAML** gem provides an alternative implementation of `YAML.load` suitable for accepting user input in Ruby applications. Unlike Ruby's built-in implementation of `YAML.load`, SafeYAML's version will not expose apps to arbitrary code execution exploits (such as [the ones discovered](http://www.reddit.com/r/netsec/comments/167c11/serious_vulnerability_in_ruby_on_rails_allowing/) [in Rails in early 2013](http://www.h-online.com/open/news/item/Rails-developers-close-another-extremely-critical-flaw-1793511.html)).
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            **If you encounter any issues with SafeYAML, check out the 'Common Issues' section below.** If you don't see anything that addresses the problem you're experiencing, by all means, [create an issue](https://github.com/dtao/safe_yaml/issues/new)!
         | 
| 7 9 |  | 
| 8 10 | 
             
            Installation
         | 
| 9 11 | 
             
            ------------
         | 
| @@ -20,13 +22,23 @@ Or install it yourself as: | |
| 20 22 |  | 
| 21 23 | 
             
                $ gem install safe_yaml
         | 
| 22 24 |  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            +
            Configuration
         | 
| 26 | 
            +
            -------------
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            Configuring SafeYAML should be quick. In most cases, you will probably only have to think about two things:
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            1. What do you want the `YAML` module's *default* behavior to be? Set the `SafeYAML::OPTIONS[:default_mode]` option to either `:safe` or `:unsafe` to control this. If you do neither, SafeYAML will default to `:safe` mode but will issue a warning the first time you call `YAML.load`.
         | 
| 31 | 
            +
            2. Do you want to allow symbols by default? Set the `SafeYAML::OPTIONS[:deserialize_symbols]` option to `true` or `false` to control this. The default is `false`, which means that SafeYAML will deserialize symbols in YAML documents as strings.
         | 
| 25 32 |  | 
| 26 | 
            -
             | 
| 33 | 
            +
            For more information on these and other options, see the "Usage" section down below.
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            Explanation
         | 
| 36 | 
            +
            -----------
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            Suppose your application were to use a popular open source library which contained code like this:
         | 
| 27 39 |  | 
| 28 40 | 
             
            ```ruby
         | 
| 29 | 
            -
            class  | 
| 41 | 
            +
            class ClassBuilder
         | 
| 30 42 | 
             
              def []=(key, value)
         | 
| 31 43 | 
             
                @class ||= Class.new
         | 
| 32 44 |  | 
| @@ -43,50 +55,47 @@ class ExploitableClassBuilder | |
| 43 55 | 
             
            end
         | 
| 44 56 | 
             
            ```
         | 
| 45 57 |  | 
| 46 | 
            -
            Now, if you were to use `YAML.load` on user input anywhere in your application without the SafeYAML gem installed, an attacker could  | 
| 58 | 
            +
            Now, if you were to use `YAML.load` on user input anywhere in your application without the SafeYAML gem installed, an attacker who suspected you were using this library could send a request with a carefully-crafted YAML string to execute arbitrary code (yes, including `system("unix command")`) on your servers.
         | 
| 47 59 |  | 
| 48 | 
            -
             | 
| 60 | 
            +
            This simple example demonstrates the vulnerability:
         | 
| 49 61 |  | 
| 50 62 | 
             
            ```ruby
         | 
| 51 63 | 
             
            yaml = <<-EOYAML
         | 
| 52 | 
            -
            --- !ruby/hash: | 
| 64 | 
            +
            --- !ruby/hash:ClassBuilder
         | 
| 53 65 | 
             
            "foo; end; puts %(I'm in yr system!); def bar": "baz"
         | 
| 54 66 | 
             
            EOYAML
         | 
| 55 67 | 
             
            ```
         | 
| 56 68 |  | 
| 57 69 | 
             
                > YAML.load(yaml)
         | 
| 58 70 | 
             
                I'm in yr system!
         | 
| 59 | 
            -
                => #< | 
| 71 | 
            +
                => #<ClassBuilder:0x007fdbbe2e25d8 @class=#<Class:0x007fdbbe2e2510>>
         | 
| 60 72 |  | 
| 61 | 
            -
            With SafeYAML,  | 
| 73 | 
            +
            With SafeYAML, the same attacker would be thwarted:
         | 
| 62 74 |  | 
| 63 75 | 
             
                > require "safe_yaml"
         | 
| 64 76 | 
             
                => true
         | 
| 65 | 
            -
                > YAML. | 
| 77 | 
            +
                > YAML.load(yaml, :safe => true)
         | 
| 66 78 | 
             
                => {"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}
         | 
| 67 79 |  | 
| 68 80 | 
             
            Usage
         | 
| 69 81 | 
             
            -----
         | 
| 70 82 |  | 
| 71 | 
            -
            `YAML. | 
| 83 | 
            +
            When you require the safe_yaml gem in your project, `YAML.load` is patched to accept one additional (optional) `options` parameter. This changes the method signature as follows:
         | 
| 72 84 |  | 
| 73 | 
            -
             | 
| 85 | 
            +
            - for Syck and Psych prior to Ruby 1.9.3: `YAML.load(yaml, options={})`
         | 
| 86 | 
            +
            - for Psych in 1.9.3 and later: `YAML.load(yaml, filename=nil, options={})`
         | 
| 74 87 |  | 
| 75 | 
            -
             | 
| 88 | 
            +
            The most important option is the `:safe` option (default: `true`), which controls whether or not to deserialize arbitrary objects when parsing a YAML document. The other options, along with explanations, are as follows.
         | 
| 76 89 |  | 
| 77 | 
            -
             | 
| 78 | 
            -
            # Ruby >= 1.9.3
         | 
| 79 | 
            -
            YAML.load(yaml, filename, :safe => true) # calls safe_load
         | 
| 80 | 
            -
            YAML.load(yaml, filename, :safe => false) # calls unsafe_load
         | 
| 90 | 
            +
            - `:deserialize_symbols` (default: `false`): Controls whether or not YAML will deserialize symbols. It is probably best to only enable this option where necessary, e.g. to make trusted libraries work. Symbols receive special treatment in Ruby and are not garbage collected, which means deserializing them indiscriminately may render your site vulnerable to a DOS attack (hence `false` as a default value).
         | 
| 81 91 |  | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
            ```
         | 
| 92 | 
            +
            - `:whitelisted_tags`: Accepts an array of YAML tags that designate trusted types, e.g., ones that can be deserialized without worrying about any resulting security vulnerabilities. When any of the given tags are encountered in a YAML document, the associated data will be parsed by the underlying YAML engine (Syck or Psych) for the version of Ruby you are using. See the "Whitelisting Trusted Types" section below for more information.
         | 
| 93 | 
            +
             | 
| 94 | 
            +
            - `:custom_initializers`: Similar to the `:whitelisted_tags` option, but allows you to provide your own initializers for specified tags rather than using Syck or Psyck. Accepts a hash with string tags for keys and lambdas for values.
         | 
| 86 95 |  | 
| 87 | 
            -
             | 
| 96 | 
            +
            - `:raise_on_unknown_tag` (default: `false`): Represents the highest possible level of paranoia (not necessarily a bad thing); if the YAML engine encounters any tag other than ones that are automatically trusted by SafeYAML or that you've explicitly whitelisted, it will raise an exception. This may be a good choice if you expect to always be dealing with perfectly safe YAML and want your application to fail loudly upon encountering questionable data.
         | 
| 88 97 |  | 
| 89 | 
            -
             | 
| 98 | 
            +
            All of the above options can be set at the global level via `SafeYAML::OPTIONS`. You can also set each one individually per call to `YAML.load`; an option explicitly passed to `load` will take precedence over an option specified globally.
         | 
| 90 99 |  | 
| 91 100 | 
             
            Supported Types
         | 
| 92 101 | 
             
            ---------------
         | 
| @@ -102,30 +111,36 @@ The way that SafeYAML works is by restricting the kinds of objects that can be d | |
| 102 111 | 
             
            - Booleans
         | 
| 103 112 | 
             
            - Nils
         | 
| 104 113 |  | 
| 105 | 
            -
             | 
| 114 | 
            +
            Again, deserialization of symbols can be enabled globally by setting `SafeYAML::OPTIONS[:deserialize_symbols] = true`, or in a specific call to `YAML.load([some yaml], :deserialize_symbols => true)`.
         | 
| 106 115 |  | 
| 107 116 | 
             
            Whitelisting Trusted Types
         | 
| 108 117 | 
             
            --------------------------
         | 
| 109 118 |  | 
| 110 | 
            -
            SafeYAML  | 
| 119 | 
            +
            SafeYAML supports whitelisting certain YAML tags for trusted types. This is handy when your application uses YAML to serialize and deserialize certain types not listed above, which you know to be free of any deserialization-related vulnerabilities.
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            The easiest way to whitelist types is by calling `SafeYAML.whitelist!`, which can accept a variable number of safe types, e.g.:
         | 
| 122 | 
            +
             | 
| 123 | 
            +
            ```ruby
         | 
| 124 | 
            +
            SafeYAML.whitelist!(FrobDispenser, GobbleFactory)
         | 
| 125 | 
            +
            ```
         | 
| 126 | 
            +
             | 
| 127 | 
            +
            You can also whitelist YAML *tags* via the `:whitelisted_tags` option:
         | 
| 111 128 |  | 
| 112 129 | 
             
            ```ruby
         | 
| 113 | 
            -
            # Using Syck | 
| 130 | 
            +
            # Using Syck
         | 
| 114 131 | 
             
            SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
         | 
| 115 132 |  | 
| 116 133 | 
             
            # Using Psych
         | 
| 117 134 | 
             
            SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
         | 
| 118 135 | 
             
            ```
         | 
| 119 136 |  | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
            **However**, this feature will *not* allow would-be attackers to embed untrusted types within trusted types:
         | 
| 137 | 
            +
            And in case you were wondering: no, this feature will *not* allow would-be attackers to embed untrusted types within trusted types:
         | 
| 123 138 |  | 
| 124 139 | 
             
            ```ruby
         | 
| 125 140 | 
             
            yaml = <<-EOYAML
         | 
| 126 141 | 
             
            --- !ruby/object:OpenStruct 
         | 
| 127 142 | 
             
            table: 
         | 
| 128 | 
            -
              :backdoor: !ruby/hash: | 
| 143 | 
            +
              :backdoor: !ruby/hash:ClassBuilder 
         | 
| 129 144 | 
             
                "foo; end; puts %(I'm in yr system!); def bar": "baz"
         | 
| 130 145 | 
             
            EOYAML
         | 
| 131 146 | 
             
            ```
         | 
| @@ -133,22 +148,17 @@ EOYAML | |
| 133 148 | 
             
                > YAML.safe_load(yaml)
         | 
| 134 149 | 
             
                => #<OpenStruct :backdoor={"foo; end; puts %(I'm in yr system!); def bar"=>"baz"}>
         | 
| 135 150 |  | 
| 136 | 
            -
            You may prefer, rather than quietly sanitizing and accepting YAML documents with unknown tags, to fail loudly when questionable data is encountered. In this case, you can also set the `:raise_on_unknown_tag` option to `true`:
         | 
| 137 | 
            -
             | 
| 138 | 
            -
            ```ruby
         | 
| 139 | 
            -
            SafeYAML::OPTIONS[:raise_on_unknown_tag] = true
         | 
| 140 | 
            -
            ```
         | 
| 141 | 
            -
             | 
| 142 | 
            -
                > YAML.safe_load(yaml)
         | 
| 143 | 
            -
                => RuntimeError: Unknown YAML tag '!ruby/hash:ExploitableClassBuilder'
         | 
| 144 | 
            -
             | 
| 145 | 
            -
            Pretty sweet, right?
         | 
| 146 | 
            -
             | 
| 147 151 | 
             
            Known Issues
         | 
| 148 152 | 
             
            ------------
         | 
| 149 153 |  | 
| 150 | 
            -
             | 
| 154 | 
            +
            If you add SafeYAML to your project and start seeing any errors about missing keys, or you notice mysterious strings that look like `":foo"` (i.e., start with a colon), it's likely you're seeing errors from symbols being saved in YAML format. If you are able to modify the offending code, you might want to consider changing your YAML content to use plain vanilla strings instead of symbols. If not, you may need to set the `:deserialize_symbols` option to `true`, either in calls to `YAML.load` or--as a last resort--globally, with `SafeYAML::OPTIONS[:deserialize_symbols]`.
         | 
| 155 | 
            +
             | 
| 156 | 
            +
            Also be aware that some Ruby libraries, particularly those requiring inter-process communication, leverage YAML's object deserialization functionality and therefore may break or otherwise be impacted by SafeYAML. The following list includes known instances of SafeYAML's interaction with other Ruby gems:
         | 
| 151 157 |  | 
| 158 | 
            +
            - [**ActiveRecord**](https://github.com/rails/rails/tree/master/activerecord): uses YAML to control serialization of model objects using the `serialize` class method. If you find that accessing serialized properties on your ActiveRecord models is causing errors, chances are you may need to:
         | 
| 159 | 
            +
              1. set the `:deserialize_symbols` option to `true`,
         | 
| 160 | 
            +
              2. whitelist some of the types in your serialized data via `SafeYAML.whitelist!` or the `:whitelisted_tags` option, or
         | 
| 161 | 
            +
              3. both
         | 
| 152 162 | 
             
            - [**Guard**](https://github.com/guard/guard): Uses YAML as a serialization format for notifications. The data serialized uses symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` is necessary to allow Guard to work.
         | 
| 153 163 | 
             
            - [**sidekiq**](https://github.com/mperham/sidekiq): Uses a YAML configiuration file with symbolic keys, so setting `SafeYAML::OPTIONS[:deserialize_symbols] = true` should allow it to work.
         | 
| 154 164 |  | 
| @@ -157,7 +167,9 @@ The above list will grow over time, as more issues are discovered. | |
| 157 167 | 
             
            Caveat
         | 
| 158 168 | 
             
            ------
         | 
| 159 169 |  | 
| 160 | 
            -
             | 
| 170 | 
            +
            My intention is to eventually adopt [semantic versioning](http://semver.org/) with this gem, if it ever gets to version 1.0 (i.e., doesn't become obsolete by then). Since it isn't there yet, that means that API may well change from one version to the next. Please keep that in mind if you are using it in your application.
         | 
| 171 | 
            +
             | 
| 172 | 
            +
            To be clear: my *goal* is for SafeYAML to make it as easy as possible to protect existing applications from object deserialization exploits. Any and all feedback is more than welcome!
         | 
| 161 173 |  | 
| 162 174 | 
             
            Requirements
         | 
| 163 175 | 
             
            ------------
         | 
    
        data/lib/safe_yaml.rb
    CHANGED
    
    | @@ -10,46 +10,109 @@ require "safe_yaml/transform/to_nil" | |
| 10 10 | 
             
            require "safe_yaml/transform/to_symbol"
         | 
| 11 11 | 
             
            require "safe_yaml/transform"
         | 
| 12 12 | 
             
            require "safe_yaml/resolver"
         | 
| 13 | 
            +
            require "safe_yaml/deep"
         | 
| 13 14 |  | 
| 14 15 | 
             
            module SafeYAML
         | 
| 15 16 | 
             
              MULTI_ARGUMENT_YAML_LOAD = YAML.method(:load).arity != 1
         | 
| 16 17 | 
             
              YAML_ENGINE = defined?(YAML::ENGINE) ? YAML::ENGINE.yamler : "syck"
         | 
| 17 18 |  | 
| 18 | 
            -
              DEFAULT_OPTIONS = {
         | 
| 19 | 
            +
              DEFAULT_OPTIONS = Deep.freeze({
         | 
| 19 20 | 
             
                :default_mode         => nil,
         | 
| 20 21 | 
             
                :suppress_warnings    => false,
         | 
| 21 22 | 
             
                :deserialize_symbols  => false,
         | 
| 22 23 | 
             
                :whitelisted_tags     => [],
         | 
| 23 24 | 
             
                :custom_initializers  => {},
         | 
| 24 25 | 
             
                :raise_on_unknown_tag => false
         | 
| 25 | 
            -
              } | 
| 26 | 
            +
              })
         | 
| 26 27 |  | 
| 27 | 
            -
              OPTIONS = DEFAULT_OPTIONS | 
| 28 | 
            +
              OPTIONS = Deep.copy(DEFAULT_OPTIONS)
         | 
| 28 29 |  | 
| 29 30 | 
             
              module_function
         | 
| 30 31 | 
             
              def restore_defaults!
         | 
| 31 | 
            -
                OPTIONS.clear.merge!(DEFAULT_OPTIONS)
         | 
| 32 | 
            +
                OPTIONS.clear.merge!(Deep.copy(DEFAULT_OPTIONS))
         | 
| 32 33 | 
             
              end
         | 
| 33 34 |  | 
| 34 | 
            -
              def tag_safety_check!(tag)
         | 
| 35 | 
            +
              def tag_safety_check!(tag, options)
         | 
| 35 36 | 
             
                return if tag.nil?
         | 
| 36 | 
            -
                if  | 
| 37 | 
            +
                if options[:raise_on_unknown_tag] && !options[:whitelisted_tags].include?(tag) && !tag_is_explicitly_trusted?(tag)
         | 
| 37 38 | 
             
                  raise "Unknown YAML tag '#{tag}'"
         | 
| 38 39 | 
             
                end
         | 
| 39 40 | 
             
              end
         | 
| 40 41 |  | 
| 42 | 
            +
              def whitelist!(*classes)
         | 
| 43 | 
            +
                classes.each do |klass|
         | 
| 44 | 
            +
                  whitelist_class!(klass)
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
              def whitelist_class!(klass)
         | 
| 49 | 
            +
                raise "#{klass} not a Class" unless klass.is_a?(::Class)
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                klass_name = klass.name
         | 
| 52 | 
            +
                raise "#{klass} cannot be anonymous" if klass_name.nil? || klass_name.empty?
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                # Whitelist any built-in YAML tags supplied by Syck or Psych.
         | 
| 55 | 
            +
                predefined_tag = predefined_tags[klass]
         | 
| 56 | 
            +
                if predefined_tag
         | 
| 57 | 
            +
                  OPTIONS[:whitelisted_tags] << predefined_tag
         | 
| 58 | 
            +
                  return
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # Exception is exceptional (har har).
         | 
| 62 | 
            +
                tag_class  = klass < Exception ? "exception" : "object"
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                tag_prefix = case YAML_ENGINE
         | 
| 65 | 
            +
                             when "psych" then "!ruby/#{tag_class}"
         | 
| 66 | 
            +
                             when "syck"  then "tag:ruby.yaml.org,2002:#{tag_class}"
         | 
| 67 | 
            +
                             else raise "unknown YAML_ENGINE #{YAML_ENGINE}"
         | 
| 68 | 
            +
                             end
         | 
| 69 | 
            +
                OPTIONS[:whitelisted_tags] << "#{tag_prefix}:#{klass_name}"
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              def predefined_tags
         | 
| 73 | 
            +
                if @predefined_tags.nil?
         | 
| 74 | 
            +
                  @predefined_tags = {}
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  if YAML_ENGINE == "syck"
         | 
| 77 | 
            +
                    YAML.tagged_classes.each do |tag, klass|
         | 
| 78 | 
            +
                      @predefined_tags[klass] = tag
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  else
         | 
| 82 | 
            +
                    # Special tags appear to be hard-coded in Psych:
         | 
| 83 | 
            +
                    # https://github.com/tenderlove/psych/blob/v1.3.4/lib/psych/visitors/to_ruby.rb
         | 
| 84 | 
            +
                    # Fortunately, there aren't many that SafeYAML doesn't already support.
         | 
| 85 | 
            +
                    @predefined_tags.merge!({
         | 
| 86 | 
            +
                      Exception => "!ruby/exception",
         | 
| 87 | 
            +
                      Range     => "!ruby/range",
         | 
| 88 | 
            +
                      Regexp    => "!ruby/regexp",
         | 
| 89 | 
            +
                    })
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                @predefined_tags
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
             | 
| 41 96 | 
             
              if YAML_ENGINE == "psych"
         | 
| 42 97 | 
             
                def tag_is_explicitly_trusted?(tag)
         | 
| 43 98 | 
             
                  false
         | 
| 44 99 | 
             
                end
         | 
| 45 100 |  | 
| 46 101 | 
             
              else
         | 
| 47 | 
            -
                TRUSTED_TAGS = [
         | 
| 48 | 
            -
                  "tag:yaml.org,2002: | 
| 49 | 
            -
                  "tag:yaml.org,2002: | 
| 102 | 
            +
                TRUSTED_TAGS = Set.new([
         | 
| 103 | 
            +
                  "tag:yaml.org,2002:binary",
         | 
| 104 | 
            +
                  "tag:yaml.org,2002:bool#no",
         | 
| 105 | 
            +
                  "tag:yaml.org,2002:bool#yes",
         | 
| 106 | 
            +
                  "tag:yaml.org,2002:float",
         | 
| 50 107 | 
             
                  "tag:yaml.org,2002:float#fix",
         | 
| 108 | 
            +
                  "tag:yaml.org,2002:int",
         | 
| 109 | 
            +
                  "tag:yaml.org,2002:map",
         | 
| 110 | 
            +
                  "tag:yaml.org,2002:null",
         | 
| 111 | 
            +
                  "tag:yaml.org,2002:seq",
         | 
| 112 | 
            +
                  "tag:yaml.org,2002:str",
         | 
| 113 | 
            +
                  "tag:yaml.org,2002:timestamp",
         | 
| 51 114 | 
             
                  "tag:yaml.org,2002:timestamp#ymd"
         | 
| 52 | 
            -
                ].freeze
         | 
| 115 | 
            +
                ]).freeze
         | 
| 53 116 |  | 
| 54 117 | 
             
                def tag_is_explicitly_trusted?(tag)
         | 
| 55 118 | 
             
                  TRUSTED_TAGS.include?(tag)
         | 
| @@ -58,40 +121,62 @@ module SafeYAML | |
| 58 121 | 
             
            end
         | 
| 59 122 |  | 
| 60 123 | 
             
            module YAML
         | 
| 61 | 
            -
              def self.load_with_options(yaml, * | 
| 62 | 
            -
                options | 
| 124 | 
            +
              def self.load_with_options(yaml, *original_arguments)
         | 
| 125 | 
            +
                filename, options = filename_and_options_from_arguments(original_arguments)
         | 
| 63 126 | 
             
                safe_mode = safe_mode_from_options("load", options)
         | 
| 64 127 | 
             
                arguments = [yaml]
         | 
| 65 | 
            -
             | 
| 66 | 
            -
                safe_mode == :safe | 
| 128 | 
            +
             | 
| 129 | 
            +
                if safe_mode == :safe
         | 
| 130 | 
            +
                  arguments << filename if SafeYAML::YAML_ENGINE == "psych"
         | 
| 131 | 
            +
                  arguments << options_for_safe_load(options)
         | 
| 132 | 
            +
                  safe_load(*arguments)
         | 
| 133 | 
            +
                else
         | 
| 134 | 
            +
                  arguments << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
         | 
| 135 | 
            +
                  unsafe_load(*arguments)
         | 
| 136 | 
            +
                end
         | 
| 67 137 | 
             
              end
         | 
| 68 138 |  | 
| 69 139 | 
             
              def self.load_file_with_options(file, options={})
         | 
| 70 140 | 
             
                safe_mode = safe_mode_from_options("load_file", options)
         | 
| 71 | 
            -
                safe_mode == :safe | 
| 141 | 
            +
                if safe_mode == :safe
         | 
| 142 | 
            +
                  safe_load_file(file, options_for_safe_load(options))
         | 
| 143 | 
            +
                else
         | 
| 144 | 
            +
                  unsafe_load_file(file)
         | 
| 145 | 
            +
                end
         | 
| 72 146 | 
             
              end
         | 
| 73 147 |  | 
| 74 148 | 
             
              if SafeYAML::YAML_ENGINE == "psych"
         | 
| 75 | 
            -
                require "safe_yaml/ | 
| 149 | 
            +
                require "safe_yaml/psych_handler"
         | 
| 76 150 | 
             
                require "safe_yaml/psych_resolver"
         | 
| 77 | 
            -
                 | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 151 | 
            +
                require "safe_yaml/safe_to_ruby_visitor"
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                def self.safe_load(yaml, filename=nil, options={})
         | 
| 154 | 
            +
                  # If the user hasn't whitelisted any tags, we can go with this implementation which is
         | 
| 155 | 
            +
                  # significantly faster.
         | 
| 156 | 
            +
                  if (options && options[:whitelisted_tags] || SafeYAML::OPTIONS[:whitelisted_tags]).empty?
         | 
| 157 | 
            +
                    safe_handler = SafeYAML::PsychHandler.new(options)
         | 
| 158 | 
            +
                    arguments_for_parse = [yaml]
         | 
| 159 | 
            +
                    arguments_for_parse << filename if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
         | 
| 160 | 
            +
                    Psych::Parser.new(safe_handler).parse(*arguments_for_parse)
         | 
| 161 | 
            +
                    return safe_handler.result || false
         | 
| 162 | 
            +
             | 
| 81 163 | 
             
                  else
         | 
| 82 | 
            -
                     | 
| 164 | 
            +
                    safe_resolver = SafeYAML::PsychResolver.new(options)
         | 
| 165 | 
            +
                    tree = SafeYAML::MULTI_ARGUMENT_YAML_LOAD ?
         | 
| 166 | 
            +
                      Psych.parse(yaml, filename) :
         | 
| 167 | 
            +
                      Psych.parse(yaml)
         | 
| 168 | 
            +
                    return safe_resolver.resolve_node(tree)
         | 
| 83 169 | 
             
                  end
         | 
| 84 | 
            -
                  return safe_resolver.resolve_node(tree)
         | 
| 85 170 | 
             
                end
         | 
| 86 171 |  | 
| 87 | 
            -
                def self.safe_load_file(filename)
         | 
| 88 | 
            -
                  File.open(filename, 'r:bom|utf-8') { |f| self.safe_load | 
| 172 | 
            +
                def self.safe_load_file(filename, options={})
         | 
| 173 | 
            +
                  File.open(filename, 'r:bom|utf-8') { |f| self.safe_load(f, filename, options) }
         | 
| 89 174 | 
             
                end
         | 
| 90 175 |  | 
| 91 176 | 
             
                def self.unsafe_load_file(filename)
         | 
| 92 177 | 
             
                  if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
         | 
| 93 178 | 
             
                    # https://github.com/tenderlove/psych/blob/v1.3.2/lib/psych.rb#L296-298
         | 
| 94 | 
            -
                    File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load | 
| 179 | 
            +
                    File.open(filename, 'r:bom|utf-8') { |f| self.unsafe_load(f, filename) }
         | 
| 95 180 | 
             
                  else
         | 
| 96 181 | 
             
                    # https://github.com/tenderlove/psych/blob/v1.2.2/lib/psych.rb#L231-233
         | 
| 97 182 | 
             
                    self.unsafe_load File.open(filename)
         | 
| @@ -102,19 +187,19 @@ module YAML | |
| 102 187 | 
             
                require "safe_yaml/syck_resolver"
         | 
| 103 188 | 
             
                require "safe_yaml/syck_node_monkeypatch"
         | 
| 104 189 |  | 
| 105 | 
            -
                def self.safe_load(yaml)
         | 
| 106 | 
            -
                  resolver = SafeYAML::SyckResolver.new
         | 
| 190 | 
            +
                def self.safe_load(yaml, options={})
         | 
| 191 | 
            +
                  resolver = SafeYAML::SyckResolver.new(SafeYAML::OPTIONS.merge(options || {}))
         | 
| 107 192 | 
             
                  tree = YAML.parse(yaml)
         | 
| 108 193 | 
             
                  return resolver.resolve_node(tree)
         | 
| 109 194 | 
             
                end
         | 
| 110 195 |  | 
| 111 | 
            -
                def self.safe_load_file(filename)
         | 
| 112 | 
            -
                  File.open(filename) { |f| self.safe_load | 
| 196 | 
            +
                def self.safe_load_file(filename, options={})
         | 
| 197 | 
            +
                  File.open(filename) { |f| self.safe_load(f, options) }
         | 
| 113 198 | 
             
                end
         | 
| 114 199 |  | 
| 115 200 | 
             
                def self.unsafe_load_file(filename)
         | 
| 116 201 | 
             
                  # https://github.com/indeyets/syck/blob/master/ext/ruby/lib/yaml.rb#L133-135
         | 
| 117 | 
            -
                  File.open(filename) { |f| self.unsafe_load | 
| 202 | 
            +
                  File.open(filename) { |f| self.unsafe_load(f) }
         | 
| 118 203 | 
             
                end
         | 
| 119 204 | 
             
              end
         | 
| 120 205 |  | 
| @@ -123,37 +208,20 @@ module YAML | |
| 123 208 | 
             
                alias_method :load, :load_with_options
         | 
| 124 209 | 
             
                alias_method :load_file, :load_file_with_options
         | 
| 125 210 |  | 
| 126 | 
            -
                 | 
| 127 | 
            -
             | 
| 128 | 
            -
                   | 
| 129 | 
            -
             | 
| 130 | 
            -
             | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
             | 
| 134 | 
            -
                end
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                def disable_symbol_parsing!
         | 
| 137 | 
            -
                  warn_of_deprecated_method("set the SafeYAML::OPTIONS[:deserialize_symbols] option instead")
         | 
| 138 | 
            -
                  SafeYAML::OPTIONS[:deserialize_symbols] = false
         | 
| 139 | 
            -
                end
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                def enable_arbitrary_object_deserialization?
         | 
| 142 | 
            -
                  warn_of_deprecated_method("set the SafeYAML::OPTIONS[:default_mode] to either :safe or :unsafe")
         | 
| 143 | 
            -
                  SafeYAML::OPTIONS[:default_mode] == :unsafe
         | 
| 144 | 
            -
                end
         | 
| 145 | 
            -
             | 
| 146 | 
            -
                def enable_arbitrary_object_deserialization!
         | 
| 147 | 
            -
                  warn_of_deprecated_method("set the SafeYAML::OPTIONS[:default_mode] to either :safe or :unsafe")
         | 
| 148 | 
            -
                  SafeYAML::OPTIONS[:default_mode] = :unsafe
         | 
| 149 | 
            -
                end
         | 
| 211 | 
            +
                private
         | 
| 212 | 
            +
                def filename_and_options_from_arguments(arguments)
         | 
| 213 | 
            +
                  if arguments.count == 1
         | 
| 214 | 
            +
                    if arguments.first.is_a?(String)
         | 
| 215 | 
            +
                      return arguments.first, {}
         | 
| 216 | 
            +
                    else
         | 
| 217 | 
            +
                      return nil, arguments.first || {}
         | 
| 218 | 
            +
                    end
         | 
| 150 219 |  | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
                   | 
| 220 | 
            +
                  else
         | 
| 221 | 
            +
                    return arguments.first, arguments.last || {}
         | 
| 222 | 
            +
                  end
         | 
| 154 223 | 
             
                end
         | 
| 155 224 |  | 
| 156 | 
            -
                private
         | 
| 157 225 | 
             
                def safe_mode_from_options(method, options={})
         | 
| 158 226 | 
             
                  if options[:safe].nil?
         | 
| 159 227 | 
             
                    safe_mode = SafeYAML::OPTIONS[:default_mode] || :safe
         | 
| @@ -167,9 +235,10 @@ module YAML | |
| 167 235 | 
             
                  options[:safe] ? :safe : :unsafe
         | 
| 168 236 | 
             
                end
         | 
| 169 237 |  | 
| 170 | 
            -
                def  | 
| 171 | 
            -
                   | 
| 172 | 
            -
                   | 
| 238 | 
            +
                def options_for_safe_load(base_options)
         | 
| 239 | 
            +
                  options = base_options.dup
         | 
| 240 | 
            +
                  options.delete(:safe)
         | 
| 241 | 
            +
                  options
         | 
| 173 242 | 
             
                end
         | 
| 174 243 | 
             
              end
         | 
| 175 244 | 
             
            end
         | 
| @@ -0,0 +1,34 @@ | |
| 1 | 
            +
            module SafeYAML
         | 
| 2 | 
            +
              class Deep
         | 
| 3 | 
            +
                def self.freeze(object)
         | 
| 4 | 
            +
                  object.each do |*entry|
         | 
| 5 | 
            +
                    value = entry.last
         | 
| 6 | 
            +
                    case value
         | 
| 7 | 
            +
                    when String, Regexp
         | 
| 8 | 
            +
                      value.freeze
         | 
| 9 | 
            +
                    when Enumerable
         | 
| 10 | 
            +
                      Deep.freeze(value)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  return object.freeze
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def self.copy(object)
         | 
| 18 | 
            +
                  duplicate = object.dup rescue object
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  case object
         | 
| 21 | 
            +
                  when Array
         | 
| 22 | 
            +
                    (0...duplicate.count).each do |i|
         | 
| 23 | 
            +
                      duplicate[i] = Deep.copy(duplicate[i])
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  when Hash
         | 
| 26 | 
            +
                    duplicate.keys.each do |key|
         | 
| 27 | 
            +
                      duplicate[key] = Deep.copy(duplicate[key])
         | 
| 28 | 
            +
                    end
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  duplicate
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
            end
         | 
| @@ -0,0 +1,92 @@ | |
| 1 | 
            +
            require "psych"
         | 
| 2 | 
            +
            require "base64"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module SafeYAML
         | 
| 5 | 
            +
              class PsychHandler < Psych::Handler
         | 
| 6 | 
            +
                def initialize(options)
         | 
| 7 | 
            +
                  @options      = SafeYAML::OPTIONS.merge(options || {})
         | 
| 8 | 
            +
                  @initializers = @options[:custom_initializers] || {}
         | 
| 9 | 
            +
                  @anchors      = {}
         | 
| 10 | 
            +
                  @stack        = []
         | 
| 11 | 
            +
                  @current_key  = nil
         | 
| 12 | 
            +
                  @result       = nil
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def result
         | 
| 16 | 
            +
                  @result
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def add_to_current_structure(value, anchor=nil, quoted=nil, tag=nil)
         | 
| 20 | 
            +
                  value = Transform.to_proper_type(value, quoted, tag, @options)
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  @anchors[anchor] = value if anchor
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  if @result.nil?
         | 
| 25 | 
            +
                    @result = value
         | 
| 26 | 
            +
                    @current_structure = @result
         | 
| 27 | 
            +
                    return
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  if @current_structure.respond_to?(:<<)
         | 
| 31 | 
            +
                    @current_structure << value
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  elsif @current_structure.respond_to?(:[]=)
         | 
| 34 | 
            +
                    if @current_key.nil?
         | 
| 35 | 
            +
                      @current_key = value
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                    else
         | 
| 38 | 
            +
                      if @current_key == "<<"
         | 
| 39 | 
            +
                        @current_structure.merge!(value)
         | 
| 40 | 
            +
                      else
         | 
| 41 | 
            +
                        @current_structure[@current_key] = value
         | 
| 42 | 
            +
                      end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                      @current_key = nil
         | 
| 45 | 
            +
                    end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  else
         | 
| 48 | 
            +
                    raise "Don't know how to add to a #{@current_structure.class}!"
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def end_current_structure
         | 
| 53 | 
            +
                  @stack.pop
         | 
| 54 | 
            +
                  @current_structure = @stack.last
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def streaming?
         | 
| 58 | 
            +
                  false
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                # event handlers
         | 
| 62 | 
            +
                def alias(anchor)
         | 
| 63 | 
            +
                  add_to_current_structure(@anchors[anchor])
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def scalar(value, anchor, tag, plain, quoted, style)
         | 
| 67 | 
            +
                  add_to_current_structure(value, anchor, quoted, tag)
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def start_mapping(anchor, tag, implicit, style)
         | 
| 71 | 
            +
                  map = @initializers.include?(tag) ? @initializers[tag].call : {}
         | 
| 72 | 
            +
                  self.add_to_current_structure(map, anchor)
         | 
| 73 | 
            +
                  @current_structure = map
         | 
| 74 | 
            +
                  @stack.push(map)
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def end_mapping
         | 
| 78 | 
            +
                  self.end_current_structure()
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def start_sequence(anchor, tag, implicit, style)
         | 
| 82 | 
            +
                  seq = @initializers.include?(tag) ? @initializers[tag].call : []
         | 
| 83 | 
            +
                  self.add_to_current_structure(seq, anchor)
         | 
| 84 | 
            +
                  @current_structure = seq
         | 
| 85 | 
            +
                  @stack.push(seq)
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def end_sequence
         | 
| 89 | 
            +
                  self.end_current_structure()
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
            end
         | 
    
        data/lib/safe_yaml/resolver.rb
    CHANGED
    
    | @@ -1,16 +1,16 @@ | |
| 1 1 | 
             
            module SafeYAML
         | 
| 2 2 | 
             
              class Resolver
         | 
| 3 | 
            -
                def initialize
         | 
| 4 | 
            -
                  @ | 
| 5 | 
            -
                  @ | 
| 6 | 
            -
                  @ | 
| 3 | 
            +
                def initialize(options)
         | 
| 4 | 
            +
                  @options              = SafeYAML::OPTIONS.merge(options || {})
         | 
| 5 | 
            +
                  @whitelist            = @options[:whitelisted_tags] || []
         | 
| 6 | 
            +
                  @initializers         = @options[:custom_initializers] || {}
         | 
| 7 | 
            +
                  @raise_on_unknown_tag = @options[:raise_on_unknown_tag]
         | 
| 7 8 | 
             
                end
         | 
| 8 9 |  | 
| 9 10 | 
             
                def resolve_node(node)
         | 
| 10 | 
            -
                  if  | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                  
         | 
| 11 | 
            +
                  return node if !node
         | 
| 12 | 
            +
                  return self.native_resolve(node) if tag_is_whitelisted?(self.get_node_tag(node))
         | 
| 13 | 
            +
             | 
| 14 14 | 
             
                  case self.get_node_type(node)
         | 
| 15 15 | 
             
                  when :root
         | 
| 16 16 | 
             
                    resolve_root(node)
         | 
| @@ -28,12 +28,9 @@ module SafeYAML | |
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| 30 30 | 
             
                def resolve_map(node)
         | 
| 31 | 
            -
                  tag | 
| 32 | 
            -
                  return self.native_resolve(node) if tag_is_whitelisted?(tag)
         | 
| 33 | 
            -
             | 
| 31 | 
            +
                  tag  = get_and_check_node_tag(node)
         | 
| 34 32 | 
             
                  hash = @initializers.include?(tag) ? @initializers[tag].call : {}
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  map = normalize_map(self.get_node_value(node))
         | 
| 33 | 
            +
                  map  = normalize_map(self.get_node_value(node))
         | 
| 37 34 |  | 
| 38 35 | 
             
                  # Take the "<<" key nodes first, as these are meant to approximate a form of inheritance.
         | 
| 39 36 | 
             
                  inheritors = map.select { |key_node, value_node| resolve_node(key_node) == "<<" }
         | 
| @@ -59,12 +56,12 @@ module SafeYAML | |
| 59 56 | 
             
                end
         | 
| 60 57 |  | 
| 61 58 | 
             
                def resolve_scalar(node)
         | 
| 62 | 
            -
                  Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), get_and_check_node_tag(node))
         | 
| 59 | 
            +
                  Transform.to_proper_type(self.get_node_value(node), self.value_is_quoted?(node), get_and_check_node_tag(node), @options)
         | 
| 63 60 | 
             
                end
         | 
| 64 61 |  | 
| 65 62 | 
             
                def get_and_check_node_tag(node)
         | 
| 66 63 | 
             
                  tag = self.get_node_tag(node)
         | 
| 67 | 
            -
                  SafeYAML.tag_safety_check!(tag)
         | 
| 64 | 
            +
                  SafeYAML.tag_safety_check!(tag, @options)
         | 
| 68 65 | 
             
                  tag
         | 
| 69 66 | 
             
                end
         | 
| 70 67 |  | 
| @@ -72,6 +69,10 @@ module SafeYAML | |
| 72 69 | 
             
                  @whitelist.include?(tag)
         | 
| 73 70 | 
             
                end
         | 
| 74 71 |  | 
| 72 | 
            +
                def options
         | 
| 73 | 
            +
                  @options
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 75 76 | 
             
                private
         | 
| 76 77 | 
             
                def normalize_map(map)
         | 
| 77 78 | 
             
                  # Syck creates Hashes from maps.
         | 
| @@ -8,7 +8,7 @@ module SafeYAML | |
| 8 8 | 
             
                def accept(node)
         | 
| 9 9 | 
             
                  if node.tag
         | 
| 10 10 | 
             
                    return super if @resolver.tag_is_whitelisted?(node.tag)
         | 
| 11 | 
            -
                    raise "Unknown YAML tag '#{node.tag}'" if  | 
| 11 | 
            +
                    raise "Unknown YAML tag '#{node.tag}'" if @resolver.options[:raise_on_unknown_tag]
         | 
| 12 12 | 
             
                  end
         | 
| 13 13 |  | 
| 14 14 | 
             
                  @resolver.resolve_node(node)
         | 
| @@ -1,12 +1,34 @@ | |
| 1 | 
            +
            # This is, admittedly, pretty insane. Fundamentally the challenge here is this: if we want to allow
         | 
| 2 | 
            +
            # whitelisting of tags (while still leveraging Syck's internal functionality), then we have to
         | 
| 3 | 
            +
            # change how Syck::Node#transform works. But since we (SafeYAML) do not control instantiation of
         | 
| 4 | 
            +
            # Syck::Node objects, we cannot, for example, subclass Syck::Node and override #tranform the "easy"
         | 
| 5 | 
            +
            # way. So the only choice is to monkeypatch, like this. And the only way to make this work
         | 
| 6 | 
            +
            # recursively with potentially call-specific options (that my feeble brain can think of) is to set
         | 
| 7 | 
            +
            # pseudo-global options on the first call and unset them once the recursive stack has fully unwound.
         | 
| 8 | 
            +
             | 
| 1 9 | 
             
            monkeypatch = <<-EORUBY
         | 
| 2 10 | 
             
              class Node
         | 
| 3 | 
            -
                 | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 6 | 
            -
             | 
| 7 | 
            -
                   | 
| 11 | 
            +
                @@safe_transform_depth     = 0
         | 
| 12 | 
            +
                @@safe_transform_whitelist = nil
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def safe_transform(options={})
         | 
| 15 | 
            +
                  begin
         | 
| 16 | 
            +
                    @@safe_transform_depth += 1
         | 
| 17 | 
            +
                    @@safe_transform_whitelist ||= options[:whitelisted_tags]
         | 
| 8 18 |  | 
| 9 | 
            -
             | 
| 19 | 
            +
                    if self.type_id
         | 
| 20 | 
            +
                      SafeYAML.tag_safety_check!(self.type_id, options)
         | 
| 21 | 
            +
                      return unsafe_transform if @@safe_transform_whitelist.include?(self.type_id)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    SafeYAML::SyckResolver.new.resolve_node(self)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  ensure
         | 
| 27 | 
            +
                    @@safe_transform_depth -= 1
         | 
| 28 | 
            +
                    if @@safe_transform_depth == 0
         | 
| 29 | 
            +
                      @@safe_transform_whitelist = nil
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 10 32 | 
             
                end
         | 
| 11 33 |  | 
| 12 34 | 
             
                alias_method :unsafe_transform, :transform
         | 
| @@ -1,6 +1,9 @@ | |
| 1 1 | 
             
            module SafeYAML
         | 
| 2 2 | 
             
              class SyckResolver < Resolver
         | 
| 3 | 
            -
                QUOTE_STYLES = [ | 
| 3 | 
            +
                QUOTE_STYLES = [
         | 
| 4 | 
            +
                  :quote1,
         | 
| 5 | 
            +
                  :quote2
         | 
| 6 | 
            +
                ].freeze
         | 
| 4 7 |  | 
| 5 8 | 
             
                NODE_TYPES = {
         | 
| 6 9 | 
             
                  Hash   => :map,
         | 
| @@ -8,12 +11,12 @@ module SafeYAML | |
| 8 11 | 
             
                  String => :scalar
         | 
| 9 12 | 
             
                }.freeze
         | 
| 10 13 |  | 
| 11 | 
            -
                def initialize
         | 
| 12 | 
            -
                  super | 
| 14 | 
            +
                def initialize(options={})
         | 
| 15 | 
            +
                  super
         | 
| 13 16 | 
             
                end
         | 
| 14 17 |  | 
| 15 18 | 
             
                def native_resolve(node)
         | 
| 16 | 
            -
                  node.transform
         | 
| 19 | 
            +
                  node.transform(self.options)
         | 
| 17 20 | 
             
                end
         | 
| 18 21 |  | 
| 19 22 | 
             
                def get_node_type(node)
         | 
    
        data/lib/safe_yaml/transform.rb
    CHANGED
    
    | @@ -11,12 +11,15 @@ module SafeYAML | |
| 11 11 | 
             
                  Transform::ToDate.new
         | 
| 12 12 | 
             
                ]
         | 
| 13 13 |  | 
| 14 | 
            -
                def self.to_guessed_type(value, quoted=false)
         | 
| 14 | 
            +
                def self.to_guessed_type(value, quoted=false, options=nil)
         | 
| 15 15 | 
             
                  return value if quoted
         | 
| 16 16 |  | 
| 17 17 | 
             
                  if value.is_a?(String)
         | 
| 18 18 | 
             
                    TRANSFORMERS.each do |transformer|
         | 
| 19 | 
            -
                      success, transformed_value = transformer.transform? | 
| 19 | 
            +
                      success, transformed_value = transformer.method(:transform?).arity == 1 ?
         | 
| 20 | 
            +
                        transformer.transform?(value) :
         | 
| 21 | 
            +
                        transformer.transform?(value, options)
         | 
| 22 | 
            +
             | 
| 20 23 | 
             
                      return transformed_value if success
         | 
| 21 24 | 
             
                    end
         | 
| 22 25 | 
             
                  end
         | 
| @@ -24,14 +27,14 @@ module SafeYAML | |
| 24 27 | 
             
                  value
         | 
| 25 28 | 
             
                end
         | 
| 26 29 |  | 
| 27 | 
            -
                def self.to_proper_type(value, quoted=false, tag=nil)
         | 
| 30 | 
            +
                def self.to_proper_type(value, quoted=false, tag=nil, options=nil)
         | 
| 28 31 | 
             
                  case tag
         | 
| 29 32 | 
             
                  when "tag:yaml.org,2002:binary", "x-private:binary", "!binary"
         | 
| 30 33 | 
             
                    decoded = Base64.decode64(value)
         | 
| 31 34 | 
             
                    decoded = decoded.force_encoding(value.encoding) if decoded.respond_to?(:force_encoding)
         | 
| 32 35 | 
             
                    decoded
         | 
| 33 36 | 
             
                  else
         | 
| 34 | 
            -
                    self.to_guessed_type(value, quoted)
         | 
| 37 | 
            +
                    self.to_guessed_type(value, quoted, options)
         | 
| 35 38 | 
             
                  end
         | 
| 36 39 | 
             
                end
         | 
| 37 40 | 
             
              end
         | 
| @@ -3,8 +3,9 @@ module SafeYAML | |
| 3 3 | 
             
                class ToSymbol
         | 
| 4 4 | 
             
                  MATCHER = /\A:"?(\w+)"?\Z/.freeze
         | 
| 5 5 |  | 
| 6 | 
            -
                  def transform?(value)
         | 
| 7 | 
            -
                     | 
| 6 | 
            +
                  def transform?(value, options=nil)
         | 
| 7 | 
            +
                    options ||= SafeYAML::OPTIONS
         | 
| 8 | 
            +
                    return false unless options[:deserialize_symbols] && MATCHER.match(value)
         | 
| 8 9 | 
             
                    return true, $1.to_sym
         | 
| 9 10 | 
             
                  end
         | 
| 10 11 | 
             
                end
         | 
    
        data/lib/safe_yaml/version.rb
    CHANGED
    
    
    
        data/spec/resolver_specs.rb
    CHANGED
    
    | @@ -169,26 +169,20 @@ module ResolverSpecs | |
| 169 169 | 
             
                    end
         | 
| 170 170 | 
             
                  end
         | 
| 171 171 |  | 
| 172 | 
            -
                  # This does in fact appear to be a Ruby version thing as opposed to a YAML engine thing.
         | 
| 173 172 | 
             
                  context "for Ruby version #{RUBY_VERSION}" do
         | 
| 174 | 
            -
                     | 
| 175 | 
            -
                       | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
             | 
| 179 | 
            -
             | 
| 180 | 
            -
                       | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
                       | 
| 186 | 
            -
                       | 
| 187 | 
            -
                        it "applies the same transformation to keys" do
         | 
| 188 | 
            -
                          parse "2013-01-29 05:58:00 -0800: time"
         | 
| 189 | 
            -
                          result.should == { Time.utc(2013, 1, 29, 13, 58, 0) => "time" }
         | 
| 190 | 
            -
                        end
         | 
| 191 | 
            -
                      end
         | 
| 173 | 
            +
                    it "translates valid time values" do
         | 
| 174 | 
            +
                      parse "time: 2013-01-29 05:58:00 -0800"
         | 
| 175 | 
            +
                      result.should == { "time" => Time.utc(2013, 1, 29, 13, 58, 0) }
         | 
| 176 | 
            +
                    end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                    it "applies the same transformation to elements in sequences" do
         | 
| 179 | 
            +
                      parse "- 2013-01-29 05:58:00 -0800"
         | 
| 180 | 
            +
                      result.should == [Time.utc(2013, 1, 29, 13, 58, 0)]
         | 
| 181 | 
            +
                    end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                    it "applies the same transformation to keys" do
         | 
| 184 | 
            +
                      parse "2013-01-29 05:58:00 -0800: time"
         | 
| 185 | 
            +
                      result.should == { Time.utc(2013, 1, 29, 13, 58, 0) => "time" }
         | 
| 192 186 | 
             
                    end
         | 
| 193 187 | 
             
                  end
         | 
| 194 188 |  | 
    
        data/spec/safe_yaml_spec.rb
    CHANGED
    
    | @@ -11,10 +11,23 @@ describe YAML do | |
| 11 11 | 
             
                $VERBOSE = true
         | 
| 12 12 | 
             
              end
         | 
| 13 13 |  | 
| 14 | 
            +
              def safe_load_round_trip(object, options={})
         | 
| 15 | 
            +
                yaml = object.to_yaml
         | 
| 16 | 
            +
                if SafeYAML::YAML_ENGINE == "psych"
         | 
| 17 | 
            +
                  YAML.safe_load(yaml, nil, options)
         | 
| 18 | 
            +
                else
         | 
| 19 | 
            +
                  YAML.safe_load(yaml, options)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 14 23 | 
             
              before :each do
         | 
| 15 24 | 
             
                SafeYAML.restore_defaults!
         | 
| 16 25 | 
             
              end
         | 
| 17 26 |  | 
| 27 | 
            +
              after :each do
         | 
| 28 | 
            +
                SafeYAML.restore_defaults!
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 18 31 | 
             
              describe "unsafe_load" do
         | 
| 19 32 | 
             
                if SafeYAML::YAML_ENGINE == "psych" && RUBY_VERSION >= "1.9.3"
         | 
| 20 33 | 
             
                  it "allows exploits through objects defined in YAML w/ !ruby/hash via custom :[]= methods" do
         | 
| @@ -35,11 +48,7 @@ describe YAML do | |
| 35 48 |  | 
| 36 49 | 
             
                context "with special whitelisted tags defined" do
         | 
| 37 50 | 
             
                  before :each do
         | 
| 38 | 
            -
                     | 
| 39 | 
            -
                      SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
         | 
| 40 | 
            -
                    else
         | 
| 41 | 
            -
                      SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
         | 
| 42 | 
            -
                    end
         | 
| 51 | 
            +
                    SafeYAML::whitelist!(OpenStruct)
         | 
| 43 52 | 
             
                  end
         | 
| 44 53 |  | 
| 45 54 | 
             
                  it "effectively ignores the whitelist (since everything is whitelisted)" do
         | 
| @@ -69,18 +78,27 @@ describe YAML do | |
| 69 78 |  | 
| 70 79 | 
             
                context "for YAML engine #{SafeYAML::YAML_ENGINE}" do
         | 
| 71 80 | 
             
                  if SafeYAML::YAML_ENGINE == "psych"
         | 
| 72 | 
            -
                    let(: | 
| 73 | 
            -
             | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
             | 
| 81 | 
            +
                    let(:options) { nil }
         | 
| 82 | 
            +
                    let(:arguments) { ["foo: bar", nil, options] }
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    context "when no tags are whitelisted" do
         | 
| 85 | 
            +
                      it "constructs a SafeYAML::PsychHandler to resolve nodes as they're parsed, for optimal performance" do
         | 
| 86 | 
            +
                        Psych::Parser.should_receive(:new).with an_instance_of(SafeYAML::PsychHandler)
         | 
| 87 | 
            +
                        # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
         | 
| 88 | 
            +
                        YAML.safe_load(*arguments) rescue nil
         | 
| 77 89 | 
             
                      end
         | 
| 78 | 
            -
                     | 
| 90 | 
            +
                    end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                    context "when whitelisted tags are specified" do
         | 
| 93 | 
            +
                      let(:options) {
         | 
| 94 | 
            +
                        { :whitelisted_tags => ["foo"] }
         | 
| 95 | 
            +
                      }
         | 
| 79 96 |  | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 97 | 
            +
                      it "instead uses Psych to construct a full tree before examining the nodes" do
         | 
| 98 | 
            +
                        Psych.should_receive(:parse)
         | 
| 99 | 
            +
                        # This won't work now; we just want to ensure Psych::Parser#parse was in fact called.
         | 
| 100 | 
            +
                        YAML.safe_load(*arguments) rescue nil
         | 
| 101 | 
            +
                      end
         | 
| 84 102 | 
             
                    end
         | 
| 85 103 | 
             
                  end
         | 
| 86 104 |  | 
| @@ -281,11 +299,7 @@ describe YAML do | |
| 281 299 |  | 
| 282 300 | 
             
                context "with special whitelisted tags defined" do
         | 
| 283 301 | 
             
                  before :each do
         | 
| 284 | 
            -
                     | 
| 285 | 
            -
                      SafeYAML::OPTIONS[:whitelisted_tags] = ["!ruby/object:OpenStruct"]
         | 
| 286 | 
            -
                    else
         | 
| 287 | 
            -
                      SafeYAML::OPTIONS[:whitelisted_tags] = ["tag:ruby.yaml.org,2002:object:OpenStruct"]
         | 
| 288 | 
            -
                    end
         | 
| 302 | 
            +
                    SafeYAML::whitelist!(OpenStruct)
         | 
| 289 303 |  | 
| 290 304 | 
             
                    # Necessary for deserializing OpenStructs properly.
         | 
| 291 305 | 
             
                    SafeYAML::OPTIONS[:deserialize_symbols] = true
         | 
| @@ -372,6 +386,56 @@ describe YAML do | |
| 372 386 | 
             
                    end
         | 
| 373 387 | 
             
                  end
         | 
| 374 388 | 
             
                end
         | 
| 389 | 
            +
             | 
| 390 | 
            +
                context "when options are passed direclty to #load which differ from the defaults" do
         | 
| 391 | 
            +
                  let(:default_options) { {} }
         | 
| 392 | 
            +
             | 
| 393 | 
            +
                  before :each do
         | 
| 394 | 
            +
                    SafeYAML::OPTIONS.merge!(default_options)
         | 
| 395 | 
            +
                  end
         | 
| 396 | 
            +
             | 
| 397 | 
            +
                  context "(for example, when symbol deserialization is enabled by default)" do
         | 
| 398 | 
            +
                    let(:default_options) { { :deserialize_symbols => true } }
         | 
| 399 | 
            +
             | 
| 400 | 
            +
                    it "goes with the default option when it is not overridden" do
         | 
| 401 | 
            +
                      silence_warnings do
         | 
| 402 | 
            +
                        YAML.load(":foo: bar").should == { :foo => "bar" }
         | 
| 403 | 
            +
                      end
         | 
| 404 | 
            +
                    end
         | 
| 405 | 
            +
             | 
| 406 | 
            +
                    it "allows the default option to be overridden on a per-call basis" do
         | 
| 407 | 
            +
                      silence_warnings do
         | 
| 408 | 
            +
                        YAML.load(":foo: bar", :deserialize_symbols => false).should == { ":foo" => "bar" }
         | 
| 409 | 
            +
                        YAML.load(":foo: bar", :deserialize_symbols => true).should == { :foo => "bar" }
         | 
| 410 | 
            +
                      end
         | 
| 411 | 
            +
                    end
         | 
| 412 | 
            +
                  end
         | 
| 413 | 
            +
             | 
| 414 | 
            +
                  context "(or, for example, when certain tags are whitelisted)" do
         | 
| 415 | 
            +
                    let(:default_options) {
         | 
| 416 | 
            +
                      {
         | 
| 417 | 
            +
                        :deserialize_symbols => true,
         | 
| 418 | 
            +
                        :whitelisted_tags => SafeYAML::YAML_ENGINE == "psych" ?
         | 
| 419 | 
            +
                          ["!ruby/object:OpenStruct"] :
         | 
| 420 | 
            +
                          ["tag:ruby.yaml.org,2002:object:OpenStruct"]
         | 
| 421 | 
            +
                      }
         | 
| 422 | 
            +
                    }
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                    it "goes with the default option when it is not overridden" do
         | 
| 425 | 
            +
                      result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
         | 
| 426 | 
            +
                      result.should be_a(OpenStruct)
         | 
| 427 | 
            +
                      result.foo.should == "bar"
         | 
| 428 | 
            +
                    end
         | 
| 429 | 
            +
             | 
| 430 | 
            +
                    it "allows the default option to be overridden on a per-call basis" do
         | 
| 431 | 
            +
                      result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :whitelisted_tags => [])
         | 
| 432 | 
            +
                      result.should == { "table" => { :foo => "bar" } }
         | 
| 433 | 
            +
             | 
| 434 | 
            +
                      result = safe_load_round_trip(OpenStruct.new(:foo => "bar"), :deserialize_symbols => false, :whitelisted_tags => [])
         | 
| 435 | 
            +
                      result.should == { "table" => { ":foo" => "bar" } }
         | 
| 436 | 
            +
                    end
         | 
| 437 | 
            +
                  end
         | 
| 438 | 
            +
                end
         | 
| 375 439 | 
             
              end
         | 
| 376 440 |  | 
| 377 441 | 
             
              describe "unsafe_load_file" do
         | 
| @@ -408,11 +472,13 @@ describe YAML do | |
| 408 472 | 
             
              end
         | 
| 409 473 |  | 
| 410 474 | 
             
              describe "load" do
         | 
| 475 | 
            +
                let(:options) { {} }
         | 
| 476 | 
            +
             | 
| 411 477 | 
             
                let (:arguments) {
         | 
| 412 478 | 
             
                  if SafeYAML::MULTI_ARGUMENT_YAML_LOAD
         | 
| 413 | 
            -
                    ["foo: bar", nil]
         | 
| 479 | 
            +
                    ["foo: bar", nil, options]
         | 
| 414 480 | 
             
                  else
         | 
| 415 | 
            -
                    ["foo: bar"]
         | 
| 481 | 
            +
                    ["foo: bar", options]
         | 
| 416 482 | 
             
                  end
         | 
| 417 483 | 
             
                }
         | 
| 418 484 |  | 
| @@ -430,7 +496,31 @@ describe YAML do | |
| 430 496 | 
             
                  end
         | 
| 431 497 | 
             
                end
         | 
| 432 498 |  | 
| 433 | 
            -
                 | 
| 499 | 
            +
                context "when the :safe options is specified" do
         | 
| 500 | 
            +
                  let(:safe_mode) { true }
         | 
| 501 | 
            +
                  let(:options) { { :safe => safe_mode } }
         | 
| 502 | 
            +
             | 
| 503 | 
            +
                  it "doesn't issue a warning" do
         | 
| 504 | 
            +
                    Kernel.should_not_receive(:warn)
         | 
| 505 | 
            +
                    YAML.load(*arguments)
         | 
| 506 | 
            +
                  end
         | 
| 507 | 
            +
             | 
| 508 | 
            +
                  it "calls #safe_load if the :safe option is set to true" do
         | 
| 509 | 
            +
                    YAML.should_receive(:safe_load)
         | 
| 510 | 
            +
                    YAML.load(*arguments)
         | 
| 511 | 
            +
                  end
         | 
| 512 | 
            +
             | 
| 513 | 
            +
                  context "when the :safe option is set to false" do
         | 
| 514 | 
            +
                    let(:safe_mode) { false }
         | 
| 515 | 
            +
             | 
| 516 | 
            +
                    it "calls #unsafe_load if the :safe option is set to false" do
         | 
| 517 | 
            +
                      YAML.should_receive(:unsafe_load)
         | 
| 518 | 
            +
                      YAML.load(*arguments)
         | 
| 519 | 
            +
                    end
         | 
| 520 | 
            +
                  end
         | 
| 521 | 
            +
                end
         | 
| 522 | 
            +
             | 
| 523 | 
            +
                it "issues a warning when the :safe option is omitted" do
         | 
| 434 524 | 
             
                  silence_warnings do
         | 
| 435 525 | 
             
                    Kernel.should_receive(:warn)
         | 
| 436 526 | 
             
                    YAML.load(*arguments)
         | 
| @@ -444,43 +534,28 @@ describe YAML do | |
| 444 534 | 
             
                  end
         | 
| 445 535 | 
             
                end
         | 
| 446 536 |  | 
| 447 | 
            -
                it "doesn't issue a warning as long as the :safe option is specified" do
         | 
| 448 | 
            -
                  Kernel.should_not_receive(:warn)
         | 
| 449 | 
            -
                  YAML.load(*(arguments + [{:safe => true}]))
         | 
| 450 | 
            -
                end
         | 
| 451 | 
            -
             | 
| 452 537 | 
             
                it "defaults to safe mode if the :safe option is omitted" do
         | 
| 453 538 | 
             
                  silence_warnings do
         | 
| 454 | 
            -
                    YAML.should_receive(:safe_load) | 
| 539 | 
            +
                    YAML.should_receive(:safe_load)
         | 
| 455 540 | 
             
                    YAML.load(*arguments)
         | 
| 456 541 | 
             
                  end
         | 
| 457 542 | 
             
                end
         | 
| 458 543 |  | 
| 459 | 
            -
                 | 
| 460 | 
            -
                  YAML.should_receive(:safe_load).with(*arguments)
         | 
| 461 | 
            -
                  YAML.load(*(arguments + [{:safe => true}]))
         | 
| 462 | 
            -
                end
         | 
| 463 | 
            -
             | 
| 464 | 
            -
                it "calls #unsafe_load if the :safe option is set to false" do
         | 
| 465 | 
            -
                  YAML.should_receive(:unsafe_load).with(*arguments)
         | 
| 466 | 
            -
                  YAML.load(*(arguments + [{:safe => false}]))
         | 
| 467 | 
            -
                end
         | 
| 468 | 
            -
             | 
| 469 | 
            -
                context "with arbitrary object deserialization enabled by default" do
         | 
| 544 | 
            +
                context "with the default mode set to :unsafe" do
         | 
| 470 545 | 
             
                  before :each do
         | 
| 471 546 | 
             
                    SafeYAML::OPTIONS[:default_mode] = :unsafe
         | 
| 472 547 | 
             
                  end
         | 
| 473 548 |  | 
| 474 549 | 
             
                  it "defaults to unsafe mode if the :safe option is omitted" do
         | 
| 475 550 | 
             
                    silence_warnings do
         | 
| 476 | 
            -
                      YAML.should_receive(:unsafe_load) | 
| 551 | 
            +
                      YAML.should_receive(:unsafe_load)
         | 
| 477 552 | 
             
                      YAML.load(*arguments)
         | 
| 478 553 | 
             
                    end
         | 
| 479 554 | 
             
                  end
         | 
| 480 555 |  | 
| 481 556 | 
             
                  it "calls #safe_load if the :safe option is set to true" do
         | 
| 482 | 
            -
                    YAML.should_receive(:safe_load) | 
| 483 | 
            -
                    YAML.load(*(arguments + [{:safe => true}]))
         | 
| 557 | 
            +
                    YAML.should_receive(:safe_load)
         | 
| 558 | 
            +
                    YAML.load(*(arguments + [{ :safe => true }]))
         | 
| 484 559 | 
             
                  end
         | 
| 485 560 | 
             
                end
         | 
| 486 561 | 
             
              end
         | 
| @@ -502,18 +577,18 @@ describe YAML do | |
| 502 577 |  | 
| 503 578 | 
             
                it "defaults to safe mode if the :safe option is omitted" do
         | 
| 504 579 | 
             
                  silence_warnings do
         | 
| 505 | 
            -
                    YAML.should_receive(:safe_load_file) | 
| 580 | 
            +
                    YAML.should_receive(:safe_load_file)
         | 
| 506 581 | 
             
                    YAML.load_file(filename)
         | 
| 507 582 | 
             
                  end
         | 
| 508 583 | 
             
                end
         | 
| 509 584 |  | 
| 510 585 | 
             
                it "calls #safe_load_file if the :safe option is set to true" do
         | 
| 511 | 
            -
                  YAML.should_receive(:safe_load_file) | 
| 586 | 
            +
                  YAML.should_receive(:safe_load_file)
         | 
| 512 587 | 
             
                  YAML.load_file(filename, :safe => true)
         | 
| 513 588 | 
             
                end
         | 
| 514 589 |  | 
| 515 590 | 
             
                it "calls #unsafe_load_file if the :safe option is set to false" do
         | 
| 516 | 
            -
                  YAML.should_receive(:unsafe_load_file) | 
| 591 | 
            +
                  YAML.should_receive(:unsafe_load_file)
         | 
| 517 592 | 
             
                  YAML.load_file(filename, :safe => false)
         | 
| 518 593 | 
             
                end
         | 
| 519 594 |  | 
| @@ -524,15 +599,80 @@ describe YAML do | |
| 524 599 |  | 
| 525 600 | 
             
                  it "defaults to unsafe mode if the :safe option is omitted" do
         | 
| 526 601 | 
             
                    silence_warnings do
         | 
| 527 | 
            -
                      YAML.should_receive(:unsafe_load_file) | 
| 602 | 
            +
                      YAML.should_receive(:unsafe_load_file)
         | 
| 528 603 | 
             
                      YAML.load_file(filename)
         | 
| 529 604 | 
             
                    end
         | 
| 530 605 | 
             
                  end
         | 
| 531 606 |  | 
| 532 607 | 
             
                  it "calls #safe_load if the :safe option is set to true" do
         | 
| 533 | 
            -
                    YAML.should_receive(:safe_load_file) | 
| 608 | 
            +
                    YAML.should_receive(:safe_load_file)
         | 
| 534 609 | 
             
                    YAML.load_file(filename, :safe => true)
         | 
| 535 610 | 
             
                  end
         | 
| 536 611 | 
             
                end
         | 
| 537 612 | 
             
              end
         | 
| 613 | 
            +
             | 
| 614 | 
            +
              describe "whitelist!" do
         | 
| 615 | 
            +
                context "not a class" do
         | 
| 616 | 
            +
                  it "should raise" do
         | 
| 617 | 
            +
                    expect { SafeYAML::whitelist! :foo }.to raise_error(/not a Class/)
         | 
| 618 | 
            +
                    SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
         | 
| 619 | 
            +
                  end
         | 
| 620 | 
            +
                end
         | 
| 621 | 
            +
             | 
| 622 | 
            +
                context "anonymous class" do
         | 
| 623 | 
            +
                  it "should raise" do
         | 
| 624 | 
            +
                    expect { SafeYAML::whitelist! Class.new }.to raise_error(/cannot be anonymous/)
         | 
| 625 | 
            +
                    SafeYAML::OPTIONS[:whitelisted_tags].should be_empty
         | 
| 626 | 
            +
                  end
         | 
| 627 | 
            +
                end
         | 
| 628 | 
            +
             | 
| 629 | 
            +
                context "with a Class as its argument" do
         | 
| 630 | 
            +
                  it "should configure correctly" do
         | 
| 631 | 
            +
                    expect { SafeYAML::whitelist! OpenStruct }.to_not raise_error
         | 
| 632 | 
            +
                    SafeYAML::OPTIONS[:whitelisted_tags].grep(/OpenStruct\Z/).should_not be_empty
         | 
| 633 | 
            +
                  end
         | 
| 634 | 
            +
             | 
| 635 | 
            +
                  it "successfully deserializes the specified class" do
         | 
| 636 | 
            +
                    SafeYAML.whitelist!(OpenStruct)
         | 
| 637 | 
            +
             | 
| 638 | 
            +
                    # necessary for properly assigning OpenStruct attributes
         | 
| 639 | 
            +
                    SafeYAML::OPTIONS[:deserialize_symbols] = true
         | 
| 640 | 
            +
             | 
| 641 | 
            +
                    result = safe_load_round_trip(OpenStruct.new(:foo => "bar"))
         | 
| 642 | 
            +
                    result.should be_a(OpenStruct)
         | 
| 643 | 
            +
                    result.foo.should == "bar"
         | 
| 644 | 
            +
                  end
         | 
| 645 | 
            +
             | 
| 646 | 
            +
                  it "works for ranges" do
         | 
| 647 | 
            +
                    SafeYAML.whitelist!(Range)
         | 
| 648 | 
            +
                    safe_load_round_trip(1..10).should == (1..10)
         | 
| 649 | 
            +
                  end
         | 
| 650 | 
            +
             | 
| 651 | 
            +
                  it "works for regular expressions" do
         | 
| 652 | 
            +
                    SafeYAML.whitelist!(Regexp)
         | 
| 653 | 
            +
                    safe_load_round_trip(/foo/).should == /foo/
         | 
| 654 | 
            +
                  end
         | 
| 655 | 
            +
             | 
| 656 | 
            +
                  it "works for multiple classes" do
         | 
| 657 | 
            +
                    SafeYAML.whitelist!(Range, Regexp)
         | 
| 658 | 
            +
                    safe_load_round_trip([(1..10), /bar/]).should == [(1..10), /bar/]
         | 
| 659 | 
            +
                  end
         | 
| 660 | 
            +
             | 
| 661 | 
            +
                  it "works for arbitrary Exception subclasses" do
         | 
| 662 | 
            +
                    class CustomException < Exception
         | 
| 663 | 
            +
                      attr_reader :custom_message
         | 
| 664 | 
            +
             | 
| 665 | 
            +
                      def initialize(custom_message)
         | 
| 666 | 
            +
                        @custom_message = custom_message
         | 
| 667 | 
            +
                      end
         | 
| 668 | 
            +
                    end
         | 
| 669 | 
            +
             | 
| 670 | 
            +
                    SafeYAML.whitelist!(CustomException)
         | 
| 671 | 
            +
             | 
| 672 | 
            +
                    ex = safe_load_round_trip(CustomException.new("blah"))
         | 
| 673 | 
            +
                    ex.should be_a(CustomException)
         | 
| 674 | 
            +
                    ex.custom_message.should == "blah"
         | 
| 675 | 
            +
                  end
         | 
| 676 | 
            +
                end
         | 
| 677 | 
            +
              end
         | 
| 538 678 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,15 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: safe_yaml
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.9.0
         | 
| 5 | 
            +
              prerelease: 
         | 
| 5 6 | 
             
            platform: ruby
         | 
| 6 7 | 
             
            authors:
         | 
| 7 8 | 
             
            - Dan Tao
         | 
| 8 9 | 
             
            autorequire: 
         | 
| 9 10 | 
             
            bindir: bin
         | 
| 10 11 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2013-03- | 
| 12 | 
            +
            date: 2013-03-23 00:00:00.000000000 Z
         | 
| 12 13 | 
             
            dependencies: []
         | 
| 13 14 | 
             
            description: Parse YAML safely, without that pesky arbitrary object deserialization
         | 
| 14 15 | 
             
              vulnerability
         | 
| @@ -24,9 +25,11 @@ files: | |
| 24 25 | 
             
            - README.md
         | 
| 25 26 | 
             
            - Rakefile
         | 
| 26 27 | 
             
            - lib/safe_yaml.rb
         | 
| 28 | 
            +
            - lib/safe_yaml/deep.rb
         | 
| 27 29 | 
             
            - lib/safe_yaml/parse/date.rb
         | 
| 28 30 | 
             
            - lib/safe_yaml/parse/hexadecimal.rb
         | 
| 29 31 | 
             
            - lib/safe_yaml/parse/sexagesimal.rb
         | 
| 32 | 
            +
            - lib/safe_yaml/psych_handler.rb
         | 
| 30 33 | 
             
            - lib/safe_yaml/psych_resolver.rb
         | 
| 31 34 | 
             
            - lib/safe_yaml/resolver.rb
         | 
| 32 35 | 
             
            - lib/safe_yaml/safe_to_ruby_visitor.rb
         | 
| @@ -58,26 +61,27 @@ files: | |
| 58 61 | 
             
            homepage: http://dtao.github.com/safe_yaml/
         | 
| 59 62 | 
             
            licenses:
         | 
| 60 63 | 
             
            - MIT
         | 
| 61 | 
            -
            metadata: {}
         | 
| 62 64 | 
             
            post_install_message: 
         | 
| 63 65 | 
             
            rdoc_options: []
         | 
| 64 66 | 
             
            require_paths:
         | 
| 65 67 | 
             
            - lib
         | 
| 66 68 | 
             
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 69 | 
            +
              none: false
         | 
| 67 70 | 
             
              requirements:
         | 
| 68 | 
            -
              - - '>='
         | 
| 71 | 
            +
              - - ! '>='
         | 
| 69 72 | 
             
                - !ruby/object:Gem::Version
         | 
| 70 73 | 
             
                  version: 1.8.7
         | 
| 71 74 | 
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 75 | 
            +
              none: false
         | 
| 72 76 | 
             
              requirements:
         | 
| 73 | 
            -
              - - '>='
         | 
| 77 | 
            +
              - - ! '>='
         | 
| 74 78 | 
             
                - !ruby/object:Gem::Version
         | 
| 75 79 | 
             
                  version: '0'
         | 
| 76 80 | 
             
            requirements: []
         | 
| 77 81 | 
             
            rubyforge_project: 
         | 
| 78 | 
            -
            rubygems_version:  | 
| 82 | 
            +
            rubygems_version: 1.8.25
         | 
| 79 83 | 
             
            signing_key: 
         | 
| 80 | 
            -
            specification_version:  | 
| 84 | 
            +
            specification_version: 3
         | 
| 81 85 | 
             
            summary: SameYAML provides an alternative implementation of YAML.load suitable for
         | 
| 82 86 | 
             
              accepting user input in Ruby applications.
         | 
| 83 87 | 
             
            test_files:
         | 
    
        checksums.yaml
    DELETED
    
    | @@ -1,7 +0,0 @@ | |
| 1 | 
            -
            ---
         | 
| 2 | 
            -
            SHA1:
         | 
| 3 | 
            -
              metadata.gz: cacc423c7a4bb7bafd942e34978dc51e317bb483
         | 
| 4 | 
            -
              data.tar.gz: 540a9ddba02abd5993e1f0901907983b4aea7088
         | 
| 5 | 
            -
            SHA512:
         | 
| 6 | 
            -
              metadata.gz: 348b33b5126f29f523580988749fa8c260a560868d07f2bb1dccd5ea723aaf761aa26a2c19be1a8c102092edf4402b780ffaa49090b6c28f721a7e6fc27e3ab4
         | 
| 7 | 
            -
              data.tar.gz: a66a0b50feec1e5db74382340ce36ff1ef909fa27f74f44d702a9e2e131e8285a7700978bc4b8ed6b2e1ab63e960c93fcdce2385bf635c289a9bc14a243b430d
         |