~5 minutes

ActiveSupport: Humor, Defaults, Security

Reading the source of Rails gives insight into the minds of the contributors over the years. It lets you know how they like to write code (of course), but it also gives you little glimpses into the personalities of particular people who shaped Rails in big and small ways. As a reader, you walk away from comment reading sessions knowing just a little bit more about the code, and the people behind it.

Reading code is fun

For instance, I learned that DHH thinks like a human:

(from array/prepend_and_append.rb)

class Array
  # The human way of thinking about adding stuff to the end of a list is with append
  alias_method :append,  :<<

This is something I really enjoy about the Ruby community: a clever sense of humor and a goal to be nice like Matz. If you’re looking for evidence of ‘nice’, you only need to take a cursory glance at the source comments…very generous in their detail, very helpful.

Hash#fetch for defaults

Moving on, I’ve come across the following pattern a few times…I believe one time was while watching Ruby Tapas episode 12, and the other was probably during a DestroyAllSoftware screencast, but it’s cool enough to highlight every time.

Why do:

  def foo(*args)
    options = args.extract_options!
    options[:rush] = "rules" unless options[:rush]
    # or a bit better syntax but messy to chain
    options[:neil_peart] ||= "best drummer ever"

When you could do? (from class/attribute.rb)

  def class_attribute(*attrs)
    options = attrs.extract_options!
    # double assignment is used to avoid "assigned but unused variable" warning
    instance_reader = instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
    instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
    instance_predicate = options.fetch(:instance_predicate, true)

Hurray for fetch for defaults! Nice and clean…as long as you don’t look at the double assignment thing there. Ignore those ;)

Meta with Module#class_eval

The next little piece of code showcases the dynamic nature of Ruby as well as some metaprogramming essentials. Here a class is opened up and a new class level attribute is shoved onto it (if it doesn’t already exist) and then a method to access said attribute is defined.

(from class/attribute_accessors.rb)

  def cattr_reader(*syms)
    options = syms.extract_options!
    syms.each do |sym|
      raise NameError.new("invalid class attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
        unless defined? @@#{sym}
          @@#{sym} = nil
        end

        def self.#{sym}
          @@#{sym}
        end
      EOS

      unless options[:instance_reader] == false || options[:instance_accessor] == false
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
          def #{sym}
            @@#{sym}
          end
        EOS
      end
    end
  end

For clarity, Module#class_eval takes optional arguments of filename and line number for showing error messages. I was curious about the __FILE__, __LINE__ + 1 first time I saw that, so that’s the reason for it.

Parsing XML Safely

Remember back a few months when all those security CVEs where coming out? There were a few exploits related to arbitrary code execution and a denial of service from parsing XML. Well lo and behold, I just found some of the code that addresses those issues in hash/conversions.rb

  class Hash
    def from_xml(xml, disallowed_types = nil)
      ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
    end

    # Builds a Hash from XML just like <tt>Hash.from_xml</tt>, but also allows Symbol and YAML.
    def from_trusted_xml(xml)
      from_xml xml, []
    end
  end

module ActiveSupport
  class XMLConverter # :nodoc:
    class DisallowedType < StandardError
      def initialize(type)
        super "Disallowed type attribute: #{type.inspect}"
      end
    end

    DISALLOWED_TYPES = %w(symbol yaml)

    def initialize(xml, disallowed_types = nil)
      @xml = normalize_keys(XmlMini.parse(xml))
      @disallowed_types = disallowed_types || DISALLOWED_TYPES
    end

    private
      def process_hash(value)
        if value.include?('type') && !value['type'].is_a?(Hash) && @disallowed_types.include?(value['type'])
          raise DisallowedType, value['type']
        end

Here you can see how the security hole was patched up:

  • fork the from_xml method into a untrusted and trusted version
  • blacklist certain types that could be specified in an XML document
  • raise the DisallowedType exception if we encounter one of those while trying to parse

There’s more to it than the code shown here, but nonetheless, you get the gist. Very approachable solution, I like it.

That’s all for now, still a ways to go in ActiveSupport but I think I’m about half way through the core extensions.

Tagged with: programming

My first novel is coming soon-ish!

Check out Singular