123456789_123456789_123456789_123456789_123456789_

Module: ActiveRecord::Sanitization::ClassMethods

Relationships & Source Files
Defined in: activerecord/lib/active_record/sanitization.rb

Instance Method Summary

Instance Method Details

#disallow_raw_sql!(args, permit: adapter_class.column_name_matcher)

This method is for internal use only.
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 183

def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
  unexpected = nil
  args.each do |arg|
    next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
    (unexpected ||= []) << arg
  end

  if unexpected
    raise(ActiveRecord::UnknownAttributeReference,
      "Dangerous query method (method whose arguments are used as raw " \
      "SQL) called with non-attribute argument(s): " \
      "#{unexpected.map(&:inspect).join(", ")}." \
      "This method should not be called with user-provided values, such as request " \
      "parameters or model attributes. Known-safe values can be passed " \
      "by wrapping them in Arel.sql()."
    )
  end
end

#quote_bound_value(connection, value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 233

def quote_bound_value(connection, value)
  if value.respond_to?(:map) && !value.acts_like?(:string)
    values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
    if values.empty?
      connection.quote(connection.cast_bound_value(nil))
    else
      values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
    end
  else
    value = value.id_for_database if value.respond_to?(:id_for_database)
    connection.quote(connection.cast_bound_value(value))
  end
end

#raise_if_bind_arity_mismatch(statement, expected, provided) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 247

def raise_if_bind_arity_mismatch(statement, expected, provided)
  unless expected == provided
    raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
  end
end

#replace_bind_variable(connection, value) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 211

def replace_bind_variable(connection, value)
  if ActiveRecord::Relation === value
    value.to_sql
  else
    quote_bound_value(connection, value)
  end
end

#replace_bind_variables(connection, statement, values) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 203

def replace_bind_variables(connection, statement, values)
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
  bound = values.dup
  statement.gsub(/\?/) do
    replace_bind_variable(connection, bound.shift)
  end
end

#replace_named_bind_variables(connection, statement, bind_vars) (private)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 219

def replace_named_bind_variables(connection, statement, bind_vars)
  statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
    if $1 == ":" # skip PostgreSQL casts
      match # return the whole match
    elsif $1 == "\\" # escaped literal colon
      match[1..-1] # return match with escaping backlash char removed
    elsif bind_vars.include?(match = $2.to_sym)
      replace_bind_variable(connection, bind_vars[match])
    else
      raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
    end
  end
end

#sanitize_sql(condition)

[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 41

alias :sanitize_sql :sanitize_sql_for_conditions

#sanitize_sql_array(ary)

Accepts an array of conditions. The array has each value sanitized and interpolated into the SQL statement. If using named bind variables in SQL statements where a colon is required verbatim use a backslash to escape.

sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
# => "name='foo''bar' and group_id=4"

sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
# => "name='foo''bar' and group_id=4"

sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
# => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"

sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
# => "name='foo''bar' and group_id='4'"

Note that this sanitization method is not schema-aware, hence won’t do any type casting and will directly use the database adapter’s quote method. For MySQL specifically this means that numeric parameters will be quoted as strings to prevent query manipulation attacks.

sanitize_sql_array(["role = ?", 0])
# => "role = '0'"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 164

def sanitize_sql_array(ary)
  statement, *values = ary
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
    with_connection do |c|
      replace_named_bind_variables(c, statement, values.first)
    end
  elsif statement.include?("?")
    with_connection do |c|
      replace_bind_variables(c, statement, values)
    end
  elsif statement.blank?
    statement
  else
    with_connection do |c|
      statement % values.collect { |value| c.quote_string(value.to_s) }
    end
  end
end

#sanitize_sql_for_assignment(assignments, default_table_name = table_name)

Accepts an array or hash of SQL conditions and sanitizes them into a valid SQL fragment for a SET clause.

sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
# => "name=NULL and group_id=4"

sanitize_sql_for_assignment(["name=:name and group_id=:group_id", name: nil, group_id: 4])
# => "name=NULL and group_id=4"

Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
# => "`posts`.`name` = NULL, {posts}.`group_id` = 4"

This method will NOT sanitize an SQL string since it won’t contain any conditions in it and will return the string as is.

sanitize_sql_for_assignment("name=NULL and group_id='4'")
# => "name=NULL and group_id='4'"

Note that this sanitization method is not schema-aware, hence won’t do any type casting and will directly use the database adapter’s quote method. For MySQL specifically this means that numeric parameters will be quoted as strings to prevent query manipulation attacks.

sanitize_sql_for_assignment(["role = ?", 0])
# => "role = '0'"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 68

def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
  case assignments
  when Array; sanitize_sql_array(assignments)
  when Hash;  sanitize_sql_hash_for_assignment(assignments, default_table_name)
  else        assignments
  end
end

#sanitize_sql_for_conditions(condition) Also known as: #sanitize_sql

Accepts an array of SQL conditions and sanitizes them into a valid SQL fragment for a WHERE clause.

sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
# => "name='foo''bar' and group_id=4"

sanitize_sql_for_conditions(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
# => "name='foo''bar' and group_id='4'"

sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
# => "name='foo''bar' and group_id='4'"

This method will NOT sanitize an SQL string since it won’t contain any conditions in it and will return the string as is.

sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
# => "name='foo''bar' and group_id='4'"

Note that this sanitization method is not schema-aware, hence won’t do any type casting and will directly use the database adapter’s quote method. For MySQL specifically this means that numeric parameters will be quoted as strings to prevent query manipulation attacks.

sanitize_sql_for_conditions(["role = ?", 0])
# => "role = '0'"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 33

def sanitize_sql_for_conditions(condition)
  return nil if condition.blank?

  case condition
  when Array; sanitize_sql_array(condition)
  else        condition
  end
end

#sanitize_sql_for_order(condition)

Accepts an array, or string of SQL conditions and sanitizes them into a valid SQL fragment for an ORDER clause.

sanitize_sql_for_order([Arel.sql("field(id, ?)"), [1,3,2]])
# => "field(id, 1,3,2)"

sanitize_sql_for_order("id ASC")
# => "id ASC"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 84

def sanitize_sql_for_order(condition)
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
    disallow_raw_sql!(
      [condition.first],
      permit: adapter_class.column_name_with_order_matcher
    )

    # Ensure we aren't dealing with a subclass of String that might
    # override methods we use (e.g. Arel::Nodes::SqlLiteral).
    if condition.first.kind_of?(String) && !condition.first.instance_of?(String)
      condition = [String.new(condition.first), *condition[1..-1]]
    end

    Arel.sql(sanitize_sql_array(condition))
  else
    condition
  end
end

#sanitize_sql_hash_for_assignment(attrs, table)

Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.

sanitize_sql_hash_for_assignment({ status: nil, group_id: 1 }, "posts")
# => "`posts`.`status` = NULL, {posts}.`group_id` = 1"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 107

def sanitize_sql_hash_for_assignment(attrs, table)
  with_connection do |c|
    attrs.map do |attr, value|
      type = type_for_attribute(attr)
      value = type.serialize(type.cast(value))
      "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}"
    end.join(", ")
  end
end

#sanitize_sql_like(string, escape_character = "\\")

Sanitizes a string so that it is safe to use within an SQL LIKE statement. This method uses escape_character to escape all occurrences of itself, “_” and “%”.

sanitize_sql_like("100% true!")
# => "100\\% true!"

sanitize_sql_like("snake_cased_string")
# => "snake\\_cased\\_string"

sanitize_sql_like("100% true!", "!")
# => "100!% true!!"

sanitize_sql_like("snake_cased_string", "!")
# => "snake!_cased!_string"
[ GitHub ]

  
# File 'activerecord/lib/active_record/sanitization.rb', line 132

def sanitize_sql_like(string, escape_character = "\")
  if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
    string = string.gsub(escape_character, '\0\0')
  end

  string.gsub(/(?=[%_])/, escape_character)
end