Compare commits

..

48 Commits

Author SHA1 Message Date
Vicent Marti
bc657e4f46 Damn operators 2013-03-08 14:07:56 +01:00
Vicent Marti
f5f3eaa9d8 ops, load from here 2013-03-08 13:22:38 +01:00
Vicent Marti
af5825c68f Add support for multi-domain cookies
Handles Chrome bug with subdomain cookies
2013-03-08 13:19:20 +01:00
Aman Gupta
55d463eeea merge routing_tricks plugin for subdomain routing 2013-03-05 17:21:59 -08:00
Aman Gupta
5f847d2fa7 fix default to_a deprecation warnings
lib/active_record/associations/association_proxy.rb:215: warning: default `to_a' will be obsolete
2013-03-01 18:03:08 -08:00
Aman Gupta
e4652359c3 fix regression on ruby 1.8 /cc github/rails#4 2013-03-01 17:45:22 -08:00
Aman Gupta
a03cb40ce5 Merge pull request #4 from github/association-fix
Ruby 2.0 compat
2013-03-01 17:06:55 -08:00
Aman Gupta
9ab900156d fix for ruby 2.0
this was manifesting as:

  NoMethodError: undefined method `insert_record' for #<Array:0x007fb0ca5fa7f8>
      gems/activerecord-2.3.14.github17/lib/active_record/associations/association_proxy.rb:149:in `send'

see http://tenderlovemaking.com/2012/09/07/protected-methods-and-ruby-2-0.html
2013-03-01 17:02:58 -08:00
Aman Gupta
c6bbe648e8 whitespace 2013-03-01 17:02:10 -08:00
Aaron Patterson
dc6f44fae6 just use return value of load_target 2013-03-01 17:02:10 -08:00
Aaron Patterson
76e373c559 just call methods on return value of load_target 2013-03-01 17:02:06 -08:00
Aaron Patterson
1d8013e2ce @loaded is defined in initialize, so we should not need this
Conflicts:
	activerecord/lib/active_record/associations/association_proxy.rb
2013-03-01 17:01:29 -08:00
sblackstone
cb312a2e76 Association Proxy should not undefine the default respond_to_missing
Conflicts:
	activerecord/lib/active_record/associations/association_proxy.rb
2013-02-28 18:56:30 -08:00
Aman Gupta
e7be98f40c Revert "fix multiple queries when chaining named scopes"
This reverts commit d43ecd5b32.
2013-02-28 18:50:26 -08:00
Aman Gupta
d8f1980343 remove tmail 2013-02-28 18:49:27 -08:00
Aman Gupta
76d83c0d5c use OutputBuffer#safe_append= for <%== to avoid extra string allocations 2013-02-26 01:07:29 -08:00
Aman Gupta
7335865bd9 avoid method call 2013-02-26 01:06:36 -08:00
Aman Gupta
e43316238d flush whitespace before statements to preserve line numbers 2013-02-25 22:13:11 -08:00
Aman Gupta
c3c6f25ec7 Merge pull request #3 from github/no-escape_once
XSS escaping
2013-02-24 21:47:24 -08:00
Aman Gupta
331461a65e optimize generated erb to reduce method calls
before:

        ');@output_buffer.append= ( content_icon row[:content] );@output_buffer.safe_concat('
');@output_buffer.safe_concat('        ');@output_buffer.append= ( spinner_img );@output_buffer.safe_concat('
');@output_buffer.safe_concat('      </td>
      <td class="content">
        ');@output_buffer.append= ( content_link row[:content] );@output_buffer.safe_concat('
');@output_buffer.safe_concat('      </td>
      <td class="message">
');

after:

        ';@output_buffer.append=( content_icon row[:content] );@output_buffer.safe_append='
        ';@output_buffer.append=( spinner_img );@output_buffer.safe_append='
      </td>
      <td class="content">
        ';@output_buffer.append=( content_link row[:content] );@output_buffer.safe_append='
      </td>
      <td class="message">
';
2013-02-22 02:56:17 -08:00
Aman Gupta
fd05501b4d ActionView::OutputBuffer and Erubis handler from rails 3.2 2013-02-21 06:03:01 -08:00
Aman Gupta
0fa76e01de ActiveSupport::SafeBuffer from upstream rails 3.2 2013-02-21 04:54:58 -08:00
Aman Gupta
1c215bab58 integrate monkey-patches from rails_xss/action_view 2013-02-21 03:20:01 -08:00
Aman Gupta
c7238a0746 faster html escaping code from rails master 2013-02-21 02:44:37 -08:00
Aman Gupta
71123b2913 fix boot on 1.9.3-p385
rails-2.3.14.github14/lib/initializer.rb:906:in `replace': can't modify frozen String (RuntimeError)
  from gems/rails-2.3.14.github14/lib/initializer.rb:906:in `set_root_path!'
  from gems/rails-2.3.14.github14/lib/initializer.rb:860:in `initialize'
  from gems/rails-2.3.14.github14/lib/initializer.rb:110:in `new'
  from gems/rails-2.3.14.github14/lib/initializer.rb:110:in `run'
  from github/config/environment.rb:22:in `<top (required)>'
2013-02-20 02:45:37 -08:00
Xavier Noria
2eede7e5ac s/escape_once/html_escape/, since html safety is the contract that now says whether something has to be escaped
Conflicts:
	actionpack/CHANGELOG
	actionpack/lib/action_view/helpers/form_tag_helper.rb
	actionpack/lib/action_view/helpers/url_helper.rb
	actionpack/test/template/url_helper_test.rb
2013-02-16 20:44:20 -08:00
Xavier Noria
507b8182cf url_for no longer escapes HTML, the :escape option is also gone
Rationale: url_for is just a path/URL generator, it is the responsability of the caller to escape conveniently HTML needs it, JavaScript needs different escaping, a text mail needs no escaping at all, etc.

Backported to 2.3. Conflicts:
	actionpack/CHANGELOG
	actionpack/lib/action_view/helpers/url_helper.rb
	actionpack/test/template/url_helper_test.rb
2013-02-16 20:32:13 -08:00
rizwanreza
3df96518be Allow content_tag options to take an array [#1741 state:resolved] [rizwanreza, Nick Quaranto]
Example:
  content_tag('p', "limelight", :class => ["song", "play"])
  # => <p class="song play">limelight</p>

Signed-off-by: Pratik Naik <pratiknaik@gmail.com>
2013-02-16 20:22:41 -08:00
Aman Gupta
84420c7f12 short-circuit String#blank? when string is empty 2013-02-16 17:06:30 -08:00
Aman Gupta
c57e85fd13 Revert "ignore "invalid byte sequence in UTF-8" from String#=~"
This reverts commit 18e9b2ffc9.
2013-02-16 17:05:59 -08:00
Corey Donohoe
2eca011798 Merge pull request #2 from github/cve-2013-0277
apply patch for cve-2013-0277
2013-02-11 10:51:52 -08:00
Corey Donohoe
f6cf01337f apply patch for cve-2013-0277 2013-02-11 10:47:45 -08:00
Corey Donohoe
0ad86343c6 Merge pull request #1 from github/cve-2013-0333
Backport Patches for CVE-2013-0333
2013-01-28 16:33:32 -08:00
Corey Donohoe
42524c2bf1 backport patches for CVE-2013-0333
https://groups.google.com/forum/?fromgroups=#!topic/rubyonrails-security/1h2DR63ViGo
2013-01-28 13:23:53 -08:00
rick
46f1ddbff9 backport dynamic finder fix (CVE-2012-5664) 2013-01-02 15:02:25 -07:00
Aman Gupta
b18f5c9af1 bump mocha dependency 2012-12-08 20:35:13 -08:00
Aman Gupta
18e9b2ffc9 ignore "invalid byte sequence in UTF-8" from String#=~ 2012-11-15 04:33:45 -08:00
Aman Gupta
9ec3637bc5 backport String#encoding_aware? and String#blank? encoding fixes from rails3 2012-11-13 20:50:29 -08:00
Aman Gupta
ba9248e6e3 Remove call to Kernel#gem 2012-11-09 14:47:38 -08:00
Joshua Peek
a27559cddf Skip primary key check for HABTM inserts 2012-09-05 21:43:48 -05:00
Aman Gupta
e786726603 fix encoding errors inside ActiveSupport::BufferedLogger
http://developer.uservoice.com/blog/2012/03/04/how-to-upgrade-a-rails-2-3-app-to-ruby-1-9-3/
2012-08-17 18:14:52 -07:00
Aman Gupta
a1d2a22047 fix TZInfo on ruby 1.9
http://developer.uservoice.com/blog/2012/03/04/how-to-upgrade-a-rails-2-3-app-to-ruby-1-9-3/
2012-08-16 11:52:43 -07:00
Aman Gupta
d43ecd5b32 fix multiple queries when chaining named scopes
https://rails.lighthouseapp.com/projects/8994/tickets/5410-multiple-database-queries-when-chaining-named-scopes-with-rails-238-and-ruby-192
2012-08-15 17:35:23 -07:00
Aman Gupta
61359bf6ad Use String#encode to do transliteration on ruby 1.9 2012-08-15 11:30:20 -07:00
Aman Gupta
a2beda1177 force binary strings when logging sql statements 2012-08-14 12:52:51 -07:00
Aman Gupta
52c895d565 handle load errors on 1.9
https://github.com/rails/rails/pull/3745
http://groups.google.com/group/rubyonrails-core/browse_thread/thread/81be70a119260e59?pli=1
2012-08-14 12:52:31 -07:00
Aman Gupta
74f90612ec avoid iconv require warning on 1.9 2012-08-14 12:52:01 -07:00
Justin Collins
a6eb61b7e4 Fix SQL injection via nested hashes in conditions 2012-06-12 23:14:10 -07:00
41 changed files with 1125 additions and 214 deletions

View File

@@ -58,5 +58,3 @@ module Net
end
autoload :MailHelper, 'action_mailer/mail_helper'
require 'action_mailer/vendor/tmail'

View File

@@ -79,6 +79,7 @@ spec = Gem::Specification.new do |s|
s.requirements << 'none'
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.add_dependency('erubis', '~> 2.7.0')
s.add_dependency('rack', '~> 1.1')
s.require_path = 'lib'

View File

@@ -31,7 +31,6 @@ rescue LoadError
end
end
gem 'rack', '~> 1.1'
require 'rack'
require 'action_controller/cgi_ext'

View File

@@ -212,6 +212,10 @@ module ActionController
def recognition_conditions
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
result << "conditions[:host] === env[:host]" if conditions[:host]
result << "conditions[:domain] === env[:domain]" if conditions[:domain]
result << "conditions[:subdomain] === env[:subdomain]" if conditions[:subdomain]
result << "conditions[:fullsubdomain] === env[:fullsubdomain]" if conditions[:fullsubdomain]
result
end

View File

@@ -496,7 +496,13 @@ module ActionController
# Subclasses and plugins may override this method to extract further attributes
# from the request, for use by route conditions and such.
def extract_request_environment(request)
{ :method => request.method }
{
:method => request.method,
:host => request.host,
:domain => request.domain,
:subdomain => request.subdomains.first,
:fullsubdomain => request.subdomains.join('.')
}
end
end
end

View File

@@ -151,12 +151,20 @@ module ActionController
current_session_id(env).present?
end
def load_session_cookies(env)
cookies_str = env["HTTP_COOKIE"]
cookies = Rack::Utils.parse_query(cookies_str, ';,')
(Array === cookies[@key]) ? cookies[@key] : [cookies[@key]]
end
def unpacked_cookie_data(env)
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
stale_session_check! do
request = Rack::Request.new(env)
session_data = request.cookies[@key]
unmarshal(session_data) || {}
valid_cookie = nil
load_session_cookies(env).each do |cookie|
break if valid_cookie = unmarshal(cookie)
end
valid_cookie || {}
end
end
end

View File

@@ -37,6 +37,7 @@ module ActionView
end
autoload :Base, 'action_view/base'
autoload :OutputBuffer, 'action_view/buffers'
autoload :Helpers, 'action_view/helpers'
autoload :InlineTemplate, 'action_view/inline_template'
autoload :Partials, 'action_view/partials'
@@ -49,6 +50,8 @@ module ActionView
autoload :TemplateHandler, 'action_view/template_handler'
autoload :TemplateHandlers, 'action_view/template_handlers'
autoload :Helpers, 'action_view/helpers'
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
require 'active_support/core_ext/string/output_safety'

View File

@@ -189,7 +189,7 @@ module ActionView #:nodoc:
# :nodoc:
def self.xss_safe?
false
true
end
def self.cache_template_loading?

View File

@@ -0,0 +1,21 @@
require 'active_support/core_ext/string/output_safety'
module ActionView
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
def initialize(*)
super
end
def <<(value)
return self if value.nil?
super(value.to_s)
end
alias :append= :<<
def safe_concat(value)
return self if value.nil?
super(value.to_s)
end
alias :safe_append= :safe_concat
end
end

View File

@@ -118,13 +118,17 @@ module ActionView
def content_for(name, content = nil, &block)
ivar = "@content_for_#{name}"
content = capture(&block) if block_given?
instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{content}".html_safe)
instance_variable_set(ivar, "#{instance_variable_get(ivar)}#{ERB::Util.html_escape(content)}".html_safe)
nil
end
# Use an alternate output buffer for the duration of the block.
# Defaults to a new empty string.
def with_output_buffer(buf = '') #:nodoc:
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
buf.force_encoding(output_buffer.encoding) if output_buffer.respond_to?(:encoding) && buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer

View File

@@ -442,6 +442,8 @@ module ActionView
def html_options_for_form(url_for_options, options, *parameters_for_url)
options.stringify_keys.tap do |html_options|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
# The following URL is unescaped, this is just a hash of options, and it is the
# responsability of the caller to escape all the values.
html_options["action"] = url_for(url_for_options, *parameters_for_url)
end
end

View File

@@ -464,7 +464,7 @@ module ActionView
url_options = options[:url]
url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash)
function << "'#{escape_javascript(url_for(url_options))}'"
function << "'#{html_escape(escape_javascript(url_for(url_options)))}'"
function << ", #{javascript_options})"
function = "#{options[:before]}; #{function}" if options[:before]

View File

@@ -104,7 +104,7 @@ module ActionView
# escape_once("&lt;&lt; Accept & Checkout")
# # => "&lt;&lt; Accept &amp; Checkout"
def escape_once(html)
ActiveSupport::Multibyte.clean(html.to_s).gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| ERB::Util::HTML_ESCAPE[special] }
ERB::Util.html_escape_once(html)
end
private
@@ -126,22 +126,20 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
"<#{name}#{tag_options}>#{content}</#{name}>".html_safe
"<#{name}#{tag_options}>#{escape ? ERB::Util.html_escape(content) : content}</#{name}>".html_safe
end
def tag_options(options, escape = true)
unless options.blank?
attrs = []
if escape
options.each_pair do |key, value|
if BOOLEAN_ATTRIBUTES.include?(key)
attrs << %(#{key}="#{key}") if value
else
attrs << %(#{key}="#{escape_once(value)}") if !value.nil?
end
options.each_pair do |key, value|
if BOOLEAN_ATTRIBUTES.include?(key)
attrs << %(#{key}="#{key}") if value
elsif !value.nil?
final_value = value.is_a?(Array) ? value.join(" ") : value
final_value = html_escape(final_value) if escape
attrs << %(#{key}="#{final_value}")
end
else
attrs = options.map { |key, value| %(#{key}="#{value}") }
end
" #{attrs.sort * ' '}".html_safe unless attrs.empty?
end

View File

@@ -29,7 +29,7 @@ module ActionView
ActiveSupport::Deprecation.warn("The binding argument of #concat is no longer needed. Please remove it from your views and helpers.", caller)
end
output_buffer.safe_concat(string)
output_buffer << string
end
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
@@ -323,7 +323,7 @@ module ActionView
# # => "<p class='description'>Look ma! A class!</p>"
def simple_format(text, html_options={})
start_tag = tag('p', html_options, true)
text = text.to_s.dup
text = ERB::Util.html_escape(text).to_str.dup
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br

View File

@@ -15,9 +15,6 @@ module ActionView
# <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative /controller/action
# instead of the fully qualified URL like http://example.com/controller/action.
#
# When called from a view, url_for returns an HTML escaped url. If you
# need an unescaped url, pass <tt>:escape => false</tt> in the +options+.
#
# ==== Options
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path.
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified).
@@ -27,7 +24,6 @@ module ActionView
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
# * <tt>:escape</tt> - Determines whether the returned URL will be HTML escaped or not (<tt>true</tt> by default).
#
# ==== Relying on named routes
#
@@ -49,10 +45,7 @@ module ActionView
# <%= url_for(:action => 'play', :anchor => 'player') %>
# # => /messages/play/#player
#
# <%= url_for(:action => 'checkout', :anchor => 'tax&ship') %>
# # => /testing/jump/#tax&amp;ship
#
# <%= url_for(:action => 'checkout', :anchor => 'tax&ship', :escape => false) %>
# <%= url_for(:action => 'jump', :anchor => 'tax&ship') %>
# # => /testing/jump/#tax&ship
#
# <%= url_for(Workshop.new) %>
@@ -77,21 +70,17 @@ module ActionView
options ||= {}
url = case options
when String
escape = true
options
when Hash
options = { :only_path => options[:host].nil? }.update(options.symbolize_keys)
escape = options.key?(:escape) ? options.delete(:escape) : true
@controller.send(:url_for, options)
when :back
escape = false
@controller.request.env["HTTP_REFERER"] || 'javascript:history.back()'
else
escape = false
polymorphic_path(options)
end
escape ? escape_once(url).html_safe : url
url
end
# Creates a link tag of the given +name+ using a URL created by the set
@@ -236,8 +225,8 @@ module ActionView
tag_options = nil
end
href_attr = "href=\"#{url}\"" unless href
"<a #{href_attr}#{tag_options}>#{name || url}</a>".html_safe
href_attr = "href=\"#{html_escape(url)}\"" unless href
"<a #{href_attr}#{tag_options}>#{html_escape(name || url)}</a>".html_safe
end
end
@@ -308,7 +297,7 @@ module ActionView
html_options.merge!("type" => "submit", "value" => name)
("<form method=\"#{form_method}\" action=\"#{escape_once url}\" class=\"button-to\"><div>" +
("<form method=\"#{form_method}\" action=\"#{html_escape(url)}\" class=\"button-to\"><div>" +
method_tag + tag("input", html_options) + request_token_tag + "</div></form>").html_safe
end
@@ -454,28 +443,30 @@ module ActionView
# :subject => "This is an example email"
# # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a>
def mail_to(email_address, name = nil, html_options = {})
email_address = html_escape(email_address)
html_options = html_options.stringify_keys
encode = html_options.delete("encode").to_s
cc, bcc, subject, body = html_options.delete("cc"), html_options.delete("bcc"), html_options.delete("subject"), html_options.delete("body")
string = ''
extras = ''
extras << "cc=#{CGI.escape(cc).gsub("+", "%20")}&" unless cc.nil?
extras << "bcc=#{CGI.escape(bcc).gsub("+", "%20")}&" unless bcc.nil?
extras << "body=#{CGI.escape(body).gsub("+", "%20")}&" unless body.nil?
extras << "subject=#{CGI.escape(subject).gsub("+", "%20")}&" unless subject.nil?
extras = "?" << extras.gsub!(/&?$/,"") unless extras.empty?
extras = []
extras << "cc=#{Rack::Utils.escape(cc).gsub("+", "%20")}" unless cc.nil?
extras << "bcc=#{Rack::Utils.escape(bcc).gsub("+", "%20")}" unless bcc.nil?
extras << "body=#{Rack::Utils.escape(body).gsub("+", "%20")}" unless body.nil?
extras << "subject=#{Rack::Utils.escape(subject).gsub("+", "%20")}" unless subject.nil?
extras = extras.empty? ? '' : '?' + html_escape(extras.join('&'))
email_address_obfuscated = html_escape(email_address)
email_address_obfuscated = email_address.dup
email_address_obfuscated.gsub!(/@/, html_options.delete("replace_at")) if html_options.has_key?("replace_at")
email_address_obfuscated.gsub!(/\./, html_options.delete("replace_dot")) if html_options.has_key?("replace_dot")
string = ''
if encode == "javascript"
html = content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:"+html_escape(email_address)+extras }))
"document.write('#{escape_javascript(html)}');".each_byte do |c|
"document.write('#{content_tag("a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
"<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>".html_safe
elsif encode == "hex"
email_address_encoded = ''
email_address_obfuscated.each_byte do |c|
@@ -489,9 +480,9 @@ module ActionView
char = c.chr
string << (char =~ /\w/ ? sprintf("%%%x", c) : char)
end
content_tag "a", name || email_address_encoded.html_safe, html_options.merge({ "href" => "#{string}#{extras}" })
content_tag "a", name || email_address_encoded.html_safe, html_options.merge("href" => "#{string}#{extras}".html_safe)
else
content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge({ "href" => "mailto:#{email_address}#{extras}" })
content_tag "a", name || email_address_obfuscated.html_safe, html_options.merge("href" => "mailto:#{email_address}#{extras}".html_safe)
end
end
@@ -538,7 +529,7 @@ module ActionView
# current_page?(:controller => 'library', :action => 'checkout')
# # => false
def current_page?(options)
url_string = CGI.unescapeHTML(url_for(options))
url_string = url_for(options)
request = @controller.request
# We ignore any extra parameters in the request_uri if the
# submitted url doesn't have any either. This lets the function

View File

@@ -1,24 +1,99 @@
require 'erubis'
module ActionView
module TemplateHandlers
class Erubis < ::Erubis::Eruby
def add_preamble(src)
@newline_pending = 0
src << "@output_buffer = ActionView::OutputBuffer.new;"
end
def add_text(src, text)
return if text.empty?
if text == "\n"
@newline_pending += 1
else
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
src << "';"
@newline_pending = 0
end
end
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
def add_expr_literal(src, code)
flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
# In rails3, block helpers return strings, so they simply:
#
# src << '@output_buffer.append= ' << code
#
# But in rails2, block helpers use capture. We still want to
# support the new syntax, so we silently convert any
# <%= helper do %> to <% helper do %> for forward compatibility.
add_stmt(src, code)
else
src << '@output_buffer.append=(' << code << ');'
end
end
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
if code =~ BLOCK_EXPR
# src << "@output_buffer.safe_append= " << code
fail '<%== with a block helper not supported before Rails3'
else
src << "@output_buffer.safe_append=(" << code << ");"
end
end
def add_stmt(src, code)
flush_newline_if_pending(src)
super
end
def add_postamble(src)
flush_newline_if_pending(src)
src << '@output_buffer.to_s'
end
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
@newline_pending = 0
end
end
end
class ERB < TemplateHandler
include Compilable
##
# :singleton-method:
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERb documentation for suitable values.
cattr_accessor :erb_trim_mode
self.erb_trim_mode = '-'
# Default implementation used.
cattr_accessor :erb_implementation
self.erb_implementation = Erubis
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
def compile(template)
magic = $1 if template.source =~ /\A(<%#.*coding[:=]\s*(\S+)\s*-?%>)/
erb = "#{magic}<% __in_erb_template=true %>#{template.source}"
erb = "<% __in_erb_template=true %>#{template.source}"
if erb.respond_to?(:force_encoding)
erb.force_encoding(template.source.encoding)
end
::ERB.new(erb, nil, erb_trim_mode, '@output_buffer').src
self.class.erb_implementation.new(
erb,
:trim => (self.class.erb_trim_mode == "-")
).src
end
end
end

View File

@@ -8,8 +8,8 @@ require 'yaml'
require 'stringio'
require 'test/unit'
gem 'mocha', '>= 0.9.7'
require 'mocha'
gem 'mocha', '>= 0.13.1'
require 'mocha/setup'
begin
require 'ruby-debug'

View File

@@ -72,6 +72,19 @@ class TagHelperTest < ActionView::TestCase
assert_equal '<p><b>Hello</b></p>', output_buffer
end
def test_content_tag_with_escaped_array_class
str = content_tag('p', "limelight", :class => ["song", "play>"])
assert_equal "<p class=\"song play&gt;\">limelight</p>", str
str = content_tag('p', "limelight", :class => ["song", "play"])
assert_equal "<p class=\"song play\">limelight</p>", str
end
def test_content_tag_with_unescaped_array_class
str = content_tag('p', "limelight", {:class => ["song", "play>"]}, false)
assert_equal "<p class=\"song play>\">limelight</p>", str
end
def test_cdata_section
assert_equal "<![CDATA[<hello world>]]>", cdata_section("<hello world>")
end
@@ -80,9 +93,9 @@ class TagHelperTest < ActionView::TestCase
assert_equal '1 &lt; 2 &amp; 3', escape_once('1 < 2 &amp; 3')
end
def test_double_escaping_attributes
def test_tag_honors_html_safe_for_param_values
['1&amp;2', '1 &lt; 2', '&#8220;test&#8220;'].each do |escaped|
assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped)
assert_equal %(<a href="#{escaped}" />), tag('a', :href => escaped.html_safe)
end
end

View File

@@ -1,8 +1,8 @@
require 'rubygems'
require 'test/unit'
gem 'mocha', '>= 0.9.3'
require 'mocha'
gem 'mocha', '>= 0.13.1'
require 'mocha/setup'
require 'active_model'
require 'active_model/state_machine'

View File

@@ -345,7 +345,7 @@ module ActiveRecord
protected
def construct_find_options!(options)
end
def load_target
if !@owner.new_record? || foreign_key_present
begin
@@ -395,7 +395,7 @@ module ActiveRecord
end
elsif @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args)
else
else
with_scope(construct_scope) do
if block_given?
@reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }

View File

@@ -49,7 +49,7 @@ module ActiveRecord
alias_method :proxy_respond_to?, :respond_to?
alias_method :proxy_extend, :extend
delegate :to_param, :to => :proxy_target
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id)$|^__|^respond_to_missing|proxy_/ }
def initialize(owner, reflection)
@owner, @reflection = owner, reflection
@@ -82,8 +82,7 @@ module ActiveRecord
# Forwards <tt>===</tt> explicitly to the \target because the instance method
# removal above doesn't catch it. Loads the \target if needed.
def ===(other)
load_target
other === @target
other === load_target
end
# Returns the name of the table of the related class:
@@ -137,16 +136,14 @@ module ActiveRecord
# Forwards the call to the target. Loads the \target if needed.
def inspect
load_target
@target.inspect
load_target.inspect
end
def send(method, *args)
if proxy_respond_to?(method)
if proxy_respond_to?(method, true)
super
else
load_target
@target.send(method, *args)
load_target.send(method, *args)
end
end
@@ -211,7 +208,9 @@ module ActiveRecord
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
if load_target
if @target.respond_to?(method)
if method == :to_a
Array(@target)
elsif @target.respond_to?(method)
@target.send(method, *args, &block)
else
super
@@ -230,8 +229,6 @@ module ActiveRecord
# ActiveRecord::RecordNotFound is rescued within the method, and it is
# not reraised. The proxy is \reset and +nil+ is the return value.
def load_target
return nil unless defined?(@loaded)
if !loaded? and (!@owner.new_record? || foreign_key_present)
@target = find_target
end

View File

@@ -34,17 +34,12 @@ module ActiveRecord
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
options[:select] ||= (@reflection.options[:select] || '*')
end
def count_records
load_target.size
end
def insert_record(record, force = true, validate = true)
if has_primary_key?
raise ActiveRecord::ConfigurationError,
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
end
if record.new_record?
if force
record.save!

View File

@@ -80,9 +80,11 @@ module ActiveRecord
end
unless instance_method_already_implemented?("#{name}=")
if create_time_zone_conversion_attribute?(name, column)
if self.serialized_attributes[name]
define_write_method_for_serialized_attribute(name)
elsif create_time_zone_conversion_attribute?(name, column)
define_write_method_for_time_zone_conversion(name)
else
else
define_write_method(name.to_sym)
end
end
@@ -184,7 +186,20 @@ module ActiveRecord
def define_write_method(attr_name)
evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
end
# Defined for all serialized attributes. Disallows assigning already serialized YAML.
def define_write_method_for_serialized_attribute(attr_name)
method_body = <<-EOV
def #{attr_name}=(value)
if value.is_a?(String) and value =~ /^---/
raise ActiveRecordError, "You tried to assign already serialized content to #{attr_name}. This is disabled due to security issues."
end
write_attribute(:#{attr_name}, value)
end
EOV
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
end
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
def define_write_method_for_time_zone_conversion(attr_name)

View File

@@ -331,7 +331,7 @@ module ActiveRecord
end
# reconstruct the SQL queries now that we know the owner's id
association.send(:construct_sql) if association.respond_to?(:construct_sql)
association.send(:construct_sql) if association.respond_to?(:construct_sql, true)
end
end

View File

@@ -1897,7 +1897,11 @@ module ActiveRecord #:nodoc:
# end
self.class_eval <<-EOS, __FILE__, __LINE__ + 1
def self.#{method_id}(*args)
options = args.extract_options!
options = if args.length > #{attribute_names.size}
args.extract_options!
else
{}
end
attributes = construct_attributes_from_arguments(
[:#{attribute_names.join(',:')}],
args
@@ -2333,17 +2337,17 @@ module ActiveRecord #:nodoc:
# And for value objects on a composed_of relationship:
# { :address => Address.new("123 abc st.", "chicago") }
# # => "address_street='123 abc st.' and address_city='chicago'"
def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name)
def sanitize_sql_hash_for_conditions(attrs, default_table_name = quoted_table_name, top_level = true)
attrs = expand_hash_conditions_for_aggregates(attrs)
conditions = attrs.map do |attr, value|
table_name = default_table_name
unless value.is_a?(Hash)
if not value.is_a?(Hash)
attr = attr.to_s
# Extract table name from qualified attribute names.
if attr.include?('.')
if attr.include?('.') and top_level
attr_table_name, attr = attr.split('.', 2)
attr_table_name = connection.quote_table_name(attr_table_name)
else
@@ -2351,8 +2355,10 @@ module ActiveRecord #:nodoc:
end
attribute_condition("#{attr_table_name}.#{connection.quote_column_name(attr)}", value)
elsif top_level
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s), false)
else
sanitize_sql_hash_for_conditions(value, connection.quote_table_name(attr.to_s))
raise ActiveRecord::StatementInvalid
end
end.join(' AND ')

View File

@@ -195,6 +195,7 @@ module ActiveRecord
def log_info(sql, name, ms)
if @logger && @logger.debug?
name = '%s (%.1fms)' % [name || 'SQL', ms]
sql.force_encoding 'binary' if sql.respond_to?(:force_encoding)
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end

View File

@@ -1499,6 +1499,12 @@ class BasicsTest < ActiveRecord::TestCase
assert_nil topic.content
end
def test_should_raise_exception_on_assigning_aready_serialized_content
topic = Topic.new
serialized_content = %w[foo bar].to_yaml
assert_raise(ActiveRecord::ActiveRecordError) { topic.content = serialized_content }
end
def test_should_raise_exception_on_serialized_attribute_with_type_mismatch
myobj = MyObject.new('value1', 'value2')
topic = Topic.new(:content => myobj)

View File

@@ -363,6 +363,22 @@ class FinderTest < ActiveRecord::TestCase
}
end
def test_hash_condition_find_with_improper_nested_hashes
assert_raise(ActiveRecord::StatementInvalid) {
Company.find(:first, :conditions => { :name => { :companies => { :id => 1 }}})
}
end
def test_hash_condition_find_with_dot_in_nested_column_name
assert_raise(ActiveRecord::StatementInvalid) {
Company.find(:first, :conditions => { :name => { "companies.id" => 1 }})
}
end
def test_hash_condition_find_with_dot_in_column_name_okay
assert Company.find(:first, :conditions => { "companies.id" => 1 })
end
def test_hash_condition_find_with_escaped_characters
Company.create("name" => "Ain't noth'n like' \#stuff")
assert Company.find(:first, :conditions => { :name => "Ain't noth'n like' \#stuff" })

View File

@@ -63,7 +63,11 @@ module ActiveSupport
# If a newline is necessary then create a new message ending with a newline.
# Ensures that the original message is not mutated.
message = "#{message}\n" unless message[-1] == ?\n
buffer << message
if message.respond_to?(:force_encoding)
buffer << message.force_encoding(Encoding.default_external)
else
buffer << message
end
auto_flush
message
end

View File

@@ -20,7 +20,8 @@ class MissingSourceFile < LoadError #:nodoc:
REGEXPS = [
[/^no such file to load -- (.+)$/i, 1],
[/^Missing \w+ (file\s*)?([^\s]+.rb)$/i, 2],
[/^Missing API definition file in (.+)$/i, 1]
[/^Missing API definition file in (.+)$/i, 1],
[/^cannot load such file -- (.+)$/i, 1]
] unless defined?(REGEXPS)
end

View File

@@ -1,3 +1,6 @@
# encoding: utf-8
require 'active_support/core_ext/string/encoding'
class Object
# An object is blank if it's false, empty, or a whitespace string.
# For example, "", " ", +nil+, [], and {} are blank.
@@ -63,9 +66,26 @@ class Hash #:nodoc:
alias_method :blank?, :empty?
end
class String #:nodoc:
class String
# 0x3000: fullwidth whitespace
NON_WHITESPACE_REGEXP = %r![^\s#{[0x3000].pack("U")}]!
# A string is blank if it's empty or contains whitespaces only:
#
# "".blank? # => true
# " ".blank? # => true
# " ".blank? # => true
# " something here ".blank? # => false
#
def blank?
self !~ /\S/
return true if empty?
# 1.8 does not takes [:space:] properly
if encoding_aware?
self !~ /[^[:space:]]/
else
self !~ NON_WHITESPACE_REGEXP
end
end
end

View File

@@ -0,0 +1,11 @@
class String
if defined?(Encoding) && "".respond_to?(:encode)
def encoding_aware?
true
end
else
def encoding_aware?
false
end
end
end

View File

@@ -1,49 +1,94 @@
require 'erb'
require 'active_support/core_ext/kernel/singleton_class'
class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003E', '<' => '\u003C' }
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+));)/
JSON_ESCAPE_REGEXP = /[&"><]/
# A utility method for escaping HTML tag characters.
# This method is also aliased as <tt>h</tt>.
#
# In your ERb templates, use this method to escape any unsafe content. For example:
# In your ERB templates, use this method to escape any unsafe content. For example:
# <%=h @person.name %>
#
# ==== Example:
# puts html_escape("is a > 0 & a < 10?")
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
def html_escape(s)
s = s.to_s
if s.html_safe?
s
else
s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;").html_safe
if RUBY_VERSION > '1.9'
def html_escape(s)
s = s.to_s
if s.html_safe?
s
else
s.gsub(/[&"'><]/, HTML_ESCAPE).html_safe
end
end
else
def html_escape(s)
s = s.to_s
if s.html_safe?
s
else
s.gsub(/[&"'><]/){ |x| HTML_ESCAPE[x] }.html_safe
end
end
end
undef :h
# Aliasing twice issues a warning "discarding old...". Remove first to avoid it.
remove_method(:h)
alias h html_escape
module_function :html_escape
module_function :h
# A utility method for escaping HTML entities in JSON strings.
# This method is also aliased as <tt>j</tt>.
singleton_class.send(:remove_method, :html_escape)
module_function :html_escape
# A utility method for escaping HTML without affecting existing escaped entities.
#
# In your ERb templates, use this method to escape any HTML entities:
# <%=j @person.to_json %>
# html_escape_once('1 < 2 &amp; 3')
# # => "1 &lt; 2 &amp; 3"
#
# ==== Example:
# puts json_escape("is a > 0 & a < 10?")
# # => is a \u003E 0 \u0026 a \u003C 10?
def json_escape(s)
s.to_s.gsub(/[&"><]/) { |special| JSON_ESCAPE[special] }
# html_escape_once('&lt;&lt; Accept & Checkout')
# # => "&lt;&lt; Accept &amp; Checkout"
if RUBY_VERSION > '1.9'
def html_escape_once(s)
result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
s.html_safe? ? result.html_safe : result
end
else
def html_escape_once(s)
result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP) { |special| HTML_ESCAPE[special] }
s.html_safe? ? result.html_safe : result
end
end
module_function :html_escape_once
# A utility method for escaping HTML entities in JSON strings
# using \uXXXX JavaScript escape sequences for string literals:
#
# json_escape('is a > 0 & a < 10?')
# # => is a \u003E 0 \u0026 a \u003C 10?
#
# Note that after this operation is performed the output is not
# valid JSON. In particular double quotes are removed:
#
# json_escape('{"name":"john","created_at":"2010-04-28T01:39:31Z","id":1}')
# # => {name:john,created_at:2010-04-28T01:39:31Z,id:1}
if RUBY_VERSION > '1.9'
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
s.html_safe? ? result.html_safe : result
end
else
def json_escape(s)
result = s.to_s.gsub(JSON_ESCAPE_REGEXP) { |special| JSON_ESCAPE[special] }
s.html_safe? ? result.html_safe : result
end
end
alias j json_escape
module_function :j
module_function :json_escape
end
end
@@ -54,7 +99,7 @@ class Object
end
end
class Fixnum
class Numeric
def html_safe?
true
end
@@ -62,40 +107,114 @@ end
module ActiveSupport #:nodoc:
class SafeBuffer < String
UNSAFE_STRING_METHODS = %w(
capitalize chomp chop delete downcase gsub lstrip next reverse rstrip
slice squeeze strip sub succ swapcase tr tr_s upcase prepend
)
alias_method :original_concat, :concat
private :original_concat
class SafeConcatError < StandardError
def initialize
super 'Could not concatenate to the buffer because it is not html safe.'
end
end
def [](*args)
if args.size < 2
super
else
if html_safe?
new_safe_buffer = super
new_safe_buffer.instance_eval { @html_safe = true }
new_safe_buffer
else
to_str[*args]
end
end
end
def safe_concat(value)
raise SafeConcatError unless @html_safe
original_concat(value)
end
def initialize(*)
@html_safe = true
super
end
def initialize_copy(other)
super
@html_safe = other.html_safe?
end
def clone_empty
self[0, 0]
end
def concat(value)
if !html_safe? || value.html_safe?
super(value)
else
super(ERB::Util.h(value))
end
end
alias << concat
def +(other)
dup.concat(other)
end
def html_safe?
true
def %(args)
args = Array(args).map do |arg|
if !html_safe? || arg.html_safe?
arg
else
ERB::Util.h(arg)
end
end
self.class.new(super(args))
end
def html_safe
self
def html_safe?
defined?(@html_safe) && @html_safe
end
def to_s
self
end
def to_param
to_str
end
def to_yaml(*args)
return super() if defined?(YAML::ENGINE) && !YAML::ENGINE.syck?
to_str.to_yaml(*args)
end
UNSAFE_STRING_METHODS.each do |unsafe_method|
if 'String'.respond_to?(unsafe_method)
class_eval <<-EOT, __FILE__, __LINE__ + 1
def #{unsafe_method}(*args, &block) # def capitalize(*args, &block)
to_str.#{unsafe_method}(*args, &block) # to_str.capitalize(*args, &block)
end # end
def #{unsafe_method}!(*args) # def capitalize!(*args)
@html_safe = false # @html_safe = false
super # super
end # end
EOT
end
end
end
end
class String
alias safe_concat concat
def as_str
self
end
def html_safe
ActiveSupport::SafeBuffer.new(self)
end
def html_safe?
false
end
end

View File

@@ -1,6 +1,6 @@
# encoding: utf-8
require 'singleton'
require 'iconv'
require 'iconv' if RUBY_VERSION < '1.9'
require 'kconv'
module ActiveSupport
@@ -283,8 +283,7 @@ module ActiveSupport
if RUBY_VERSION >= '1.9'
undef_method :transliterate
def transliterate(string)
warn "Ruby 1.9 doesn't support Unicode normalization yet"
string.dup
string.encode("us-ascii", :undef => :replace, :invalid => :replace, :replace => "")
end
# The iconv transliteration code doesn't function correctly

View File

@@ -0,0 +1,644 @@
module ActiveSupport
# Include OkJson as a replacement for the Yaml backend
# encoding: UTF-8
#
# Copyright 2011, 2012 Keith Rarick
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# See https://github.com/kr/okjson for updates.
require 'stringio'
# Some parts adapted from
# http://golang.org/src/pkg/json/decode.go and
# http://golang.org/src/pkg/utf8/utf8.go
module OkJson
Upstream = 'LTD7LBKLZWFF7OZK'
extend self
# Decodes a json document in string s and
# returns the corresponding ruby value.
# String s must be valid UTF-8. If you have
# a string in some other encoding, convert
# it first.
#
# String values in the resulting structure
# will be UTF-8.
def decode(s)
ts = lex(s)
v, ts = textparse(ts)
if ts.length > 0
raise Error, 'trailing garbage'
end
v
end
# Parses a "json text" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
# Note: this is almost the same as valparse,
# except that it does not accept atomic values.
def textparse(ts)
if ts.length < 0
raise Error, 'empty'
end
typ, _, val = ts[0]
case typ
when '{' then objparse(ts)
when '[' then arrparse(ts)
else
raise Error, "unexpected #{val.inspect}"
end
end
# Parses a "value" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def valparse(ts)
if ts.length < 0
raise Error, 'empty'
end
typ, _, val = ts[0]
case typ
when '{' then objparse(ts)
when '[' then arrparse(ts)
when :val,:str then [val, ts[1..-1]]
else
raise Error, "unexpected #{val.inspect}"
end
end
# Parses an "object" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def objparse(ts)
ts = eat('{', ts)
obj = {}
if ts[0][0] == '}'
return obj, ts[1..-1]
end
k, v, ts = pairparse(ts)
obj[k] = v
if ts[0][0] == '}'
return obj, ts[1..-1]
end
loop do
ts = eat(',', ts)
k, v, ts = pairparse(ts)
obj[k] = v
if ts[0][0] == '}'
return obj, ts[1..-1]
end
end
end
# Parses a "member" in the sense of RFC 4627.
# Returns the parsed values and any trailing tokens.
def pairparse(ts)
(typ, _, k), ts = ts[0], ts[1..-1]
if typ != :str
raise Error, "unexpected #{k.inspect}"
end
ts = eat(':', ts)
v, ts = valparse(ts)
[k, v, ts]
end
# Parses an "array" in the sense of RFC 4627.
# Returns the parsed value and any trailing tokens.
def arrparse(ts)
ts = eat('[', ts)
arr = []
if ts[0][0] == ']'
return arr, ts[1..-1]
end
v, ts = valparse(ts)
arr << v
if ts[0][0] == ']'
return arr, ts[1..-1]
end
loop do
ts = eat(',', ts)
v, ts = valparse(ts)
arr << v
if ts[0][0] == ']'
return arr, ts[1..-1]
end
end
end
def eat(typ, ts)
if ts[0][0] != typ
raise Error, "expected #{typ} (got #{ts[0].inspect})"
end
ts[1..-1]
end
# Scans s and returns a list of json tokens,
# excluding white space (as defined in RFC 4627).
def lex(s)
ts = []
while s.length > 0
typ, lexeme, val = tok(s)
if typ == nil
raise Error, "invalid character at #{s[0,10].inspect}"
end
if typ != :space
ts << [typ, lexeme, val]
end
s = s[lexeme.length..-1]
end
ts
end
# Scans the first token in s and
# returns a 3-element list, or nil
# if s does not begin with a valid token.
#
# The first list element is one of
# '{', '}', ':', ',', '[', ']',
# :val, :str, and :space.
#
# The second element is the lexeme.
#
# The third element is the value of the
# token for :val and :str, otherwise
# it is the lexeme.
def tok(s)
case s[0]
when ?{ then ['{', s[0,1], s[0,1]]
when ?} then ['}', s[0,1], s[0,1]]
when ?: then [':', s[0,1], s[0,1]]
when ?, then [',', s[0,1], s[0,1]]
when ?[ then ['[', s[0,1], s[0,1]]
when ?] then [']', s[0,1], s[0,1]]
when ?n then nulltok(s)
when ?t then truetok(s)
when ?f then falsetok(s)
when ?" then strtok(s)
when Spc then [:space, s[0,1], s[0,1]]
when ?\t then [:space, s[0,1], s[0,1]]
when ?\n then [:space, s[0,1], s[0,1]]
when ?\r then [:space, s[0,1], s[0,1]]
else numtok(s)
end
end
def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
def numtok(s)
m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
if m && m.begin(0) == 0
if m[3] && !m[2]
[:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
elsif m[2]
[:val, m[0], Float(m[0])]
else
[:val, m[0], Integer(m[0])]
end
else
[]
end
end
def strtok(s)
m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
if ! m
raise Error, "invalid string literal at #{abbrev(s)}"
end
[:str, m[0], unquote(m[0])]
end
def abbrev(s)
t = s[0,10]
p = t['`']
t = t[0,p] if p
t = t + '...' if t.length < s.length
'`' + t + '`'
end
# Converts a quoted json string literal q into a UTF-8-encoded string.
# The rules are different than for Ruby, so we cannot use eval.
# Unquote will raise an error if q contains control characters.
def unquote(q)
q = q[1...-1]
a = q.dup # allocate a big enough string
rubydoesenc = false
# In ruby >= 1.9, a[w] is a codepoint, not a byte.
if a.class.method_defined?(:force_encoding)
a.force_encoding('UTF-8')
rubydoesenc = true
end
r, w = 0, 0
while r < q.length
c = q[r]
case true
when c == ?\\
r += 1
if r >= q.length
raise Error, "string literal ends with a \"\\\": \"#{q}\""
end
case q[r]
when ?",?\\,?/,?'
a[w] = q[r]
r += 1
w += 1
when ?b,?f,?n,?r,?t
a[w] = Unesc[q[r]]
r += 1
w += 1
when ?u
r += 1
uchar = begin
hexdec4(q[r,4])
rescue RuntimeError => e
raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
end
r += 4
if surrogate? uchar
if q.length >= r+6
uchar1 = hexdec4(q[r+2,4])
uchar = subst(uchar, uchar1)
if uchar != Ucharerr
# A valid pair; consume.
r += 6
end
end
end
if rubydoesenc
a[w] = '' << uchar
w += 1
else
w += ucharenc(a, w, uchar)
end
else
raise Error, "invalid escape char #{q[r]} in \"#{q}\""
end
when c == ?", c < Spc
raise Error, "invalid character in string literal \"#{q}\""
else
# Copy anything else byte-for-byte.
# Valid UTF-8 will remain valid UTF-8.
# Invalid UTF-8 will remain invalid UTF-8.
# In ruby >= 1.9, c is a codepoint, not a byte,
# in which case this is still what we want.
a[w] = c
r += 1
w += 1
end
end
a[0,w]
end
# Encodes unicode character u as UTF-8
# bytes in string a at position i.
# Returns the number of bytes written.
def ucharenc(a, i, u)
case true
when u <= Uchar1max
a[i] = (u & 0xff).chr
1
when u <= Uchar2max
a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
a[i+1] = (Utagx | (u&Umaskx)).chr
2
when u <= Uchar3max
a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
a[i+2] = (Utagx | (u&Umaskx)).chr
3
else
a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
a[i+3] = (Utagx | (u&Umaskx)).chr
4
end
end
def hexdec4(s)
if s.length != 4
raise Error, 'short'
end
(nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
end
def subst(u1, u2)
if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
end
return Ucharerr
end
def surrogate?(u)
Usurr1 <= u && u < Usurr3
end
def nibble(c)
case true
when ?0 <= c && c <= ?9 then c.ord - ?0.ord
when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
else
raise Error, "invalid hex code #{c}"
end
end
# Encodes x into a json text. It may contain only
# Array, Hash, String, Numeric, true, false, nil.
# (Note, this list excludes Symbol.)
# X itself must be an Array or a Hash.
# No other value can be encoded, and an error will
# be raised if x contains any other value, such as
# Nan, Infinity, Symbol, and Proc, or if a Hash key
# is not a String.
# Strings contained in x must be valid UTF-8.
def encode(x)
case x
when Hash then objenc(x)
when Array then arrenc(x)
else
raise Error, 'root value must be an Array or a Hash'
end
end
def valenc(x)
case x
when Hash then objenc(x)
when Array then arrenc(x)
when String then strenc(x)
when Numeric then numenc(x)
when true then "true"
when false then "false"
when nil then "null"
else
raise Error, "cannot encode #{x.class}: #{x.inspect}"
end
end
def objenc(x)
'{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
end
def arrenc(a)
'[' + a.map{|x| valenc(x)}.join(',') + ']'
end
def keyenc(k)
case k
when String then strenc(k)
else
raise Error, "Hash key is not a string: #{k.inspect}"
end
end
def strenc(s)
t = StringIO.new
t.putc(?")
r = 0
# In ruby >= 1.9, s[r] is a codepoint, not a byte.
rubydoesenc = s.class.method_defined?(:encoding)
while r < s.length
case s[r]
when ?" then t.print('\\"')
when ?\\ then t.print('\\\\')
when ?\b then t.print('\\b')
when ?\f then t.print('\\f')
when ?\n then t.print('\\n')
when ?\r then t.print('\\r')
when ?\t then t.print('\\t')
else
c = s[r]
case true
when rubydoesenc
begin
c.ord # will raise an error if c is invalid UTF-8
t.write(c)
rescue
t.write(Ustrerr)
end
when Spc <= c && c <= ?~
t.putc(c)
else
n = ucharcopy(t, s, r) # ensure valid UTF-8 output
r += n - 1 # r is incremented below
end
end
r += 1
end
t.putc(?")
t.string
end
def numenc(x)
if ((x.nan? || x.infinite?) rescue false)
raise Error, "Numeric cannot be represented: #{x}"
end
"#{x}"
end
# Copies the valid UTF-8 bytes of a single character
# from string s at position i to I/O object t, and
# returns the number of bytes copied.
# If no valid UTF-8 char exists at position i,
# ucharcopy writes Ustrerr and returns 1.
def ucharcopy(t, s, i)
n = s.length - i
raise Utf8Error if n < 1
c0 = s[i].ord
# 1-byte, 7-bit sequence?
if c0 < Utagx
t.putc(c0)
return 1
end
raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
raise Utf8Error if n < 2 # need continuation byte
c1 = s[i+1].ord
raise Utf8Error if c1 < Utagx || Utag2 <= c1
# 2-byte, 11-bit sequence?
if c0 < Utag3
raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
t.putc(c0)
t.putc(c1)
return 2
end
# need second continuation byte
raise Utf8Error if n < 3
c2 = s[i+2].ord
raise Utf8Error if c2 < Utagx || Utag2 <= c2
# 3-byte, 16-bit sequence?
if c0 < Utag4
u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
raise Utf8Error if u <= Uchar2max
t.putc(c0)
t.putc(c1)
t.putc(c2)
return 3
end
# need third continuation byte
raise Utf8Error if n < 4
c3 = s[i+3].ord
raise Utf8Error if c3 < Utagx || Utag2 <= c3
# 4-byte, 21-bit sequence?
if c0 < Utag5
u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
raise Utf8Error if u <= Uchar3max
t.putc(c0)
t.putc(c1)
t.putc(c2)
t.putc(c3)
return 4
end
raise Utf8Error
rescue Utf8Error
t.write(Ustrerr)
return 1
end
class Utf8Error < ::StandardError
end
class Error < ::StandardError
end
Utagx = 0x80 # 1000 0000
Utag2 = 0xc0 # 1100 0000
Utag3 = 0xe0 # 1110 0000
Utag4 = 0xf0 # 1111 0000
Utag5 = 0xF8 # 1111 1000
Umaskx = 0x3f # 0011 1111
Umask2 = 0x1f # 0001 1111
Umask3 = 0x0f # 0000 1111
Umask4 = 0x07 # 0000 0111
Uchar1max = (1<<7) - 1
Uchar2max = (1<<11) - 1
Uchar3max = (1<<16) - 1
Ucharerr = 0xFFFD # unicode "replacement char"
Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
Usurrself = 0x10000
Usurr1 = 0xd800
Usurr2 = 0xdc00
Usurr3 = 0xe000
Spc = ' '[0]
Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
end
module JSON
module Backends
module OkJson
ParseError = ::ActiveSupport::OkJson::Error
extend self
# Parses a JSON string or IO and convert it into an object
def decode(json)
if json.respond_to?(:read)
json = json.read
end
data = ActiveSupport::OkJson.decode(json)
if ActiveSupport.parse_json_times
convert_dates_from(data)
else
data
end
end
private
def convert_dates_from(data)
case data
when nil
nil
when DATE_REGEX
begin
DateTime.parse(data)
rescue ArgumentError
data
end
when Array
data.map! { |d| convert_dates_from(d) }
when Hash
data.each do |key, value|
data[key] = convert_dates_from(value)
end
else
data
end
end
end
end
end
end

View File

@@ -7,79 +7,12 @@ module ActiveSupport
ParseError = ::StandardError
extend self
# Converts a JSON string into a Ruby object.
def decode(json)
YAML.load(convert_json_to_yaml(json))
rescue ArgumentError => e
raise ParseError, "Invalid JSON string"
raise "The Yaml backend has been deprecated due to security risks, you should set ActiveSupport::JSON.backend = 'OkJson'"
end
protected
# Ensure that ":" and "," are always followed by a space
def convert_json_to_yaml(json) #:nodoc:
require 'strscan' unless defined? ::StringScanner
scanner, quoting, marks, pos, times = ::StringScanner.new(json), false, [], nil, []
while scanner.scan_until(/(\\['"]|['":,\\]|\\.)/)
case char = scanner[1]
when '"', "'"
if !quoting
quoting = char
pos = scanner.pos
elsif quoting == char
if json[pos..scanner.pos-2] =~ DATE_REGEX
# found a date, track the exact positions of the quotes so we can
# overwrite them with spaces later.
times << pos << scanner.pos
end
quoting = false
end
when ":",","
marks << scanner.pos - 1 unless quoting
when "\\"
scanner.skip(/\\/)
end
end
if marks.empty?
json.gsub(/\\([\\\/]|u[[:xdigit:]]{4})/) do
ustr = $1
if ustr.start_with?('u')
[ustr[1..-1].to_i(16)].pack("U")
elsif ustr == '\\'
'\\\\'
else
ustr
end
end
else
left_pos = [-1].push(*marks)
right_pos = marks << scanner.pos + scanner.rest_size
output = []
left_pos.each_with_index do |left, i|
scanner.pos = left.succ
chunk = scanner.peek(right_pos[i] - scanner.pos + 1)
# overwrite the quotes found around the dates with spaces
while times.size > 0 && times[0] <= right_pos[i]
chunk[times.shift - scanner.pos - 1] = ' '
end
chunk.gsub!(/\\([\\\/]|u[[:xdigit:]]{4})/) do
ustr = $1
if ustr.start_with?('u')
[ustr[1..-1].to_i(16)].pack("U")
elsif ustr == '\\'
'\\\\'
else
ustr
end
end
output << chunk
end
output = output * " "
output.gsub!(/\\\//, '/')
output
end
end
end
end
end

View File

@@ -6,7 +6,7 @@ module ActiveSupport
module JSON
# Listed in order of preference.
DECODERS = %w(Yajl Yaml)
DECODERS = %w(Yajl OkJson)
class << self
attr_reader :parse_error

View File

@@ -5,8 +5,8 @@ require 'active_support/testing/deprecation'
require 'active_support/testing/declarative'
begin
gem 'mocha', ">= 0.9.7"
require 'mocha'
gem 'mocha', ">= 0.13.1"
require 'mocha/setup'
rescue LoadError
# Fake Mocha::ExpectationError so we can rescue it in #run. Bleh.
Object.const_set :Mocha, Module.new

View File

@@ -47,10 +47,31 @@ module TZInfo
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
DateTime.new!(ajd, of, sg)
end
else
elsif DateTime.respond_to? :new0
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
DateTime.new0(ajd, of, sg)
end
else
HALF_DAYS_IN_DAY = rational_new!(1, 2)
def self.datetime_new!(ajd = 0, of = 0, sg = Date::ITALY)
# Convert from an Astronomical Julian Day number to a civil Julian Day number.
jd = ajd + of + HALF_DAYS_IN_DAY
# Ruby trunk revision 31862 changed the behaviour of DateTime.jd so that it will no
# longer accept a fractional civil Julian Day number if further arguments are specified.
# Calculate the hours, minutes and seconds to pass to jd.
jd_i = jd.to_i
jd_i -= 1 if jd < 0
hours = (jd - jd_i) * 24
hours_i = hours.to_i
minutes = (hours - hours_i) * 60
minutes_i = minutes.to_i
seconds = (minutes - minutes_i) * 60
DateTime.jd(jd_i, hours_i, minutes_i, seconds, of, sg)
end
end
end
end
end

View File

@@ -42,9 +42,9 @@ class TestJSONDecoding < ActiveSupport::TestCase
}
# load the default JSON backend
ActiveSupport::JSON.backend = 'Yaml'
ActiveSupport::JSON.backend = 'OkJson'
backends = %w(Yaml)
backends = %w(OkJson)
backends << "JSONGem" if defined?(::JSON)
backends << "Yajl" if defined?(::Yajl)

View File

@@ -903,7 +903,7 @@ Run `rake gems:install` to install the missing gems.
end
Object.const_set(:RELATIVE_RAILS_ROOT, ::RAILS_ROOT.dup) unless defined?(::RELATIVE_RAILS_ROOT)
::RAILS_ROOT.replace @root_path
::RAILS_ROOT.replace @root_path if ::RAILS_ROOT != @root_path
end
# Enable threaded mode. Allows concurrent requests to controller actions and