Rails 2.3.3.1
Update to latest Rails. A little bit of jiggery-pokery is involved, since they neglected to re-include vendored Rack in this release.
This commit is contained in:
parent
329fafafce
commit
664552ac02
257 changed files with 4346 additions and 1682 deletions
|
@ -63,7 +63,10 @@ module ActionController
|
|||
|
||||
# Support partial arguments for hash redirections
|
||||
if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
||||
return true if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||
if options.all? {|(key, value)| @response.redirected_to[key] == value}
|
||||
::ActiveSupport::Deprecation.warn("Using assert_redirected_to with partial hash arguments is deprecated. Specify the full set arguments instead", caller)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
redirected_to_after_normalisation = normalize_argument_to_redirection(@response.redirected_to)
|
||||
|
@ -82,6 +85,9 @@ module ActionController
|
|||
# # assert that the "new" view template was rendered
|
||||
# assert_template "new"
|
||||
#
|
||||
# # assert that the "new" view template was rendered with Symbol
|
||||
# assert_template :new
|
||||
#
|
||||
# # assert that the "_customer" partial was rendered twice
|
||||
# assert_template :partial => '_customer', :count => 2
|
||||
#
|
||||
|
@ -91,7 +97,7 @@ module ActionController
|
|||
def assert_template(options = {}, message = nil)
|
||||
clean_backtrace do
|
||||
case options
|
||||
when NilClass, String
|
||||
when NilClass, String, Symbol
|
||||
rendered = @response.rendered[:template].to_s
|
||||
msg = build_message(message,
|
||||
"expecting <?> but rendering with <?>",
|
||||
|
@ -100,7 +106,7 @@ module ActionController
|
|||
if options.nil?
|
||||
@response.rendered[:template].blank?
|
||||
else
|
||||
rendered.to_s.match(options)
|
||||
rendered.to_s.match(options.to_s)
|
||||
end
|
||||
end
|
||||
when Hash
|
||||
|
@ -123,6 +129,8 @@ module ActionController
|
|||
assert @response.rendered[:partials].empty?,
|
||||
"Expected no partials to be rendered"
|
||||
end
|
||||
else
|
||||
raise ArgumentError
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -491,6 +491,10 @@ module ActionController #:nodoc:
|
|||
filtered_parameters[key] = '[FILTERED]'
|
||||
elsif value.is_a?(Hash)
|
||||
filtered_parameters[key] = filter_parameters(value)
|
||||
elsif value.is_a?(Array)
|
||||
filtered_parameters[key] = value.collect do |item|
|
||||
filter_parameters(item)
|
||||
end
|
||||
elsif block_given?
|
||||
key = key.dup
|
||||
value = value.dup if value
|
||||
|
@ -950,8 +954,9 @@ module ActionController #:nodoc:
|
|||
response.content_type ||= Mime::JS
|
||||
render_for_text(js, options[:status])
|
||||
|
||||
elsif json = options[:json]
|
||||
json = json.to_json unless json.is_a?(String)
|
||||
elsif options.include?(:json)
|
||||
json = options[:json]
|
||||
json = ActiveSupport::JSON.encode(json) unless json.is_a?(String)
|
||||
json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
|
||||
response.content_type ||= Mime::JSON
|
||||
render_for_text(json, options[:status])
|
||||
|
|
|
@ -27,7 +27,7 @@ module ActionController #:nodoc:
|
|||
autoload :Actions, 'action_controller/caching/actions'
|
||||
autoload :Fragments, 'action_controller/caching/fragments'
|
||||
autoload :Pages, 'action_controller/caching/pages'
|
||||
autoload :Sweeper, 'action_controller/caching/sweeping'
|
||||
autoload :Sweeper, 'action_controller/caching/sweeper'
|
||||
autoload :Sweeping, 'action_controller/caching/sweeping'
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
|
|
|
@ -61,7 +61,9 @@ module ActionController #:nodoc:
|
|||
filter_options = { :only => actions, :if => options.delete(:if), :unless => options.delete(:unless) }
|
||||
|
||||
cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path), :store_options => options)
|
||||
around_filter(cache_filter, filter_options)
|
||||
around_filter(filter_options) do |controller, action|
|
||||
cache_filter.filter(controller, action)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -83,6 +85,12 @@ module ActionController #:nodoc:
|
|||
@options = options
|
||||
end
|
||||
|
||||
def filter(controller, action)
|
||||
should_continue = before(controller)
|
||||
action.call if should_continue
|
||||
after(controller)
|
||||
end
|
||||
|
||||
def before(controller)
|
||||
cache_path = ActionCachePath.new(controller, path_options_for(controller, @options.slice(:cache_path)))
|
||||
if cache = controller.read_fragment(cache_path.path, @options[:store_options])
|
||||
|
|
45
vendor/rails/actionpack/lib/action_controller/caching/sweeper.rb
vendored
Normal file
45
vendor/rails/actionpack/lib/action_controller/caching/sweeper.rb
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
require 'active_record'
|
||||
|
||||
module ActionController #:nodoc:
|
||||
module Caching
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before) if controller.perform_caching
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after) if controller.perform_caching
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments, &block)
|
||||
return if @controller.nil?
|
||||
@controller.__send__(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -51,47 +51,5 @@ module ActionController #:nodoc:
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
||||
attr_accessor :controller
|
||||
|
||||
def before(controller)
|
||||
self.controller = controller
|
||||
callback(:before) if controller.perform_caching
|
||||
end
|
||||
|
||||
def after(controller)
|
||||
callback(:after) if controller.perform_caching
|
||||
# Clean up, so that the controller can be collected after this request
|
||||
self.controller = nil
|
||||
end
|
||||
|
||||
protected
|
||||
# gets the action cache path for the given options.
|
||||
def action_path_for(options)
|
||||
ActionController::Caching::Actions::ActionCachePath.path_for(controller, options)
|
||||
end
|
||||
|
||||
# Retrieve instance variables set in the controller.
|
||||
def assigns(key)
|
||||
controller.instance_variable_get("@#{key}")
|
||||
end
|
||||
|
||||
private
|
||||
def callback(timing)
|
||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
||||
|
||||
__send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
|
||||
__send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
|
||||
end
|
||||
|
||||
def method_missing(method, *arguments, &block)
|
||||
return if @controller.nil?
|
||||
@controller.__send__(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
require 'erb'
|
||||
|
||||
module ActionController
|
||||
# The Failsafe middleware is usually the top-most middleware in the Rack
|
||||
# middleware chain. It returns the underlying middleware's response, but if
|
||||
# the underlying middle raises an exception then Failsafe will log the
|
||||
# exception into the Rails log file, and will attempt to return an error
|
||||
# message response.
|
||||
#
|
||||
# Failsafe is a last resort for logging errors and for telling the HTTP
|
||||
# client that something went wrong. Do not confuse this with the
|
||||
# ActionController::Rescue module, which is responsible for catching
|
||||
# exceptions at deeper levels. Unlike Failsafe, which is as simple as
|
||||
# possible, Rescue provides features that allow developers to hook into
|
||||
# the error handling logic, and can customize the error message response
|
||||
# based on the HTTP client's IP.
|
||||
class Failsafe
|
||||
cattr_accessor :error_file_path
|
||||
self.error_file_path = Rails.public_path if defined?(Rails.public_path)
|
||||
|
@ -11,7 +26,7 @@ module ActionController
|
|||
@app.call(env)
|
||||
rescue Exception => exception
|
||||
# Reraise exception in test environment
|
||||
if env["rack.test"]
|
||||
if defined?(Rails) && Rails.env.test?
|
||||
raise exception
|
||||
else
|
||||
failsafe_response(exception)
|
||||
|
@ -21,18 +36,37 @@ module ActionController
|
|||
private
|
||||
def failsafe_response(exception)
|
||||
log_failsafe_exception(exception)
|
||||
[500, {'Content-Type' => 'text/html'}, failsafe_response_body]
|
||||
[500, {'Content-Type' => 'text/html'}, [failsafe_response_body]]
|
||||
rescue Exception => failsafe_error # Logger or IO errors
|
||||
$stderr.puts "Error during failsafe response: #{failsafe_error}"
|
||||
end
|
||||
|
||||
def failsafe_response_body
|
||||
error_path = "#{self.class.error_file_path}/500.html"
|
||||
if File.exist?(error_path)
|
||||
File.read(error_path)
|
||||
error_template_path = "#{self.class.error_file_path}/500.html"
|
||||
if File.exist?(error_template_path)
|
||||
begin
|
||||
result = render_template(error_template_path)
|
||||
rescue Exception
|
||||
result = nil
|
||||
end
|
||||
else
|
||||
"<html><body><h1>500 Internal Server Error</h1></body></html>"
|
||||
result = nil
|
||||
end
|
||||
if result.nil?
|
||||
result = "<html><body><h1>500 Internal Server Error</h1>" <<
|
||||
"If you are the administrator of this website, then please read this web " <<
|
||||
"application's log file to find out what went wrong.</body></html>"
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# The default 500.html uses the h() method.
|
||||
def h(text) # :nodoc:
|
||||
ERB::Util.h(text)
|
||||
end
|
||||
|
||||
def render_template(filename)
|
||||
ERB.new(File.read(filename)).result(binding)
|
||||
end
|
||||
|
||||
def log_failsafe_exception(exception)
|
||||
|
|
|
@ -120,6 +120,11 @@ module ActionController #:nodoc:
|
|||
(@used.keys - keys).each{ |k| @used.delete(k) }
|
||||
end
|
||||
|
||||
def store(session, key = "flash")
|
||||
return if self.empty?
|
||||
session[key] = self
|
||||
end
|
||||
|
||||
private
|
||||
# Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
|
||||
# use() # marks the entire flash as used
|
||||
|
@ -139,7 +144,10 @@ module ActionController #:nodoc:
|
|||
protected
|
||||
def perform_action_with_flash
|
||||
perform_action_without_flash
|
||||
remove_instance_variable(:@_flash) if defined? @_flash
|
||||
if defined? @_flash
|
||||
@_flash.store(session)
|
||||
remove_instance_variable(:@_flash)
|
||||
end
|
||||
end
|
||||
|
||||
def reset_session_with_flash
|
||||
|
@ -151,8 +159,8 @@ module ActionController #:nodoc:
|
|||
# read a notice you put there or <tt>flash["notice"] = "hello"</tt>
|
||||
# to put a new one.
|
||||
def flash #:doc:
|
||||
unless defined? @_flash
|
||||
@_flash = session["flash"] ||= FlashHash.new
|
||||
if !defined?(@_flash)
|
||||
@_flash = session["flash"] || FlashHash.new
|
||||
@_flash.sweep
|
||||
end
|
||||
|
||||
|
|
|
@ -183,7 +183,7 @@ module ActionController
|
|||
request.env['REDIRECT_X_HTTP_AUTHORIZATION']
|
||||
end
|
||||
|
||||
# Raises error unless the request credentials response value matches the expected value.
|
||||
# Returns false unless the request credentials response value matches the expected value.
|
||||
# First try the password as a ha1 digest password. If this fails, then try it as a plain
|
||||
# text password.
|
||||
def validate_digest_response(request, realm, &password_procedure)
|
||||
|
@ -192,9 +192,12 @@ module ActionController
|
|||
|
||||
if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque]
|
||||
password = password_procedure.call(credentials[:username])
|
||||
return false unless password
|
||||
|
||||
method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD']
|
||||
|
||||
[true, false].any? do |password_is_ha1|
|
||||
expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1)
|
||||
expected = expected_response(method, request.env['REQUEST_URI'], credentials, password, password_is_ha1)
|
||||
expected == credentials[:response]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -292,9 +292,7 @@ module ActionController
|
|||
"rack.errors" => StringIO.new,
|
||||
"rack.multithread" => true,
|
||||
"rack.multiprocess" => true,
|
||||
"rack.run_once" => false,
|
||||
|
||||
"rack.test" => true
|
||||
"rack.run_once" => false
|
||||
)
|
||||
|
||||
(headers || {}).each do |key, value|
|
||||
|
@ -311,12 +309,7 @@ module ActionController
|
|||
|
||||
ActionController::Base.clear_last_instantiation!
|
||||
|
||||
app = @application
|
||||
# Rack::Lint doesn't accept String headers or bodies in Ruby 1.9
|
||||
unless RUBY_VERSION >= '1.9.0' && Rack.release <= '0.9.0'
|
||||
app = Rack::Lint.new(app)
|
||||
end
|
||||
|
||||
app = Rack::Lint.new(@application)
|
||||
status, headers, body = app.call(env)
|
||||
@request_count += 1
|
||||
|
||||
|
@ -333,7 +326,7 @@ module ActionController
|
|||
end
|
||||
|
||||
@body = ""
|
||||
if body.is_a?(String)
|
||||
if body.respond_to?(:to_str)
|
||||
@body << body
|
||||
else
|
||||
body.each { |part| @body << part }
|
||||
|
@ -416,7 +409,7 @@ module ActionController
|
|||
def multipart_requestify(params, first=true)
|
||||
returning Hash.new do |p|
|
||||
params.each do |key, value|
|
||||
k = first ? CGI.escape(key.to_s) : "[#{CGI.escape(key.to_s)}]"
|
||||
k = first ? key.to_s : "[#{key.to_s}]"
|
||||
if Hash === value
|
||||
multipart_requestify(value, false).each do |subkey, subvalue|
|
||||
p[k + subkey] = subvalue
|
||||
|
|
|
@ -7,7 +7,6 @@ use "ActionController::Failsafe"
|
|||
use lambda { ActionController::Base.session_store },
|
||||
lambda { ActionController::Base.session_options }
|
||||
|
||||
use "ActionController::RewindableInput"
|
||||
use "ActionController::ParamsParser"
|
||||
use "Rack::MethodOverride"
|
||||
use "Rack::Head"
|
||||
|
|
|
@ -1,14 +1,45 @@
|
|||
module ActionController
|
||||
class Reloader
|
||||
class BodyWrapper
|
||||
def initialize(body)
|
||||
@body = body
|
||||
end
|
||||
|
||||
def close
|
||||
@body.close if @body.respond_to?(:close)
|
||||
ensure
|
||||
Dispatcher.cleanup_application
|
||||
end
|
||||
|
||||
def method_missing(*args, &block)
|
||||
@body.send(*args, &block)
|
||||
end
|
||||
|
||||
def respond_to?(symbol, include_private = false)
|
||||
symbol == :close || @body.respond_to?(symbol, include_private)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
Dispatcher.reload_application
|
||||
@app.call(env)
|
||||
ensure
|
||||
Dispatcher.cleanup_application
|
||||
status, headers, body = @app.call(env)
|
||||
# We do not want to call 'cleanup_application' in an ensure block
|
||||
# because the returned Rack response body may lazily generate its data. This
|
||||
# is for example the case if one calls
|
||||
#
|
||||
# render :text => lambda { ... code here which refers to application models ... }
|
||||
#
|
||||
# in an ActionController.
|
||||
#
|
||||
# Instead, we will want to cleanup the application code after the request is
|
||||
# completely finished. So we wrap the body in a BodyWrapper class so that
|
||||
# when the Rack handler calls #close during the end of the request, we get to
|
||||
# run our cleanup code.
|
||||
[status, headers, BodyWrapper.new(body)]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,6 +95,10 @@ module ActionController
|
|||
end
|
||||
end
|
||||
|
||||
def media_type
|
||||
content_type.to_s
|
||||
end
|
||||
|
||||
# Returns the accepted MIME type for the request.
|
||||
def accepts
|
||||
@accepts ||= begin
|
||||
|
@ -383,7 +387,7 @@ EOM
|
|||
alias_method :params, :parameters
|
||||
|
||||
def path_parameters=(parameters) #:nodoc:
|
||||
@env["rack.routing_args"] = parameters
|
||||
@env["action_controller.request.path_parameters"] = parameters
|
||||
@symbolized_path_parameters = @parameters = nil
|
||||
end
|
||||
|
||||
|
@ -399,7 +403,7 @@ EOM
|
|||
#
|
||||
# See <tt>symbolized_path_parameters</tt> for symbolized keys.
|
||||
def path_parameters
|
||||
@env["rack.routing_args"] ||= {}
|
||||
@env["action_controller.request.path_parameters"] ||= {}
|
||||
end
|
||||
|
||||
# The request body is an IO input stream. If the RAW_POST_DATA environment
|
||||
|
|
|
@ -151,8 +151,8 @@ module ActionController # :nodoc:
|
|||
if @body.respond_to?(:call)
|
||||
@writer = lambda { |x| callback.call(x) }
|
||||
@body.call(self, self)
|
||||
elsif @body.is_a?(String)
|
||||
@body.each_line(&callback)
|
||||
elsif @body.respond_to?(:to_str)
|
||||
yield @body
|
||||
else
|
||||
@body.each(&callback)
|
||||
end
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
module ActionController
|
||||
class RewindableInput
|
||||
class RewindableIO < ActiveSupport::BasicObject
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@rewindable = io.is_a?(::StringIO)
|
||||
end
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
unless @rewindable
|
||||
@io = ::StringIO.new(@io.read)
|
||||
@rewindable = true
|
||||
end
|
||||
|
||||
@io.__send__(method, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
env['rack.input'] = RewindableIO.new(env['rack.input'])
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -305,6 +305,7 @@ module ActionController
|
|||
end
|
||||
|
||||
def add_route(path, options = {})
|
||||
options.each { |k, v| options[k] = v.to_s if [:controller, :action].include?(k) && v.is_a?(Symbol) }
|
||||
route = builder.build(path, options)
|
||||
routes << route
|
||||
route
|
||||
|
@ -436,7 +437,7 @@ module ActionController
|
|||
def recognize(request)
|
||||
params = recognize_path(request.path, extract_request_environment(request))
|
||||
request.path_parameters = params.with_indifferent_access
|
||||
"#{params[:controller].camelize}Controller".constantize
|
||||
"#{params[:controller].to_s.camelize}Controller".constantize
|
||||
end
|
||||
|
||||
def recognize_path(path, environment={})
|
||||
|
|
|
@ -161,7 +161,7 @@ module ActionController #:nodoc:
|
|||
content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers
|
||||
|
||||
headers.merge!(
|
||||
'Content-Length' => options[:length],
|
||||
'Content-Length' => options[:length].to_s,
|
||||
'Content-Type' => content_type,
|
||||
'Content-Disposition' => disposition,
|
||||
'Content-Transfer-Encoding' => 'binary'
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
require 'rack/session/abstract/id'
|
||||
module ActionController #:nodoc:
|
||||
class TestRequest < Request #:nodoc:
|
||||
attr_accessor :cookies, :session_options
|
||||
|
@ -13,6 +14,8 @@ module ActionController #:nodoc:
|
|||
|
||||
@query_parameters = {}
|
||||
@session = TestSession.new
|
||||
default_rack_options = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
|
||||
@session_options ||= {:id => generate_sid(default_rack_options[:sidbits])}.merge(default_rack_options)
|
||||
|
||||
initialize_default_values
|
||||
initialize_containers
|
||||
|
@ -110,6 +113,7 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
def recycle!
|
||||
@env["action_controller.request.request_parameters"] = {}
|
||||
self.query_parameters = {}
|
||||
self.path_parameters = {}
|
||||
@headers, @request_method, @accepts, @content_type = nil, nil, nil, nil
|
||||
|
@ -120,6 +124,10 @@ module ActionController #:nodoc:
|
|||
end
|
||||
|
||||
private
|
||||
def generate_sid(sidbits)
|
||||
"%0#{sidbits / 4}x" % rand(2**sidbits - 1)
|
||||
end
|
||||
|
||||
def initialize_containers
|
||||
@cookies = {}
|
||||
end
|
||||
|
@ -250,7 +258,7 @@ module ActionController #:nodoc:
|
|||
def cookies
|
||||
cookies = {}
|
||||
Array(headers['Set-Cookie']).each do |cookie|
|
||||
key, value = cookie.split(";").first.split("=")
|
||||
key, value = cookie.split(";").first.split("=").map {|val| Rack::Utils.unescape(val)}
|
||||
cookies[key] = value
|
||||
end
|
||||
cookies
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
require 'thread'
|
||||
|
||||
module Rack
|
||||
# Rack::Reloader checks on every request, but at most every +secs+
|
||||
# seconds, if a file loaded changed, and reloads it, logging to
|
||||
# rack.errors.
|
||||
#
|
||||
# It is recommended you use ShowExceptions to catch SyntaxErrors etc.
|
||||
|
||||
class Reloader
|
||||
def initialize(app, secs=10)
|
||||
@app = app
|
||||
@secs = secs # reload every @secs seconds max
|
||||
@last = Time.now
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if Time.now > @last + @secs
|
||||
Thread.exclusive {
|
||||
reload!(env['rack.errors'])
|
||||
@last = Time.now
|
||||
}
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def reload!(stderr=$stderr)
|
||||
need_reload = $LOADED_FEATURES.find_all { |loaded|
|
||||
begin
|
||||
if loaded =~ /\A[.\/]/ # absolute filename or 1.9
|
||||
abs = loaded
|
||||
else
|
||||
abs = $LOAD_PATH.map { |path| ::File.join(path, loaded) }.
|
||||
find { |file| ::File.exist? file }
|
||||
end
|
||||
|
||||
if abs
|
||||
::File.mtime(abs) > @last - @secs rescue false
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
need_reload.each { |l|
|
||||
$LOADED_FEATURES.delete l
|
||||
}
|
||||
|
||||
need_reload.each { |to_load|
|
||||
begin
|
||||
if require to_load
|
||||
stderr.puts "#{self.class}: reloaded `#{to_load}'"
|
||||
end
|
||||
rescue LoadError, SyntaxError => e
|
||||
raise e # Possibly ShowExceptions
|
||||
end
|
||||
}
|
||||
|
||||
stderr.flush
|
||||
need_reload
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,8 @@
|
|||
# Rack is freely distributable under the terms of an MIT-style license.
|
||||
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
$:.unshift(File.expand_path(File.dirname(__FILE__)))
|
||||
path = File.expand_path(File.dirname(__FILE__))
|
||||
$:.unshift(path) unless $:.include?(path)
|
||||
|
||||
|
||||
# The Rack main module, serving as a namespace for all core Rack
|
||||
|
@ -14,7 +15,7 @@ $:.unshift(File.expand_path(File.dirname(__FILE__)))
|
|||
|
||||
module Rack
|
||||
# The Rack protocol version number implemented.
|
||||
VERSION = [0,1]
|
||||
VERSION = [1,0]
|
||||
|
||||
# Return the Rack protocol version as a dotted string.
|
||||
def self.version
|
||||
|
@ -23,7 +24,7 @@ module Rack
|
|||
|
||||
# Return the Rack release as a dotted string.
|
||||
def self.release
|
||||
"1.0 bundled"
|
||||
"1.0"
|
||||
end
|
||||
|
||||
autoload :Builder, "rack/builder"
|
|
@ -26,6 +26,8 @@ module Rack
|
|||
headers = Utils::HeaderHash.new(headers)
|
||||
if etag_matches?(env, headers) || modified_since?(env, headers)
|
||||
status = 304
|
||||
headers.delete('Content-Type')
|
||||
headers.delete('Content-Length')
|
||||
body = []
|
||||
end
|
||||
[status, headers, body]
|
|
@ -33,17 +33,15 @@ module Rack
|
|||
|
||||
case encoding
|
||||
when "gzip"
|
||||
headers['Content-Encoding'] = "gzip"
|
||||
headers.delete('Content-Length')
|
||||
mtime = headers.key?("Last-Modified") ?
|
||||
Time.httpdate(headers["Last-Modified"]) : Time.now
|
||||
body = self.class.gzip(body, mtime)
|
||||
size = Rack::Utils.bytesize(body)
|
||||
headers = headers.merge("Content-Encoding" => "gzip", "Content-Length" => size.to_s)
|
||||
[status, headers, [body]]
|
||||
[status, headers, GzipStream.new(body, mtime)]
|
||||
when "deflate"
|
||||
body = self.class.deflate(body)
|
||||
size = Rack::Utils.bytesize(body)
|
||||
headers = headers.merge("Content-Encoding" => "deflate", "Content-Length" => size.to_s)
|
||||
[status, headers, [body]]
|
||||
headers['Content-Encoding'] = "deflate"
|
||||
headers.delete('Content-Length')
|
||||
[status, headers, DeflateStream.new(body)]
|
||||
when "identity"
|
||||
[status, headers, body]
|
||||
when nil
|
||||
|
@ -52,34 +50,47 @@ module Rack
|
|||
end
|
||||
end
|
||||
|
||||
def self.gzip(body, mtime)
|
||||
io = StringIO.new
|
||||
gzip = Zlib::GzipWriter.new(io)
|
||||
gzip.mtime = mtime
|
||||
class GzipStream
|
||||
def initialize(body, mtime)
|
||||
@body = body
|
||||
@mtime = mtime
|
||||
end
|
||||
|
||||
# TODO: Add streaming
|
||||
body.each { |part| gzip << part }
|
||||
def each(&block)
|
||||
@writer = block
|
||||
gzip =::Zlib::GzipWriter.new(self)
|
||||
gzip.mtime = @mtime
|
||||
@body.each { |part| gzip << part }
|
||||
@body.close if @body.respond_to?(:close)
|
||||
gzip.close
|
||||
@writer = nil
|
||||
end
|
||||
|
||||
gzip.close
|
||||
return io.string
|
||||
def write(data)
|
||||
@writer.call(data)
|
||||
end
|
||||
end
|
||||
|
||||
DEFLATE_ARGS = [
|
||||
Zlib::DEFAULT_COMPRESSION,
|
||||
# drop the zlib header which causes both Safari and IE to choke
|
||||
-Zlib::MAX_WBITS,
|
||||
Zlib::DEF_MEM_LEVEL,
|
||||
Zlib::DEFAULT_STRATEGY
|
||||
]
|
||||
class DeflateStream
|
||||
DEFLATE_ARGS = [
|
||||
Zlib::DEFAULT_COMPRESSION,
|
||||
# drop the zlib header which causes both Safari and IE to choke
|
||||
-Zlib::MAX_WBITS,
|
||||
Zlib::DEF_MEM_LEVEL,
|
||||
Zlib::DEFAULT_STRATEGY
|
||||
]
|
||||
|
||||
# Loosely based on Mongrel's Deflate handler
|
||||
def self.deflate(body)
|
||||
deflater = Zlib::Deflate.new(*DEFLATE_ARGS)
|
||||
def initialize(body)
|
||||
@body = body
|
||||
end
|
||||
|
||||
# TODO: Add streaming
|
||||
body.each { |part| deflater << part }
|
||||
|
||||
return deflater.finish
|
||||
def each
|
||||
deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
|
||||
@body.each { |part| yield deflater.deflate(part) }
|
||||
@body.close if @body.respond_to?(:close)
|
||||
yield deflater.finish
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,16 +10,37 @@ module Rack
|
|||
module Handler
|
||||
def self.get(server)
|
||||
return unless server
|
||||
server = server.to_s
|
||||
|
||||
if klass = @handlers[server]
|
||||
obj = Object
|
||||
klass.split("::").each { |x| obj = obj.const_get(x) }
|
||||
obj
|
||||
else
|
||||
Rack::Handler.const_get(server.capitalize)
|
||||
try_require('rack/handler', server)
|
||||
const_get(server)
|
||||
end
|
||||
end
|
||||
|
||||
# Transforms server-name constants to their canonical form as filenames,
|
||||
# then tries to require them but silences the LoadError if not found
|
||||
#
|
||||
# Naming convention:
|
||||
#
|
||||
# Foo # => 'foo'
|
||||
# FooBar # => 'foo_bar.rb'
|
||||
# FooBAR # => 'foobar.rb'
|
||||
# FOObar # => 'foobar.rb'
|
||||
# FOOBAR # => 'foobar.rb'
|
||||
# FooBarBaz # => 'foo_bar_baz.rb'
|
||||
def self.try_require(prefix, const_name)
|
||||
file = const_name.gsub(/^[A-Z]+/) { |pre| pre.downcase }.
|
||||
gsub(/[A-Z]+[^A-Z]/, '_\&').downcase
|
||||
|
||||
require(::File.join(prefix, file))
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
def self.register(server, klass)
|
||||
@handlers ||= {}
|
||||
@handlers[server] = klass
|
|
@ -15,7 +15,7 @@ module Rack
|
|||
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
|
||||
env.update({"rack.version" => [0,1],
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => $stdin,
|
||||
"rack.errors" => $stderr,
|
||||
|
|
@ -1,6 +1,17 @@
|
|||
require 'fcgi'
|
||||
require 'socket'
|
||||
require 'rack/content_length'
|
||||
require 'rack/rewindable_input'
|
||||
|
||||
class FCGI::Stream
|
||||
alias _rack_read_without_buffer read
|
||||
|
||||
def read(n, buffer=nil)
|
||||
buf = _rack_read_without_buffer n
|
||||
buffer.replace(buf.to_s) if buffer
|
||||
buf
|
||||
end
|
||||
end
|
||||
|
||||
module Rack
|
||||
module Handler
|
||||
|
@ -13,34 +24,18 @@ module Rack
|
|||
}
|
||||
end
|
||||
|
||||
module ProperStream # :nodoc:
|
||||
def each # This is missing by default.
|
||||
while line = gets
|
||||
yield line
|
||||
end
|
||||
end
|
||||
|
||||
def read(*args)
|
||||
if args.empty?
|
||||
super || "" # Empty string on EOF.
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.serve(request, app)
|
||||
app = Rack::ContentLength.new(app)
|
||||
|
||||
env = request.env
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
|
||||
request.in.extend ProperStream
|
||||
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
|
||||
env.update({"rack.version" => [0,1],
|
||||
"rack.input" => request.in,
|
||||
rack_input = RewindableInput.new(request.in)
|
||||
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => rack_input,
|
||||
"rack.errors" => request.err,
|
||||
|
||||
"rack.multithread" => false,
|
||||
|
@ -57,12 +52,16 @@ module Rack
|
|||
env.delete "CONTENT_TYPE" if env["CONTENT_TYPE"] == ""
|
||||
env.delete "CONTENT_LENGTH" if env["CONTENT_LENGTH"] == ""
|
||||
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
send_headers request.out, status, headers
|
||||
send_body request.out, body
|
||||
status, headers, body = app.call(env)
|
||||
begin
|
||||
send_headers request.out, status, headers
|
||||
send_body request.out, body
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
end
|
||||
ensure
|
||||
body.close if body.respond_to? :close
|
||||
rack_input.close
|
||||
request.finish
|
||||
end
|
||||
end
|
|
@ -15,7 +15,7 @@ module Rack
|
|||
env = ENV.to_hash
|
||||
env.delete "HTTP_CONTENT_LENGTH"
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
env.update({"rack.version" => [0,1],
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => StringIO.new($stdin.read.to_s),
|
||||
"rack.errors" => $stderr,
|
||||
"rack.multithread" => false,
|
|
@ -10,7 +10,7 @@ module Rack
|
|||
server = ::Mongrel::HttpServer.new(options[:Host] || '0.0.0.0',
|
||||
options[:Port] || 8080)
|
||||
# Acts like Rack::URLMap, utilizing Mongrel's own path finding methods.
|
||||
# Use is similar to #run, replacing the app argument with a hash of
|
||||
# Use is similar to #run, replacing the app argument with a hash of
|
||||
# { path=>app, ... } or an instance of Rack::URLMap.
|
||||
if options[:map]
|
||||
if app.is_a? Hash
|
||||
|
@ -45,7 +45,7 @@ module Rack
|
|||
|
||||
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
|
||||
|
||||
env.update({"rack.version" => [0,1],
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => request.body || StringIO.new(""),
|
||||
"rack.errors" => $stderr,
|
||||
|
|
@ -7,14 +7,14 @@ module Rack
|
|||
module Handler
|
||||
class SCGI < ::SCGI::Processor
|
||||
attr_accessor :app
|
||||
|
||||
|
||||
def self.run(app, options=nil)
|
||||
new(options.merge(:app=>app,
|
||||
:host=>options[:Host],
|
||||
:port=>options[:Port],
|
||||
:socket=>options[:Socket])).listen
|
||||
end
|
||||
|
||||
|
||||
def initialize(settings = {})
|
||||
@app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app]))
|
||||
@log = Object.new
|
||||
|
@ -22,7 +22,7 @@ module Rack
|
|||
def @log.error(*args); end
|
||||
super(settings)
|
||||
end
|
||||
|
||||
|
||||
def process_request(request, input_body, socket)
|
||||
env = {}.replace(request)
|
||||
env.delete "HTTP_CONTENT_TYPE"
|
||||
|
@ -32,7 +32,7 @@ module Rack
|
|||
env["PATH_INFO"] = env["REQUEST_PATH"]
|
||||
env["QUERY_STRING"] ||= ""
|
||||
env["SCRIPT_NAME"] = ""
|
||||
env.update({"rack.version" => [0,1],
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => StringIO.new(input_body),
|
||||
"rack.errors" => $stderr,
|
||||
|
|
@ -23,7 +23,7 @@ module Rack
|
|||
env = req.meta_vars
|
||||
env.delete_if { |k, v| v.nil? }
|
||||
|
||||
env.update({"rack.version" => [0,1],
|
||||
env.update({"rack.version" => [1,0],
|
||||
"rack.input" => StringIO.new(req.body.to_s),
|
||||
"rack.errors" => $stderr,
|
||||
|
|
@ -88,9 +88,9 @@ module Rack
|
|||
## within the application. This may be an
|
||||
## empty string, if the request URL targets
|
||||
## the application root and does not have a
|
||||
## trailing slash. This information should be
|
||||
## decoded by the server if it comes from a
|
||||
## URL.
|
||||
## trailing slash. This value may be
|
||||
## percent-encoded when I originating from
|
||||
## a URL.
|
||||
|
||||
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
|
||||
## follows the <tt>?</tt>, if any. May be
|
||||
|
@ -111,19 +111,48 @@ module Rack
|
|||
## In addition to this, the Rack environment must include these
|
||||
## Rack-specific variables:
|
||||
|
||||
## <tt>rack.version</tt>:: The Array [0,1], representing this version of Rack.
|
||||
## <tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
|
||||
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
|
||||
## <tt>rack.input</tt>:: See below, the input stream.
|
||||
## <tt>rack.errors</tt>:: See below, the error stream.
|
||||
## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
|
||||
## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
|
||||
## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
|
||||
##
|
||||
|
||||
## Additional environment specifications have approved to
|
||||
## standardized middleware APIs. None of these are required to
|
||||
## be implemented by the server.
|
||||
|
||||
## <tt>rack.session</tt>:: A hash like interface for storing request session data.
|
||||
## The store must implement:
|
||||
if session = env['rack.session']
|
||||
## store(key, value) (aliased as []=);
|
||||
assert("session #{session.inspect} must respond to store and []=") {
|
||||
session.respond_to?(:store) && session.respond_to?(:[]=)
|
||||
}
|
||||
|
||||
## fetch(key, default = nil) (aliased as []);
|
||||
assert("session #{session.inspect} must respond to fetch and []") {
|
||||
session.respond_to?(:fetch) && session.respond_to?(:[])
|
||||
}
|
||||
|
||||
## delete(key);
|
||||
assert("session #{session.inspect} must respond to delete") {
|
||||
session.respond_to?(:delete)
|
||||
}
|
||||
|
||||
## clear;
|
||||
assert("session #{session.inspect} must respond to clear") {
|
||||
session.respond_to?(:clear)
|
||||
}
|
||||
end
|
||||
|
||||
## The server or the application can store their own data in the
|
||||
## environment, too. The keys must contain at least one dot,
|
||||
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
|
||||
## is reserved for use with the Rack core distribution and must
|
||||
## not be used otherwise.
|
||||
## is reserved for use with the Rack core distribution and other
|
||||
## accepted specifications and must not be used otherwise.
|
||||
##
|
||||
|
||||
%w[REQUEST_METHOD SERVER_NAME SERVER_PORT
|
||||
|
@ -202,9 +231,12 @@ module Rack
|
|||
end
|
||||
|
||||
## === The Input Stream
|
||||
##
|
||||
## The input stream is an IO-like object which contains the raw HTTP
|
||||
## POST data. If it is a file then it must be opened in binary mode.
|
||||
def check_input(input)
|
||||
## The input stream must respond to +gets+, +each+ and +read+.
|
||||
[:gets, :each, :read].each { |method|
|
||||
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
|
||||
[:gets, :each, :read, :rewind].each { |method|
|
||||
assert("rack.input #{input} does not respond to ##{method}") {
|
||||
input.respond_to? method
|
||||
}
|
||||
|
@ -222,10 +254,6 @@ module Rack
|
|||
@input.size
|
||||
end
|
||||
|
||||
def rewind
|
||||
@input.rewind
|
||||
end
|
||||
|
||||
## * +gets+ must be called without arguments and return a string,
|
||||
## or +nil+ on EOF.
|
||||
def gets(*args)
|
||||
|
@ -237,21 +265,44 @@ module Rack
|
|||
v
|
||||
end
|
||||
|
||||
## * +read+ must be called without or with one integer argument
|
||||
## and return a string, or +nil+ on EOF.
|
||||
## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
|
||||
## If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must
|
||||
## be a String and may not be nil. If +length+ is given and not nil, then this method
|
||||
## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
|
||||
## then this method reads all data until EOF.
|
||||
## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
|
||||
## if +length+ is not given or is nil.
|
||||
## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
|
||||
## newly created String object.
|
||||
def read(*args)
|
||||
assert("rack.input#read called with too many arguments") {
|
||||
args.size <= 1
|
||||
args.size <= 2
|
||||
}
|
||||
if args.size == 1
|
||||
assert("rack.input#read called with non-integer argument") {
|
||||
args.first.kind_of? Integer
|
||||
if args.size >= 1
|
||||
assert("rack.input#read called with non-integer and non-nil length") {
|
||||
args.first.kind_of?(Integer) || args.first.nil?
|
||||
}
|
||||
assert("rack.input#read called with a negative length") {
|
||||
args.first.nil? || args.first >= 0
|
||||
}
|
||||
end
|
||||
if args.size >= 2
|
||||
assert("rack.input#read called with non-String buffer") {
|
||||
args[1].kind_of?(String)
|
||||
}
|
||||
end
|
||||
|
||||
v = @input.read(*args)
|
||||
assert("rack.input#read didn't return a String") {
|
||||
|
||||
assert("rack.input#read didn't return nil or a String") {
|
||||
v.nil? or v.instance_of? String
|
||||
}
|
||||
if args[0].nil?
|
||||
assert("rack.input#read(nil) returned nil on EOF") {
|
||||
!v.nil?
|
||||
}
|
||||
end
|
||||
|
||||
v
|
||||
end
|
||||
|
||||
|
@ -266,6 +317,23 @@ module Rack
|
|||
}
|
||||
end
|
||||
|
||||
## * +rewind+ must be called without arguments. It rewinds the input
|
||||
## stream back to the beginning. It must not raise Errno::ESPIPE:
|
||||
## that is, it may not be a pipe or a socket. Therefore, handler
|
||||
## developers must buffer the input data into some rewindable object
|
||||
## if the underlying input stream is not rewindable.
|
||||
def rewind(*args)
|
||||
assert("rack.input#rewind called with arguments") { args.size == 0 }
|
||||
assert("rack.input#rewind raised Errno::ESPIPE") {
|
||||
begin
|
||||
@input.rewind
|
||||
true
|
||||
rescue Errno::ESPIPE
|
||||
false
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
## * +close+ must never be called on the input stream.
|
||||
def close(*args)
|
||||
assert("rack.input#close must not be called") { false }
|
||||
|
@ -316,13 +384,14 @@ module Rack
|
|||
|
||||
## === The Status
|
||||
def check_status(status)
|
||||
## The status, if parsed as integer (+to_i+), must be greater than or equal to 100.
|
||||
## This is an HTTP status. When parsed as integer (+to_i+), it must be
|
||||
## greater than or equal to 100.
|
||||
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
|
||||
end
|
||||
|
||||
## === The Headers
|
||||
def check_headers(header)
|
||||
## The header must respond to each, and yield values of key and value.
|
||||
## The header must respond to +each+, and yield values of key and value.
|
||||
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
|
||||
header.respond_to? :each
|
||||
}
|
||||
|
@ -344,7 +413,8 @@ module Rack
|
|||
## The values of the header must be Strings,
|
||||
assert("a header value must be a String, but the value of " +
|
||||
"'#{key}' is a #{value.class}") { value.kind_of? String }
|
||||
## consisting of lines (for multiple header values) seperated by "\n".
|
||||
## consisting of lines (for multiple header values, e.g. multiple
|
||||
## <tt>Set-Cookie</tt> values) seperated by "\n".
|
||||
value.split("\n").each { |item|
|
||||
## The lines must not contain characters below 037.
|
||||
assert("invalid header value #{key}: #{item.inspect}") {
|
||||
|
@ -416,7 +486,7 @@ module Rack
|
|||
## === The Body
|
||||
def each
|
||||
@closed = false
|
||||
## The Body must respond to #each
|
||||
## The Body must respond to +each+
|
||||
@body.each { |part|
|
||||
## and must only yield String values.
|
||||
assert("Body yielded non-string value #{part.inspect}") {
|
||||
|
@ -425,14 +495,19 @@ module Rack
|
|||
yield part
|
||||
}
|
||||
##
|
||||
## If the Body responds to #close, it will be called after iteration.
|
||||
## The Body itself should not be an instance of String, as this will
|
||||
## break in Ruby 1.9.
|
||||
##
|
||||
## If the Body responds to +close+, it will be called after iteration.
|
||||
# XXX howto: assert("Body has not been closed") { @closed }
|
||||
|
||||
|
||||
##
|
||||
## If the Body responds to #to_path, it must return a String
|
||||
## If the Body responds to +to_path+, it must return a String
|
||||
## identifying the location of a file whose contents are identical
|
||||
## to that produced by calling #each.
|
||||
## to that produced by calling +each+; this may be used by the
|
||||
## server as an alternative, possibly more efficient way to
|
||||
## transport the response.
|
||||
|
||||
if @body.respond_to?(:to_path)
|
||||
assert("The file identified by body.to_path does not exist") {
|
|
@ -40,7 +40,7 @@ module Rack
|
|||
end
|
||||
|
||||
DEFAULT_ENV = {
|
||||
"rack.version" => [0,1],
|
||||
"rack.version" => [1,0],
|
||||
"rack.input" => StringIO.new,
|
||||
"rack.errors" => StringIO.new,
|
||||
"rack.multithread" => true,
|
||||
|
@ -73,14 +73,17 @@ module Rack
|
|||
# Return the Rack environment used for a request to +uri+.
|
||||
def self.env_for(uri="", opts={})
|
||||
uri = URI(uri)
|
||||
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
|
||||
|
||||
env = DEFAULT_ENV.dup
|
||||
|
||||
env["REQUEST_METHOD"] = opts[:method] || "GET"
|
||||
env["REQUEST_METHOD"] = opts[:method] ? opts[:method].to_s.upcase : "GET"
|
||||
env["SERVER_NAME"] = uri.host || "example.org"
|
||||
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
|
||||
env["QUERY_STRING"] = uri.query.to_s
|
||||
env["PATH_INFO"] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
||||
env["rack.url_scheme"] = uri.scheme || "http"
|
||||
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
|
||||
|
||||
env["SCRIPT_NAME"] = opts[:script_name] || ""
|
||||
|
||||
|
@ -90,6 +93,27 @@ module Rack
|
|||
env["rack.errors"] = StringIO.new
|
||||
end
|
||||
|
||||
if params = opts[:params]
|
||||
if env["REQUEST_METHOD"] == "GET"
|
||||
params = Utils.parse_nested_query(params) if params.is_a?(String)
|
||||
params.update(Utils.parse_nested_query(env["QUERY_STRING"]))
|
||||
env["QUERY_STRING"] = Utils.build_nested_query(params)
|
||||
elsif !opts.has_key?(:input)
|
||||
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
|
||||
if params.is_a?(Hash)
|
||||
if data = Utils::Multipart.build_multipart(params)
|
||||
opts[:input] = data
|
||||
opts["CONTENT_LENGTH"] ||= data.length.to_s
|
||||
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
|
||||
else
|
||||
opts[:input] = Utils.build_nested_query(params)
|
||||
end
|
||||
else
|
||||
opts[:input] = params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
opts[:input] ||= ""
|
||||
if String === opts[:input]
|
||||
env["rack.input"] = StringIO.new(opts[:input])
|
||||
|
@ -125,7 +149,7 @@ module Rack
|
|||
@body = ""
|
||||
body.each { |part| @body << part }
|
||||
|
||||
@errors = errors.string
|
||||
@errors = errors.string if errors.respond_to?(:string)
|
||||
end
|
||||
|
||||
# Status
|
106
vendor/rails/actionpack/lib/action_controller/vendor/rack-1.1.pre/rack/reloader.rb
vendored
Normal file
106
vendor/rails/actionpack/lib/action_controller/vendor/rack-1.1.pre/rack/reloader.rb
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Copyright (c) 2009 Michael Fellinger m.fellinger@gmail.com
|
||||
# All files in this distribution are subject to the terms of the Ruby license.
|
||||
|
||||
require 'pathname'
|
||||
|
||||
module Rack
|
||||
|
||||
# High performant source reloader
|
||||
#
|
||||
# This class acts as Rack middleware.
|
||||
#
|
||||
# What makes it especially suited for use in a production environment is that
|
||||
# any file will only be checked once and there will only be made one system
|
||||
# call stat(2).
|
||||
#
|
||||
# Please note that this will not reload files in the background, it does so
|
||||
# only when actively called.
|
||||
#
|
||||
# It is performing a check/reload cycle at the start of every request, but
|
||||
# also respects a cool down time, during which nothing will be done.
|
||||
class Reloader
|
||||
def initialize(app, cooldown = 10, backend = Stat)
|
||||
@app = app
|
||||
@cooldown = cooldown
|
||||
@last = (Time.now - cooldown)
|
||||
@cache = {}
|
||||
@mtimes = {}
|
||||
|
||||
extend backend
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if @cooldown and Time.now > @last + @cooldown
|
||||
if Thread.list.size > 1
|
||||
Thread.exclusive{ reload! }
|
||||
else
|
||||
reload!
|
||||
end
|
||||
|
||||
@last = Time.now
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
|
||||
def reload!(stderr = $stderr)
|
||||
rotation do |file, mtime|
|
||||
previous_mtime = @mtimes[file] ||= mtime
|
||||
safe_load(file, mtime, stderr) if mtime > previous_mtime
|
||||
end
|
||||
end
|
||||
|
||||
# A safe Kernel::load, issuing the hooks depending on the results
|
||||
def safe_load(file, mtime, stderr = $stderr)
|
||||
load(file)
|
||||
stderr.puts "#{self.class}: reloaded `#{file}'"
|
||||
file
|
||||
rescue LoadError, SyntaxError => ex
|
||||
stderr.puts ex
|
||||
ensure
|
||||
@mtimes[file] = mtime
|
||||
end
|
||||
|
||||
module Stat
|
||||
def rotation
|
||||
files = [$0, *$LOADED_FEATURES].uniq
|
||||
paths = ['./', *$LOAD_PATH].uniq
|
||||
|
||||
files.map{|file|
|
||||
next if file =~ /\.(so|bundle)$/ # cannot reload compiled files
|
||||
|
||||
found, stat = figure_path(file, paths)
|
||||
next unless found and stat and mtime = stat.mtime
|
||||
|
||||
@cache[file] = found
|
||||
|
||||
yield(found, mtime)
|
||||
}.compact
|
||||
end
|
||||
|
||||
# Takes a relative or absolute +file+ name, a couple possible +paths+ that
|
||||
# the +file+ might reside in. Returns the full path and File::Stat for the
|
||||
# path.
|
||||
def figure_path(file, paths)
|
||||
found = @cache[file]
|
||||
found = file if !found and Pathname.new(file).absolute?
|
||||
found, stat = safe_stat(found)
|
||||
return found, stat if found
|
||||
|
||||
paths.each do |possible_path|
|
||||
path = ::File.join(possible_path, file)
|
||||
found, stat = safe_stat(path)
|
||||
return ::File.expand_path(found), stat if found
|
||||
end
|
||||
end
|
||||
|
||||
def safe_stat(file)
|
||||
return unless file
|
||||
stat = ::File.stat(file)
|
||||
return file, stat if stat.file?
|
||||
rescue Errno::ENOENT, Errno::ENOTDIR
|
||||
@cache.delete(file) and false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -17,7 +17,7 @@ module Rack
|
|||
# The environment of the request.
|
||||
attr_reader :env
|
||||
|
||||
def self.new(env)
|
||||
def self.new(env, *args)
|
||||
if self == Rack::Request
|
||||
env["rack.request"] ||= super
|
||||
else
|
||||
|
@ -38,6 +38,8 @@ module Rack
|
|||
def query_string; @env["QUERY_STRING"].to_s end
|
||||
def content_length; @env['CONTENT_LENGTH'] end
|
||||
def content_type; @env['CONTENT_TYPE'] end
|
||||
def session; @env['rack.session'] ||= {} end
|
||||
def session_options; @env['rack.session.options'] ||= {} end
|
||||
|
||||
# The media type (type/subtype) portion of the CONTENT_TYPE header
|
||||
# without any media type parameters. e.g., when CONTENT_TYPE is
|
||||
|
@ -46,7 +48,7 @@ module Rack
|
|||
# For more information on the use of media types in HTTP, see:
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
|
||||
def media_type
|
||||
content_type && content_type.split(/\s*[;,]\s*/, 2)[0].downcase
|
||||
content_type && content_type.split(/\s*[;,]\s*/, 2).first.downcase
|
||||
end
|
||||
|
||||
# The media type parameters provided in CONTENT_TYPE as a Hash, or
|
||||
|
@ -92,6 +94,14 @@ module Rack
|
|||
'multipart/form-data'
|
||||
]
|
||||
|
||||
# The set of media-types. Requests that do not indicate
|
||||
# one of the media types presents in this list will not be eligible
|
||||
# for param parsing like soap attachments or generic multiparts
|
||||
PARSEABLE_DATA_MEDIA_TYPES = [
|
||||
'multipart/related',
|
||||
'multipart/mixed'
|
||||
]
|
||||
|
||||
# Determine whether the request body contains form-data by checking
|
||||
# the request media_type against registered form-data media-types:
|
||||
# "application/x-www-form-urlencoded" and "multipart/form-data". The
|
||||
|
@ -101,6 +111,12 @@ module Rack
|
|||
FORM_DATA_MEDIA_TYPES.include?(media_type)
|
||||
end
|
||||
|
||||
# Determine whether the request body contains data by checking
|
||||
# the request media_type against registered parse-data media-types
|
||||
def parseable_data?
|
||||
PARSEABLE_DATA_MEDIA_TYPES.include?(media_type)
|
||||
end
|
||||
|
||||
# Returns the data recieved in the query string.
|
||||
def GET
|
||||
if @env["rack.request.query_string"] == query_string
|
||||
|
@ -119,7 +135,7 @@ module Rack
|
|||
def POST
|
||||
if @env["rack.request.form_input"].eql? @env["rack.input"]
|
||||
@env["rack.request.form_hash"]
|
||||
elsif form_data?
|
||||
elsif form_data? || parseable_data?
|
||||
@env["rack.request.form_input"] = @env["rack.input"]
|
||||
unless @env["rack.request.form_hash"] =
|
||||
Utils::Multipart.parse_multipart(env)
|
||||
|
@ -131,12 +147,7 @@ module Rack
|
|||
@env["rack.request.form_vars"] = form_vars
|
||||
@env["rack.request.form_hash"] = Utils.parse_nested_query(form_vars)
|
||||
|
||||
begin
|
||||
@env["rack.input"].rewind if @env["rack.input"].respond_to?(:rewind)
|
||||
rescue Errno::ESPIPE
|
||||
# Handles exceptions raised by input streams that cannot be rewound
|
||||
# such as when using plain CGI under Apache
|
||||
end
|
||||
@env["rack.input"].rewind
|
||||
end
|
||||
@env["rack.request.form_hash"]
|
||||
else
|
||||
|
@ -212,10 +223,12 @@ module Rack
|
|||
url
|
||||
end
|
||||
|
||||
def path
|
||||
script_name + path_info
|
||||
end
|
||||
|
||||
def fullpath
|
||||
path = script_name + path_info
|
||||
path << "?" << query_string unless query_string.empty?
|
||||
path
|
||||
query_string.empty? ? path : "#{path}?#{query_string}"
|
||||
end
|
||||
|
||||
def accept_encoding
|
|
@ -95,6 +95,10 @@ module Rack
|
|||
:expires => Time.at(0) }.merge(value))
|
||||
end
|
||||
|
||||
def redirect(target, status=302)
|
||||
self.status = status
|
||||
self["Location"] = target
|
||||
end
|
||||
|
||||
def finish(&block)
|
||||
@block = block
|
||||
|
@ -120,7 +124,7 @@ module Rack
|
|||
#
|
||||
def write(str)
|
||||
s = str.to_s
|
||||
@length += s.size
|
||||
@length += Rack::Utils.bytesize(s)
|
||||
@writer.call s
|
||||
|
||||
header["Content-Length"] = @length.to_s
|
98
vendor/rails/actionpack/lib/action_controller/vendor/rack-1.1.pre/rack/rewindable_input.rb
vendored
Normal file
98
vendor/rails/actionpack/lib/action_controller/vendor/rack-1.1.pre/rack/rewindable_input.rb
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
require 'tempfile'
|
||||
|
||||
module Rack
|
||||
# Class which can make any IO object rewindable, including non-rewindable ones. It does
|
||||
# this by buffering the data into a tempfile, which is rewindable.
|
||||
#
|
||||
# rack.input is required to be rewindable, so if your input stream IO is non-rewindable
|
||||
# by nature (e.g. a pipe or a socket) then you can wrap it in an object of this class
|
||||
# to easily make it rewindable.
|
||||
#
|
||||
# Don't forget to call #close when you're done. This frees up temporary resources that
|
||||
# RewindableInput uses, though it does *not* close the original IO object.
|
||||
class RewindableInput
|
||||
def initialize(io)
|
||||
@io = io
|
||||
@rewindable_io = nil
|
||||
@unlinked = false
|
||||
end
|
||||
|
||||
def gets
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.gets
|
||||
end
|
||||
|
||||
def read(*args)
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.read(*args)
|
||||
end
|
||||
|
||||
def each(&block)
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.each(&block)
|
||||
end
|
||||
|
||||
def rewind
|
||||
make_rewindable unless @rewindable_io
|
||||
@rewindable_io.rewind
|
||||
end
|
||||
|
||||
# Closes this RewindableInput object without closing the originally
|
||||
# wrapped IO oject. Cleans up any temporary resources that this RewindableInput
|
||||
# has created.
|
||||
#
|
||||
# This method may be called multiple times. It does nothing on subsequent calls.
|
||||
def close
|
||||
if @rewindable_io
|
||||
if @unlinked
|
||||
@rewindable_io.close
|
||||
else
|
||||
@rewindable_io.close!
|
||||
end
|
||||
@rewindable_io = nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Ruby's Tempfile class has a bug. Subclass it and fix it.
|
||||
class Tempfile < ::Tempfile
|
||||
def _close
|
||||
@tmpfile.close if @tmpfile
|
||||
@data[1] = nil if @data
|
||||
@tmpfile = nil
|
||||
end
|
||||
end
|
||||
|
||||
def make_rewindable
|
||||
# Buffer all data into a tempfile. Since this tempfile is private to this
|
||||
# RewindableInput object, we chmod it so that nobody else can read or write
|
||||
# it. On POSIX filesystems we also unlink the file so that it doesn't
|
||||
# even have a file entry on the filesystem anymore, though we can still
|
||||
# access it because we have the file handle open.
|
||||
@rewindable_io = Tempfile.new('RackRewindableInput')
|
||||
@rewindable_io.chmod(0000)
|
||||
if filesystem_has_posix_semantics?
|
||||
@rewindable_io.unlink
|
||||
@unlinked = true
|
||||
end
|
||||
|
||||
buffer = ""
|
||||
while @io.read(1024 * 4, buffer)
|
||||
entire_buffer_written_out = false
|
||||
while !entire_buffer_written_out
|
||||
written = @rewindable_io.write(buffer)
|
||||
entire_buffer_written_out = written == buffer.size
|
||||
if !entire_buffer_written_out
|
||||
buffer.slice!(0 .. written - 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
@rewindable_io.rewind
|
||||
end
|
||||
|
||||
def filesystem_has_posix_semantics?
|
||||
RUBY_PLATFORM !~ /(mswin|mingw|cygwin|java)/
|
||||
end
|
||||
end
|
||||
end
|
|
@ -30,7 +30,7 @@ module Rack
|
|||
location = location.chomp('/')
|
||||
|
||||
[host, location, app]
|
||||
}.sort_by { |(h, l, a)| [-l.size, h.to_s.size] } # Longest path first
|
||||
}.sort_by { |(h, l, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
|
||||
end
|
||||
|
||||
def call(env)
|
|
@ -1,3 +1,5 @@
|
|||
# -*- encoding: binary -*-
|
||||
|
||||
require 'set'
|
||||
require 'tempfile'
|
||||
|
||||
|
@ -63,7 +65,7 @@ module Rack
|
|||
module_function :parse_nested_query
|
||||
|
||||
def normalize_params(params, name, v = nil)
|
||||
name =~ %r([\[\]]*([^\[\]]+)\]*)
|
||||
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
||||
k = $1 || ''
|
||||
after = $' || ''
|
||||
|
||||
|
@ -73,12 +75,12 @@ module Rack
|
|||
params[k] = v
|
||||
elsif after == "[]"
|
||||
params[k] ||= []
|
||||
raise TypeError unless params[k].is_a?(Array)
|
||||
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||||
params[k] << v
|
||||
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
|
||||
child_key = $1
|
||||
params[k] ||= []
|
||||
raise TypeError unless params[k].is_a?(Array)
|
||||
raise TypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
|
||||
if params[k].last.is_a?(Hash) && !params[k].last.key?(child_key)
|
||||
normalize_params(params[k].last, child_key, v)
|
||||
else
|
||||
|
@ -86,6 +88,7 @@ module Rack
|
|||
end
|
||||
else
|
||||
params[k] ||= {}
|
||||
raise TypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Hash)
|
||||
params[k] = normalize_params(params[k], after, v)
|
||||
end
|
||||
|
||||
|
@ -104,6 +107,25 @@ module Rack
|
|||
end
|
||||
module_function :build_query
|
||||
|
||||
def build_nested_query(value, prefix = nil)
|
||||
case value
|
||||
when Array
|
||||
value.map { |v|
|
||||
build_nested_query(v, "#{prefix}[]")
|
||||
}.join("&")
|
||||
when Hash
|
||||
value.map { |k, v|
|
||||
build_nested_query(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
|
||||
}.join("&")
|
||||
when String
|
||||
raise ArgumentError, "value must be a Hash" if prefix.nil?
|
||||
"#{prefix}=#{escape(value)}"
|
||||
else
|
||||
prefix
|
||||
end
|
||||
end
|
||||
module_function :build_nested_query
|
||||
|
||||
# Escape ampersands, brackets and quotes to their HTML/XML entities.
|
||||
def escape_html(string)
|
||||
string.to_s.gsub("&", "&").
|
||||
|
@ -288,11 +310,39 @@ module Rack
|
|||
# Usually, Rack::Request#POST takes care of calling this.
|
||||
|
||||
module Multipart
|
||||
class UploadedFile
|
||||
# The filename, *not* including the path, of the "uploaded" file
|
||||
attr_reader :original_filename
|
||||
|
||||
# The content type of the "uploaded" file
|
||||
attr_accessor :content_type
|
||||
|
||||
def initialize(path, content_type = "text/plain", binary = false)
|
||||
raise "#{path} file does not exist" unless ::File.exist?(path)
|
||||
@content_type = content_type
|
||||
@original_filename = ::File.basename(path)
|
||||
@tempfile = Tempfile.new(@original_filename)
|
||||
@tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
|
||||
@tempfile.binmode if binary
|
||||
FileUtils.copy_file(path, @tempfile.path)
|
||||
end
|
||||
|
||||
def path
|
||||
@tempfile.path
|
||||
end
|
||||
alias_method :local_path, :path
|
||||
|
||||
def method_missing(method_name, *args, &block) #:nodoc:
|
||||
@tempfile.__send__(method_name, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
EOL = "\r\n"
|
||||
MULTIPART_BOUNDARY = "AaB03x"
|
||||
|
||||
def self.parse_multipart(env)
|
||||
unless env['CONTENT_TYPE'] =~
|
||||
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
|
||||
%r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
|
||||
nil
|
||||
else
|
||||
boundary = "--#{$1}"
|
||||
|
@ -301,13 +351,16 @@ module Rack
|
|||
buf = ""
|
||||
content_length = env['CONTENT_LENGTH'].to_i
|
||||
input = env['rack.input']
|
||||
input.rewind
|
||||
|
||||
boundary_size = boundary.size + EOL.size
|
||||
boundary_size = Utils.bytesize(boundary) + EOL.size
|
||||
bufsize = 16384
|
||||
|
||||
content_length -= boundary_size
|
||||
|
||||
status = input.read(boundary_size)
|
||||
read_buffer = ''
|
||||
|
||||
status = input.read(boundary_size, read_buffer)
|
||||
raise EOFError, "bad content body" unless status == boundary + EOL
|
||||
|
||||
rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
|
||||
|
@ -318,15 +371,15 @@ module Rack
|
|||
filename = content_type = name = nil
|
||||
|
||||
until head && buf =~ rx
|
||||
if !head && i = buf.index("\r\n\r\n")
|
||||
if !head && i = buf.index(EOL+EOL)
|
||||
head = buf.slice!(0, i+2) # First \r\n
|
||||
buf.slice!(0, 2) # Second \r\n
|
||||
|
||||
filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
|
||||
content_type = head[/Content-Type: (.*)\r\n/ni, 1]
|
||||
name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
|
||||
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
|
||||
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
|
||||
|
||||
if filename
|
||||
if content_type || filename
|
||||
body = Tempfile.new("RackMultipart")
|
||||
body.binmode if body.respond_to?(:binmode)
|
||||
end
|
||||
|
@ -339,7 +392,7 @@ module Rack
|
|||
body << buf.slice!(0, buf.size - (boundary_size+4))
|
||||
end
|
||||
|
||||
c = input.read(bufsize < content_length ? bufsize : content_length)
|
||||
c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
|
||||
raise EOFError, "bad content body" if c.nil? || c.empty?
|
||||
buf << c
|
||||
content_length -= c.size
|
||||
|
@ -368,6 +421,12 @@ module Rack
|
|||
|
||||
data = {:filename => filename, :type => content_type,
|
||||
:name => name, :tempfile => body, :head => head}
|
||||
elsif !filename && content_type
|
||||
body.rewind
|
||||
|
||||
# Generic multipart cases, not coming from a form
|
||||
data = {:type => content_type,
|
||||
:name => name, :tempfile => body, :head => head}
|
||||
else
|
||||
data = body
|
||||
end
|
||||
|
@ -377,16 +436,81 @@ module Rack
|
|||
break if buf.empty? || content_length == -1
|
||||
}
|
||||
|
||||
begin
|
||||
input.rewind if input.respond_to?(:rewind)
|
||||
rescue Errno::ESPIPE
|
||||
# Handles exceptions raised by input streams that cannot be rewound
|
||||
# such as when using plain CGI under Apache
|
||||
end
|
||||
input.rewind
|
||||
|
||||
params
|
||||
end
|
||||
end
|
||||
|
||||
def self.build_multipart(params, first = true)
|
||||
if first
|
||||
unless params.is_a?(Hash)
|
||||
raise ArgumentError, "value must be a Hash"
|
||||
end
|
||||
|
||||
multipart = false
|
||||
query = lambda { |value|
|
||||
case value
|
||||
when Array
|
||||
value.each(&query)
|
||||
when Hash
|
||||
value.values.each(&query)
|
||||
when UploadedFile
|
||||
multipart = true
|
||||
end
|
||||
}
|
||||
params.values.each(&query)
|
||||
return nil unless multipart
|
||||
end
|
||||
|
||||
flattened_params = Hash.new
|
||||
|
||||
params.each do |key, value|
|
||||
k = first ? key.to_s : "[#{key}]"
|
||||
|
||||
case value
|
||||
when Array
|
||||
value.map { |v|
|
||||
build_multipart(v, false).each { |subkey, subvalue|
|
||||
flattened_params["#{k}[]#{subkey}"] = subvalue
|
||||
}
|
||||
}
|
||||
when Hash
|
||||
build_multipart(value, false).each { |subkey, subvalue|
|
||||
flattened_params[k + subkey] = subvalue
|
||||
}
|
||||
else
|
||||
flattened_params[k] = value
|
||||
end
|
||||
end
|
||||
|
||||
if first
|
||||
flattened_params.map { |name, file|
|
||||
if file.respond_to?(:original_filename)
|
||||
::File.open(file.path, "rb") do |f|
|
||||
f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
|
||||
Content-Type: #{file.content_type}\r
|
||||
Content-Length: #{::File.stat(file.path).size}\r
|
||||
\r
|
||||
#{f.read}\r
|
||||
EOF
|
||||
end
|
||||
else
|
||||
<<-EOF
|
||||
--#{MULTIPART_BOUNDARY}\r
|
||||
Content-Disposition: form-data; name="#{name}"\r
|
||||
\r
|
||||
#{file}\r
|
||||
EOF
|
||||
end
|
||||
}.join + "--#{MULTIPART_BOUNDARY}--\r"
|
||||
else
|
||||
flattened_params
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue