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
|
@ -58,7 +58,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
process 'create_system', 'password' => 'a_password', 'web_name' => 'My Wiki',
|
||||
'web_address' => 'my_wiki'
|
||||
|
||||
assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => @wiki.webs.keys.first, :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
assert_equal wiki_before, @wiki
|
||||
# and no new web should be created either
|
||||
assert_equal old_size, @wiki.webs.size
|
||||
|
@ -68,7 +68,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
def test_create_system_no_form_and_wiki_already_initialized
|
||||
assert @wiki.setup?
|
||||
process('create_system')
|
||||
assert_redirected_to :web => @wiki.webs.keys.first, :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => @wiki.webs.keys.first, :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
assert(@response.has_flash_object?(:error))
|
||||
end
|
||||
|
||||
|
@ -78,7 +78,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
|
||||
process 'create_web', 'system_password' => 'pswd', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'new', :id => 'HomePage'
|
||||
wiki2 = @wiki.webs['wiki2']
|
||||
assert wiki2
|
||||
assert_equal 'Wiki Two', wiki2.name
|
||||
|
@ -90,7 +90,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
|
||||
process 'create_web', 'system_password' => 'instiki', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => 'wiki2', :action => 'new', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'new', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_create_web_failed_authentication
|
||||
|
@ -98,7 +98,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
|
||||
process 'create_web', 'system_password' => 'wrong', 'name' => 'Wiki Two', 'address' => 'wiki2'
|
||||
|
||||
assert_redirected_to :web => nil, :action => 'create_web'
|
||||
assert_redirected_to :controller => 'admin', :action => 'create_web'
|
||||
assert_nil @wiki.webs['wiki2']
|
||||
end
|
||||
|
||||
|
@ -108,7 +108,6 @@ class AdminControllerTest < ActionController::TestCase
|
|||
assert_response :success
|
||||
end
|
||||
|
||||
|
||||
def test_edit_web_no_form
|
||||
process 'edit_web', 'web' => 'wiki1'
|
||||
# this action simply renders a form
|
||||
|
@ -125,7 +124,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
'brackets_only' => 'on', 'count_pages' => 'on', 'allow_uploads' => 'on',
|
||||
'max_upload_size' => '300')
|
||||
|
||||
assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'renamed_wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
@web = Web.find(@web.id)
|
||||
assert_equal 'renamed_wiki1', @web.address
|
||||
assert_equal 'Renamed Wiki1', @web.name
|
||||
|
@ -164,7 +163,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
# safe_mode, published, brackets_only, count_pages, allow_uploads not set
|
||||
# and should become false
|
||||
|
||||
assert_redirected_to :web => 'renamed_wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'renamed_wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
@web = Web.find(@web.id)
|
||||
assert !@web.safe_mode?
|
||||
assert !@web.published?
|
||||
|
@ -237,7 +236,7 @@ class AdminControllerTest < ActionController::TestCase
|
|||
|
||||
# third pass does not destroy HomePage
|
||||
r = process('remove_orphaned_pages', 'web' => 'wiki1', 'system_password_orphaned' => 'pswd')
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'list'
|
||||
@web.pages(true)
|
||||
assert_equal page_order, @web.select.sort,
|
||||
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
|
||||
|
@ -271,14 +270,14 @@ class AdminControllerTest < ActionController::TestCase
|
|||
# third pass does does nothing, since there are no pages in the
|
||||
# 'leaves' category.
|
||||
r = process('remove_orphaned_pages_in_category', 'web' => 'wiki1', 'category' => 'leaves', 'system_password_orphaned_in_category' => 'pswd')
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list'
|
||||
@web.pages(true)
|
||||
assert_equal page_order, @web.select.sort,
|
||||
"Pages are not as expected: #{@web.select.sort.map {|p| p.name}.inspect}"
|
||||
|
||||
# fourth pass destroys Oak
|
||||
r = process('remove_orphaned_pages_in_category', 'web' => 'wiki1', 'category' => 'trees', 'system_password_orphaned_in_category' => 'pswd')
|
||||
assert_redirected_to :action => 'list'
|
||||
assert_redirected_to :controller => 'wiki', :web => 'wiki1', :action => 'list'
|
||||
@web.pages(true)
|
||||
page_order.delete(@oak)
|
||||
assert_equal page_order, @web.select.sort,
|
||||
|
|
|
@ -39,7 +39,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
set_web_property :password, 'pswd'
|
||||
|
||||
get :authenticate, :web => 'wiki1', :password => 'pswd'
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
assert_equal 'pswd', @response.cookies['wiki1']
|
||||
end
|
||||
|
||||
|
@ -47,7 +47,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
set_web_property :password, 'pswd'
|
||||
|
||||
r = process('authenticate', 'web' => 'wiki1', 'password' => 'wrong password')
|
||||
assert_redirected_to :action => 'login', :web => 'wiki1'
|
||||
assert_redirected_to :action => 'login', :controller => 'wiki', :web => 'wiki1'
|
||||
assert_nil r.cookies['web_address']
|
||||
end
|
||||
|
||||
|
@ -72,7 +72,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
|
||||
r = process('cancel_edit', 'web' => 'wiki1', 'id' => 'Oak')
|
||||
|
||||
assert_redirected_to :action => 'show', :id => 'Oak'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'Oak'
|
||||
assert !Page.find(@oak.id).locked?(Time.now)
|
||||
end
|
||||
|
||||
|
@ -85,7 +85,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
def test_edit_page_locked_page
|
||||
@home.lock(Time.now, 'Locky')
|
||||
process 'edit', 'web' => 'wiki1', 'id' => 'HomePage'
|
||||
assert_redirected_to :action => 'locked'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'locked', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_edit_page_break_lock
|
||||
|
@ -233,19 +233,19 @@ class WikiControllerTest < ActionController::TestCase
|
|||
# delete extra web fixture
|
||||
webs(:instiki).destroy
|
||||
process('index')
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_index_multiple_webs
|
||||
@wiki.create_web('Test Wiki 2', 'wiki2')
|
||||
process('index')
|
||||
assert_redirected_to :action => 'web_list'
|
||||
assert_redirected_to :controller => 'wiki', :action => 'web_list'
|
||||
end
|
||||
|
||||
def test_index_multiple_webs_web_explicit
|
||||
@wiki.create_web('Test Wiki 2', 'wiki2')
|
||||
process('index', 'web' => 'wiki2')
|
||||
assert_redirected_to :web => 'wiki2', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki2', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
end
|
||||
|
||||
def test_index_wiki_not_initialized
|
||||
|
@ -598,7 +598,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page',
|
||||
'author' => 'AuthorOfNewPage'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'NewPage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'NewPage'
|
||||
assert_equal 'AuthorOfNewPage', r.cookies['author']
|
||||
assert_match @eternity, r.headers["Set-Cookie"][0]
|
||||
new_page = @wiki.read_page('wiki1', 'NewPage')
|
||||
|
@ -610,7 +610,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n\000",
|
||||
'author' => 'AuthorOfNewPage'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage', :content => ''
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
|
||||
assert_equal 'AuthorOfNewPage', r.cookies['author']
|
||||
assert_match @eternity, r.headers["Set-Cookie"][0]
|
||||
end
|
||||
|
@ -619,7 +619,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n",
|
||||
'author' => 'AuthorOfNewPage'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
|
||||
assert_equal 'AuthorOfNewPage', r.cookies['author']
|
||||
assert_match @eternity, r.headers["Set-Cookie"][0]
|
||||
end
|
||||
|
@ -628,7 +628,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => "Contents of a new page\r\n",
|
||||
'author' => 'AuthorOfNewPage'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'NewPage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'NewPage', :content => ''
|
||||
assert_equal 'AuthorOfNewPage', r.cookies['author']
|
||||
assert_match @eternity, r.headers["Set-Cookie"][0]
|
||||
end
|
||||
|
@ -640,7 +640,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => 'Revised HomePage',
|
||||
'author' => 'Batman'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :id => 'HomePage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'show', :id => 'HomePage'
|
||||
assert_equal 'Batman', r.cookies['author']
|
||||
home_page = @wiki.read_page('wiki1', 'HomePage')
|
||||
assert_equal current_revisions+1, home_page.revisions.size
|
||||
|
@ -656,7 +656,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', 'web' => 'wiki1', 'id' => 'HomePage', 'content' => "Revised HomePage\000",
|
||||
'author' => 'Batman'
|
||||
|
||||
assert_redirected_to :web => 'wiki1', :action => 'edit', :id => 'HomePage',
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'edit', :id => 'HomePage',
|
||||
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width' +
|
||||
'=3em]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEng' +
|
||||
'ine in that SmartEngineGUI'
|
||||
|
@ -688,7 +688,10 @@ class WikiControllerTest < ActionController::TestCase
|
|||
r = process 'save', {'web' => 'wiki1', 'id' => 'HomePage',
|
||||
'content' => @home.revisions.last.content.dup + "\n Try viagra.\n",
|
||||
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'}
|
||||
assert_redirected_to :action => 'edit', :web => 'wiki1', :id => 'HomePage'
|
||||
assert_redirected_to :action => 'edit', :controller => 'wiki', :web => 'wiki1', :id => 'HomePage',
|
||||
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width=3e'+
|
||||
'm]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in'+
|
||||
" that SmartEngineGUI\n Try viagra.\n"
|
||||
assert r.flash[:error].to_s == "Your edit was blocked by spam filtering"
|
||||
end
|
||||
|
||||
|
@ -700,7 +703,10 @@ class WikiControllerTest < ActionController::TestCase
|
|||
'content' => @home.revisions.last.content.dup,
|
||||
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/HomePage'}
|
||||
|
||||
assert_redirected_to :action => 'edit', :web => 'wiki1', :id => 'HomePage'
|
||||
assert_redirected_to :action => 'edit', :controller => 'wiki', :web => 'wiki1', :id => 'HomePage',
|
||||
:content => 'HisWay would be MyWay $\sin(x)\begin{svg}<svg/>\end{svg}\includegraphics[width=3e'+
|
||||
'm]{foo}$ in kinda ThatWay in HisWay though MyWay \OverThere -- see SmartEngine in'+
|
||||
' that SmartEngineGUI'
|
||||
assert r.flash[:error].to_s == "You have tried to save page 'HomePage' without changing its content"
|
||||
|
||||
revisions_after = @home.revisions.size
|
||||
|
@ -717,7 +723,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
'content' => @liquor.revisions.last.content.dup, 'new_name' => 'booze',
|
||||
'author' => 'SomeOtherAuthor'}, {:return_to => '/wiki1/show/booze'}
|
||||
|
||||
assert_redirected_to :action => 'show', :web => 'wiki1', :id => 'booze'
|
||||
assert_redirected_to :action => 'show', :controller => 'wiki', :web => 'wiki1', :id => 'booze'
|
||||
|
||||
revisions_after = @liquor.revisions.size
|
||||
assert_equal revisions_before + 1, revisions_after
|
||||
|
@ -741,20 +747,20 @@ class WikiControllerTest < ActionController::TestCase
|
|||
def test_save_invalid_author_name
|
||||
r = process 'save', 'web' => 'wiki1', 'id' => 'NewPage', 'content' => 'Contents of a new page',
|
||||
'author' => 'foo.bar'
|
||||
assert_redirected_to :action => 'new', :web => 'wiki1', :id => 'NewPage'
|
||||
assert_redirected_to :action => 'new', :controller => 'wiki', :web => 'wiki1', :id => 'NewPage'
|
||||
assert r.flash[:error].to_s == 'Your name cannot contain a "."'
|
||||
|
||||
r = process 'save', 'web' => 'wiki1', 'id' => 'AnotherPage', 'content' => 'Contents of a new page',
|
||||
'author' => "\000"
|
||||
|
||||
assert_redirected_to :action => 'new', :web => 'wiki1', :id => 'AnotherPage'
|
||||
assert_redirected_to :action => 'new', :controller => 'wiki', :web => 'wiki1', :id => 'AnotherPage'
|
||||
assert r.flash[:error].to_s == "Your name was not valid utf-8"
|
||||
end
|
||||
|
||||
def test_search
|
||||
r = process 'search', 'web' => 'wiki1', 'query' => '\s[A-Z]ak'
|
||||
|
||||
assert_redirected_to :action => 'show', :id => 'Oak'
|
||||
assert_redirected_to :web => 'wiki1', :action => 'show', :controller => 'wiki', :id => 'Oak'
|
||||
end
|
||||
|
||||
def test_search_multiple_results
|
||||
|
@ -873,7 +879,7 @@ class WikiControllerTest < ActionController::TestCase
|
|||
|
||||
def test_show_page_nonexistant_page
|
||||
process('show', 'id' => 'UnknownPage', 'web' => 'wiki1')
|
||||
assert_redirected_to :web => 'wiki1', :action => 'new', :id => 'UnknownPage'
|
||||
assert_redirected_to :web => 'wiki1', :controller => 'wiki', :action => 'new', :id => 'UnknownPage'
|
||||
end
|
||||
|
||||
def test_show_no_page
|
||||
|
|
4
vendor/rails/actionmailer/CHANGELOG
vendored
4
vendor/rails/actionmailer/CHANGELOG
vendored
|
@ -1,3 +1,7 @@
|
|||
*2.3.3 (July 12, 2009)*
|
||||
|
||||
* No changes, just a version bump.
|
||||
|
||||
*2.3.2 [Final] (March 15, 2009)*
|
||||
|
||||
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
|
||||
|
|
5
vendor/rails/actionmailer/Rakefile
vendored
5
vendor/rails/actionmailer/Rakefile
vendored
|
@ -4,7 +4,6 @@ require 'rake/testtask'
|
|||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -55,7 +54,7 @@ spec = Gem::Specification.new do |s|
|
|||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 2.3.2' + PKG_BUILD)
|
||||
s.add_dependency('actionpack', '= 2.3.3' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
@ -76,12 +75,14 @@ end
|
|||
|
||||
desc "Publish the API documentation"
|
||||
task :pgem => [:package] do
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/am", "doc").upload
|
||||
end
|
||||
|
||||
|
|
|
@ -674,7 +674,7 @@ module ActionMailer #:nodoc:
|
|||
def perform_delivery_smtp(mail)
|
||||
destinations = mail.destinations
|
||||
mail.ready_to_send
|
||||
sender = (mail['return-path'] && mail['return-path'].spec) || mail.from
|
||||
sender = (mail['return-path'] && mail['return-path'].spec) || mail['from']
|
||||
|
||||
smtp = Net::SMTP.new(smtp_settings[:address], smtp_settings[:port])
|
||||
smtp.enable_starttls_auto if smtp_settings[:enable_starttls_auto] && smtp.respond_to?(:enable_starttls_auto)
|
||||
|
|
|
@ -2,7 +2,7 @@ module ActionMailer
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 2
|
||||
TINY = 3
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
|
||||
gem 'mocha', '>= 0.9.5'
|
||||
require 'mocha'
|
||||
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../activesupport/lib"
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
|
|
5
vendor/rails/actionpack/CHANGELOG
vendored
5
vendor/rails/actionpack/CHANGELOG
vendored
|
@ -1,3 +1,8 @@
|
|||
*2.3.3 (July 12, 2009)*
|
||||
|
||||
* Fixed that TestResponse.cookies was returning cookies unescaped #1867 [Doug McInnes]
|
||||
|
||||
|
||||
*2.3.2 [Final] (March 15, 2009)*
|
||||
|
||||
* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH]
|
||||
|
|
6
vendor/rails/actionpack/Rakefile
vendored
6
vendor/rails/actionpack/Rakefile
vendored
|
@ -4,7 +4,6 @@ require 'rake/testtask'
|
|||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/sshpublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
|
@ -80,7 +79,8 @@ spec = Gem::Specification.new do |s|
|
|||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD)
|
||||
s.add_dependency('activesupport', '= 2.3.3' + PKG_BUILD)
|
||||
s.add_dependency('rack', '~> 1.0.0')
|
||||
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_controller'
|
||||
|
@ -136,12 +136,14 @@ task :update_js => [ :update_scriptaculous ]
|
|||
|
||||
desc "Publish the API documentation"
|
||||
task :pgem => [:package] do
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
require 'rake/contrib/sshpublisher'
|
||||
Rake::SshDirPublisher.new("wrath.rubyonrails.org", "public_html/ap", "doc").upload
|
||||
end
|
||||
|
||||
|
|
|
@ -32,10 +32,10 @@ rescue LoadError
|
|||
end
|
||||
|
||||
begin
|
||||
gem 'rack', '~> 1.0.0'
|
||||
gem 'rack', '~> 1.1.0'
|
||||
require 'rack'
|
||||
rescue Gem::LoadError
|
||||
require 'action_controller/vendor/rack-1.0/rack'
|
||||
require 'action_controller/vendor/rack-1.1.pre/rack'
|
||||
end
|
||||
|
||||
module ActionController
|
||||
|
@ -45,7 +45,6 @@ module ActionController
|
|||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
end
|
||||
|
||||
autoload :AbstractRequest, 'action_controller/request'
|
||||
autoload :Base, 'action_controller/base'
|
||||
autoload :Benchmarking, 'action_controller/benchmarking'
|
||||
autoload :Caching, 'action_controller/caching'
|
||||
|
|
|
@ -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
|
|
@ -2,7 +2,7 @@ module ActionPack #:nodoc:
|
|||
module VERSION #:nodoc:
|
||||
MAJOR = 2
|
||||
MINOR = 3
|
||||
TINY = 2
|
||||
TINY = 3
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
|
|
|
@ -11,7 +11,7 @@ module ActionView #:nodoc:
|
|||
autoload :FormHelper, 'action_view/helpers/form_helper'
|
||||
autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper'
|
||||
autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
|
||||
autoload :JavascriptHelper, 'action_view/helpers/javascript_helper'
|
||||
autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
|
||||
autoload :NumberHelper, 'action_view/helpers/number_helper'
|
||||
autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
|
||||
autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
|
||||
|
|
|
@ -272,14 +272,17 @@ module ActionView
|
|||
# javascript_include_tag :all, :cache => true, :recursive => true
|
||||
def javascript_include_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
concat = options.delete("concat")
|
||||
cache = concat || options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
if concat || (ActionController::Base.perform_caching && cache)
|
||||
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
||||
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
||||
joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : JAVASCRIPTS_DIR, joined_javascript_name)
|
||||
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
|
||||
unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
|
||||
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
|
||||
end
|
||||
javascript_src_tag(joined_javascript_name, options)
|
||||
else
|
||||
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
|
||||
|
@ -410,16 +413,25 @@ module ActionView
|
|||
# The <tt>:recursive</tt> option is also available for caching:
|
||||
#
|
||||
# stylesheet_link_tag :all, :cache => true, :recursive => true
|
||||
#
|
||||
# To force concatenation (even in development mode) set <tt>:concat</tt> to true. This is useful if
|
||||
# you have too many stylesheets for IE to load.
|
||||
#
|
||||
# stylesheet_link_tag :all, :concat => true
|
||||
#
|
||||
def stylesheet_link_tag(*sources)
|
||||
options = sources.extract_options!.stringify_keys
|
||||
cache = options.delete("cache")
|
||||
concat = options.delete("concat")
|
||||
cache = concat || options.delete("cache")
|
||||
recursive = options.delete("recursive")
|
||||
|
||||
if ActionController::Base.perform_caching && cache
|
||||
if concat || (ActionController::Base.perform_caching && cache)
|
||||
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
||||
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
||||
joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? ASSETS_DIR : STYLESHEETS_DIR, joined_stylesheet_name)
|
||||
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
|
||||
unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
|
||||
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
|
||||
end
|
||||
stylesheet_tag(joined_stylesheet_name, options)
|
||||
else
|
||||
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
|
||||
|
@ -679,4 +691,4 @@ module ActionView
|
|||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -493,7 +493,8 @@ module ActionView
|
|||
# Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object
|
||||
# assigned to the template (identified by +object+). The text of label will default to the attribute name unless you specify
|
||||
# it explicitly. Additional options on the label tag can be passed as a hash with +options+. These options will be tagged
|
||||
# onto the HTML as an HTML element attribute as in the example shown.
|
||||
# onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to
|
||||
# target labels for radio_button tags (where the value is used in the ID of the input tag).
|
||||
#
|
||||
# ==== Examples
|
||||
# label(:post, :title)
|
||||
|
@ -505,6 +506,9 @@ module ActionView
|
|||
# label(:post, :title, "A short title", :class => "title_label")
|
||||
# # => <label for="post_title" class="title_label">A short title</label>
|
||||
#
|
||||
# label(:post, :privacy, "Public Post", :value => "public")
|
||||
# # => <label for="post_privacy_public">Public Post</label>
|
||||
#
|
||||
def label(object_name, method, text = nil, options = {})
|
||||
InstanceTag.new(object_name, method, self, options.delete(:object)).to_label_tag(text, options)
|
||||
end
|
||||
|
@ -720,8 +724,9 @@ module ActionView
|
|||
|
||||
def to_label_tag(text = nil, options = {})
|
||||
options = options.stringify_keys
|
||||
tag_value = options.delete("value")
|
||||
name_and_id = options.dup
|
||||
add_default_name_and_id(name_and_id)
|
||||
add_default_name_and_id_for_value(tag_value, name_and_id)
|
||||
options.delete("index")
|
||||
options["for"] ||= name_and_id["id"]
|
||||
content = (text.blank? ? nil : text.to_s) || method_name.humanize
|
||||
|
@ -753,11 +758,7 @@ module ActionView
|
|||
checked = self.class.radio_button_checked?(value(object), tag_value)
|
||||
end
|
||||
options["checked"] = "checked" if checked
|
||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||
options["id"] ||= defined?(@auto_index) ?
|
||||
"#{tag_id_with_index(@auto_index)}_#{pretty_tag_value}" :
|
||||
"#{tag_id}_#{pretty_tag_value}"
|
||||
add_default_name_and_id(options)
|
||||
add_default_name_and_id_for_value(tag_value, options)
|
||||
tag("input", options)
|
||||
end
|
||||
|
||||
|
@ -858,6 +859,17 @@ module ActionView
|
|||
end
|
||||
|
||||
private
|
||||
def add_default_name_and_id_for_value(tag_value, options)
|
||||
if tag_value
|
||||
pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
|
||||
specified_id = options["id"]
|
||||
add_default_name_and_id(options)
|
||||
options["id"] += "_#{pretty_tag_value}" unless specified_id
|
||||
else
|
||||
add_default_name_and_id(options)
|
||||
end
|
||||
end
|
||||
|
||||
def add_default_name_and_id(options)
|
||||
if options.has_key?("index")
|
||||
options["name"] ||= tag_name_with_index(options["index"])
|
||||
|
@ -905,6 +917,7 @@ module ActionView
|
|||
attr_accessor :object_name, :object, :options
|
||||
|
||||
def initialize(object_name, object, template, options, proc)
|
||||
@nested_child_index = {}
|
||||
@object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
|
||||
@default_options = @options ? @options.slice(:index) : {}
|
||||
if @object_name.to_s.match(/\[\]$/)
|
||||
|
@ -1007,7 +1020,7 @@ module ActionView
|
|||
explicit_child_index = args.last[:child_index] if args.last.is_a?(Hash)
|
||||
|
||||
children.map do |child|
|
||||
fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index}]", child, args, block)
|
||||
fields_for_nested_model("#{name}[#{explicit_child_index || nested_child_index(name)}]", child, args, block)
|
||||
end.join
|
||||
else
|
||||
fields_for_nested_model(name, explicit_object || association, args, block)
|
||||
|
@ -1025,9 +1038,9 @@ module ActionView
|
|||
end
|
||||
end
|
||||
|
||||
def nested_child_index
|
||||
@nested_child_index ||= -1
|
||||
@nested_child_index += 1
|
||||
def nested_child_index(name)
|
||||
@nested_child_index[name] ||= -1
|
||||
@nested_child_index[name] += 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1036,4 +1049,4 @@ module ActionView
|
|||
cattr_accessor :default_form_builder
|
||||
self.default_form_builder = ::ActionView::Helpers::FormBuilder
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -230,6 +230,8 @@ module ActionView
|
|||
#
|
||||
# NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
|
||||
def options_for_select(container, selected = nil)
|
||||
return container if String === container
|
||||
|
||||
container = container.to_a if Hash === container
|
||||
selected, disabled = extract_selected_and_disabled(selected)
|
||||
|
||||
|
|
|
@ -230,6 +230,8 @@ module ActionView
|
|||
# * <tt>:rows</tt> - Specify the number of rows in the textarea
|
||||
# * <tt>:cols</tt> - Specify the number of columns in the textarea
|
||||
# * <tt>:disabled</tt> - If set to true, the user will not be able to use this input.
|
||||
# * <tt>:escape</tt> - By default, the contents of the text input are HTML escaped.
|
||||
# If you need unescaped contents, set this to false.
|
||||
# * Any other key creates standard HTML attributes for the tag.
|
||||
#
|
||||
# ==== Examples
|
||||
|
@ -257,7 +259,10 @@ module ActionView
|
|||
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
||||
end
|
||||
|
||||
content_tag :textarea, content, { "name" => name, "id" => name }.update(options.stringify_keys)
|
||||
escape = options.key?("escape") ? options.delete("escape") : true
|
||||
content = html_escape(content) if escape
|
||||
|
||||
content_tag :textarea, content, { "name" => name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
|
||||
end
|
||||
|
||||
# Creates a check box form input tag.
|
||||
|
@ -353,7 +358,8 @@ module ActionView
|
|||
disable_with << ";#{options.delete('onclick')}" if options['onclick']
|
||||
|
||||
options["onclick"] = "if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }"
|
||||
options["onclick"] << "else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }"
|
||||
options["onclick"] << "else { hiddenCommit = document.createElement('input');hiddenCommit.type = 'hidden';"
|
||||
options["onclick"] << "hiddenCommit.value = this.value;hiddenCommit.name = this.name;this.form.appendChild(hiddenCommit); }"
|
||||
options["onclick"] << "this.setAttribute('originalValue', this.value);this.disabled = true;#{disable_with};"
|
||||
options["onclick"] << "result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());"
|
||||
options["onclick"] << "if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;"
|
||||
|
@ -444,10 +450,10 @@ module ActionView
|
|||
''
|
||||
when /^post$/i, "", nil
|
||||
html_options["method"] = "post"
|
||||
protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0') : ''
|
||||
protect_against_forgery? ? content_tag(:div, token_tag, :style => 'margin:0;padding:0;display:inline') : ''
|
||||
else
|
||||
html_options["method"] = "post"
|
||||
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0')
|
||||
content_tag(:div, tag(:input, :type => "hidden", :name => "_method", :value => method) + token_tag, :style => 'margin:0;padding:0;display:inline')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -686,7 +686,7 @@ module ActionView
|
|||
# Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript
|
||||
# expression as an argument to another JavaScriptGenerator method.
|
||||
def literal(code)
|
||||
ActiveSupport::JSON::Variable.new(code.to_s)
|
||||
::ActiveSupport::JSON::Variable.new(code.to_s)
|
||||
end
|
||||
|
||||
# Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be
|
||||
|
@ -973,7 +973,7 @@ module ActionView
|
|||
def loop_on_multiple_args(method, ids)
|
||||
record(ids.size>1 ?
|
||||
"#{javascript_object_for(ids)}.each(#{method})" :
|
||||
"#{method}(#{ids.first.to_json})")
|
||||
"#{method}(#{::ActiveSupport::JSON.encode(ids.first)})")
|
||||
end
|
||||
|
||||
def page
|
||||
|
@ -997,7 +997,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def javascript_object_for(object)
|
||||
object.respond_to?(:to_json) ? object.to_json : object.inspect
|
||||
::ActiveSupport::JSON.encode(object)
|
||||
end
|
||||
|
||||
def arguments_for_call(arguments, block = nil)
|
||||
|
@ -1139,7 +1139,7 @@ module ActionView
|
|||
class JavaScriptElementProxy < JavaScriptProxy #:nodoc:
|
||||
def initialize(generator, id)
|
||||
@id = id
|
||||
super(generator, "$(#{id.to_json})")
|
||||
super(generator, "$(#{::ActiveSupport::JSON.encode(id)})")
|
||||
end
|
||||
|
||||
# Allows access of element attributes through +attribute+. Examples:
|
||||
|
@ -1211,7 +1211,7 @@ module ActionView
|
|||
enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block
|
||||
else
|
||||
add_variable_assignment!(variable)
|
||||
append_enumerable_function!("eachSlice(#{number.to_json});")
|
||||
append_enumerable_function!("eachSlice(#{::ActiveSupport::JSON.encode(number)});")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1232,7 +1232,7 @@ module ActionView
|
|||
|
||||
def pluck(variable, property)
|
||||
add_variable_assignment!(variable)
|
||||
append_enumerable_function!("pluck(#{property.to_json});")
|
||||
append_enumerable_function!("pluck(#{::ActiveSupport::JSON.encode(property)});")
|
||||
end
|
||||
|
||||
def zip(variable, *arguments, &block)
|
||||
|
@ -1296,7 +1296,7 @@ module ActionView
|
|||
|
||||
class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\
|
||||
def initialize(generator, pattern)
|
||||
super(generator, "$$(#{pattern.to_json})")
|
||||
super(generator, "$$(#{::ActiveSupport::JSON.encode(pattern)})")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ module ActionView
|
|||
# You can change the behaviour with various options, see
|
||||
# http://script.aculo.us for more documentation.
|
||||
def visual_effect(name, element_id = false, js_options = {})
|
||||
element = element_id ? element_id.to_json : "element"
|
||||
element = element_id ? ActiveSupport::JSON.encode(element_id) : "element"
|
||||
|
||||
js_options[:queue] = if js_options[:queue].is_a?(Hash)
|
||||
'{' + js_options[:queue].map {|k, v| k == :limit ? "#{k}:#{v}" : "#{k}:'#{v}'" }.join(',') + '}'
|
||||
|
@ -138,7 +138,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def sortable_element_js(element_id, options = {}) #:nodoc:
|
||||
options[:with] ||= "Sortable.serialize(#{element_id.to_json})"
|
||||
options[:with] ||= "Sortable.serialize(#{ActiveSupport::JSON.encode(element_id)})"
|
||||
options[:onUpdate] ||= "function(){" + remote_function(options) + "}"
|
||||
options.delete_if { |key, value| PrototypeHelper::AJAX_OPTIONS.include?(key) }
|
||||
|
||||
|
@ -149,7 +149,7 @@ module ActionView
|
|||
options[:containment] = array_or_string_for_javascript(options[:containment]) if options[:containment]
|
||||
options[:only] = array_or_string_for_javascript(options[:only]) if options[:only]
|
||||
|
||||
%(Sortable.create(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
%(Sortable.create(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ draggable.
|
||||
|
@ -164,7 +164,7 @@ module ActionView
|
|||
end
|
||||
|
||||
def draggable_element_js(element_id, options = {}) #:nodoc:
|
||||
%(new Draggable(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
%(new Draggable(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
|
||||
# Makes the element with the DOM ID specified by +element_id+ receive
|
||||
|
@ -219,7 +219,7 @@ module ActionView
|
|||
# Confirmation happens during the onDrop callback, so it can be removed from the options
|
||||
options.delete(:confirm) if options[:confirm]
|
||||
|
||||
%(Droppables.add(#{element_id.to_json}, #{options_for_javascript(options)});)
|
||||
%(Droppables.add(#{ActiveSupport::JSON.encode(element_id)}, #{options_for_javascript(options)});)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -271,8 +271,8 @@ module ActionView
|
|||
end
|
||||
|
||||
# Returns the text with all the Markdown codes turned into HTML tags.
|
||||
# <i>This method requires BlueCloth[http://www.deveiate.org/projects/BlueCloth]
|
||||
# to be available</i>.
|
||||
# <i>This method requires BlueCloth[http://www.deveiate.org/projects/BlueCloth] or another
|
||||
# Markdown library to be installed.</i>.
|
||||
#
|
||||
# ==== Examples
|
||||
# markdown("We are using __Markdown__ now!")
|
||||
|
@ -288,7 +288,7 @@ module ActionView
|
|||
# markdown('![The ROR logo](http://rubyonrails.com/images/rails.png "Ruby on Rails")')
|
||||
# # => '<p><img src="http://rubyonrails.com/images/rails.png" alt="The ROR logo" title="Ruby on Rails" /></p>'
|
||||
def markdown(text)
|
||||
text.blank? ? "" : BlueCloth.new(text).to_html
|
||||
text.blank? ? "" : Markdown.new(text).to_html
|
||||
end
|
||||
|
||||
# Returns +text+ transformed into HTML using simple formatting rules.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'action_view/helpers/javascript_helper'
|
||||
#require 'action_view/helpers/javascript_helper'
|
||||
|
||||
module ActionView
|
||||
module Helpers #:nodoc:
|
||||
|
|
|
@ -61,7 +61,7 @@ module ActionView #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path)
|
||||
return Template.new(original_template_path) if File.file?(original_template_path)
|
||||
|
||||
raise MissingTemplate.new(self, original_template_path, format)
|
||||
end
|
||||
|
|
|
@ -107,9 +107,8 @@ module ActionView #:nodoc:
|
|||
attr_accessor :locale, :name, :format, :extension
|
||||
delegate :to_s, :to => :path
|
||||
|
||||
def initialize(template_path, load_path)
|
||||
@template_path = template_path.dup
|
||||
@load_path, @filename = load_path, File.join(load_path, template_path)
|
||||
def initialize(template_path, load_path = nil)
|
||||
@template_path, @load_path = template_path.dup, load_path
|
||||
@base_path, @name, @locale, @format, @extension = split(template_path)
|
||||
@base_path.to_s.gsub!(/\/$/, '') # Push to split method
|
||||
|
||||
|
@ -180,6 +179,12 @@ module ActionView #:nodoc:
|
|||
@@exempt_from_layout.any? { |exempted| path =~ exempted }
|
||||
end
|
||||
|
||||
def filename
|
||||
# no load_path means this is an "absolute pathed" template
|
||||
load_path ? File.join(load_path, template_path) : template_path
|
||||
end
|
||||
memoize :filename
|
||||
|
||||
def source
|
||||
File.read(filename)
|
||||
end
|
||||
|
@ -212,46 +217,30 @@ module ActionView #:nodoc:
|
|||
end
|
||||
|
||||
def valid_locale?(locale)
|
||||
I18n.available_locales.include?(locale.to_sym)
|
||||
locale && I18n.available_locales.include?(locale.to_sym)
|
||||
end
|
||||
|
||||
# Returns file split into an array
|
||||
# [base_path, name, locale, format, extension]
|
||||
def split(file)
|
||||
if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/)
|
||||
base_path = m[1]
|
||||
name = m[2]
|
||||
extensions = m[3]
|
||||
else
|
||||
return
|
||||
[m[1], m[2], *parse_extensions(m[3])]
|
||||
end
|
||||
end
|
||||
|
||||
# returns parsed extensions as an array
|
||||
# [locale, format, extension]
|
||||
def parse_extensions(extensions)
|
||||
exts = extensions.split(".")
|
||||
|
||||
if extension = valid_extension?(exts.last) && exts.pop || nil
|
||||
locale = valid_locale?(exts.first) && exts.shift || nil
|
||||
format = exts.join('.') if exts.any? # join('.') is needed for multipart templates
|
||||
else # no extension, just format
|
||||
format = exts.last
|
||||
end
|
||||
|
||||
locale = nil
|
||||
format = nil
|
||||
extension = nil
|
||||
|
||||
if m = extensions.split(".")
|
||||
if valid_locale?(m[0]) && m[1] && valid_extension?(m[2]) # All three
|
||||
locale = m[0]
|
||||
format = m[1]
|
||||
extension = m[2]
|
||||
elsif m[0] && m[1] && valid_extension?(m[2]) # Multipart formats
|
||||
format = "#{m[0]}.#{m[1]}"
|
||||
extension = m[2]
|
||||
elsif valid_locale?(m[0]) && valid_extension?(m[1]) # locale and extension
|
||||
locale = m[0]
|
||||
extension = m[1]
|
||||
elsif valid_extension?(m[1]) # format and extension
|
||||
format = m[0]
|
||||
extension = m[1]
|
||||
elsif valid_extension?(m[0]) # Just extension
|
||||
extension = m[0]
|
||||
else # No extension
|
||||
format = m[0]
|
||||
end
|
||||
end
|
||||
|
||||
[base_path, name, locale, format, extension]
|
||||
[locale, format, extension]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,7 +8,7 @@ require 'yaml'
|
|||
require 'stringio'
|
||||
require 'test/unit'
|
||||
|
||||
gem 'mocha', '>= 0.9.5'
|
||||
gem 'mocha', '>= 0.9.7'
|
||||
require 'mocha'
|
||||
|
||||
begin
|
||||
|
|
|
@ -27,9 +27,9 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|||
end
|
||||
|
||||
def call_reset_session
|
||||
session[:bar]
|
||||
session[:foo]
|
||||
reset_session
|
||||
session[:bar] = "baz"
|
||||
session[:foo] = "baz"
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
@ -86,7 +86,7 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|||
|
||||
get '/get_session_value'
|
||||
assert_response :success
|
||||
assert_equal 'foo: nil', response.body
|
||||
assert_equal 'foo: "baz"', response.body
|
||||
|
||||
get '/get_session_id'
|
||||
assert_response :success
|
||||
|
|
|
@ -11,6 +11,9 @@ class ActionPackAssertionsController < ActionController::Base
|
|||
|
||||
# a standard template
|
||||
def hello_xml_world() render :template => "test/hello_xml_world"; end
|
||||
|
||||
# a standard partial
|
||||
def partial() render :partial => 'test/partial'; end
|
||||
|
||||
# a redirect to an internal location
|
||||
def redirect_internal() redirect_to "/nothing"; end
|
||||
|
@ -332,6 +335,30 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase
|
|||
assert @response.rendered[:template]
|
||||
assert 'hello_world', @response.rendered[:template].to_s
|
||||
end
|
||||
|
||||
def test_assert_template_with_partial
|
||||
get :partial
|
||||
assert_template :partial => '_partial'
|
||||
end
|
||||
|
||||
def test_assert_template_with_nil
|
||||
get :nothing
|
||||
assert_template nil
|
||||
end
|
||||
|
||||
def test_assert_template_with_string
|
||||
get :hello_world
|
||||
assert_template 'hello_world'
|
||||
end
|
||||
|
||||
def test_assert_template_with_symbol
|
||||
get :hello_world
|
||||
assert_template :hello_world
|
||||
end
|
||||
|
||||
def test_assert_template_with_bad_argument
|
||||
assert_raise(ArgumentError) { assert_template 1 }
|
||||
end
|
||||
|
||||
# check the redirection location
|
||||
def test_redirection_location
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
require 'fileutils'
|
||||
require 'abstract_unit'
|
||||
require 'active_record_unit'
|
||||
|
||||
CACHE_DIR = 'test_cache'
|
||||
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
|
||||
|
@ -7,6 +8,10 @@ FILE_STORE_PATH = File.join(File.dirname(__FILE__), '/../temp/', CACHE_DIR)
|
|||
ActionController::Base.page_cache_directory = FILE_STORE_PATH
|
||||
ActionController::Base.cache_store = :file_store, FILE_STORE_PATH
|
||||
|
||||
# Force sweeper classes to load
|
||||
ActionController::Caching::Sweeper
|
||||
ActionController::Caching::Sweeping
|
||||
|
||||
class PageCachingTestController < ActionController::Base
|
||||
caches_page :ok, :no_content, :if => Proc.new { |c| !c.request.format.json? }
|
||||
caches_page :found, :not_found
|
||||
|
@ -152,6 +157,7 @@ class ActionCachingTestController < ActionController::Base
|
|||
caches_action :edit, :cache_path => Proc.new { |c| c.params[:id] ? "http://test.host/#{c.params[:id]};edit" : "http://test.host/edit" }
|
||||
caches_action :with_layout
|
||||
caches_action :layout_false, :layout => false
|
||||
caches_action :record_not_found, :four_oh_four, :simple_runtime_error
|
||||
|
||||
layout 'talk_from_action.erb'
|
||||
|
||||
|
@ -174,6 +180,18 @@ class ActionCachingTestController < ActionController::Base
|
|||
render :text => @cache_this, :layout => true
|
||||
end
|
||||
|
||||
def record_not_found
|
||||
raise ActiveRecord::RecordNotFound, "oops!"
|
||||
end
|
||||
|
||||
def four_oh_four
|
||||
render :text => "404'd!", :status => 404
|
||||
end
|
||||
|
||||
def simple_runtime_error
|
||||
raise "oops!"
|
||||
end
|
||||
|
||||
alias_method :show, :index
|
||||
alias_method :edit, :index
|
||||
alias_method :destroy, :index
|
||||
|
@ -456,6 +474,27 @@ class ActionCacheTest < ActionController::TestCase
|
|||
assert_response :success
|
||||
end
|
||||
|
||||
def test_record_not_found_returns_404_for_multiple_requests
|
||||
get :record_not_found
|
||||
assert_response 404
|
||||
get :record_not_found
|
||||
assert_response 404
|
||||
end
|
||||
|
||||
def test_four_oh_four_returns_404_for_multiple_requests
|
||||
get :four_oh_four
|
||||
assert_response 404
|
||||
get :four_oh_four
|
||||
assert_response 404
|
||||
end
|
||||
|
||||
def test_simple_runtime_error_returns_500_for_multiple_requests
|
||||
get :simple_runtime_error
|
||||
assert_response 500
|
||||
get :simple_runtime_error
|
||||
assert_response 500
|
||||
end
|
||||
|
||||
private
|
||||
def content_to_cache
|
||||
assigns(:cache_this)
|
||||
|
|
|
@ -6,6 +6,10 @@ class CookieTest < ActionController::TestCase
|
|||
cookies["user_name"] = "david"
|
||||
end
|
||||
|
||||
def set_with_with_escapable_characters
|
||||
cookies["that & guy"] = "foo & bar => baz"
|
||||
end
|
||||
|
||||
def authenticate_for_fourteen_days
|
||||
cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) }
|
||||
end
|
||||
|
@ -53,6 +57,12 @@ class CookieTest < ActionController::TestCase
|
|||
assert_equal({"user_name" => "david"}, @response.cookies)
|
||||
end
|
||||
|
||||
def test_setting_with_escapable_characters
|
||||
get :set_with_with_escapable_characters
|
||||
assert_equal ["that+%26+guy=foo+%26+bar+%3D%3E+baz; path=/"], @response.headers["Set-Cookie"]
|
||||
assert_equal({"that & guy" => "foo & bar => baz"}, @response.cookies)
|
||||
end
|
||||
|
||||
def test_setting_cookie_for_fourteen_days
|
||||
get :authenticate_for_fourteen_days
|
||||
assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"]
|
||||
|
|
|
@ -25,7 +25,8 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
|
||||
def test_clears_dependencies_after_dispatch_if_in_loading_mode
|
||||
ActiveSupport::Dependencies.expects(:clear).once
|
||||
dispatch(false)
|
||||
# Close the response so dependencies kicks in
|
||||
dispatch(false).last.close
|
||||
end
|
||||
|
||||
def test_reloads_routes_before_dispatch_if_in_loading_mode
|
||||
|
@ -49,13 +50,14 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
Dispatcher.any_instance.expects(:dispatch).raises('b00m')
|
||||
ActionController::Failsafe.any_instance.expects(:log_failsafe_exception)
|
||||
|
||||
response = nil
|
||||
assert_nothing_raised do
|
||||
assert_equal [
|
||||
500,
|
||||
{"Content-Type" => "text/html"},
|
||||
"<html><body><h1>500 Internal Server Error</h1></body></html>"
|
||||
], dispatch
|
||||
response = dispatch
|
||||
end
|
||||
assert_equal 3, response.size
|
||||
assert_equal 500, response[0]
|
||||
assert_equal({"Content-Type" => "text/html"}, response[1])
|
||||
assert_match /500 Internal Server Error/, response[2].join
|
||||
end
|
||||
|
||||
def test_prepare_callbacks
|
||||
|
@ -94,7 +96,7 @@ class DispatcherTest < Test::Unit::TestCase
|
|||
def dispatch(cache_classes = true)
|
||||
ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response'])
|
||||
Dispatcher.define_dispatcher_callbacks(cache_classes)
|
||||
Dispatcher.new.call({})
|
||||
Dispatcher.new.call({'rack.input' => StringIO.new('')})
|
||||
end
|
||||
|
||||
def assert_subclasses(howmany, klass, message = klass.subclasses.inspect)
|
||||
|
|
60
vendor/rails/actionpack/test/controller/failsafe_test.rb
vendored
Normal file
60
vendor/rails/actionpack/test/controller/failsafe_test.rb
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
require 'abstract_unit'
|
||||
require 'stringio'
|
||||
require 'logger'
|
||||
|
||||
class FailsafeTest < ActionController::TestCase
|
||||
FIXTURE_PUBLIC = "#{File.dirname(__FILE__)}/../fixtures/failsafe".freeze
|
||||
|
||||
def setup
|
||||
@old_error_file_path = ActionController::Failsafe.error_file_path
|
||||
ActionController::Failsafe.error_file_path = FIXTURE_PUBLIC
|
||||
@app = mock
|
||||
@log_io = StringIO.new
|
||||
@logger = Logger.new(@log_io)
|
||||
@failsafe = ActionController::Failsafe.new(@app)
|
||||
@failsafe.stubs(:failsafe_logger).returns(@logger)
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionController::Failsafe.error_file_path = @old_error_file_path
|
||||
end
|
||||
|
||||
def app_will_raise_error!
|
||||
@app.expects(:call).then.raises(RuntimeError.new("Printer on fire"))
|
||||
end
|
||||
|
||||
def test_calls_app_and_returns_its_return_value
|
||||
@app.expects(:call).returns([200, { "Content-Type" => "text/html" }, "ok"])
|
||||
assert_equal [200, { "Content-Type" => "text/html" }, "ok"], @failsafe.call({})
|
||||
end
|
||||
|
||||
def test_writes_to_log_file_on_exception
|
||||
app_will_raise_error!
|
||||
@failsafe.call({})
|
||||
assert_match /Printer on fire/, @log_io.string # Logs exception message.
|
||||
assert_match /failsafe_test\.rb/, @log_io.string # Logs backtrace.
|
||||
end
|
||||
|
||||
def test_returns_500_internal_server_error_on_exception
|
||||
app_will_raise_error!
|
||||
response = @failsafe.call({})
|
||||
assert_equal 3, response.size # It is a valid Rack response.
|
||||
assert_equal 500, response[0] # Status is 500.
|
||||
end
|
||||
|
||||
def test_renders_error_page_file_with_erb
|
||||
app_will_raise_error!
|
||||
response = @failsafe.call({})
|
||||
assert_equal 500, response[0]
|
||||
assert_equal "hello my world", response[2].join
|
||||
end
|
||||
|
||||
def test_returns_a_default_message_if_erb_rendering_failed
|
||||
app_will_raise_error!
|
||||
@failsafe.expects(:render_template).raises(RuntimeError.new("Harddisk is crashing"))
|
||||
response = @failsafe.call({})
|
||||
assert_equal 500, response[0]
|
||||
assert_match /500 Internal Server Error/, response[2].join
|
||||
assert_match %r(please read this web application's log file), response[2].join
|
||||
end
|
||||
end
|
|
@ -23,7 +23,8 @@ class FilterParamTest < Test::Unit::TestCase
|
|||
[{'foo'=>'bar', 'bar'=>'foo'},{'foo'=>'[FILTERED]', 'bar'=>'foo'},%w'foo baz'],
|
||||
[{'foo'=>'bar', 'baz'=>'foo'},{'foo'=>'[FILTERED]', 'baz'=>'[FILTERED]'},%w'foo baz'],
|
||||
[{'bar'=>{'foo'=>'bar','bar'=>'foo'}},{'bar'=>{'foo'=>'[FILTERED]','bar'=>'foo'}},%w'fo'],
|
||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana']]
|
||||
[{'foo'=>{'foo'=>'bar','bar'=>'foo'}},{'foo'=>'[FILTERED]'},%w'f banana'],
|
||||
[{'baz'=>[{'foo'=>'baz'}]}, {'baz'=>[{'foo'=>'[FILTERED]'}]}, %w(foo)]]
|
||||
|
||||
test_hashes.each do |before_filter, after_filter, filter_words|
|
||||
FilterParamController.filter_parameter_logging(*filter_words)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue