Compare commits

..

8 Commits

Author SHA1 Message Date
Aman Gupta
b75d7ea2c6 fix integration runner 2013-03-28 23:51:46 -07:00
Aman Gupta
735c4e790d rewrite_options is dead 2013-03-27 20:26:10 -07:00
Aman Gupta
0e467e376b PathSegment is gone 2013-03-27 18:56:36 -07:00
Aman Gupta
e3dafa5669 fix references to ActiveModel from bad merge 2013-03-27 18:56:32 -07:00
Aman Gupta
3297a4c446 integrate backported router 2013-03-27 02:47:56 -07:00
Aman Gupta
d98e3f8489 remove UrlWriter 2013-03-27 02:46:16 -07:00
Aman Gupta
9ee6c6c082 import activesupport dependencies 2013-03-27 02:38:25 -07:00
Aman Gupta
6d4d9dd919 copy/paste router from 3-0-github 2013-03-25 23:23:53 -07:00
357 changed files with 20492 additions and 3636 deletions

View File

@@ -1 +0,0 @@
2.0.0-github

View File

@@ -1,9 +0,0 @@
gem install mocha -v=0.13.1
gem install rake -v=10.1.0
gem install rdoc -v=4.0.1
gem install sqlite3 -v=1.3.7
gem install rack -v=1.4.5
gem install erubis -v=2.7.0
gem install json -v=1.8.0
gem install i18n -v=0.6.9
gem install builder -v=3.2.2

View File

@@ -1 +0,0 @@
2.3.14.github45

View File

@@ -1,6 +0,0 @@
# GitHub Rails
This is GitHub's fork of Rails 2.3.
Please note that this fork is **unsupported**. It is not guaranteed to receive security patches or remain stable. **Use at your own risk.**

View File

@@ -3,7 +3,7 @@ require 'rdoc/task'
env = %(PKG_BUILD="#{ENV['PKG_BUILD']}") if ENV['PKG_BUILD']
PROJECTS = %w(activesupport railties actionpack actionmailer activerecord)
PROJECTS = %w(activesupport railties actionpack actionmailer activeresource activerecord)
Dir["#{File.dirname(__FILE__)}/*/lib/*/version.rb"].each do |version_path|
require version_path
@@ -15,12 +15,9 @@ task :default => :test
%w(test rdoc pgem package release gem).each do |task_name|
desc "Run #{task_name} task for all projects"
task task_name do
passed = true
PROJECTS.each do |project|
system %(cd #{project} && #{env} #{$0} #{task_name})
passed &&= $?.success?
end
exit! passed
end
end
@@ -48,6 +45,11 @@ RDoc::Task.new do |rdoc|
rdoc.rdoc_files.include('activerecord/lib/active_record/**/*.rb')
rdoc.rdoc_files.exclude('activerecord/lib/active_record/vendor/*')
rdoc.rdoc_files.include('activeresource/README')
rdoc.rdoc_files.include('activeresource/CHANGELOG')
rdoc.rdoc_files.include('activeresource/lib/active_resource.rb')
rdoc.rdoc_files.include('activeresource/lib/active_resource/*')
rdoc.rdoc_files.include('actionpack/README')
rdoc.rdoc_files.include('actionpack/CHANGELOG')
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')

View File

@@ -23,6 +23,8 @@ task :default => [ :test ]
Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/*_test.rb'
t.verbose = true
t.warning = false
}

View File

@@ -1,16 +0,0 @@
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).chomp
Gem::Specification.new do |s|
s.name = 'actionmailer'
s.version = version
s.summary = 'Service layer for easy email delivery and testing.'
s.description = 'Makes it trivial to test and deliver emails sent from a single service layer.'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.require_path = 'lib'
s.add_dependency 'actionpack', "= #{version}"
end

View File

@@ -280,7 +280,6 @@ module ActionMailer #:nodoc:
class Base
include AdvAttrAccessor, PartContainer, Quoting, Utils
if Object.const_defined?(:ActionController)
include ActionController::UrlWriter
include ActionController::Layout
end

View File

@@ -43,8 +43,6 @@ module ActionMailer
end
def set_expected_mail
failed_pre_200
@expected = TMail::Mail.new
@expected.set_content_type "text", "plain", { "charset" => charset }
@expected.mime_version = '1.0'

View File

@@ -0,0 +1,62 @@
require 'rubygems'
require 'test/unit'
$:.unshift File.expand_path('../../lib', __FILE__)
$:.unshift File.expand_path('../../../activesupport/lib', __FILE__)
$:.unshift File.expand_path('../../../actionpack/lib', __FILE__)
require 'action_mailer'
require 'action_mailer/test_case'
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
# Bogus template processors
ActionView::Template.register_template_handler :haml, lambda { |template| "Look its HAML!".inspect }
ActionView::Template.register_template_handler :bak, lambda { |template| "Lame backup".inspect }
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
ActionView::Base.cache_template_loading = true
FIXTURE_LOAD_PATH = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures'))
ActionMailer::Base.template_root = FIXTURE_LOAD_PATH
class MockSMTP
def self.deliveries
@@deliveries
end
def initialize
@@deliveries = []
end
def sendmail(mail, from, to)
@@deliveries << [mail, from, to]
end
def start(*args)
yield self
end
end
class Net::SMTP
def self.new(*args)
MockSMTP.new
end
end
def uses_gem(gem_name, test_name, version = '> 0')
gem gem_name.to_s, version
require gem_name.to_s
yield
rescue LoadError
$stderr.puts "Skipping #{test_name} tests. `gem install #{gem_name}` and try again."
end
def set_delivery_method(delivery_method)
@old_delivery_method = ActionMailer::Base.delivery_method
ActionMailer::Base.delivery_method = delivery_method
end
def restore_delivery_method
ActionMailer::Base.delivery_method = @old_delivery_method
end

View File

@@ -0,0 +1,54 @@
require 'abstract_unit'
class AssetHostMailer < ActionMailer::Base
def email_with_asset(recipient)
recipients recipient
subject "testing email containing asset path while asset_host is set"
from "tester@example.com"
end
end
class AssetHostTest < Test::Unit::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_asset_host_as_string
ActionController::Base.asset_host = "http://www.example.com"
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
end
def test_asset_host_as_one_arguement_proc
ActionController::Base.asset_host = Proc.new { |source|
if source.starts_with?('/images')
"http://images.example.com"
else
"http://assets.example.com"
end
}
mail = AssetHostMailer.deliver_email_with_asset(@recipient)
assert_equal "<img alt=\"Somelogo\" src=\"http://images.example.com/images/somelogo.png\" />", mail.body.strip
end
def test_asset_host_as_two_arguement_proc
ActionController::Base.asset_host = Proc.new {|source,request|
if request && request.ssl?
"https://www.example.com"
else
"http://www.example.com"
end
}
mail = nil
assert_nothing_raised { mail = AssetHostMailer.deliver_email_with_asset(@recipient) }
assert_equal "<img alt=\"Somelogo\" src=\"http://www.example.com/images/somelogo.png\" />", mail.body.strip
end
end

View File

@@ -0,0 +1,51 @@
require 'abstract_unit'
class DefaultDeliveryMethodMailer < ActionMailer::Base
end
class NonDefaultDeliveryMethodMailer < ActionMailer::Base
self.delivery_method = :sendmail
end
class ActionMailerBase_delivery_method_Test < Test::Unit::TestCase
def setup
set_delivery_method :smtp
end
def teardown
restore_delivery_method
end
def test_should_be_the_default_smtp
assert_equal :smtp, ActionMailer::Base.delivery_method
end
end
class DefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase
def setup
set_delivery_method :smtp
end
def teardown
restore_delivery_method
end
def test_should_be_the_default_smtp
assert_equal :smtp, DefaultDeliveryMethodMailer.delivery_method
end
end
class NonDefaultDeliveryMethodMailer_delivery_method_Test < Test::Unit::TestCase
def setup
set_delivery_method :smtp
end
def teardown
restore_delivery_method
end
def test_should_be_the_set_delivery_method
assert_equal :sendmail, NonDefaultDeliveryMethodMailer.delivery_method
end
end

View File

@@ -0,0 +1 @@
<%= image_tag "somelogo.png" %>

View File

@@ -0,0 +1 @@
Inside

View File

@@ -0,0 +1 @@
text/html multipart

View File

@@ -0,0 +1 @@
text/plain multipart

View File

@@ -0,0 +1 @@
You logged out

View File

@@ -0,0 +1 @@
We do not spam

View File

@@ -0,0 +1 @@
first mail

View File

@@ -0,0 +1 @@
So, <%= example_format(@text) %>

View File

@@ -0,0 +1 @@
Hello, <%= person_name %>. Thanks for registering!

View File

@@ -0,0 +1 @@
This message brought to you by <%= name_of_the_mailer_class %>.

View File

@@ -0,0 +1,5 @@
From "Romeo and Juliet":
<%= block_format @text %>
Good ol' Shakespeare.

View File

@@ -0,0 +1,5 @@
module ExampleHelper
def example_format(text)
"<em><strong><small>#{h(text)}</small></strong></em>".html_safe
end
end

View File

@@ -0,0 +1 @@
Hello from layout <%= yield %>

View File

@@ -0,0 +1 @@
text/plain layout - <%= yield %>

View File

@@ -0,0 +1 @@
Spammer layout <%= yield %>

View File

@@ -0,0 +1 @@
Have a lovely picture, from me. Enjoy!

14
actionmailer/test/fixtures/raw_email vendored Normal file
View File

@@ -0,0 +1,14 @@
From jamis_buck@byu.edu Mon May 2 16:07:05 2005
Mime-Version: 1.0 (Apple Message framework v622)
Content-Transfer-Encoding: base64
Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
Content-Type: text/plain;
charset=EUC-KR;
format=flowed
To: willard15georgina@jamis.backpackit.com
From: Jamis Buck <jamis@37signals.com>
Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?=
Date: Mon, 2 May 2005 16:07:05 -0600
tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
wLogSmFtaXPA1LTPtNku

20
actionmailer/test/fixtures/raw_email10 vendored Normal file
View File

@@ -0,0 +1,20 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Content-Type: text/plain; charset=X-UNKNOWN
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

32
actionmailer/test/fixtures/raw_email12 vendored Normal file
View File

@@ -0,0 +1,32 @@
Mime-Version: 1.0 (Apple Message framework v730)
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
--Apple-Mail-13-196941151
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed
This is the first part.
--Apple-Mail-13-196941151
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-Location: Photo25.jpg
Content-ID: <qbFGyPQAS8>
Content-Disposition: inline
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
--Apple-Mail-13-196941151--

29
actionmailer/test/fixtures/raw_email13 vendored Normal file
View File

@@ -0,0 +1,29 @@
Mime-Version: 1.0 (Apple Message framework v730)
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
--Apple-Mail-13-196941151
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed
This is the first part.
--Apple-Mail-13-196941151
Content-Type: text/x-ruby-script; name="hello.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="api.rb"
puts "Hello, world!"
gets
--Apple-Mail-13-196941151--

114
actionmailer/test/fixtures/raw_email2 vendored Normal file
View File

@@ -0,0 +1,114 @@
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com
Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com
Received: from localhost (localhost [127.0.0.1])
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D
for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT)
Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1])
by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>;
Sun, 8 May 2005 19:09:12 +0000 (GMT)
Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150])
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960
for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT)
Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199])
by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB
for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT)
Received: by zproxy.gmail.com with SMTP id 13so1233405nzp
for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT)
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
s=beta; d=gmail.com;
h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references;
b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8=
Received: by 10.36.10.16 with SMTP id 16mr1012493nzj;
Sun, 08 May 2005 12:09:11 -0700 (PDT)
Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT)
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
Date: Sun, 8 May 2005 14:09:11 -0500
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
Subject: Fwd: Signed email causes file attachments
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_5028_7368284.1115579351471"
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
------=_Part_5028_7368284.1115579351471
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
We should not include these files or vcards as attachments.
---------- Forwarded message ----------
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
Date: May 8, 2005 1:17 PM
Subject: Signed email causes file attachments
To: xxxxxxx@xxxxxxxxxx.com
Hi,
Just started to use my xxxxxxxx account (to set-up a GTD system,
natch) and noticed that when I send content via email the signature/
certificate from my email account gets added as a file (e.g.
"smime.p7s").
Obviously I can uncheck the signature option in the Mail compose
window but how often will I remember to do that?
Is there any way these kind of files could be ignored, e.g. via some
sort of exclusions list?
------=_Part_5028_7368284.1115579351471
Content-Type: application/pkcs7-signature; name=smime.p7s
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla
hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp
+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1
ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw
DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd
L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/
lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3
DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD
YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0
aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg
Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3
MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv
bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz
c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f
6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk
KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj
gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo
YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R
BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM
0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ
GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ
Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl
IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls
IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp
fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG
A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv
bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW
8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8
rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA
nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7
Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0
fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA
------=_Part_5028_7368284.1115579351471--

70
actionmailer/test/fixtures/raw_email3 vendored Normal file
View File

@@ -0,0 +1,70 @@
From xxxx@xxxx.com Tue May 10 11:28:07 2005
Return-Path: <xxxx@xxxx.com>
X-Original-To: xxxx@xxxx.com
Delivered-To: xxxx@xxxx.com
Received: from localhost (localhost [127.0.0.1])
by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT)
Received: from xxx.xxxxx.com ([127.0.0.1])
by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
with LMTP id 70060-03 for <xxxx@xxxx.com>;
Tue, 10 May 2005 17:26:49 +0000 (GMT)
Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150])
by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT)
Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203])
by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C
for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT)
Received: by xxx.xxxxx.com with SMTP id 68so1694448wri
for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT)
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
s=beta; d=xxxxx.com;
h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type;
b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A=
Received: by 10.54.96.19 with SMTP id t19mr621017wrb;
Tue, 10 May 2005 10:26:39 -0700 (PDT)
Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT)
Message-ID: <xxxx@xxxx.com>
Date: Tue, 10 May 2005 11:26:39 -0600
From: Test Tester <xxxx@xxxx.com>
Reply-To: Test Tester <xxxx@xxxx.com>
To: xxxx@xxxx.com, xxxx@xxxx.com
Subject: Another PDF
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_2192_32400445.1115745999735"
X-Virus-Scanned: amavisd-new at textdrive.com
------=_Part_2192_32400445.1115745999735
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
Just attaching another PDF, here, to see what the message looks like,
and to see if I can figure out what is going wrong here.
------=_Part_2192_32400445.1115745999735
Content-Type: application/pdf; name="broken.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="broken.pdf"
JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G
bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE
ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu
rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD
QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU
x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab
gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg
1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw
wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI
8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl
fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ
fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR
xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss
nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU
OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF
yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS
HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk
ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw
------=_Part_2192_32400445.1115745999735--

59
actionmailer/test/fixtures/raw_email4 vendored Normal file
View File

@@ -0,0 +1,59 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500
Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
Date: Sun, 8 May 2005 12:30:08 -0500
From: xxx@xxxx.xxx
To: xxx@xxxx.xxx
Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx>
Subject: Filth
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef
X-Mms-Priority: 1
X-Mms-Transaction-Id: 3198421808-0
X-Mms-Message-Type: 0
X-Mms-Sender-Visibility: 1
X-Mms-Read-Reply: 1
X-Original-To: xxx@xxxx.xxx
X-Mms-Message-Class: 0
X-Mms-Delivery-Report: 0
X-Mms-Mms-Version: 16
Delivered-To: xxx@xxxx.xxx
X-Nokia-Ag-Version: 2.0
This is a multi-part message in MIME format.
--mimepart_427e4cb4ca329_133ae40413c81ef
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217
--mimepart_427e4cb4cbd97_133ae40413c8217
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Content-Location: text.txt
Some text
--mimepart_427e4cb4cbd97_133ae40413c8217--
--mimepart_427e4cb4ca329_133ae40413c81ef
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
--
This Orange Multi Media Message was sent wirefree from an Orange
MMS phone. If you would like to reply, please text or phone the
sender directly by using the phone number listed in the sender's
address. To learn more about Orange's Multi Media Messaging
Service, find us on the Web at xxx.xxxx.xxx.uk/mms
--mimepart_427e4cb4ca329_133ae40413c81ef
--mimepart_427e4cb4ca329_133ae40413c81ef-

19
actionmailer/test/fixtures/raw_email5 vendored Normal file
View File

@@ -0,0 +1,19 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

20
actionmailer/test/fixtures/raw_email6 vendored Normal file
View File

@@ -0,0 +1,20 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Content-Type: text/plain; charset=us-ascii
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

66
actionmailer/test/fixtures/raw_email7 vendored Normal file
View File

@@ -0,0 +1,66 @@
Mime-Version: 1.0 (Apple Message framework v730)
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
--Apple-Mail-13-196941151
Content-Type: multipart/mixed;
boundary=Apple-Mail-12-196940926
--Apple-Mail-12-196940926
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed
This is the first part.
--Apple-Mail-12-196940926
Content-Transfer-Encoding: 7bit
Content-Type: text/x-ruby-script;
x-unix-mode=0666;
name="test.rb"
Content-Disposition: attachment;
filename=test.rb
puts "testing, testing"
--Apple-Mail-12-196940926
Content-Transfer-Encoding: base64
Content-Type: application/pdf;
x-unix-mode=0666;
name="test.pdf"
Content-Disposition: inline;
filename=test.pdf
YmxhaCBibGFoIGJsYWg=
--Apple-Mail-12-196940926
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
--Apple-Mail-12-196940926--
--Apple-Mail-13-196941151
Content-Transfer-Encoding: base64
Content-Type: application/pkcs7-signature;
name=smime.p7s
Content-Disposition: attachment;
filename=smime.p7s
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
--Apple-Mail-13-196941151--

47
actionmailer/test/fixtures/raw_email8 vendored Normal file
View File

@@ -0,0 +1,47 @@
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
Date: Sun, 8 May 2005 14:09:11 -0500
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
Subject: Fwd: Signed email causes file attachments
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_5028_7368284.1115579351471"
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
------=_Part_5028_7368284.1115579351471
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
We should not include these files or vcards as attachments.
---------- Forwarded message ----------
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
Date: May 8, 2005 1:17 PM
Subject: Signed email causes file attachments
To: xxxxxxx@xxxxxxxxxx.com
Hi,
Test attachments oddly encoded with japanese charset.
------=_Part_5028_7368284.1115579351471
Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3
Content-Transfer-Encoding: base64
Content-Disposition: attachment
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
------=_Part_5028_7368284.1115579351471--

28
actionmailer/test/fixtures/raw_email9 vendored Normal file
View File

@@ -0,0 +1,28 @@
Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified)
by xxx.com (CommuniGate Pro SMTP 4.2.8)
with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500
Received-SPF: softfail
receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx
quite Delivered-To: xxx@xxx.xxx
Received: by xxx.xxx.xxx (Wostfix, from userid xxx)
id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600
Date: Wed, 23 Feb 2005 18:20:17 -0400
From: "xxx xxx" <xxx@xxx.xxx>
Message-ID: <4D6AA7EB.6490534@xxx.xxx>
To: xxx@xxx.com
Subject: Stop adware/spyware once and for all.
X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang)
You are infected with:
Ad Ware and Spy Ware
Get your free scan and removal download now,
before it gets any worse.
http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt
no more? (you will still be infected)
http://xxx.xxx.info/discon/?xxx@xxx.com

View File

@@ -0,0 +1,14 @@
Mime-Version: 1.0 (Apple Message framework v730)
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain
A fax has arrived from remote ID ''.=0D=0A-----------------------=
-------------------------------------=0D=0ATime: 3/9/2006 3:50:52=
PM=0D=0AReceived from remote ID: =0D=0AInbound user ID XXXXXXXXXX, r=
outing code XXXXXXXXX=0D=0AResult: (0/352;0/0) Successful Send=0D=0AP=
age record: 1 - 1=0D=0AElapsed time: 00:58 on channel 11=0D=0A

View File

@@ -0,0 +1,104 @@
Return-Path: <mikel.other@baci>
Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000
Date: Sun, 21 Oct 2007 19:38:13 +1000
From: Mikel Lindsaar <mikel.other@baci>
Reply-To: Mikel Lindsaar <mikel.other@baci>
To: mikel.lindsaar@baci
Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
Subject: Testing outlook
Mime-Version: 1.0
Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850
Delivered-To: mikel.lindsaar@baci
X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138
X-Msmail-Priority: Normal
This is a multi-part message in MIME format.
------=_NextPart_000_0093_01C81419.EB75E850
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: Quoted-printable
Hello
This is an outlook test
So there.
Me.
------=_NextPart_000_0093_01C81419.EB75E850
Content-Type: text/html; charset=iso-8859-1
Content-Transfer-Encoding: Quoted-printable
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV>
<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20
test</STRONG></FONT></DIV>
<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML>
------=_NextPart_000_0093_01C81419.EB75E850--
Return-Path: <mikel.other@baci>
Received: from some.isp.com by baci with ESMTP id 632BD5758 for <mikel.lindsaar@baci>; Sun, 21 Oct 2007 19:38:21 +1000
Date: Sun, 21 Oct 2007 19:38:13 +1000
From: Mikel Lindsaar <mikel.other@baci>
Reply-To: Mikel Lindsaar <mikel.other@baci>
To: mikel.lindsaar@baci
Message-Id: <009601c813c6$19df3510$0437d30a@mikel091a>
Subject: Testing outlook
Mime-Version: 1.0
Content-Type: multipart/alternative; boundary=----=_NextPart_000_0093_01C81419.EB75E850
Delivered-To: mikel.lindsaar@baci
X-Mimeole: Produced By Microsoft MimeOLE V6.00.2900.3138
X-Msmail-Priority: Normal
This is a multi-part message in MIME format.
------=_NextPart_000_0093_01C81419.EB75E850
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: Quoted-printable
Hello
This is an outlook test
So there.
Me.
------=_NextPart_000_0093_01C81419.EB75E850
Content-Type: text/html; charset=iso-8859-1
Content-Transfer-Encoding: Quoted-printable
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML><HEAD>
<META http-equiv=3DContent-Type content=3D"text/html; =
charset=3Diso-8859-1">
<META content=3D"MSHTML 6.00.6000.16525" name=3DGENERATOR>
<STYLE></STYLE>
</HEAD>
<BODY bgColor=3D#ffffff>
<DIV><FONT face=3DArial size=3D2>Hello</FONT></DIV>
<DIV><FONT face=3DArial size=3D2><STRONG>This is an outlook=20
test</STRONG></FONT></DIV>
<DIV><FONT face=3DArial size=3D2><STRONG></STRONG></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2><STRONG>So there.</STRONG></FONT></DIV>
<DIV><FONT face=3DArial size=3D2></FONT>&nbsp;</DIV>
<DIV><FONT face=3DArial size=3D2>Me.</FONT></DIV></BODY></HTML>
------=_NextPart_000_0093_01C81419.EB75E850--

View File

@@ -0,0 +1,100 @@
From jamis@37signals.com Thu Feb 22 11:20:31 2007
Mime-Version: 1.0 (Apple Message framework v752.3)
Message-Id: <2CCE0408-10C7-4045-9B16-A1C11C31469B@37signals.com>
Content-Type: multipart/signed;
micalg=sha1;
boundary=Apple-Mail-42-587703407;
protocol="application/pkcs7-signature"
To: Jamis Buck <jamis@jamisbuck.org>
Subject: Testing attachments
From: Jamis Buck <jamis@37signals.com>
Date: Thu, 22 Feb 2007 11:20:31 -0700
--Apple-Mail-42-587703407
Content-Type: multipart/mixed;
boundary=Apple-Mail-41-587703287
--Apple-Mail-41-587703287
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
Here is a test of an attachment via email.
- Jamis
--Apple-Mail-41-587703287
Content-Transfer-Encoding: base64
Content-Type: image/png;
x-unix-mode=0644;
name=byo-ror-cover.png
Content-Disposition: inline;
filename=truncated.png
iVBORw0KGgoAAAANSUhEUgAAAKUAAADXCAYAAAB7wZEQAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz
AAALEgAACxIB0t1+/AAAABd0RVh0Q3JlYXRpb24gVGltZQAxLzI1LzIwMDeD9CJVAAAAGHRFWHRT
b2Z0d2FyZQBBZG9iZSBGaXJld29ya3NPsx9OAAAyBWlUWHRYTUw6Y29tLmFkb2JlLnhtcDw/eHBh
Y2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1l
dGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDQuMS1j
MDIwIDEuMjU1NzE2LCBUdWUgT2N0IDEwIDIwMDYgMjM6MTY6MzQiPgogICA8cmRmOlJERiB4bWxu
czpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAg
ICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczp4YXA9Imh0
dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iPgogICAgICAgICA8eGFwOkNyZWF0b3JUb29sPkFk
b2JlIEZpcmV3b3JrcyBDUzM8L3hhcDpDcmVhdG9yVG9vbD4KICAgICAgICAgPHhhcDpDcmVhdGVE
YXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhhcDpN
b2RpZnlEYXRlPjIwMDctMDEtMjVUMDU6Mjg6MjFaPC94YXA6TW9kaWZ5RGF0ZT4KICAgICAgPC9y
ZGY6RGVzY3JpcHRpb24+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg
ICAgICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+CiAgICAg
ICAgIDxkYzpmb3JtYXQ+aW1hZ2UvcG5nPC9kYzpmb3JtYXQ+CiAgICAgIDwvcmRmOkRlc2NyaXB0
hhojpmnJMfaYFmSkXWg5PGCmHXVj/c9At0hSK2xGdd8F3muk0VFjb4f5Ue0ksQ8qAcq0delaXhdb
DjKNnF+3B3t9kObZYmk7AZgWYqO9anpR3wpM9sQ5XslB9a+kWyTtNb0fOmudzGHfPFBQDKesyycm
DBL7Cw5bXjIEuci+SSOm/LYnXDZu6iuPEj8lYBb+OU8xx1f9m+e5rhJiYKqjo5vHfiZp+VUkW9xc
Ufd6JHNWc47PkQqb9ie3SLEZB/ZqyAssiqURY+G35iOMZUrHbasHnb80QAPv9FHtAbJIyro7bi5b
ai2TEAKen5+LJNWrglZjm3UbZvt7KryA2J5b5J1jZF8kL6GzvG1Zqx54Y1y7J7n20wMOt9frG2sW
uwGP07kNz3732vf6bfvAvLldfS+9fts2euXY37D+R29FGZdlnhzV4TTFmPJduBP2RbNNua4rTqcT
Qt7Xy1KUB0AHSdP5AZQYvHZg7WD1XvYeMO1A9HhZPqMX5KXbMBrn2efxns/ee21674efxz4Tp/fq
2HZ648dgYaC1i3Vq1IbNPq3PvDTPezY9FaRISjvnzWqdgcWN8EJgjnNq+Z7ktOm9l2Nfth28EZi4
bG/we5JwxM+Tql47/D/X6b38I8/RyxvxPJrX6zvQbo3h9jyJx+C0ALX327QETHl5eYlaYCT5rPTb
+5/rAq26t3lKIxV/p88hq6ptngdgCzoPjJqndiLfc/6y5A14WeDFGNPct4iUsJBV2bYzLEV7m83s
6Rp63VPhHKC/g/LzaU9qexJRr56043JWinqAtfZqsSm1sjoznthl54dtCqv+uL4nIY+oYWuc3+nH
kGfn8b0HQpvOYLQAZUDanbJs3jQhITZEgdarZK+cO6ySlL13rut5nFaN23s7u3Snz6eRPTkCoc2/
Vp1zHfZVFpZ87FiMVLV1iqyK5rlzfji2GzjfDsodlD+Weo5UD4h6PwKqzQMqID0tq2VjjFVSMpis
ZLRAs7sePZBZAHI+gIanB8I7MD+femAceeUe2Kxa5jS950kZ1p5eNEdeX1+jFmSpZ+1EdWCsDcne
NPNgUHNw3aYpnzv9PGTX0uo94EtN9qq1rOdxe3kc79T8ukeHJJ8Fnxej6qlylbLLsjQLOy6Xy2a1
kefs/N+nM7+S7IG5/E5Yc7F003pWErLjbH0O5cGadiMptSB/DZ5U5DI9yeg5MFYyMj8lC/Y7/Xjq
OZlWcnpg9aQfXz2HRq+Wn5xOp6gN8tWq8R44e2pfyzLYemEgprst+XXk2Zj2nXlbsG05BprndTMv
C3QRaXczshhVsHnMgfYn80Y2g5JureA6wBasPeP7LkE/jvZMJAaf/g/U2RelHsisvan5FqweIAHg
Pwc7L68GxvVDAAAAAElFTkSuQmCC
--Apple-Mail-41-587703287--
--Apple-Mail-42-587703407
Content-Transfer-Encoding: base64
Content-Type: application/pkcs7-signature;
name=smime.p7s
Content-Disposition: attachment;
filename=smime.p7s
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGJzCCAuAw
ggJJoAMCAQICEFjnFNYXwDEZRWY5EkfzopUwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCWkEx
JTAjBgNVBAoTHFRoYXd0ZSBDb25zdWx0aW5nIChQdHkpIEx0ZC4xLDAqBgNVBAMTI1RoYXd0ZSBQ
ZXJzb25hbCBGcmVlbWFpbCBJc3N1aW5nIENBMB4XDTA2MDkxMjE3MDExMloXDTA3MDkxMjE3MDEx
MlowRTEfMB0GA1UEAxMWVGhhd3RlIEZyZWVtYWlsIE1lbWJlcjEiMCAGCSqGSIb3DQEJARYTamFt
aXNAMzdzaWduYWxzLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO2A9JeOFIFJ
G6z8pTcAldrZ2nMe+Xb1tNrbHgoVzN/QhHXM4qst2Ml93cmFLjMmwG7P9RJeU4oNx+jTqVoBB7NV
Ne1/o56Do0KhfMZ9iUDQdPLbkZMq4EEpFMdm6PyM3muRKwPhj66iAWe/osCb8DowUK2f66vaRx0Z
Y0MQHIIrXE02Ta4IfAhIfPqBLkZ4WgTYBHN9vMdYea1jF0GO4gqGk1wqwb3yxv2QMYMbwJ6SI+k/
ZjkSR/OilTCBhwYLKoZIhvcNAQkQAgsxeKB2MGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
aWwgSXNzdWluZyBDQQIQWOcU1hfAMRlFZjkSR/OilTANBgkqhkiG9w0BAQEFAASCAQCfwQiC3v6/
yleRDGv3bJ4nQYQ+c3mz3+mn3Xi6uU35n3piwxZZaWRdmLyiXPvU+QReHpSf3l2qsEZM3sdE0XF9
eRul/+QTFJcDNXOEAxG1zC2Gpz+6c6RrX4Ou12Pwkp+pNrZWTSY/mZgdqcArupOBcZi7qBjoWcy5
wb54dfvSSjrjmqLbkH/E8ww/6gGQuU/xXpAUZgUrTmQHrNKeIdSh5oDkOxFaFWvnmb8Z/2ixKqW/
Ux6WqamyvBtTs/5YBEtnpZOk+uVoscYEUBhU+DVJ2OSvTdXSivMtBdXmGTsG22k+P1NGUHi/A7ev
xPaO0uk4V8xyjNlN4HPuGpkrlXwPAAAAAAAA
--Apple-Mail-42-587703407--

View File

@@ -0,0 +1,14 @@
From jamis@37signals.com Mon May 2 16:07:05 2005
Mime-Version: 1.0 (Apple Message framework v622)
Content-Transfer-Encoding: base64
Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
Content-Type: text/plain;
charset=EUC-KR;
format=flowed
To: jamis@37signals.com
From: Jamis Buck <jamis@37signals.com>
Subject: Re: Test: =?UTF-8?B?Iua8ouWtlyI=?= mid =?UTF-8?B?Iua8ouWtlyI=?= tail
Date: Mon, 2 May 2005 16:07:05 -0600
tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
wLogSmFtaXPA1LTPtNku

View File

@@ -0,0 +1 @@
second mail

View File

@@ -0,0 +1,3 @@
Hello there,
Mr. <%= @recipient %>

View File

@@ -0,0 +1 @@
let's go!

View File

@@ -0,0 +1,2 @@
body: <%= @body %>
bar: <%= @bar %>

View File

@@ -0,0 +1,6 @@
%p Hello there,
%p
Mr.
= @recipient
from haml

View File

@@ -0,0 +1,6 @@
%p Hello there,
%p
Mr.
= @recipient
from haml

View File

@@ -0,0 +1 @@
Ignored when searching for implicitly multipart parts.

View File

@@ -0,0 +1 @@
Ignored when searching for implicitly multipart parts.

View File

@@ -0,0 +1,10 @@
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>

View File

@@ -0,0 +1,10 @@
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>

View File

@@ -0,0 +1,2 @@
Plain text to <%= @recipient %>.
Plain text to <%= @recipient %>.

View File

@@ -0,0 +1 @@
yaml to: <%= @recipient %>

View File

@@ -0,0 +1 @@
Hey Ho, <%= render :partial => "subtemplate" %>

View File

@@ -0,0 +1,2 @@
xml.instruct!
xml.test

View File

@@ -0,0 +1,2 @@
xml.instruct!
xml.test

View File

@@ -0,0 +1,3 @@
Hello there,
Mr. <%= @recipient %>

View File

@@ -0,0 +1,5 @@
Hello there,
Mr. <%= @recipient %>. Please see our greeting at <%= @welcome_url %> <%= welcome_url %>
<%= image_tag "somelogo.png" %>

View File

@@ -0,0 +1,95 @@
require 'abstract_unit'
module MailerHelper
def person_name
"Mr. Joe Person"
end
end
class HelperMailer < ActionMailer::Base
helper MailerHelper
helper :example
def use_helper(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
end
def use_example_helper(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
self.body = { :text => "emphasize me!" }
end
def use_mail_helper(recipient)
recipients recipient
subject "using mailing helpers"
from "tester@example.com"
self.body = { :text =>
"But soft! What light through yonder window breaks? It is the east, " +
"and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
"which is sick and pale with grief that thou, her maid, art far more " +
"fair than she. Be not her maid, for she is envious! Her vestal " +
"livery is but sick and green, and none but fools do wear it. Cast " +
"it off!"
}
end
def use_helper_method(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
self.body = { :text => "emphasize me!" }
end
private
def name_of_the_mailer_class
self.class.name
end
helper_method :name_of_the_mailer_class
end
class MailerHelperTest < Test::Unit::TestCase
def new_mail( charset="utf-8" )
mail = TMail::Mail.new
mail.set_content_type "text", "plain", { "charset" => charset } if charset
mail
end
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_use_helper
mail = HelperMailer.create_use_helper(@recipient)
assert_match %r{Mr. Joe Person}, mail.encoded
end
def test_use_example_helper
mail = HelperMailer.create_use_example_helper(@recipient)
assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
end
def test_use_helper_method
mail = HelperMailer.create_use_helper_method(@recipient)
assert_match %r{HelperMailer}, mail.encoded
end
def test_use_mail_helper
mail = HelperMailer.create_use_mail_helper(@recipient)
assert_match %r{ But soft!}, mail.encoded
assert_match %r{east, and\n Juliet}, mail.encoded
end
end

View File

@@ -0,0 +1,123 @@
require 'abstract_unit'
class AutoLayoutMailer < ActionMailer::Base
def hello(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
end
def spam(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :layout => 'spam', :body => { :world => "Earth" })
end
def nolayout(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :layout => false, :body => { :world => "Earth" })
end
def multipart(recipient, type = nil)
recipients recipient
subject "You have a mail"
from "tester@example.com"
content_type(type) if type
end
end
class ExplicitLayoutMailer < ActionMailer::Base
layout 'spam', :except => [:logout]
def signup(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
end
def logout(recipient)
recipients recipient
subject "You have a mail"
from "tester@example.com"
end
end
class LayoutMailerTest < Test::Unit::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_should_pickup_default_layout
mail = AutoLayoutMailer.create_hello(@recipient)
assert_equal "Hello from layout Inside", mail.body.strip
end
def test_should_pickup_multipart_layout
mail = AutoLayoutMailer.create_multipart(@recipient)
assert_equal "multipart/alternative", mail.content_type
assert_equal 2, mail.parts.size
assert_equal 'text/plain', mail.parts.first.content_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
assert_equal 'text/html', mail.parts.last.content_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
end
def test_should_pickup_multipartmixed_layout
mail = AutoLayoutMailer.create_multipart(@recipient, "multipart/mixed")
assert_equal "multipart/mixed", mail.content_type
assert_equal 2, mail.parts.size
assert_equal 'text/plain', mail.parts.first.content_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
assert_equal 'text/html', mail.parts.last.content_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
end
def test_should_fix_multipart_layout
mail = AutoLayoutMailer.create_multipart(@recipient, "text/plain")
assert_equal "multipart/alternative", mail.content_type
assert_equal 2, mail.parts.size
assert_equal 'text/plain', mail.parts.first.content_type
assert_equal "text/plain layout - text/plain multipart", mail.parts.first.body
assert_equal 'text/html', mail.parts.last.content_type
assert_equal "Hello from layout text/html multipart", mail.parts.last.body
end
def test_should_pickup_layout_given_to_render
mail = AutoLayoutMailer.create_spam(@recipient)
assert_equal "Spammer layout Hello, Earth", mail.body.strip
end
def test_should_respect_layout_false
mail = AutoLayoutMailer.create_nolayout(@recipient)
assert_equal "Hello, Earth", mail.body.strip
end
def test_explicit_class_layout
mail = ExplicitLayoutMailer.create_signup(@recipient)
assert_equal "Spammer layout We do not spam", mail.body.strip
end
def test_explicit_layout_exceptions
mail = ExplicitLayoutMailer.create_logout(@recipient)
assert_equal "You logged out", mail.body.strip
end
end

View File

@@ -0,0 +1,116 @@
require 'abstract_unit'
class RenderMailer < ActionMailer::Base
def inline_template(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
end
def file_template(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
body render(:file => "signed_up", :body => { :recipient => recipient })
end
def rxml_template(recipient)
recipients recipient
subject "rendering rxml template"
from "tester@example.com"
end
def included_subtemplate(recipient)
recipients recipient
subject "Including another template in the one being rendered"
from "tester@example.com"
end
def included_old_subtemplate(recipient)
recipients recipient
subject "Including another template in the one being rendered"
from "tester@example.com"
body render(:inline => "Hello, <%= render \"subtemplate\" %>", :body => { :world => "Earth" })
end
def initialize_defaults(method_name)
super
mailer_name "test_mailer"
end
end
class FirstMailer < ActionMailer::Base
def share(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
end
end
class SecondMailer < ActionMailer::Base
def share(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
end
end
class RenderHelperTest < Test::Unit::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_inline_template
mail = RenderMailer.create_inline_template(@recipient)
assert_equal "Hello, Earth", mail.body.strip
end
def test_file_template
mail = RenderMailer.create_file_template(@recipient)
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
end
def test_rxml_template
mail = RenderMailer.deliver_rxml_template(@recipient)
assert_equal "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<test/>", mail.body.strip
end
def test_included_subtemplate
mail = RenderMailer.deliver_included_subtemplate(@recipient)
assert_equal "Hey Ho, let's go!", mail.body.strip
end
end
class FirstSecondHelperTest < Test::Unit::TestCase
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_ordering
mail = FirstMailer.create_share(@recipient)
assert_equal "first mail", mail.body.strip
mail = SecondMailer.create_share(@recipient)
assert_equal "second mail", mail.body.strip
mail = FirstMailer.create_share(@recipient)
assert_equal "first mail", mail.body.strip
mail = SecondMailer.create_share(@recipient)
assert_equal "second mail", mail.body.strip
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,105 @@
# encoding: utf-8
require 'abstract_unit'
require 'tempfile'
class QuotingTest < Test::Unit::TestCase
# Move some tests from TMAIL here
def test_unquote_quoted_printable
a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
end
def test_unquote_base64
a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
end
def test_unquote_without_charset
a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
end
def test_unqoute_multiple
quoted ="=?utf-8?q?Re=3A_=5B12=5D_=23137=3A_Inkonsistente_verwendung_von_=22Hin?==?utf-8?b?enVmw7xnZW4i?="
actual = TMail::Unquoter.unquote_and_convert_to(quoted, 'utf-8')
expected = "Re: [12] #137: Inkonsistente verwendung von \"Hinzuf\303\274gen\""
expected.force_encoding 'ASCII-8BIT' if expected.respond_to?(:force_encoding)
assert_equal expected, actual
end
def test_unqoute_in_the_middle
a ="Re: Photos =?ISO-8859-1?Q?Brosch=FCre_Rand?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "Re: Photos Brosch\303\274re Rand", b
end
def test_unqoute_iso
a ="=?ISO-8859-1?Q?Brosch=FCre_Rand?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'iso-8859-1')
expected = "Brosch\374re Rand"
expected.force_encoding 'iso-8859-1' if expected.respond_to?(:force_encoding)
assert_equal expected, b
end
def test_quote_multibyte_chars
original = "\303\246 \303\270 and \303\245"
original.force_encoding('ASCII-8BIT') if original.respond_to?(:force_encoding)
result = execute_in_sandbox(<<-CODE)
$:.unshift(File.dirname(__FILE__) + "/../lib/")
if RUBY_VERSION < '1.9'
$KCODE = 'u'
require 'jcode'
end
require 'action_mailer/quoting'
include ActionMailer::Quoting
quoted_printable(#{original.inspect}, "UTF-8")
CODE
unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
assert_equal unquoted, original
end
# test an email that has been created using \r\n newlines, instead of
# \n newlines.
def test_email_quoted_with_0d0a
mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_quoted_with_0d0a"))
assert_match %r{Elapsed time}, mail.body
end
def test_email_with_partially_quoted_subject
mail = TMail::Mail.parse(IO.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_partially_quoted_subject"))
expected = "Re: Test: \"\346\274\242\345\255\227\" mid \"\346\274\242\345\255\227\" tail"
expected.force_encoding('ASCII-8BIT') if expected.respond_to?(:force_encoding)
assert_equal expected, mail.subject
end
private
# This whole thing *could* be much simpler, but I don't think Tempfile,
# popen and others exist on all platforms (like Windows).
def execute_in_sandbox(code)
test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
File.open(test_name, "w+") do |file|
file.write(<<-CODE)
block = Proc.new do
#{code}
end
puts block.call
CODE
end
system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
File.read(res_name).chomp
ensure
File.delete(test_name) rescue nil
File.delete(res_name) rescue nil
end
end

View File

@@ -0,0 +1,129 @@
require 'abstract_unit'
class TestHelperMailer < ActionMailer::Base
def test
recipients "test@example.com"
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
end
end
class TestHelperMailerTest < ActionMailer::TestCase
def test_setup_sets_right_action_mailer_options
assert_equal :test, ActionMailer::Base.delivery_method
assert ActionMailer::Base.perform_deliveries
assert_equal [], ActionMailer::Base.deliveries
end
def test_setup_creates_the_expected_mailer
assert @expected.is_a?(TMail::Mail)
assert_equal "1.0", @expected.mime_version
assert_equal "text/plain", @expected.content_type
end
def test_mailer_class_is_correctly_inferred
assert_equal TestHelperMailer, self.class.mailer_class
end
def test_determine_default_mailer_raises_correct_error
assert_raise(ActionMailer::NonInferrableMailerError) do
self.class.determine_default_mailer("NotAMailerTest")
end
end
def test_charset_is_utf_8
assert_equal "utf-8", charset
end
def test_encode
assert_equal "=?utf-8?Q?=0Aasdf=0A?=", encode("\nasdf\n")
end
def test_assert_emails
assert_nothing_raised do
assert_emails 1 do
TestHelperMailer.deliver_test
end
end
end
def test_repeated_assert_emails_calls
assert_nothing_raised do
assert_emails 1 do
TestHelperMailer.deliver_test
end
end
assert_nothing_raised do
assert_emails 2 do
TestHelperMailer.deliver_test
TestHelperMailer.deliver_test
end
end
end
def test_assert_emails_with_no_block
assert_nothing_raised do
TestHelperMailer.deliver_test
assert_emails 1
end
assert_nothing_raised do
TestHelperMailer.deliver_test
TestHelperMailer.deliver_test
assert_emails 3
end
end
def test_assert_no_emails
assert_nothing_raised do
assert_no_emails do
TestHelperMailer.create_test
end
end
end
def test_assert_emails_too_few_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_emails 2 do
TestHelperMailer.deliver_test
end
end
assert_match /2 .* but 1/, error.message
end
def test_assert_emails_too_many_sent
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_emails 1 do
TestHelperMailer.deliver_test
TestHelperMailer.deliver_test
end
end
assert_match /1 .* but 2/, error.message
end
def test_assert_no_emails_failure
error = assert_raise ActiveSupport::TestCase::Assertion do
assert_no_emails do
TestHelperMailer.deliver_test
end
end
assert_match /0 .* but 1/, error.message
end
end
class AnotherTestHelperMailerTest < ActionMailer::TestCase
tests TestHelperMailer
def setup
@test_var = "a value"
end
def test_setup_shouldnt_conflict_with_mailer_setup
assert @expected.is_a?(TMail::Mail)
assert_equal 'a value', @test_var
end
end

View File

@@ -0,0 +1,22 @@
require 'abstract_unit'
class TMailMailTest < Test::Unit::TestCase
def test_body
m = TMail::Mail.new
expected = 'something_with_underscores'
m.encoding = 'quoted-printable'
quoted_body = [expected].pack('*M')
m.body = quoted_body
assert_equal "something_with_underscores=\n", m.quoted_body
assert_equal expected, m.body
end
def test_nested_attachments_are_recognized_correctly
fixture = File.read("#{File.dirname(__FILE__)}/fixtures/raw_email_with_nested_attachment")
mail = TMail::Mail.parse(fixture)
assert_equal 2, mail.attachments.length
assert_equal "image/png", mail.attachments.first.content_type
assert_equal 1902, mail.attachments.first.length
assert_equal "application/pkcs7-signature", mail.attachments.last.content_type
end
end

View File

@@ -0,0 +1,76 @@
require 'abstract_unit'
class TestMailer < ActionMailer::Base
default_url_options[:host] = 'www.basecamphq.com'
def signed_up_with_url(recipient)
@recipients = recipient
@subject = "[Signed up] Welcome #{recipient}"
@from = "system@loudthinking.com"
@sent_on = Time.local(2004, 12, 12)
@body["recipient"] = recipient
@body["welcome_url"] = url_for :host => "example.com", :controller => "welcome", :action => "greeting"
end
class <<self
attr_accessor :received_body
end
def receive(mail)
self.class.received_body = mail.body
end
end
class ActionMailerUrlTest < Test::Unit::TestCase
include ActionMailer::Quoting
def encode( text, charset="utf-8" )
quoted_printable( text, charset )
end
def new_mail( charset="utf-8" )
mail = TMail::Mail.new
mail.mime_version = "1.0"
if charset
mail.set_content_type "text", "plain", { "charset" => charset }
end
mail
end
def setup
set_delivery_method :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def teardown
restore_delivery_method
end
def test_signed_up_with_url
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action/:id'
map.welcome 'welcome', :controller=>"foo", :action=>"bar"
end
expected = new_mail
expected.to = @recipient
expected.subject = "[Signed up] Welcome #{@recipient}"
expected.body = "Hello there, \n\nMr. #{@recipient}. Please see our greeting at http://example.com/welcome/greeting http://www.basecamphq.com/welcome\n\n<img alt=\"Somelogo\" src=\"/images/somelogo.png\" />"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
created = nil
assert_nothing_raised { created = TestMailer.create_signed_up_with_url(@recipient) }
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised { TestMailer.deliver_signed_up_with_url(@recipient) }
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
end

View File

@@ -30,12 +30,16 @@ Rake::TestTask.new(:test_action_pack) do |t|
# make sure we include the tests in alphabetical order as on some systems
# this will not happen automatically and the tests (as a whole) will error
t.test_files = Dir.glob( "test/[cftv]*/**/*_test.rb" ).sort
t.verbose = true
#t.warning = true
end
desc 'ActiveRecord Integration Tests'
Rake::TestTask.new(:test_active_record_integration) do |t|
t.libs << "test"
t.test_files = Dir.glob("test/activerecord/*_test.rb")
t.verbose = true
end
@@ -76,7 +80,8 @@ spec = Gem::Specification.new do |s|
s.add_dependency('activesupport', '= 2.3.14' + PKG_BUILD)
s.add_dependency('erubis', '~> 2.7.0')
s.add_dependency('rack', '~> 1.1')
s.add_dependency('rack', '~> 1.1')
s.add_dependency('rack-mount', '~> 0.8')
s.require_path = 'lib'

View File

@@ -1,17 +0,0 @@
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).chomp
Gem::Specification.new do |s|
s.name = 'actionpack'
s.version = version
s.summary = 'Web-flow and rendering framework putting the VC in MVC.'
s.description = 'Eases web-request routing, handling, and response as a half-way front, half-way page controller. Implemented with specific emphasis on enabling easy unit/integration testing that doesn\'t require a browser.'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.require_path = 'lib'
s.add_dependency 'activesupport', "= #{version}"
s.add_dependency 'rack', '~> 1.4'
end

View File

@@ -38,7 +38,7 @@ module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter]
end
autoload :Base, 'action_controller/base'
@@ -57,7 +57,6 @@ module ActionController
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
autoload :ParamsParser, 'action_controller/params_parser'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Reloader, 'action_controller/reloader'
autoload :Request, 'action_controller/request'
@@ -78,7 +77,6 @@ module ActionController
autoload :UploadedStringIO, 'action_controller/uploaded_file'
autoload :UploadedTempfile, 'action_controller/uploaded_file'
autoload :UrlRewriter, 'action_controller/url_rewriter'
autoload :UrlWriter, 'action_controller/url_rewriter'
autoload :Verification, 'action_controller/verification'
module Assertions
@@ -99,6 +97,10 @@ module ActionController
autoload :CookieStore, 'action_controller/session/cookie_store'
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
end
# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end
autoload :Mime, 'action_controller/mime_type'

View File

@@ -1,4 +1,5 @@
require 'set'
require 'action_controller/metal/url_for'
module ActionController #:nodoc:
class ActionControllerError < StandardError #:nodoc:
@@ -250,6 +251,7 @@ module ActionController #:nodoc:
DEFAULT_RENDER_STATUS_CODE = "200 OK"
include StatusCodes
include UrlFor
cattr_reader :protected_instance_variables
# Controller specific instance variables which will not be accessible inside views.
@@ -541,93 +543,6 @@ module ActionController #:nodoc:
response
end
# Returns a URL that has been rewritten according to the options hash and the defined routes.
# (For doing a complete redirect, use +redirect_to+).
#
# <tt>url_for</tt> is used to:
#
# All keys given to +url_for+ are forwarded to the Route module, save for the following:
# * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. For example,
# <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
# will produce "/posts/show/10#comments".
# * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>false</tt> by default).
# * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this
# is currently not recommended since it breaks caching.
# * <tt>:host</tt> - Overrides the default (current) host if provided.
# * <tt>:protocol</tt> - Overrides the default (current) protocol if provided.
# * <tt>:port</tt> - Optionally specify the port to connect to.
# * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present).
# * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present).
# * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the +relative_url_root+
# of the request so the path will include the web server relative installation directory.
#
# The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
# Routes composes a query string as the key/value pairs not included in the <base>.
#
# The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
# action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
#
# url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
# url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
# url_for :controller => 'posts', :action => 'index', :port=>'8033' # => 'proto://host.com:8033/posts'
# url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
# url_for :controller => 'posts', :user => 'd', :password => '123' # => 'proto://d:123@host.com/posts'
#
# When generating a new URL, missing values may be filled in from the current request's parameters. For example,
# <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
# other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
# path.
#  
# The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
# missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
# taken from the defaults. There are a few simple rules on how this is performed:
#
# * If the controller name begins with a slash no defaults are used:
#
# url_for :controller => '/home'
#
# In particular, a leading slash ensures no namespace is assumed. Thus,
# while <tt>url_for :controller => 'users'</tt> may resolve to
# <tt>Admin::UsersController</tt> if the current controller lives under
# that module, <tt>url_for :controller => '/users'</tt> ensures you link
# to <tt>::UsersController</tt> no matter what.
# * If the controller changes, the action will default to index unless provided
#
# The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
# route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
#
# Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
# from this page.
#
# * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
# last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
# * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
# that this URL leaves out the assumed action of 'bio'.
#
# However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
# answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
# value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
# defaults. On its own, this rule can account for much of the typical Rails URL behavior.
#  
# Although a convenience, defaults can occasionally get in your way. In some cases a default persists longer than desired.
# The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
# This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
# helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
# displayed on:
#
# url_for :controller => 'posts', :action => nil
def url_for(options = {})
options ||= {}
case options
when String
options
when Hash
@url.rewrite(rewrite_options(options))
else
polymorphic_url(options)
end
end
# Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
def controller_class_name
self.class.controller_class_name
@@ -964,6 +879,13 @@ module ActionController #:nodoc:
render_for_text(@template.render(options), options[:status])
end
elsif options[:update]
@template.send(:_evaluate_assigns_and_ivars)
generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
response.content_type = Mime::JS
render_for_text(generator.to_s, options[:status])
elsif options[:nothing]
render_for_text(nil, options[:status])
@@ -1035,27 +957,6 @@ module ActionController #:nodoc:
erase_redirect_results
end
def rewrite_options(options) #:nodoc:
if defaults = default_url_options(options)
defaults.merge(options)
else
options
end
end
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
# the form of a hash, just like the one you would use for url_for directly. Example:
#
# def default_url_options(options)
# { :project => @project.active? ? @project.url_name : "unknown" }
# end
#
# As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
# urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
# by this method.
def default_url_options(options = nil)
end
# Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
#
# * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+.
@@ -1273,7 +1174,9 @@ module ActionController #:nodoc:
end
def initialize_template_class(response)
response.template = self.class.master_helper_class.new(self.class.view_paths, {}, self)
response.template = ActionView::Base.new(self.class.view_paths, {}, self)
response.template.helpers.send :include, ActionController::Routing::Routes.url_helpers
response.template.helpers.send :include, self.class.master_helper_module
response.redirected_to = nil
@performed_render = @performed_redirect = false
end
@@ -1320,19 +1223,12 @@ module ActionController #:nodoc:
render
end
CVE_2014_0310 = Class.new(StandardError)
def perform_action
# CVE-2014-0130 protection
if action_name.include? "/"
raise CVE_2014_0310
end
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?
elsif defined?(self.method_missing) # returns "method" if method_missing is public or protected, but not if it's private
method_missing action_name.intern
elsif respond_to? :method_missing
method_missing action_name
default_render unless performed?
else
begin
@@ -1369,7 +1265,8 @@ module ActionController #:nodoc:
# Be sure to include shadowed public instance methods of this class
public_instance_methods(false).map { |m| m.to_s } -
# And always exclude explicitly hidden actions
hidden_actions
hidden_actions -
_routes.named_routes.helper_names
end
def reset_variables_added_to_assigns

View File

@@ -87,6 +87,7 @@ module ActionController #:nodoc:
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
response.headers["X-Runtime"] = "%.0f" % ms
else
perform_action_without_benchmark
end

View File

@@ -154,7 +154,7 @@ module ActionController #:nodoc:
path = controller.url_for(options).split('://').last
normalize!(path)
add_extension!(path, @extension)
@path = URI::DEFAULT_PARSER.unescape(path)
@path = URI.unescape(path)
end
private

View File

@@ -39,9 +39,9 @@ module ActionController #:nodoc:
if cache = read_fragment(name, options)
buffer.safe_concat(cache.html_safe)
else
pos = buffer.bytesize
pos = buffer.length
block.call
write_fragment(name, buffer.byteslice(pos..-1), options)
write_fragment(name, buffer[pos..-1], options)
end
else
block.call

View File

@@ -97,7 +97,7 @@ module ActionController #:nodoc:
private
def page_cache_file(path)
name = (path.empty? || path == "/") ? "/index" : URI::DEFAULT_PARSER.unescape(path.chomp('/'))
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
name << page_cache_extension unless (name.split('/').last || name).include? '.'
return name
end
@@ -149,4 +149,4 @@ module ActionController #:nodoc:
end
end
end
end
end

View File

@@ -1,6 +1,10 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
class << self
alias :escapeHTML_fail_on_nil :escapeHTML

View File

@@ -1,6 +1,4 @@
require 'delegate'
require 'cgi'
require 'cgi/cookie'
CGI.module_eval { remove_const "Cookie" }
@@ -26,7 +24,7 @@ class CGI #:nodoc:
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
# +false+). Secure cookies are only transmitted to HTTPS servers.
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
#
# These keywords correspond to attributes of the cookie object.
def initialize(name = '', *value)

View File

@@ -0,0 +1,22 @@
require 'cgi'
class CGI #:nodoc:
module QueryExtension
# Remove the old initialize_query method before redefining it.
remove_method :initialize_query
# Neuter CGI parameter parsing.
def initialize_query
# Fix some strange request environments.
env_table['REQUEST_METHOD'] ||= 'GET'
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
end
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
@params = {}
end
end
end

View File

@@ -0,0 +1,24 @@
require 'cgi'
module ActionController
module CgiExt
# Publicize the CGI's internal input stream so we can lazy-read
# request.body. Make it writable so we don't have to play $stdin games.
module Stdinput
def self.included(base)
base.class_eval do
remove_method :stdinput
attr_accessor :stdinput
end
base.alias_method_chain :initialize, :stdinput
end
def initialize_with_stdinput(type = nil, stdinput = $stdin)
@stdinput = stdinput
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
initialize_without_stdinput(type || 'query')
end
end
end
end

View File

@@ -0,0 +1,77 @@
require 'action_controller/cgi_ext'
module ActionController #:nodoc:
class CGIHandler
module ProperStream
def each
while line = gets
yield line
end
end
def read(*args)
if args.empty?
super || ""
else
super
end
end
end
def self.dispatch_cgi(app, cgi, out = $stdout)
env = cgi.__send__(:env_table)
env.delete "HTTP_CONTENT_LENGTH"
cgi.stdinput.extend ProperStream
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({
"rack.version" => [0,1],
"rack.input" => cgi.stdinput,
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
})
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = app.call(env)
begin
out.binmode if out.respond_to?(:binmode)
out.sync = false if out.respond_to?(:sync=)
headers['Status'] = status.to_s
if headers.include?('Set-Cookie')
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
end
out.write(cgi.header(headers))
body.each { |part|
out.write part
out.flush if out.respond_to?(:flush)
}
ensure
body.close if body.respond_to?(:close)
end
end
end
class CgiRequest #:nodoc:
DEFAULT_SESSION_OPTIONS = {
:database_manager => nil,
:prefix => "ruby_sess.",
:session_path => "/",
:session_key => "_session_id",
:cookie_only => true,
:session_http_only => true
}
end
end

View File

@@ -22,6 +22,11 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
new(output).dispatch_cgi(cgi, session_options)
end
# Add a preparation callback. Preparation callbacks are run before every
# request in development mode, and before the first request in production
# mode.
@@ -37,14 +42,20 @@ module ActionController
end
def run_prepare_callbacks
new.send :run_callbacks, :prepare_dispatch
if defined?(Rails) && Rails.logger
logger = Rails.logger
else
logger = Logger.new($stderr)
end
new(logger).send :run_callbacks, :prepare_dispatch
end
def reload_application
# Run prepare callbacks before every request in development mode
run_prepare_callbacks
Routing::Routes.reload
ActionController::Routing.routes_reloader.execute_if_updated
end
def cleanup_application
@@ -64,8 +75,10 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
def initialize
build_middleware_stack
# DEPRECATE: Remove arguments, since they are only used by CGI
def initialize(output = $stdout, request = nil, response = nil)
@output = output
build_middleware_stack if @@cache_classes
end
def dispatch
@@ -83,11 +96,21 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
CGIHandler.dispatch_cgi(self, cgi, @output)
end
def call(env)
if @@cache_classes
@app.call(env)
else
Reloader.run do
# When class reloading is turned on, we will want to rebuild the
# middleware stack every time we process a request. If we don't
# rebuild the middleware stack, then the stack may contain references
# to old classes metal classes, which will b0rk class reloading.
build_middleware_stack
@app.call(env)
end
end

View File

@@ -45,74 +45,37 @@ module ActionController #:nodoc:
end
def []=(k, v)
k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
@flash[k.to_s]
@flash[k]
end
end
class FlashHash < Hash
def self.from_session_value(value)
flash = case value
when FlashHash # Rails 2.3
value
when Hash # Rails 4.0
flashes = value['flashes'] || {}
flashes.stringify_keys!
discard = value['discard'] || []
discard = discard.map do |item|
item.kind_of?(Symbol) ? item.to_s : item
end
used = Hash[flashes.keys.map{|k| [k, discard.include?(k)] }]
new_from_values(flashes, used)
else
new
end
flash
end
def initialize #:nodoc:
super
@used = {}
end
def to_session_value
return nil if empty?
rails_3_discard_list = @used.map{|k,v| k if v}.compact
{'discard' => rails_3_discard_list, 'flashes' => Hash[to_a]}
end
def []=(k, v) #:nodoc:
k = k.to_s
keep(k)
super(k, v)
end
def [](k)
super(k.to_s)
end
def delete(k)
super(k.to_s)
super
end
def update(h) #:nodoc:
h.stringify_keys!
h.keys.each { |k| keep(k) }
super(h)
super
end
alias :merge! :update
def replace(h) #:nodoc:
@used = {}
super(h.stringify_keys)
super
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -163,7 +126,8 @@ module ActionController #:nodoc:
end
def store(session, key = "flash")
session[key] = to_session_value
return if self.empty?
session[key] = self
end
private
@@ -174,20 +138,11 @@ module ActionController #:nodoc:
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
def use(k=nil, v=true)
unless k.nil?
@used[k.to_s] = v
@used[k] = v
else
keys.each{ |key| use(key, v) }
end
end
def self.new_from_values(flashes, used)
new.tap do |flash_hash|
flashes.each do |k, v|
flash_hash[k] = v
end
flash_hash.instance_variable_set("@used", used)
end
end
end
module InstanceMethods #:nodoc:
@@ -213,11 +168,11 @@ module ActionController #:nodoc:
if notice = response_status_and_flash.delete(:notice)
flash[:notice] = notice
end
if other_flashes = response_status_and_flash.delete(:flash)
flash.update(other_flashes)
end
redirect_to_without_flash(options, response_status_and_flash)
end
@@ -226,19 +181,19 @@ module ActionController #:nodoc:
# to put a new one.
def flash #:doc:
if !defined?(@_flash)
@_flash = Flash::FlashHash.from_session_value(session["flash"])
@_flash = session["flash"] || FlashHash.new
@_flash.sweep
end
@_flash
end
# Convenience accessor for flash[:alert]
def alert
flash[:alert]
end
# Convenience accessor for flash[:alert]=
def alert=(message)
flash[:alert] = message
@@ -248,7 +203,7 @@ module ActionController #:nodoc:
def notice
flash[:notice]
end
# Convenience accessor for flash[:notice]=
def notice=(message)
flash[:notice] = message

View File

@@ -69,22 +69,6 @@ module ActionController #:nodoc:
# N/A | Carolina Railhaws Training Workshop
#
module ClassMethods
# To avoid extending an instance of ActionView::Base with the master_helper_module
# every single time we render a view, we're caching a class that has
# master_helper_module already included that we can just instantiate.
def master_helper_class
return @master_helper_class if @master_helper_class
@master_helper_class = Class.new(ActionView::Base).tap do |klass|
klass.send(:include, master_helper_module)
end
end
def master_helper_module=(mod)
write_inheritable_attribute(:master_helper_module, mod)
@master_helper_class = nil
end
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
# available to the templates.
@@ -198,7 +182,8 @@ module ActionController #:nodoc:
# Provides a proxy to access helpers methods from outside the view.
def helpers
unless @helper_proxy
@helper_proxy = master_helper_class.new
@helper_proxy = ActionView::Base.new
@helper_proxy.extend master_helper_module
else
@helper_proxy
end

View File

@@ -130,7 +130,7 @@ module ActionController
# performed on the location header.
def follow_redirect!
raise "not a redirect! #{@status} #{@status_message}" unless redirect?
get(interpret_uri(headers['location']))
get(headers['location'])
status
end
@@ -256,14 +256,15 @@ module ActionController
# Performs the actual request.
def process(method, path, parameters = nil, headers = nil)
data = requestify(parameters)
data = requestify(parameters) if !parameters.blank?
path = interpret_uri(path) if path =~ %r{://}
path = "/#{path}" unless path[0] == ?/
path, query = path.split('?', 2)
@path = path
env = {}
if method == :get
env["QUERY_STRING"] = data
env["QUERY_STRING"] = query || data
data = nil
end

View File

@@ -0,0 +1,22 @@
require 'active_support/concern'
module ActionController
module UrlFor
extend ActiveSupport::Concern
include ActionController::Routing::UrlFor
def url_options
super.reverse_merge(
:host => request.host_with_port,
:protocol => request.protocol,
:_path_segments => request.symbolized_path_parameters
).merge(:script_name => request.script_name)
end
def _routes
raise "In order to use #url_for, you must include routing helpers explicitly. " \
"For instance, `include Rails.application.routes.url_helpers"
end
end
end

View File

@@ -1,189 +0,0 @@
module ActionController
# Polymorphic URL helpers are methods for smart resolution to a named route call when
# given an Active Record model instance. They are to be used in combination with
# ActionController::Resources.
#
# These methods are useful when you want to generate correct URL or path to a RESTful
# resource without having to know the exact type of the record in question.
#
# Nested resources and/or namespaces are also supported, as illustrated in the example:
#
# polymorphic_url([:admin, @article, @comment])
#
# results in:
#
# admin_article_comment_url(@article, @comment)
#
# == Usage within the framework
#
# Polymorphic URL helpers are used in a number of places throughout the Rails framework:
#
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
# <tt>url_for(@article)</tt>;
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
# action;
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
# <tt>redirect_to(post)</tt> in your controllers;
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
# for feed entries.
#
# == Prefixed polymorphic helpers
#
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
# in options. Those are:
#
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
#
# Example usage:
#
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
# # calls post_url(post)
# polymorphic_url(post) # => "http://example.com/posts/1"
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
#
# ==== Options
#
# * <tt>:action</tt> - Specifies the action prefix for the named route:
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
# ==== Examples
#
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
# # a Comment record
# polymorphic_url(record) # same as comment_url(record)
#
# # it recognizes new records and maps to the collection
# record = Comment.new
# polymorphic_url(record) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
record = extract_record(record_or_hash_or_array)
args = case record_or_hash_or_array
when Hash; [ record_or_hash_or_array ]
when Array; record_or_hash_or_array.dup
else [ record_or_hash_or_array ]
end
inflection =
case
when options[:action].to_s == "new"
args.pop
:singular
when record.respond_to?(:new_record?) && record.new_record?
args.pop
:plural
else
:singular
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
url_options = options.except(:action, :routing_type)
unless url_options.empty?
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
__send__(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
def polymorphic_path(record_or_hash_or_array, options = {})
options[:routing_type] = :path
polymorphic_url(record_or_hash_or_array, options)
end
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
end # end
#
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
end # end
EOT
end
def formatted_polymorphic_url(record_or_hash, options = {})
ActiveSupport::Deprecation.warn("formatted_polymorphic_url has been deprecated. Please pass :format to the polymorphic_url method instead", caller)
options[:format] = record_or_hash.pop if Array === record_or_hash
polymorphic_url(record_or_hash, options)
end
def formatted_polymorphic_path(record_or_hash, options = {})
ActiveSupport::Deprecation.warn("formatted_polymorphic_path has been deprecated. Please pass :format to the polymorphic_path method instead", caller)
options[:format] = record_or_hash.pop if record_or_hash === Array
polymorphic_url(record_or_hash, options.merge(:routing_type => :path))
end
private
def action_prefix(options)
options[:action] ? "#{options[:action]}_" : ''
end
def routing_type(options)
options[:routing_type] || :url
end
def build_named_route_call(records, inflection, options = {})
unless records.is_a?(Array)
record = extract_record(records)
route = ''
else
record = records.pop
route = records.inject("") do |string, parent|
if parent.is_a?(Symbol) || parent.is_a?(String)
string << "#{parent}_"
else
string << RecordIdentifier.__send__("plural_class_name", parent).singularize
string << "_"
end
end
end
if record.is_a?(Symbol) || record.is_a?(String)
route << "#{record}_"
else
route << RecordIdentifier.__send__("plural_class_name", record)
route = route.singularize if inflection == :singular
route << "_"
end
action_prefix(options) + route + routing_type(options).to_s
end
def extract_record(record_or_hash_or_array)
case record_or_hash_or_array
when Array; record_or_hash_or_array.last
when Hash; record_or_hash_or_array[:id]
else record_or_hash_or_array
end
end
end
end

View File

@@ -35,6 +35,10 @@ module ActionController
@request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
end
def request_method_string
@request_method_string ||= @env['REQUEST_METHOD']
end
# Returns the HTTP request \method used for action processing as a
# lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
# method returns <tt>:get</tt> for a HEAD request because the two are
@@ -332,6 +336,10 @@ EOM
parts[0..-(tld_length+2)]
end
def subdomain(tld_length = 1)
subdomains(tld_length).join('.')
end
# Returns the query string, accounting for server idiosyncrasies.
def query_string
@env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
@@ -423,13 +431,13 @@ EOM
# Override Rack's GET method to support indifferent access
def GET
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support indifferent access
def POST
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
end
alias_method :request_parameters, :POST
@@ -469,22 +477,6 @@ EOM
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
# Remove nils from the params hash
def deep_munge(hash)
hash.each do |k, v|
case v
when Array
v.grep(Hash) { |x| deep_munge(x) }
v.compact!
hash[k] = nil if v.empty?
when Hash
deep_munge(v)
end
end
hash
end
# Convert nested Hashs to HashWithIndifferentAccess and replace
# file upload hashs with UploadedFile objects
def normalize_parameters(value)

View File

@@ -70,7 +70,7 @@ module ActionController # :nodoc:
else
"#{mime_type}; charset=#{c}"
end
self.headers["Content-Type"] = URI::DEFAULT_PARSER.escape(new_content_type, "\r\n")
self.headers["Content-Type"] = URI.escape(new_content_type, "\r\n")
end
# Returns the response's content MIME type, or nil if content type has been set.

View File

@@ -1,42 +1,17 @@
require 'cgi'
require 'uri'
require 'action_controller/routing/optimisations'
require 'action_controller/routing/routing_ext'
require 'action_controller/routing/route'
require 'action_controller/routing/segments'
require 'action_controller/routing/builder'
require 'action_controller/routing/route_set'
require 'action_controller/routing/recognition_optimisation'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
require 'active_support/file_update_checker'
module ActionController
# == Routing
#
# The routing module provides URL rewriting in native Ruby. It's a way to
# redirect incoming requests to controllers and actions. This replaces
# mod_rewrite rules. Best of all, Rails' Routing works with any web server.
# mod_rewrite rules. Best of all, Rails' \Routing works with any web server.
# Routes are defined in <tt>config/routes.rb</tt>.
#
# Consider the following route, installed by Rails when you generate your
# application:
#
# map.connect ':controller/:action/:id'
#
# This route states that it expects requests to consist of a
# <tt>:controller</tt> followed by an <tt>:action</tt> that in turn is fed
# some <tt>:id</tt>.
#
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end up
# with:
#
# params = { :controller => 'blog',
# :action => 'edit',
# :id => '22'
# }
#
# Think of creating routes as drawing a map for your requests. The map tells
# them where to go based on some predefined pattern:
#
# ActionController::Routing::Routes.draw do |map|
# AppName::Application.routes.draw do
# Pattern 1 tells some request to go to one place
# Pattern 2 tell them to go to another
# ...
@@ -49,60 +24,49 @@ module ActionController
#
# Other names simply map to a parameter as in the case of <tt>:id</tt>.
#
# == Route priority
# == Resources
#
# Not all routes are created equally. Routes have priority defined by the
# order of appearance of the routes in the <tt>config/routes.rb</tt> file. The priority goes
# from top to bottom. The last route in that file is at the lowest priority
# and will be applied last. If no route matches, 404 is returned.
# Resource routing allows you to quickly declare all of the common routes
# for a given resourceful controller. Instead of declaring separate routes
# for your +index+, +show+, +new+, +edit+, +create+, +update+ and +destroy+
# actions, a resourceful route declares them in a single line of code:
#
# Within blocks, the empty pattern is at the highest priority.
# In practice this works out nicely:
# resources :photos
#
# ActionController::Routing::Routes.draw do |map|
# map.with_options :controller => 'blog' do |blog|
# blog.show '', :action => 'list'
# end
# map.connect ':controller/:action/:view'
# Sometimes, you have a resource that clients always look up without
# referencing an ID. A common example, /profile always shows the profile of
# the currently logged in user. In this case, you can use a singular resource
# to map /profile (rather than /profile/:id) to the show action.
#
# resource :profile
#
# It's common to have resources that are logically children of other
# resources:
#
# resources :magazines do
# resources :ads
# end
#
# In this case, invoking blog controller (with an URL like '/blog/')
# without parameters will activate the 'list' action by default.
# You may wish to organize groups of controllers under a namespace. Most
# commonly, you might group a number of administrative controllers under
# an +admin+ namespace. You would place these controllers under the
# app/controllers/admin directory, and you can group them together in your
# router:
#
# == Defaults routes and default parameters
#
# Setting a default route is straightforward in Rails - you simply append a
# Hash at the end of your mapping to set any default parameters.
#
# Example:
#
# ActionController::Routing:Routes.draw do |map|
# map.connect ':controller/:action/:id', :controller => 'blog'
# namespace "admin" do
# resources :posts, :comments
# end
#
# This sets up +blog+ as the default controller if no other is specified.
# This means visiting '/' would invoke the blog controller.
#
# More formally, you can include arbitrary parameters in the route, thus:
#
# map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
#
# This will pass the :page parameter to all incoming requests that match this route.
#
# Note: The default routes, as provided by the Rails generator, make all actions in every
# controller accessible via GET requests. You should consider removing them or commenting
# them out if you're using named routes and resources.
#
# == Named routes
#
# Routes can be named with the syntax <tt>map.name_of_route options</tt>,
# Routes can be named by passing an <tt>:as</tt> option,
# allowing for easy reference within your source as +name_of_route_url+
# for the full URL and +name_of_route_path+ for the URI path.
#
# Example:
#
# # In routes.rb
# map.login 'login', :controller => 'accounts', :action => 'login'
# match '/login' => 'accounts#login', :as => 'login'
#
# # With render, redirect_to, tests, etc.
# redirect_to login_url
@@ -111,32 +75,26 @@ module ActionController
#
# redirect_to show_item_path(:id => 25)
#
# Use <tt>map.root</tt> as a shorthand to name a route for the root path "".
# Use <tt>root</tt> as a shorthand to name a route for the root path "/".
#
# # In routes.rb
# map.root :controller => 'blogs'
# root :to => 'blogs#index'
#
# # would recognize http://www.example.com/ as
# params = { :controller => 'blogs', :action => 'index' }
#
# # and provide these named routes
# root_url # => 'http://www.example.com/'
# root_path # => ''
# root_path # => '/'
#
# You can also specify an already-defined named route in your <tt>map.root</tt> call:
#
# # In routes.rb
# map.new_session :controller => 'sessions', :action => 'new'
# map.root :new_session
#
# Note: when using +with_options+, the route is simply named after the
# Note: when using +controller+, the route is simply named after the
# method you call on the block parameter rather than map.
#
# # In routes.rb
# map.with_options :controller => 'blog' do |blog|
# blog.show '', :action => 'list'
# blog.delete 'delete/:id', :action => 'delete',
# blog.edit 'edit/:id', :action => 'edit'
# controller :blog do
# match 'blog/show' => :list
# match 'blog/delete' => :delete
# match 'blog/edit/:id' => :edit
# end
#
# # provides named routes for show, delete, and edit
@@ -146,12 +104,11 @@ module ActionController
#
# Routes can generate pretty URLs. For example:
#
# map.connect 'articles/:year/:month/:day',
# :controller => 'articles',
# :action => 'find_by_date',
# :year => /\d{4}/,
# :month => /\d{1,2}/,
# :day => /\d{1,2}/
# match '/articles/:year/:month/:day' => 'articles#find_by_id', :constraints => {
# :year => /\d{4}/,
# :month => /\d{1,2}/,
# :day => /\d{1,2}/
# }
#
# Using the route above, the URL "http://localhost:3000/articles/2005/11/06"
# maps to
@@ -161,64 +118,105 @@ module ActionController
# == Regular Expressions and parameters
# You can specify a regular expression to define a format for a parameter.
#
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
# :action => 'show', :postalcode => /\d{5}(-\d{4})?/
# controller 'geocode' do
# match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /\d{5}(-\d{4})?/
# }
#
# or, more formally:
#
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
# :action => 'show', :requirements => { :postalcode => /\d{5}(-\d{4})?/ }
#
# Formats can include the 'ignorecase' and 'extended syntax' regular
# Constraints can include the 'ignorecase' and 'extended syntax' regular
# expression modifiers:
#
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
# :action => 'show', :postalcode => /hx\d\d\s\d[a-z]{2}/i
# controller 'geocode' do
# match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /hx\d\d\s\d[a-z]{2}/i
# }
# end
#
# map.geocode 'geocode/:postalcode', :controller => 'geocode',
# :action => 'show',:requirements => {
# :postalcode => /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
# /x
# }
# controller 'geocode' do
# match 'geocode/:postalcode' => :show, :constraints => {
# :postalcode => /# Postcode format
# \d{5} #Prefix
# (-\d{4})? #Suffix
# /x
# }
# end
#
# Using the multiline match modifier will raise an ArgumentError.
# Encoding regular expression modifiers are silently ignored. The
# match will always use the default encoding or ASCII.
#
# == Route globbing
# == Default route
#
# Specifying <tt>*[string]</tt> as part of a rule like:
# Consider the following route, which you will find commented out at the
# bottom of your generated <tt>config/routes.rb</tt>:
#
# map.connect '*path' , :controller => 'blog' , :action => 'unrecognized?'
# match ':controller(/:action(/:id(.:format)))'
#
# will glob all remaining parts of the route that were not recognized earlier.
# The globbed values are in <tt>params[:path]</tt> as an array of path segments.
# This route states that it expects requests to consist of a
# <tt>:controller</tt> followed optionally by an <tt>:action</tt> that in
# turn is followed optionally by an <tt>:id</tt>, which in turn is followed
# optionally by a <tt>:format</tt>.
#
# == Route conditions
# Suppose you get an incoming request for <tt>/blog/edit/22</tt>, you'll end
# up with:
#
# With conditions you can define restrictions on routes. Currently the only valid condition is <tt>:method</tt>.
# params = { :controller => 'blog',
# :action => 'edit',
# :id => '22'
# }
#
# * <tt>:method</tt> - Allows you to specify which method can access the route. Possible values are <tt>:post</tt>,
# <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. The default value is <tt>:any</tt>,
# <tt>:any</tt> means that any method can access the route.
# By not relying on default routes, you improve the security of your
# application since not all controller actions, which includes actions you
# might add at a later time, are exposed by default.
#
# Example:
# == HTTP Methods
#
# map.connect 'post/:id', :controller => 'posts', :action => 'show',
# :conditions => { :method => :get }
# map.connect 'post/:id', :controller => 'posts', :action => 'create_comment',
# :conditions => { :method => :post }
# Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method.
# Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>.
# If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>.
# The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods.
#
# Examples:
#
# match 'post/:id' => 'posts#show', :via => :get
# match 'post/:id' => "posts#create_comment', :via => :post
#
# Now, if you POST to <tt>/posts/:id</tt>, it will route to the <tt>create_comment</tt> action. A GET on the same
# URL will route to the <tt>show</tt> action.
#
# === HTTP helper methods
#
# An alternative method of specifying which HTTP method a route should respond to is to use the helper
# methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>.
#
# Examples:
#
# get 'post/:id' => 'posts#show'
# post 'post/:id' => "posts#create_comment'
#
# This syntax is less verbose and the intention is more apparent to someone else reading your code,
# however if your route needs to respond to more than one HTTP method (or all methods) then using the
# <tt>:via</tt> option on <tt>match</tt> is preferable.
#
# == External redirects
#
# You can redirect any path to another path using the redirect helper in your router:
#
# match "/stories" => redirect("/posts")
#
# == Routing to Rack Applications
#
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
# index action in the PostsController, you can specify any Rack application
# as the endpoint for a matcher:
#
# match "/application.js" => Sprockets
#
# == Reloading routes
#
# You can reload routes if you feel you must:
#
# ActionController::Routing::Routes.reload
# Rails.application.reload_routes!
#
# This will clear all named routes and reload routes.rb if the file has been modified from
# last load. To absolutely force reloading, use <tt>reload!</tt>.
@@ -262,127 +260,40 @@ module ActionController
#
# == View a list of all your routes
#
# Run <tt>rake routes</tt>.
# rake routes
#
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
#
module Routing
SEPARATORS = %w( / . ? )
autoload :DeprecatedMapper, 'action_controller/routing/deprecated_mapper'
autoload :Mapper, 'action_controller/routing/mapper'
autoload :Route, 'action_controller/routing/route'
autoload :RouteSet, 'action_controller/routing/route_set'
autoload :UrlFor, 'action_controller/routing/url_for'
autoload :PolymorphicRoutes, 'action_controller/routing/polymorphic_routes'
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options]
ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set
mattr_accessor :generate_best_match
self.generate_best_match = true
# The root paths which may contain controller files
mattr_accessor :controller_paths
self.controller_paths = []
SEPARATORS = %w( / . ? ) #:nodoc:
HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc:
# A helper module to hold URL related helpers.
module Helpers
module Helpers #:nodoc:
include PolymorphicRoutes
end
class << self
# Expects an array of controller names as the first argument.
# Executes the passed block with only the named controllers named available.
# This method is used in internal Rails testing.
def with_controllers(names)
prior_controllers = @possible_controllers
use_controllers! names
yield
ensure
use_controllers! prior_controllers
end
# Returns an array of paths, cleaned of double-slashes and relative path references.
# * "\\\" and "//" become "\\" or "/".
# * "/foo/bar/../config" becomes "/foo/config".
# The returned array is sorted by length, descending.
def normalize_paths(paths)
# do the hokey-pokey of path normalization...
paths = paths.collect do |path|
path = path.
gsub("//", "/"). # replace double / chars with a single
gsub("\\\\", "\\"). # replace double \ chars with a single
gsub(%r{(.)[\\/]$}, '\1') # drop final / or \ if path ends with it
# eliminate .. paths where possible
re = %r{[^/\\]+[/\\]\.\.[/\\]}
path.gsub!(re, "") while path.match(re)
path
end
# start with longest path, first
paths = paths.uniq.sort_by { |path| - path.length }
end
# Returns the array of controller names currently available to ActionController::Routing.
def possible_controllers
unless @possible_controllers
@possible_controllers = []
paths = controller_paths.select { |path| File.directory?(path) && path != "." }
seen_paths = Hash.new {|h, k| h[k] = true; false}
normalize_paths(paths).each do |load_path|
Dir["#{load_path}/**/*_controller.rb"].collect do |path|
next if seen_paths[path.gsub(%r{^\.[/\\]}, "")]
controller_name = path[(load_path.length + 1)..-1]
controller_name.gsub!(/_controller\.rb\Z/, '')
@possible_controllers << controller_name
end
end
# remove duplicates
@possible_controllers.uniq!
end
@possible_controllers
end
# Replaces the internal list of controllers available to ActionController::Routing with the passed argument.
# ActionController::Routing.use_controllers!([ "posts", "comments", "admin/comments" ])
def use_controllers!(controller_names)
@possible_controllers = controller_names
end
# Returns a controller path for a new +controller+ based on a +previous+ controller path.
# Handles 4 scenarios:
#
# * stay in the previous controller:
# controller_relative_to( nil, "groups/discussion" ) # => "groups/discussion"
#
# * stay in the previous namespace:
# controller_relative_to( "posts", "groups/discussion" ) # => "groups/posts"
#
# * forced move to the root namespace:
# controller_relative_to( "/posts", "groups/discussion" ) # => "posts"
#
# * previous namespace is root:
# controller_relative_to( "posts", "anything_with_no_slashes" ) # =>"posts"
#
def controller_relative_to(controller, previous)
if controller.nil? then previous
elsif controller[0] == ?/ then controller[1..-1]
elsif %r{^(.*)/} =~ previous then "#{$1}/#{controller}"
else controller
end
end
end
Routes = RouteSet.new
ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
(inflections_without_route_reloading(&block)).tap {
ActionController::Routing::Routes.reload! if block_given?
}
end
def self.routes_reloader
@routes_reloader ||= ActiveSupport::FileUpdateChecker.new([]){ reload_routes! }
end
alias_method_chain :inflections, :route_reloading
def self.reload_routes!
_routes = Routes
_routes.disable_clear_and_finalize = true
_routes.clear!
routes_reloader.paths.each { |path| load(path) }
_routes.finalize!
ensure
_routes.disable_clear_and_finalize = false
end
end
end

View File

@@ -1,197 +0,0 @@
module ActionController
module Routing
class RouteBuilder #:nodoc:
attr_reader :separators, :optional_separators
attr_reader :separator_regexp, :nonseparator_regexp, :interval_regexp
def initialize
@separators = Routing::SEPARATORS
@optional_separators = %w( / )
@separator_regexp = /[#{Regexp.escape(separators.join)}]/
@nonseparator_regexp = /\A([^#{Regexp.escape(separators.join)}]+)/
@interval_regexp = /(.*?)(#{separator_regexp}|$)/
end
# Accepts a "route path" (a string defining a route), and returns the array
# of segments that corresponds to it. Note that the segment array is only
# partially initialized--the defaults and requirements, for instance, need
# to be set separately, via the +assign_route_options+ method, and the
# <tt>optional?</tt> method for each segment will not be reliable until after
# +assign_route_options+ is called, as well.
def segments_for_route_path(path)
rest, segments = path, []
until rest.empty?
segment, rest = segment_for(rest)
segments << segment
end
segments
end
# A factory method that returns a new segment instance appropriate for the
# format of the given string.
def segment_for(string)
segment =
case string
when /\A\.(:format)?\//
OptionalFormatSegment.new
when /\A:(\w+)/
key = $1.to_sym
key == :controller ? ControllerSegment.new(key) : DynamicSegment.new(key)
when /\A\*(\w+)/
PathSegment.new($1.to_sym, :optional => true)
when /\A\?(.*?)\?/
StaticSegment.new($1, :optional => true)
when nonseparator_regexp
StaticSegment.new($1)
when separator_regexp
DividerSegment.new($&, :optional => optional_separators.include?($&))
end
[segment, $~.post_match]
end
# Split the given hash of options into requirement and default hashes. The
# segments are passed alongside in order to distinguish between default values
# and requirements.
def divide_route_options(segments, options)
options = options.except(:path_prefix, :name_prefix)
if options[:namespace]
options[:controller] = "#{options.delete(:namespace).sub(/\/$/, '')}/#{options[:controller]}"
end
requirements = (options.delete(:requirements) || {}).dup
defaults = (options.delete(:defaults) || {}).dup
conditions = (options.delete(:conditions) || {}).dup
validate_route_conditions(conditions)
path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact
options.each do |key, value|
hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements
hash[key] = value
end
[defaults, requirements, conditions]
end
# Takes a hash of defaults and a hash of requirements, and assigns them to
# the segments. Any unused requirements (which do not correspond to a segment)
# are returned as a hash.
def assign_route_options(segments, defaults, requirements)
route_requirements = {} # Requirements that do not belong to a segment
segment_named = Proc.new do |key|
segments.detect { |segment| segment.key == key if segment.respond_to?(:key) }
end
requirements.each do |key, requirement|
segment = segment_named[key]
if segment
raise TypeError, "#{key}: requirements on a path segment must be regular expressions" unless requirement.is_a?(Regexp)
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
if requirement.multiline?
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
segment.regexp = requirement
else
route_requirements[key] = requirement
end
end
defaults.each do |key, default|
segment = segment_named[key]
raise ArgumentError, "#{key}: No matching segment exists; cannot assign default" unless segment
segment.is_optional = true
segment.default = default.to_param if default
end
assign_default_route_options(segments)
ensure_required_segments(segments)
route_requirements
end
# Assign default options, such as 'index' as a default for <tt>:action</tt>. This
# method must be run *after* user supplied requirements and defaults have
# been applied to the segments.
def assign_default_route_options(segments)
segments.each do |segment|
next unless segment.is_a? DynamicSegment
case segment.key
when :action
if segment.regexp.nil? || segment.regexp.match('index').to_s == 'index'
segment.default ||= 'index'
segment.is_optional = true
end
when :id
if segment.default.nil? && segment.regexp.nil? || segment.regexp =~ ''
segment.is_optional = true
end
end
end
end
# Makes sure that there are no optional segments that precede a required
# segment. If any are found that precede a required segment, they are
# made required.
def ensure_required_segments(segments)
allow_optional = true
segments.reverse_each do |segment|
allow_optional &&= segment.optional?
if !allow_optional && segment.optional?
unless segment.optionality_implied?
warn "Route segment \"#{segment.to_s}\" cannot be optional because it precedes a required segment. This segment will be required."
end
segment.is_optional = false
elsif allow_optional && segment.respond_to?(:default) && segment.default
# if a segment has a default, then it is optional
segment.is_optional = true
end
end
end
# Construct and return a route with the given path and options.
def build(path, options)
# Wrap the path with slashes
path = "/#{path}" unless path[0] == ?/
path = "#{path}/" unless path[-1] == ?/
prefix = options[:path_prefix].to_s.gsub(/^\//,'')
path = "/#{prefix}#{path}" unless prefix.blank?
segments = segments_for_route_path(path)
defaults, requirements, conditions = divide_route_options(segments, options)
requirements = assign_route_options(segments, defaults, requirements)
# TODO: Segments should be frozen on initialize
segments.each { |segment| segment.freeze }
route = Route.new(segments, requirements, conditions)
if !route.significant_keys.include?(:controller)
raise ArgumentError, "Illegal route: the :controller must be specified!"
end
route.freeze
end
private
def validate_route_conditions(conditions)
if method = conditions[:method]
[method].flatten.each do |m|
if m == :head
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
end
unless HTTP_METHODS.include?(m.to_sym)
raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}"
end
end
end
end
end
end
end

View File

@@ -0,0 +1,527 @@
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/with_options'
require 'active_support/core_ext/object/try'
module ActionController
module Routing
class RouteSet
attr_accessor :controller_namespaces
CONTROLLER_REGEXP = /[_a-zA-Z0-9]+/
def controller_constraints
@controller_constraints ||= begin
namespaces = controller_namespaces + in_memory_controller_namespaces
source = namespaces.map { |ns| "#{Regexp.escape(ns)}/#{CONTROLLER_REGEXP.source}" }
source << CONTROLLER_REGEXP.source
Regexp.compile(source.sort.reverse.join('|'))
end
end
def in_memory_controller_namespaces
namespaces = Set.new
ActionController::Base.descendants.each do |klass|
next if klass.anonymous?
namespaces << klass.name.underscore.split('/')[0...-1].join('/')
end
namespaces.delete('')
namespaces
end
end
class DeprecatedMapper #:nodoc:
def initialize(set) #:nodoc:
# ActiveSupport::Deprecation.warn "You are using the old router DSL which will be removed in Rails 3.1. " <<
# "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/"
@set = set
end
def connect(path, options = {})
options = options.dup
if conditions = options.delete(:conditions)
conditions = conditions.dup
subdomain = conditions.delete(:subdomain)
method = [conditions.delete(:method)].flatten.compact
method.map! { |m|
m = m.to_s.upcase
if m == "HEAD"
raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers"
end
unless HTTP_METHODS.include?(m.downcase.to_sym)
raise ArgumentError, "Invalid HTTP method specified in route conditions"
end
m
}
if method.length > 1
method = Regexp.union(*method)
elsif method.length == 1
method = method.first
else
method = nil
end
end
path_prefix = options.delete(:path_prefix)
name_prefix = options.delete(:name_prefix)
namespace = options.delete(:namespace)
name = options.delete(:_name)
name = "#{name_prefix}#{name}" if name_prefix
requirements = options.delete(:requirements) || {}
defaults = options.delete(:defaults) || {}
options.each do |k, v|
if v.is_a?(Regexp)
if value = options.delete(k)
requirements[k.to_sym] = value
end
else
value = options.delete(k)
defaults[k.to_sym] = value.is_a?(Symbol) ? value : value.to_param
end
end
requirements.each do |_, requirement|
if requirement.source =~ %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
end
if requirement.multiline?
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
end
end
requirements[:controller] ||= @set.controller_constraints
if defaults[:controller]
defaults[:action] ||= 'index'
defaults[:controller] = defaults[:controller].to_s
defaults[:controller] = "#{namespace}#{defaults[:controller]}" if namespace
end
if defaults[:action]
defaults[:action] = defaults[:action].to_s
end
if path.is_a?(String)
path = "#{path_prefix}/#{path}" if path_prefix
path = path.gsub('.:format', '(.:format)')
path = optionalize_trailing_dynamic_segments(path, requirements, defaults)
glob = $1.to_sym if path =~ /\/\*(\w+)$/
path = ::Rack::Mount::Utils.normalize_path(path)
if glob && !defaults[glob].blank?
raise ActionController::RoutingError, "paths cannot have non-empty default values"
end
end
app = Routing::RouteSet::Dispatcher.new(:defaults => defaults, :glob => glob)
conditions = {}
conditions[:request_method_string] = method if method
conditions[:path_info] = path if path
conditions[:subdomain] = subdomain if subdomain
@set.add_route(app, conditions, requirements, defaults, name)
end
def optionalize_trailing_dynamic_segments(path, requirements, defaults) #:nodoc:
path = (path =~ /^\//) ? path.dup : "/#{path}"
optional, segments = true, []
required_segments = requirements.keys
required_segments -= defaults.keys.compact
old_segments = path.split('/')
old_segments.shift
length = old_segments.length
old_segments.reverse.each_with_index do |segment, index|
required_segments.each do |required|
if segment =~ /#{required}/
optional = false
break
end
end
if optional
if segment == ":id" && segments.include?(":action")
optional = false
elsif segment == ":controller" || segment == ":action" || segment == ":id"
# Ignore
elsif !(segment =~ /^:\w+$/) &&
!(segment =~ /^:\w+\(\.:format\)$/)
optional = false
elsif segment =~ /^:(\w+)$/
if defaults.has_key?($1.to_sym)
defaults.delete($1.to_sym) if defaults[$1.to_sym].nil?
else
optional = false
end
end
end
if optional && index < length - 1
segments.unshift('(/', segment)
segments.push(')')
elsif optional
segments.unshift('/(', segment)
segments.push(')')
else
segments.unshift('/', segment)
end
end
segments.join
end
private :optionalize_trailing_dynamic_segments
# Creates a named route called "root" for matching the root level request.
def root(options = {})
if options.is_a?(Symbol)
if source_route = @set.named_routes.routes[options]
options = source_route.defaults.merge({ :conditions => source_route.conditions })
end
end
named_route("root", '', options)
end
def named_route(name, path, options = {}) #:nodoc:
options[:_name] = name
connect(path, options)
end
def namespace(name, options = {}, &block)
if options[:namespace]
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
else
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
end
end
def method_missing(route_name, *args, &proc) #:nodoc:
super unless args.length >= 1 && proc.nil?
named_route(route_name, *args)
end
INHERITABLE_OPTIONS = :namespace, :shallow
class Resource #:nodoc:
DEFAULT_ACTIONS = :index, :create, :new, :edit, :show, :update, :destroy
attr_reader :collection_methods, :member_methods, :new_methods
attr_reader :path_prefix, :name_prefix, :path_segment
attr_reader :plural, :singular
attr_reader :options, :defaults
def initialize(entities, options, defaults)
@plural ||= entities
@singular ||= options[:singular] || plural.to_s.singularize
@path_segment = options.delete(:as) || @plural
@options = options
@defaults = defaults
arrange_actions
add_default_actions
set_allowed_actions
set_prefixes
end
def controller
@controller ||= "#{options[:namespace]}#{(options[:controller] || plural).to_s}"
end
def requirements(with_id = false)
@requirements ||= @options[:requirements] || {}
@id_requirement ||= { :id => @requirements.delete(:id) || /[^#{Routing::SEPARATORS.join}]+/ }
with_id ? @requirements.merge(@id_requirement) : @requirements
end
def conditions
@conditions ||= @options[:conditions] || {}
end
def path
@path ||= "#{path_prefix}/#{path_segment}"
end
def new_path
new_action = self.options[:path_names][:new] if self.options[:path_names]
new_action ||= self.defaults[:path_names][:new]
@new_path ||= "#{path}/#{new_action}"
end
def shallow_path_prefix
@shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix
end
def member_path
@member_path ||= "#{shallow_path_prefix}/#{path_segment}/:id"
end
def nesting_path_prefix
@nesting_path_prefix ||= "#{shallow_path_prefix}/#{path_segment}/:#{singular}_id"
end
def shallow_name_prefix
@shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix
end
def nesting_name_prefix
"#{shallow_name_prefix}#{singular}_"
end
def action_separator
@action_separator ||= ActionController::Base.resource_action_separator
end
def uncountable?
@singular.to_s == @plural.to_s
end
def has_action?(action)
!DEFAULT_ACTIONS.include?(action) || action_allowed?(action)
end
protected
def arrange_actions
@collection_methods = arrange_actions_by_methods(options.delete(:collection))
@member_methods = arrange_actions_by_methods(options.delete(:member))
@new_methods = arrange_actions_by_methods(options.delete(:new))
end
def add_default_actions
add_default_action(member_methods, :get, :edit)
add_default_action(new_methods, :get, :new)
end
def set_allowed_actions
only, except = @options.values_at(:only, :except)
@allowed_actions ||= {}
if only == :all || except == :none
only = nil
except = []
elsif only == :none || except == :all
only = []
except = nil
end
if only
@allowed_actions[:only] = Array(only).map {|a| a.to_sym }
elsif except
@allowed_actions[:except] = Array(except).map {|a| a.to_sym }
end
end
def action_allowed?(action)
only, except = @allowed_actions.values_at(:only, :except)
(!only || only.include?(action)) && (!except || !except.include?(action))
end
def set_prefixes
@path_prefix = options.delete(:path_prefix)
@name_prefix = options.delete(:name_prefix)
end
def arrange_actions_by_methods(actions)
(actions || {}).inject({}) do |flipped_hash, (key, value)|
(flipped_hash[value] ||= []) << key
flipped_hash
end
end
def add_default_action(collection, method, action)
(collection[method] ||= []).unshift(action)
end
end
class SingletonResource < Resource #:nodoc:
def initialize(entity, options, defaults)
@singular = @plural = entity
options[:controller] ||= @singular.to_s.pluralize
super
end
alias_method :shallow_path_prefix, :path_prefix
alias_method :shallow_name_prefix, :name_prefix
alias_method :member_path, :path
alias_method :nesting_path_prefix, :path
end
def resources(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_resource(entity, options.dup, &block) }
end
def resource(*entities, &block)
options = entities.extract_options!
entities.each { |entity| map_singleton_resource(entity, options.dup, &block) }
end
private
def map_resource(entities, options = {}, &block)
resource = Resource.new(entities, options, :path_names => @set.resources_path_names)
with_options :controller => resource.controller do |map|
map_associations(resource, options)
if block_given?
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
map_collection_actions(map, resource)
map_default_collection_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
end
end
def map_singleton_resource(entities, options = {}, &block)
resource = SingletonResource.new(entities, options, :path_names => @set.resources_path_names)
with_options :controller => resource.controller do |map|
map_associations(resource, options)
if block_given?
with_options(options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix), &block)
end
map_collection_actions(map, resource)
map_new_actions(map, resource)
map_member_actions(map, resource)
map_default_singleton_actions(map, resource)
end
end
def map_associations(resource, options)
map_has_many_associations(resource, options.delete(:has_many), options) if options[:has_many]
path_prefix = "#{options.delete(:path_prefix)}#{resource.nesting_path_prefix}"
name_prefix = "#{options.delete(:name_prefix)}#{resource.nesting_name_prefix}"
Array(options[:has_one]).each do |association|
resource(association, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => path_prefix, :name_prefix => name_prefix))
end
end
def map_has_many_associations(resource, associations, options)
case associations
when Hash
associations.each do |association,has_many|
map_has_many_associations(resource, association, options.merge(:has_many => has_many))
end
when Array
associations.each do |association|
map_has_many_associations(resource, association, options)
end
when Symbol, String
resources(associations, options.slice(*INHERITABLE_OPTIONS).merge(:path_prefix => resource.nesting_path_prefix, :name_prefix => resource.nesting_name_prefix, :has_many => options[:has_many]))
else
end
end
def map_collection_actions(map, resource)
resource.collection_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= action
map_resource_routes(map, resource, action, "#{resource.path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.name_prefix}#{resource.plural}", m)
end
end
end
end
def map_default_collection_actions(map, resource)
index_route_name = "#{resource.name_prefix}#{resource.plural}"
if resource.uncountable?
index_route_name << "_index"
end
map_resource_routes(map, resource, :index, resource.path, index_route_name)
map_resource_routes(map, resource, :create, resource.path, index_route_name)
end
def map_default_singleton_actions(map, resource)
map_resource_routes(map, resource, :create, resource.path, "#{resource.shallow_name_prefix}#{resource.singular}")
end
def map_new_actions(map, resource)
resource.new_methods.each do |method, actions|
actions.each do |action|
route_path = resource.new_path
route_name = "new_#{resource.name_prefix}#{resource.singular}"
unless action == :new
route_path = "#{route_path}#{resource.action_separator}#{action}"
route_name = "#{action}_#{route_name}"
end
map_resource_routes(map, resource, action, route_path, route_name, method)
end
end
end
def map_member_actions(map, resource)
resource.member_methods.each do |method, actions|
actions.each do |action|
[method].flatten.each do |m|
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= @set.resources_path_names[action] || action
map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true })
end
end
end
route_path = "#{resource.shallow_name_prefix}#{resource.singular}"
map_resource_routes(map, resource, :show, resource.member_path, route_path)
map_resource_routes(map, resource, :update, resource.member_path, route_path)
map_resource_routes(map, resource, :destroy, resource.member_path, route_path)
end
def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} )
if resource.has_action?(action)
action_options = action_options_for(action, resource, method, resource_options)
formatted_route_path = "#{route_path}.:format"
if route_name && @set.named_routes[route_name.to_sym].nil?
map.named_route(route_name, formatted_route_path, action_options)
else
map.connect(formatted_route_path, action_options)
end
end
end
def add_conditions_for(conditions, method)
{:conditions => conditions.dup}.tap do |options|
options[:conditions][:method] = method unless method == :any
end
end
def action_options_for(action, resource, method = nil, resource_options = {})
default_options = { :action => action.to_s }
require_id = !resource.kind_of?(SingletonResource)
force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource)
case default_options[:action]
when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements)
when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements)
when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id))
when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id))
when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id))
else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id))
end
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,130 +0,0 @@
module ActionController
module Routing
# Much of the slow performance from routes comes from the
# complexity of expiry, <tt>:requirements</tt> matching, defaults providing
# and figuring out which url pattern to use. With named routes
# we can avoid the expense of finding the right route. So if
# they've provided the right number of arguments, and have no
# <tt>:requirements</tt>, we can just build up a string and return it.
#
# To support building optimisations for other common cases, the
# generation code is separated into several classes
module Optimisation
def generate_optimisation_block(route, kind)
return "" unless route.optimise?
OPTIMISERS.inject("") do |memo, klazz|
memo << klazz.new(route, kind).source_code
memo
end
end
class Optimiser
attr_reader :route, :kind
GLOBAL_GUARD_CONDITIONS = [
"(!defined?(default_url_options) || default_url_options.blank?)",
"(!defined?(controller.default_url_options) || controller.default_url_options.blank?)",
"defined?(request)",
"request"
]
def initialize(route, kind)
@route = route
@kind = kind
end
def guard_conditions
["false"]
end
def generation_code
'nil'
end
def source_code
if applicable?
guard_condition = (GLOBAL_GUARD_CONDITIONS + guard_conditions).join(" && ")
"return #{generation_code} if #{guard_condition}\n"
else
"\n"
end
end
# Temporarily disabled <tt>:url</tt> optimisation pending proper solution to
# Issues around request.host etc.
def applicable?
true
end
end
# Given a route
#
# map.person '/people/:id'
#
# If the user calls <tt>person_url(@person)</tt>, we can simply
# return a string like "/people/#{@person.to_param}"
# rather than triggering the expensive logic in +url_for+.
class PositionalArguments < Optimiser
def guard_conditions
number_of_arguments = route.required_segment_keys.size
# if they're using foo_url(:id=>2) it's one
# argument, but we don't want to generate /foos/id2
if number_of_arguments == 1
["args.size == 1", "!args.first.is_a?(Hash)"]
else
["args.size == #{number_of_arguments}"]
end
end
def generation_code
elements = []
idx = 0
if kind == :url
elements << '#{request.protocol}'
elements << '#{request.host_with_port}'
end
elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}'
# The last entry in <tt>route.segments</tt> appears to *always* be a
# 'divider segment' for '/' but we have assertions to ensure that
# we don't include the trailing slashes, so skip them.
(route.segments.size == 1 ? route.segments : route.segments[0..-2]).each do |segment|
if segment.is_a?(DynamicSegment)
elements << segment.interpolation_chunk("args[#{idx}].to_param")
idx += 1
else
elements << segment.interpolation_chunk
end
end
%("#{elements * ''}")
end
end
# This case is mostly the same as the positional arguments case
# above, but it supports additional query parameters as the last
# argument
class PositionalArgumentsWithAdditionalParams < PositionalArguments
def guard_conditions
["args.size == #{route.segment_keys.size + 1}"] +
UrlRewriter::RESERVED_OPTIONS.collect{ |key| "!args.last.has_key?(:#{key})" }
end
# This case uses almost the same code as positional arguments,
# but add a question mark and args.last.to_query on the end,
# unless the last arg is empty
def generation_code
super.insert(-2, '#{\'?\' + args.last.to_query unless args.last.empty?}')
end
# To avoid generating "http://localhost/?host=foo.example.com" we
# can't use this optimisation on routes without any segments
def applicable?
super && route.segment_keys.size > 0
end
end
OPTIMISERS = [PositionalArguments, PositionalArgumentsWithAdditionalParams]
end
end
end

View File

@@ -0,0 +1,183 @@
module ActionController
module Routing
# Polymorphic URL helpers are methods for smart resolution to a named route call when
# given an Active Record model instance. They are to be used in combination with
# ActionController::Resources.
#
# These methods are useful when you want to generate correct URL or path to a RESTful
# resource without having to know the exact type of the record in question.
#
# Nested resources and/or namespaces are also supported, as illustrated in the example:
#
# polymorphic_url([:admin, @article, @comment])
#
# results in:
#
# admin_article_comment_url(@article, @comment)
#
# == Usage within the framework
#
# Polymorphic URL helpers are used in a number of places throughout the \Rails framework:
#
# * <tt>url_for</tt>, so you can use it with a record as the argument, e.g.
# <tt>url_for(@article)</tt>;
# * ActionView::Helpers::FormHelper uses <tt>polymorphic_path</tt>, so you can write
# <tt>form_for(@article)</tt> without having to specify <tt>:url</tt> parameter for the form
# action;
# * <tt>redirect_to</tt> (which, in fact, uses <tt>url_for</tt>) so you can write
# <tt>redirect_to(post)</tt> in your controllers;
# * ActionView::Helpers::AtomFeedHelper, so you don't have to explicitly specify URLs
# for feed entries.
#
# == Prefixed polymorphic helpers
#
# In addition to <tt>polymorphic_url</tt> and <tt>polymorphic_path</tt> methods, a
# number of prefixed helpers are available as a shorthand to <tt>:action => "..."</tt>
# in options. Those are:
#
# * <tt>edit_polymorphic_url</tt>, <tt>edit_polymorphic_path</tt>
# * <tt>new_polymorphic_url</tt>, <tt>new_polymorphic_path</tt>
#
# Example usage:
#
# edit_polymorphic_path(@post) # => "/posts/1/edit"
# polymorphic_path(@post, :format => :pdf) # => "/posts/1.pdf"
module PolymorphicRoutes
# Constructs a call to a named RESTful route for the given record and returns the
# resulting URL string. For example:
#
# # calls post_url(post)
# polymorphic_url(post) # => "http://example.com/posts/1"
# polymorphic_url([blog, post]) # => "http://example.com/blogs/1/posts/1"
# polymorphic_url([:admin, blog, post]) # => "http://example.com/admin/blogs/1/posts/1"
# polymorphic_url([user, :blog, post]) # => "http://example.com/users/1/blog/posts/1"
# polymorphic_url(Comment) # => "http://example.com/comments"
#
# ==== Options
#
# * <tt>:action</tt> - Specifies the action prefix for the named route:
# <tt>:new</tt> or <tt>:edit</tt>. Default is no prefix.
# * <tt>:routing_type</tt> - Allowed values are <tt>:path</tt> or <tt>:url</tt>.
# Default is <tt>:url</tt>.
#
# ==== Examples
#
# # an Article record
# polymorphic_url(record) # same as article_url(record)
#
# # a Comment record
# polymorphic_url(record) # same as comment_url(record)
#
# # it recognizes new records and maps to the collection
# record = Comment.new
# polymorphic_url(record) # same as comments_url()
#
# # the class of a record will also map to the collection
# polymorphic_url(Comment) # same as comments_url()
#
def polymorphic_url(record_or_hash_or_array, options = {})
if record_or_hash_or_array.kind_of?(Array)
record_or_hash_or_array = record_or_hash_or_array.compact
record_or_hash_or_array = record_or_hash_or_array[0] if record_or_hash_or_array.size == 1
end
record = extract_record(record_or_hash_or_array)
record = record.to_model if record.respond_to?(:to_model)
args = Array === record_or_hash_or_array ?
record_or_hash_or_array.dup :
[ record_or_hash_or_array ]
inflection = if options[:action] && options[:action].to_s == "new"
args.pop
:singular
elsif record.respond_to?(:new_record?) && record.new_record?
args.pop
:plural
elsif record.is_a?(Class)
args.pop
:plural
else
:singular
end
args.delete_if {|arg| arg.is_a?(Symbol) || arg.is_a?(String)}
named_route = build_named_route_call(record_or_hash_or_array, inflection, options)
url_options = options.except(:action, :routing_type)
unless url_options.empty?
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
send(named_route, *args)
end
# Returns the path component of a URL for the given record. It uses
# <tt>polymorphic_url</tt> with <tt>:routing_type => :path</tt>.
def polymorphic_path(record_or_hash_or_array, options = {})
polymorphic_url(record_or_hash_or_array, options.merge(:routing_type => :path))
end
%w(edit new).each do |action|
module_eval <<-EOT, __FILE__, __LINE__ + 1
def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}")) # options.merge(:action => "edit"))
end # end
#
def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {})
polymorphic_url( # polymorphic_url(
record_or_hash, # record_or_hash,
options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path))
end # end
EOT
end
private
def action_prefix(options)
options[:action] ? "#{options[:action]}_" : ''
end
def routing_type(options)
options[:routing_type] || :url
end
def build_named_route_call(records, inflection, options = {})
unless records.is_a?(Array)
record = extract_record(records)
route = []
else
record = records.pop
route = records.map do |parent|
if parent.is_a?(Symbol) || parent.is_a?(String)
parent
else
RecordIdentifier.__send__("plural_class_name", parent).singularize
end
end
end
if record.is_a?(Symbol) || record.is_a?(String)
route << record
else
route << RecordIdentifier.__send__("plural_class_name", record)
route = [route.join("_").singularize] if inflection == :singular
end
route << routing_type(options)
action_prefix(options) + route.join("_")
end
def extract_record(record_or_hash_or_array)
case record_or_hash_or_array
when Array; record_or_hash_or_array.last
when Hash; record_or_hash_or_array[:id]
else record_or_hash_or_array
end
end
end
end
end

View File

@@ -1,167 +0,0 @@
module ActionController
module Routing
# BEFORE: 0.191446860631307 ms/url
# AFTER: 0.029847304022858 ms/url
# Speed up: 6.4 times
#
# Route recognition is slow due to one-by-one iterating over
# a whole routeset (each map.resources generates at least 14 routes)
# and matching weird regexps on each step.
#
# We optimize this by skipping all URI segments that 100% sure can't
# be matched, moving deeper in a tree of routes (where node == segment)
# until first possible match is accured. In such case, we start walking
# a flat list of routes, matching them with accurate matcher.
# So, first step: search a segment tree for the first relevant index.
# Second step: iterate routes starting with that index.
#
# How tree is walked? We can do a recursive tests, but it's smarter:
# We just create a tree of if-s and elsif-s matching segments.
#
# We have segments of 3 flavors:
# 1) nil (no segment, route finished)
# 2) const-dot-dynamic (like "/posts.:xml", "/preview.:size.jpg")
# 3) const (like "/posts", "/comments")
# 4) dynamic ("/:id", "file.:size.:extension")
#
# We split incoming string into segments and iterate over them.
# When segment is nil, we drop immediately, on a current node index.
# When segment is equal to some const, we step into branch.
# If none constants matched, we step into 'dynamic' branch (it's a last).
# If we can't match anything, we drop to last index on a level.
#
# Note: we maintain the original routes order, so we finish building
# steps on a first dynamic segment.
#
#
# Example. Given the routes:
# 0 /posts/
# 1 /posts/:id
# 2 /posts/:id/comments
# 3 /posts/blah
# 4 /users/
# 5 /users/:id
# 6 /users/:id/profile
#
# request_uri = /users/123
#
# There will be only 4 iterations:
# 1) segm test for /posts prefix, skip all /posts/* routes
# 2) segm test for /users/
# 3) segm test for /users/:id
# (jump to list index = 5)
# 4) full test for /users/:id => here we are!
class RouteSet
def recognize_path(path, environment={})
result = recognize_optimized(path, environment) and return result
# Route was not recognized. Try to find out why (maybe wrong verb).
allows = HTTP_METHODS.select { |verb| routes.find { |r| r.recognize(path, environment.merge(:method => verb)) } }
if environment[:method] && !HTTP_METHODS.include?(environment[:method])
raise NotImplemented.new(*allows)
elsif !allows.empty?
raise MethodNotAllowed.new(*allows)
else
raise RoutingError, "No route matches #{path.inspect} with #{environment.inspect}"
end
end
def segment_tree(routes)
tree = [0]
i = -1
routes.each do |route|
i += 1
# not fast, but runs only once
segments = to_plain_segments(route.segments.inject("") { |str,s| str << s.to_s })
node = tree
segments.each do |seg|
seg = :dynamic if seg && seg[0] == ?:
node << [seg, [i]] if node.empty? || node[node.size - 1][0] != seg
node = node[node.size - 1][1]
end
end
tree
end
def generate_code(list, padding=' ', level = 0)
# a digit
return padding + "#{list[0]}\n" if list.size == 1 && !(Array === list[0])
body = padding + "(seg = segments[#{level}]; \n"
i = 0
was_nil = false
list.each do |item|
if Array === item
i += 1
start = (i == 1)
tag, sub = item
if tag == :dynamic
body += padding + "#{start ? 'if' : 'elsif'} true\n"
body += generate_code(sub, padding + " ", level + 1)
break
elsif tag == nil && !was_nil
was_nil = true
body += padding + "#{start ? 'if' : 'elsif'} seg.nil?\n"
body += generate_code(sub, padding + " ", level + 1)
else
body += padding + "#{start ? 'if' : 'elsif'} seg == '#{tag}'\n"
body += generate_code(sub, padding + " ", level + 1)
end
end
end
body += padding + "else\n"
body += padding + " #{list[0]}\n"
body += padding + "end)\n"
body
end
# this must be really fast
def to_plain_segments(str)
str = str.dup
str.sub!(/^\/+/,'')
str.sub!(/\/+$/,'')
segments = str.split(/\.[^\/]+\/+|\/+|\.[^\/]+\Z/) # cut off ".format" also
segments << nil
segments
end
private
def write_recognize_optimized!
tree = segment_tree(routes)
body = generate_code(tree)
remove_recognize_optimized!
instance_eval %{
def recognize_optimized(path, env)
segments = to_plain_segments(path)
index = #{body}
return nil unless index
while index < routes.size
result = routes[index].recognize(path, env) and return result
index += 1
end
nil
end
}, '(recognize_optimized)', 1
end
def clear_recognize_optimized!
remove_recognize_optimized!
write_recognize_optimized!
end
def remove_recognize_optimized!
if respond_to?(:recognize_optimized)
class << self
remove_method :recognize_optimized
end
end
end
end
end
end

View File

@@ -1,268 +1,64 @@
module ActionController
module Routing
class Route #:nodoc:
attr_accessor :segments, :requirements, :conditions, :optimise
attr_reader :app, :conditions, :defaults, :name
attr_reader :path, :requirements, :set
def initialize(segments = [], requirements = {}, conditions = {})
@segments = segments
@requirements = requirements
@conditions = conditions
def initialize(set, app, conditions, requirements, defaults, name, anchor)
@set = set
@app = app
@defaults = defaults
@name = name
if !significant_keys.include?(:action) && !requirements[:action]
@requirements[:action] = "index"
@significant_keys << :action
@requirements = requirements.merge(defaults)
@requirements.delete(:controller) if @requirements[:controller].is_a?(Regexp)
@requirements.delete_if { |k, v|
v == Regexp.compile("[^#{SEPARATORS.join}]+")
}
if path = conditions[:path_info]
@path = path
conditions[:path_info] = ::Rack::Mount::Strexp.compile(path, requirements, SEPARATORS, anchor)
end
# Routes cannot use the current string interpolation method
# if there are user-supplied <tt>:requirements</tt> as the interpolation
# code won't raise RoutingErrors when generating
has_requirements = @segments.detect { |segment| segment.respond_to?(:regexp) && segment.regexp }
if has_requirements || @requirements.keys.to_set != Routing::ALLOWED_REQUIREMENTS_FOR_OPTIMISATION
@optimise = false
else
@optimise = true
end
@conditions = conditions.inject({}) { |h, (k, v)|
h[k] = Rack::Mount::RegexpWithNamedGroups.new(v)
h
}
@conditions.delete_if{ |k,v| k != :path_info && !valid_condition?(k) }
@requirements.delete_if{ |k,v| !valid_condition?(k) }
end
# Indicates whether the routes should be optimised with the string interpolation
# version of the named routes methods.
def optimise?
@optimise && ActionController::Base::optimise_named_routes
def verb
if method = conditions[:request_method]
case method
when Regexp
source = method.source.upcase
source =~ /\A\^[-A-Z|]+\$\Z/ ? source[1..-2] : source
else
method.to_s.upcase
end
end
end
def segment_keys
segments.collect do |segment|
segment.key if segment.respond_to? :key
end.compact
end
def required_segment_keys
required_segments = segments.select {|seg| (!seg.optional? && !seg.is_a?(DividerSegment)) || seg.is_a?(PathSegment) }
required_segments.collect { |seg| seg.key if seg.respond_to?(:key)}.compact
@segment_keys ||= conditions[:path_info].names.compact.map { |key| key.to_sym }
end
# Build a query string from the keys of the given hash. If +only_keys+
# is given (as an array), only the keys indicated will be used to build
# the query string. The query string will correctly build array parameter
# values.
def build_query_string(hash, only_keys = nil)
elements = []
(only_keys || hash.keys).each do |key|
if value = hash[key]
elements << value.to_query(key)
end
end
elements.empty? ? '' : "?#{elements.sort * '&'}"
end
# A route's parameter shell contains parameter values that are not in the
# route's path, but should be placed in the recognized hash.
#
# For example, +{:controller => 'pages', :action => 'show'} is the shell for the route:
#
# map.connect '/page/:id', :controller => 'pages', :action => 'show', :id => /\d+/
#
def parameter_shell
@parameter_shell ||= {}.tap do |shell|
requirements.each do |key, requirement|
shell[key] = requirement unless requirement.is_a? Regexp
end
end
end
# Return an array containing all the keys that are used in this route. This
# includes keys that appear inside the path, and keys that have requirements
# placed upon them.
def significant_keys
@significant_keys ||= [].tap do |sk|
segments.each { |segment| sk << segment.key if segment.respond_to? :key }
sk.concat requirements.keys
sk.uniq!
end
end
# Return a hash of key/value pairs representing the keys in the route that
# have defaults, or which are specified by non-regexp requirements.
def defaults
@defaults ||= {}.tap do |hash|
segments.each do |segment|
next unless segment.respond_to? :default
hash[segment.key] = segment.default unless segment.default.nil?
end
requirements.each do |key,req|
next if Regexp === req || req.nil?
hash[key] = req
end
end
end
def matches_controller_and_action?(controller, action)
prepare_matching!
(@controller_requirement.nil? || @controller_requirement === controller) &&
(@action_requirement.nil? || @action_requirement === action)
def to_a
[@app, @conditions, @defaults, @name]
end
def to_s
@to_s ||= begin
segs = segments.inject("") { |str,s| str << s.to_s }
"%-6s %-40s %s" % [(conditions[:method] || :any).to_s.upcase, segs, requirements.inspect]
"%-6s %-40s %s" % [(verb || :any).to_s.upcase, path, requirements.inspect]
end
end
# TODO: Route should be prepared and frozen on initialize
def freeze
unless frozen?
write_generation!
write_recognition!
prepare_matching!
parameter_shell
significant_keys
defaults
to_s
end
super
end
def generate(options, hash, expire_on = {})
path, hash = generate_raw(options, hash, expire_on)
append_query_string(path, hash, extra_keys(options))
end
def generate_extras(options, hash, expire_on = {})
path, hash = generate_raw(options, hash, expire_on)
[path, extra_keys(options)]
end
private
def requirement_for(key)
return requirements[key] if requirements.key? key
segments.each do |segment|
return segment.regexp if segment.respond_to?(:key) && segment.key == key
end
nil
end
# Write and compile a +generate+ method for this Route.
def write_generation!
# Build the main body of the generation
body = "expired = false\n#{generation_extraction}\n#{generation_structure}"
# If we have conditions that must be tested first, nest the body inside an if
body = "if #{generation_requirements}\n#{body}\nend" if generation_requirements
args = "options, hash, expire_on = {}"
# Nest the body inside of a def block, and then compile it.
raw_method = method_decl = "def generate_raw(#{args})\npath = begin\n#{body}\nend\n[path, hash]\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
# expire_on.keys == recall.keys; in other words, the keys in the expire_on hash
# are the same as the keys that were recalled from the previous request. Thus,
# we can use the expire_on.keys to determine which keys ought to be used to build
# the query string. (Never use keys from the recalled request when building the
# query string.)
raw_method
end
# Build several lines of code that extract values from the options hash. If any
# of the values are missing or rejected then a return will be executed.
def generation_extraction
segments.collect do |segment|
segment.extraction_code
end.compact * "\n"
end
# Produce a condition expression that will check the requirements of this route
# upon generation.
def generation_requirements
requirement_conditions = requirements.collect do |key, req|
if req.is_a? Regexp
value_regexp = Regexp.new "\\A#{req.to_s}\\Z"
"hash[:#{key}] && #{value_regexp.inspect} =~ options[:#{key}]"
else
"hash[:#{key}] == #{req.inspect}"
end
end
requirement_conditions * ' && ' unless requirement_conditions.empty?
end
def generation_structure
segments.last.string_structure segments[0..-2]
end
# Write and compile a +recognize+ method for this Route.
def write_recognition!
# Create an if structure to extract the params from a match if it occurs.
body = "params = parameter_shell.dup\n#{recognition_extraction * "\n"}\nparams"
body = "if #{recognition_conditions.join(" && ")}\n#{body}\nend"
# Build the method declaration and compile it
method_decl = "def recognize(path, env = {})\n#{body}\nend"
instance_eval method_decl, "generated code (#{__FILE__}:#{__LINE__})"
method_decl
end
# Plugins may override this method to add other conditions, like checks on
# host, subdomain, and so forth. Note that changes here only affect route
# recognition, not generation.
def recognition_conditions
result = ["(match = #{Regexp.new(recognition_pattern).inspect}.match(path))"]
result << "[conditions[:method]].flatten.include?(env[:method])" if conditions[:method]
result << "conditions[:host] === env[:host]" if conditions[:host]
result << "conditions[:domain] === env[:domain]" if conditions[:domain]
result << "conditions[:subdomain] === env[:subdomain]" if conditions[:subdomain]
result << "conditions[:fullsubdomain] === env[:fullsubdomain]" if conditions[:fullsubdomain]
result
end
# Build the regular expression pattern that will match this route.
def recognition_pattern(wrap = true)
pattern = ''
segments.reverse_each do |segment|
pattern = segment.build_pattern pattern
end
wrap ? ("\\A" + pattern + "\\Z") : pattern
end
# Write the code to extract the parameters from a matched route.
def recognition_extraction
next_capture = 1
extraction = segments.collect do |segment|
x = segment.match_extraction(next_capture)
next_capture += segment.number_of_captures
x
end
extraction.compact
end
# Generate the query string with any extra keys in the hash and append
# it to the given path, returning the new path.
def append_query_string(path, hash, query_keys = nil)
return nil unless path
query_keys ||= extra_keys(hash)
"#{path}#{build_query_string(hash, query_keys)}"
end
# Determine which keys in the given hash are "extra". Extra keys are
# those that were not used to generate a particular route. The extra
# keys also do not include those recalled from the prior request, nor
# do they include any keys that were implied in the route (like a
# <tt>:controller</tt> that is required, but not explicitly used in the
# text of the route.)
def extra_keys(hash, recall = {})
(hash || {}).keys.map { |k| k.to_sym } - (recall || {}).keys - significant_keys
end
def prepare_matching!
unless defined? @matching_prepared
@controller_requirement = requirement_for(:controller)
@action_requirement = requirement_for(:action)
@matching_prepared = true
end
def valid_condition?(method)
segment_keys.include?(method) || set.valid_conditions.include?(method)
end
end
end

View File

@@ -1,60 +1,80 @@
require 'rack/mount'
require 'forwardable'
require 'active_support/concern'
require 'active_support/core_ext/object/to_query'
require 'action_controller/routing/deprecated_mapper'
module ActionController
module Routing
class RouteSet #:nodoc:
# Mapper instances are used to build routes. The object passed to the draw
# block in config/routes.rb is a Mapper instance.
#
# Mapper instances have relatively few instance methods, in order to avoid
# clashes with named routes.
class Mapper #:doc:
include ActionController::Resources
# Since the router holds references to many parts of the system
# like engines, controllers and the application itself, inspecting
# the route set can actually be really slow, therefore we default
# alias inspect to to_s.
alias inspect to_s
def initialize(set) #:nodoc:
@set = set
PARAMETERS_KEY = 'action_controller.request.path_parameters'
class Dispatcher #:nodoc:
def initialize(options={})
@defaults = options[:defaults]
@glob_param = options.delete(:glob)
@controllers = {}
end
# Create an unnamed route with the provided +path+ and +options+. See
# ActionController::Routing for an introduction to routes.
def connect(path, options = {})
@set.add_route(path, options)
end
def call(env)
params = env[PARAMETERS_KEY]
prepare_params!(params)
# Creates a named route called "root" for matching the root level request.
def root(options = {})
if options.is_a?(Symbol)
if source_route = @set.named_routes.routes[options]
options = source_route.defaults.merge({ :conditions => source_route.conditions })
end
# Just raise undefined constant errors if a controller was specified as default.
unless controller = controller(params, @defaults.key?(:controller))
return [404, {'X-Cascade' => 'pass'}, []]
end
named_route("root", '', options)
dispatch(controller, params[:action], env)
end
def named_route(name, path, options = {}) #:nodoc:
@set.add_named_route(name, path, options)
def prepare_params!(params)
merge_default_action!(params)
split_glob_param!(params) if @glob_param
end
# Enables the use of resources in a module by setting the name_prefix, path_prefix, and namespace for the model.
# Example:
#
# map.namespace(:admin) do |admin|
# admin.resources :products,
# :has_many => [ :tags, :images, :variants ]
# end
#
# This will create +admin_products_url+ pointing to "admin/products", which will look for an Admin::ProductsController.
# It'll also create +admin_product_tags_url+ pointing to "admin/products/#{product_id}/tags", which will look for
# Admin::TagsController.
def namespace(name, options = {}, &block)
if options[:namespace]
with_options({:path_prefix => "#{options.delete(:path_prefix)}/#{name}", :name_prefix => "#{options.delete(:name_prefix)}#{name}_", :namespace => "#{options.delete(:namespace)}#{name}/" }.merge(options), &block)
else
with_options({:path_prefix => name, :name_prefix => "#{name}_", :namespace => "#{name}/" }.merge(options), &block)
# If this is a default_controller (i.e. a controller specified by the user)
# we should raise an error in case it's not found, because it usually means
# an user error. However, if the controller was retrieved through a dynamic
# segment, as in :controller(/:action), we should simply return nil and
# delegate the control back to Rack cascade. Besides, if this is not a default
# controller, it means we should respect the @scope[:module] parameter.
def controller(params, default_controller=true)
if params && params.key?(:controller)
controller_param = params[:controller]
controller_reference(controller_param)
end
rescue NameError => e
raise ActionController::RoutingError, e.message, e.backtrace if default_controller
end
def method_missing(route_name, *args, &proc) #:nodoc:
super unless args.length >= 1 && proc.nil?
@set.add_named_route(route_name, *args)
private
def controller_reference(controller_param)
unless controller = @controllers[controller_param]
controller_name = "#{controller_param.camelize}Controller"
controller = @controllers[controller_param] =
ActiveSupport::Inflector.constantize(controller_name)
end
controller
end
def dispatch(controller, action, env)
controller.call(env).to_a
end
def merge_default_action!(params)
params[:action] ||= 'index'
end
def split_glob_param!(params)
params[@glob_param] = params[@glob_param].split('/').map { |v| URI.unescape(v) }
end
end
@@ -63,20 +83,22 @@ module ActionController
# named routes.
class NamedRouteCollection #:nodoc:
include Enumerable
include ActionController::Routing::Optimisation
attr_reader :routes, :helpers
attr_reader :routes, :helpers, :module
def initialize
clear!
end
def helper_names
self.module.instance_methods.map(&:to_s)
end
def clear!
@routes = {}
@helpers = []
@module ||= Module.new
@module.instance_methods.each do |selector|
@module.class_eval { remove_method selector }
@module ||= Module.new do
instance_methods.each { |selector| remove_method(selector) }
end
end
@@ -138,97 +160,107 @@ module ActionController
end
end
def named_helper_module_eval(code, *args)
@module.module_eval(code, *args)
end
def define_hash_access(route, name, kind, options)
selector = hash_access_name(name, kind)
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
# We use module_eval to avoid leaks
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(options = nil) # def hash_for_users_url(options = nil)
options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false}
end # end
protected :#{selector} # protected :hash_for_users_url
end_eval
END_EVAL
helpers << selector
end
# Create a url helper allowing ordered parameters to be associated
# with corresponding dynamic segments, so you can do:
#
# foo_url(bar, baz, bang)
#
# Instead of:
#
# foo_url(:bar => bar, :baz => baz, :bang => bang)
#
# Also allow options hash, so you can do:
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
def define_url_helper(route, name, kind, options)
selector = url_helper_name(name, kind)
# The segment keys used for positional paramters
hash_access_method = hash_access_name(name, kind)
# allow ordered parameters to be associated with corresponding
# dynamic segments, so you can do
#
# foo_url(bar, baz, bang)
#
# instead of
#
# foo_url(:bar => bar, :baz => baz, :bang => bang)
#
# Also allow options hash, so you can do
#
# foo_url(bar, baz, bang, :sort_by => 'baz')
#
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
def #{selector}(*args) # def users_url(*args)
args.compact! #
#
#{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)}
#
opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first
args.first || {} # args.first || {}
else # else
options = args.extract_options! # options = args.extract_options!
args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)|
h[k] = v # h[k] = v
h # h
end # end
options.merge(args) # options.merge(args)
end # end
#
url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts))
#
end # end
@module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1
def #{selector}(*args)
options = #{hash_access_method}(args.extract_options!)
protected :#{selector} # protected :users_url
end_eval
if args.any?
options[:_positional_args] = args
options[:_positional_keys] = #{route.segment_keys.inspect}
end
url_for(options)
end
END_EVAL
helpers << selector
end
end
attr_accessor :routes, :named_routes, :configuration_files
attr_accessor :set, :routes, :named_routes
attr_accessor :disable_clear_and_finalize, :resources_path_names
attr_accessor :default_url_options, :request_class, :valid_conditions
def initialize
self.configuration_files = []
def self.default_resources_path_names
{ :new => 'new', :edit => 'edit' }
end
def initialize(request_class = ActionController::Request)
self.routes = []
self.named_routes = NamedRouteCollection.new
self.resources_path_names = self.class.default_resources_path_names.dup
self.controller_namespaces = Set.new
self.default_url_options = {}
clear_recognize_optimized!
self.request_class = request_class
self.valid_conditions = request_class.public_instance_methods.map { |m| m.to_sym }
self.valid_conditions.delete(:id)
self.valid_conditions.push(:controller, :action)
@disable_clear_and_finalize = false
clear!
end
# Subclasses and plugins may override this method to specify a different
# RouteBuilder instance, so that other route DSL's can be created.
def builder
@builder ||= RouteBuilder.new
def draw(&block)
clear! unless @disable_clear_and_finalize
mapper = Mapper.new(self)
if block.arity == 1
mapper.instance_exec(DeprecatedMapper.new(self), &block)
else
mapper.instance_exec(&block)
end
finalize! unless @disable_clear_and_finalize
nil
end
def draw
yield Mapper.new(self)
install_helpers
def finalize!
return if @finalized
@finalized = true
@set.freeze
end
def clear!
# Clear the controller cache so we may discover new ones
@controller_constraints = nil
@finalized = false
routes.clear
named_routes.clear
@combined_regexp = nil
@routes_by_controller = nil
# This will force routing/recognition_optimization.rb
# to refresh optimisations.
clear_recognize_optimized!
@set = ::Rack::Mount::RouteSet.new(
:parameters_key => PARAMETERS_KEY,
:request_class => request_class
)
end
def install_helpers(destinations = [ActionController::Base, ActionView::Base], regenerate_code = false)
@@ -236,104 +268,183 @@ module ActionController
named_routes.install(destinations, regenerate_code)
end
def url_helpers
@url_helpers ||= begin
routes = self
helpers = Module.new do
extend ActiveSupport::Concern
include UrlFor
@routes = routes
class << self
delegate :url_for, :to => '@routes'
end
extend routes.named_routes.module
# ROUTES TODO: install_helpers isn't great... can we make a module with the stuff that
# we can include?
# Yes plz - JP
included do
routes.install_helpers(self)
singleton_class.send(:define_method, :_routes) { routes }
end
define_method(:_routes) { routes }
end
helpers
end
end
def empty?
routes.empty?
end
def add_configuration_file(path)
self.configuration_files << path
end
# Deprecated accessor
def configuration_file=(path)
add_configuration_file(path)
end
# Deprecated accessor
def configuration_file
configuration_files
end
def load!
Routing.use_controllers!(nil) # Clear the controller cache so we may discover new ones
clear!
load_routes!
end
# reload! will always force a reload whereas load checks the timestamp first
alias reload! load!
def reload
if configuration_files.any? && @routes_last_modified
if routes_changed_at == @routes_last_modified
return # routes didn't change, don't reload
else
@routes_last_modified = routes_changed_at
end
end
load!
end
def load_routes!
if configuration_files.any?
configuration_files.each { |config| load(config) }
@routes_last_modified = routes_changed_at
else
add_route ":controller/:action/:id"
end
end
def routes_changed_at
routes_changed_at = nil
configuration_files.each do |config|
config_changed_at = File.stat(config).mtime
if routes_changed_at.nil? || config_changed_at > routes_changed_at
routes_changed_at = config_changed_at
end
end
routes_changed_at
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)
def add_route(app, conditions = {}, requirements = {}, defaults = {}, name = nil, anchor = true)
raise ArgumentError, "Invalid route name: '#{name}'" unless name.blank? || name.to_s.match(/^[_a-z]\w*$/i)
route = Route.new(self, app, conditions, requirements, defaults, name, anchor)
@set.add_route(*route)
named_routes[name] = route if name
routes << route
route
end
def add_named_route(name, path, options = {})
# TODO - is options EVER used?
name = options[:name_prefix] + name.to_s if options[:name_prefix]
named_routes[name.to_sym] = add_route(path, options)
end
class Generator #:nodoc:
attr_reader :options, :recall, :set, :script_name, :named_route
def options_as_params(options)
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
#
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
#
# (the above is from the unit tests). In the above case, because the
# controller was explicitly given, but no action, the action is implied to
# be "index", not the recalled action of "show".
#
# great fun, eh?
def initialize(options, recall, set, extras = false)
@script_name = options.delete(:script_name)
@named_route = options.delete(:use_route)
@options = options.dup
@recall = recall.dup
@set = set
@extras = extras
options_as_params = options.clone
options_as_params[:action] ||= 'index' if options[:controller]
options_as_params[:action] = options_as_params[:action].to_s if options_as_params[:action]
options_as_params
end
def build_expiry(options, recall)
recall.inject({}) do |expiry, (key, recalled_value)|
expiry[key] = (options.key?(key) && options[key].to_param != recalled_value.to_param)
expiry
normalize_options!
normalize_controller_action_id!
use_relative_controller!
controller.sub!(%r{^/}, '') if controller
handle_nil_action!
end
def controller
@controller ||= @options[:controller]
end
def current_controller
@recall[:controller]
end
def use_recall_for(key)
if @recall[key] && (!@options.key?(key) || @options[key] == @recall[key])
if named_route_exists?
@options[key] = @recall.delete(key) if segment_keys.include?(key)
else
@options[key] = @recall.delete(key)
end
end
end
def normalize_options!
# If an explicit :controller was given, always make :action explicit
# too, so that action expiry works as expected for things like
#
# generate({:controller => 'content'}, {:controller => 'content', :action => 'show'})
#
# (the above is from the unit tests). In the above case, because the
# controller was explicitly given, but no action, the action is implied to
# be "index", not the recalled action of "show".
if options[:controller]
options[:action] ||= 'index'
options[:controller] = options[:controller].to_s
end
if options[:action]
options[:action] = options[:action].to_s
end
end
# This pulls :controller, :action, and :id out of the recall.
# The recall key is only used if there is no key in the options
# or if the key in the options is identical. If any of
# :controller, :action or :id is not found, don't pull any
# more keys from the recall.
def normalize_controller_action_id!
@recall[:action] ||= 'index' if current_controller
use_recall_for(:controller) or return
use_recall_for(:action) or return
use_recall_for(:id)
end
# if the current controller is "foo/bar/baz" and :controller => "baz/bat"
# is specified, the controller becomes "foo/baz/bat"
def use_relative_controller!
if !named_route && different_controller?
old_parts = current_controller.split('/')
size = controller.count("/") + 1
parts = old_parts[0...-size] << controller
@controller = @options[:controller] = parts.join("/")
end
end
# This handles the case of :action => nil being explicitly passed.
# It is identical to :action => "index"
def handle_nil_action!
if options.has_key?(:action) && options[:action].nil?
options[:action] = 'index'
end
recall[:action] = options.delete(:action) if options[:action] == 'index'
end
def generate
path, params = @set.set.generate(:path_info, named_route, options, recall, opts)
raise_routing_error unless path
params.reject! {|k,v| !v.to_param}
return [path, params.keys] if @extras
path << "?#{params.to_query}" if params.any?
"#{script_name}#{path}"
rescue Rack::Mount::RoutingError
raise_routing_error
end
def opts
parameterize = lambda do |name, value|
if name == :controller
value
elsif value.is_a?(Array)
value.map { |v| Rack::Mount::Utils.escape_uri(v.to_param) }.join('/')
else
return nil unless param = value.to_param
param.split('/').map { |v| Rack::Mount::Utils.escape_uri(v) }.join("/")
end
end
{:parameterize => parameterize}
end
def raise_routing_error
raise ActionController::RoutingError, "No route matches #{options.inspect}"
end
def different_controller?
return false unless current_controller
controller.to_param != current_controller.to_param
end
private
def named_route_exists?
named_route && set.named_routes[named_route]
end
def segment_keys
set.named_routes[named_route].segment_keys
end
end
# Generate the path indicated by the arguments, and return an array of
@@ -343,160 +454,108 @@ module ActionController
end
def generate_extras(options, recall={})
generate(options, recall, :generate_extras)
generate(options, recall, true)
end
def generate(options, recall = {}, method=:generate)
named_route_name = options.delete(:use_route)
generate_all = options.delete(:generate_all)
if named_route_name
named_route = named_routes[named_route_name]
options = named_route.parameter_shell.merge(options)
end
options = options_as_params(options)
expire_on = build_expiry(options, recall)
if options[:controller]
options[:controller] = options[:controller].to_s
end
# if the controller has changed, make sure it changes relative to the
# current controller module, if any. In other words, if we're currently
# on admin/get, and the new controller is 'set', the new controller
# should really be admin/set.
if !named_route && expire_on[:controller] && options[:controller] && options[:controller][0] != ?/
old_parts = recall[:controller].split('/')
new_parts = options[:controller].split('/')
parts = old_parts[0..-(new_parts.length + 1)] + new_parts
options[:controller] = parts.join('/')
end
# drop the leading '/' on the controller name
options[:controller] = options[:controller][1..-1] if options[:controller] && options[:controller][0] == ?/
merged = recall.merge(options)
if named_route
path = named_route.generate(options, merged, expire_on)
if path.nil?
raise_named_route_error(options, named_route, named_route_name)
else
return path
end
else
merged[:action] ||= 'index'
options[:action] ||= 'index'
controller = merged[:controller]
action = merged[:action]
raise RoutingError, "Need controller and action!" unless controller && action
if generate_all
# Used by caching to expire all paths for a resource
return routes.collect do |route|
route.__send__(method, options, merged, expire_on)
end.compact
end
# don't use the recalled keys when determining which routes to check
future_routes, deprecated_routes = routes_by_controller[controller][action][options.reject {|k,v| !v}.keys.sort_by { |x| x.object_id }]
routes = Routing.generate_best_match ? deprecated_routes : future_routes
routes.each_with_index do |route, index|
results = route.__send__(method, options, merged, expire_on)
if results && (!results.is_a?(Array) || results.first)
return results
end
end
end
raise RoutingError, "No route matches #{options.inspect}"
def generate(options, recall = {}, extras = false)
Generator.new(options, recall, self, extras).generate
end
# try to give a helpful error message when named route generation fails
def raise_named_route_error(options, named_route, named_route_name)
diff = named_route.requirements.diff(options)
unless diff.empty?
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect}, expected: #{named_route.requirements.inspect}, diff: #{named_route.requirements.diff(options).inspect}"
else
required_segments = named_route.segments.select {|seg| (!seg.optional?) && (!seg.is_a?(DividerSegment)) }
required_keys_or_values = required_segments.map { |seg| seg.key rescue seg.value } # we want either the key or the value from the segment
raise RoutingError, "#{named_route_name}_url failed to generate from #{options.inspect} - you may have ambiguous routes, or you may need to supply additional parameters for this route. content_url has the following required parameters: #{required_keys_or_values.inspect} - are they all satisfied?"
RESERVED_OPTIONS = [:anchor, :params, :only_path, :host, :protocol, :port, :trailing_slash]
def url_for(options)
finalize!
options = (options || {}).reverse_merge!(default_url_options)
handle_positional_args(options)
rewritten_url = ""
path_segments = options.delete(:_path_segments)
unless options[:only_path]
rewritten_url << (options[:protocol] || "http")
rewritten_url << "://" unless rewritten_url.match("://")
rewritten_url << rewrite_authentication(options)
raise "Missing host to link to! Please provide :host parameter or set default_url_options[:host]" unless options[:host]
rewritten_url << options[:host]
rewritten_url << ":#{options.delete(:port)}" if options.key?(:port)
end
path_options = options.except(*RESERVED_OPTIONS)
path_options = yield(path_options) if block_given?
path = generate(path_options, path_segments || {})
# ROUTES TODO: This can be called directly, so script_name should probably be set in the routes
rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
rewritten_url << "##{Rack::Mount::Utils.escape_uri(options[:anchor].to_param.to_s)}" if options[:anchor]
rewritten_url
end
def call(env)
request = Request.new(env)
app = Routing::Routes.recognize(request)
app.call(env).to_a
finalize!
@set.call(env)
end
def recognize(request)
params = recognize_path(request.path, extract_request_environment(request))
request.path_parameters = params.with_indifferent_access
"#{params[:controller].to_s.camelize}Controller".constantize
end
def recognize_path(path, environment = {})
method = (environment[:method] || "GET").to_s.upcase
path = Rack::Mount::Utils.normalize_path(path) unless path =~ %r{://}
def recognize_path(path, environment={})
raise "Not optimized! Check that routing/recognition_optimisation overrides RouteSet#recognize_path."
end
begin
env = Rack::MockRequest.env_for(path, {:method => method})
rescue URI::InvalidURIError => e
raise ActionController::RoutingError, e.message
end
def routes_by_controller
@routes_by_controller ||= Hash.new do |controller_hash, controller|
controller_hash[controller] = Hash.new do |action_hash, action|
action_hash[action] = Hash.new do |key_hash, keys|
key_hash[keys] = [
routes_for_controller_and_action_and_keys(controller, action, keys),
deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
]
req = @request_class.new(env)
@set.recognize(req) do |route, matches, params|
params.each do |key, value|
if value.is_a?(String)
value = value.dup.force_encoding(Encoding::BINARY) if value.encoding_aware?
params[key] = URI.unescape(value)
end
end
dispatcher = route.app
while dispatcher.is_a?(Mapper::Constraints) && dispatcher.matches?(env) do
dispatcher = dispatcher.app
end
if dispatcher.is_a?(Dispatcher) && dispatcher.controller(params, false)
dispatcher.prepare_params!(params)
return params
end
end
raise ActionController::RoutingError, "No route matches #{path.inspect}"
end
def routes_for(options, merged, expire_on)
raise "Need controller and action!" unless controller && action
controller = merged[:controller]
merged = options if expire_on[:controller]
action = merged[:action] || 'index'
private
def handle_positional_args(options)
return unless args = options.delete(:_positional_args)
routes_by_controller[controller][action][merged.keys][1]
end
keys = options.delete(:_positional_keys)
keys -= options.keys if args.size < keys.size - 1 # take format into account
def routes_for_controller_and_action(controller, action)
ActiveSupport::Deprecation.warn "routes_for_controller_and_action() has been deprecated. Please use routes_for()"
selected = routes.select do |route|
route.matches_controller_and_action? controller, action
args = args.zip(keys).inject({}) do |h, (v, k)|
h[k] = v
h
end
# Tell url_for to skip default_url_options
options.merge!(args)
end
(selected.length == routes.length) ? routes : selected
end
def routes_for_controller_and_action_and_keys(controller, action, keys)
routes.select do |route|
route.matches_controller_and_action? controller, action
def rewrite_authentication(options)
if options[:user] && options[:password]
"#{Rack::Utils.escape(options.delete(:user))}:#{Rack::Utils.escape(options.delete(:password))}@"
else
""
end
end
end
def deprecated_routes_for_controller_and_action_and_keys(controller, action, keys)
selected = routes.select do |route|
route.matches_controller_and_action? controller, action
end
selected.sort_by do |route|
(keys - route.significant_keys).length
end
end
# Subclasses and plugins may override this method to extract further attributes
# from the request, for use by route conditions and such.
def extract_request_environment(request)
{
:method => request.method,
:host => request.host,
:domain => request.domain,
:subdomain => request.subdomains.first,
:fullsubdomain => request.subdomains.join('.')
}
end
end
end
end

View File

@@ -1,49 +0,0 @@
class Object
def to_param
to_s
end
end
class TrueClass
def to_param
self
end
end
class FalseClass
def to_param
self
end
end
class NilClass
def to_param
self
end
end
class Regexp #:nodoc:
def number_of_captures
Regexp.new("|#{source}").match('').captures.length
end
def multiline?
options & MULTILINE == MULTILINE
end
class << self
def optionalize(pattern)
case unoptionalize(pattern)
when /\A(.|\(.*\))\Z/ then "#{pattern}?"
else "(?:#{pattern})?"
end
end
def unoptionalize(pattern)
[/\A\(\?:(.*)\)\?\Z/, /\A(.|\(.*\))\?\Z/].each do |regexp|
return $1 if regexp =~ pattern
end
return pattern
end
end
end

Some files were not shown because too many files have changed in this diff Show More