mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
48 Commits
2.3.14.git
...
multicooki
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc657e4f46 | ||
|
|
f5f3eaa9d8 | ||
|
|
af5825c68f | ||
|
|
55d463eeea | ||
|
|
5f847d2fa7 | ||
|
|
e4652359c3 | ||
|
|
a03cb40ce5 | ||
|
|
9ab900156d | ||
|
|
c6bbe648e8 | ||
|
|
dc6f44fae6 | ||
|
|
76e373c559 | ||
|
|
1d8013e2ce | ||
|
|
cb312a2e76 | ||
|
|
e7be98f40c | ||
|
|
d8f1980343 | ||
|
|
76d83c0d5c | ||
|
|
7335865bd9 | ||
|
|
e43316238d | ||
|
|
c3c6f25ec7 | ||
|
|
331461a65e | ||
|
|
fd05501b4d | ||
|
|
0fa76e01de | ||
|
|
1c215bab58 | ||
|
|
c7238a0746 | ||
|
|
71123b2913 | ||
|
|
2eede7e5ac | ||
|
|
507b8182cf | ||
|
|
3df96518be | ||
|
|
84420c7f12 | ||
|
|
c57e85fd13 | ||
|
|
2eca011798 | ||
|
|
f6cf01337f | ||
|
|
0ad86343c6 | ||
|
|
42524c2bf1 | ||
|
|
46f1ddbff9 | ||
|
|
b18f5c9af1 | ||
|
|
18e9b2ffc9 | ||
|
|
9ec3637bc5 | ||
|
|
ba9248e6e3 | ||
|
|
a27559cddf | ||
|
|
e786726603 | ||
|
|
a1d2a22047 | ||
|
|
d43ecd5b32 | ||
|
|
61359bf6ad | ||
|
|
a2beda1177 | ||
|
|
52c895d565 | ||
|
|
74f90612ec | ||
|
|
a6eb61b7e4 |
@@ -58,5 +58,3 @@ module Net
|
||||
end
|
||||
|
||||
autoload :MailHelper, 'action_mailer/mail_helper'
|
||||
|
||||
require 'action_mailer/vendor/tmail'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -31,7 +31,6 @@ rescue LoadError
|
||||
end
|
||||
end
|
||||
|
||||
gem 'rack', '~> 1.1'
|
||||
require 'rack'
|
||||
require 'action_controller/cgi_ext'
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -189,7 +189,7 @@ module ActionView #:nodoc:
|
||||
|
||||
# :nodoc:
|
||||
def self.xss_safe?
|
||||
false
|
||||
true
|
||||
end
|
||||
|
||||
def self.cache_template_loading?
|
||||
|
||||
21
actionpack/lib/action_view/buffers.rb
Normal file
21
actionpack/lib/action_view/buffers.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -104,7 +104,7 @@ module ActionView
|
||||
# escape_once("<< Accept & Checkout")
|
||||
# # => "<< Accept & 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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&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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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>\">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 < 2 & 3', escape_once('1 < 2 & 3')
|
||||
end
|
||||
|
||||
def test_double_escaping_attributes
|
||||
def test_tag_honors_html_safe_for_param_values
|
||||
['1&2', '1 < 2', '“test“'].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
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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) }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ')
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" })
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
11
activesupport/lib/active_support/core_ext/string/encoding.rb
Normal file
11
activesupport/lib/active_support/core_ext/string/encoding.rb
Normal 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
|
||||
@@ -1,49 +1,94 @@
|
||||
require 'erb'
|
||||
require 'active_support/core_ext/kernel/singleton_class'
|
||||
|
||||
class ERB
|
||||
module Util
|
||||
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
||||
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' }
|
||||
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 > 0 & a < 10?
|
||||
def html_escape(s)
|
||||
s = s.to_s
|
||||
if s.html_safe?
|
||||
s
|
||||
else
|
||||
s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/</, "<").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 & 3')
|
||||
# # => "1 < 2 & 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('<< Accept & Checkout')
|
||||
# # => "<< Accept & 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
|
||||
|
||||
@@ -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
|
||||
|
||||
644
activesupport/lib/active_support/json/backends/okjson.rb
Normal file
644
activesupport/lib/active_support/json/backends/okjson.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user