mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
202 Commits
3-2-github
...
v1.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5988aaa50e | ||
|
|
025f895415 | ||
|
|
e9e86fd134 | ||
|
|
81b94c2f10 | ||
|
|
a3dafb07ec | ||
|
|
cc99f3fc18 | ||
|
|
d25ae9b6e1 | ||
|
|
517628d028 | ||
|
|
5d0fd52305 | ||
|
|
aa30fc7914 | ||
|
|
5f114177ce | ||
|
|
143cc0ec2f | ||
|
|
5385859dcf | ||
|
|
2d5bf4bbed | ||
|
|
2b6954aa8a | ||
|
|
68849e3239 | ||
|
|
debaf20803 | ||
|
|
8f84165cb4 | ||
|
|
18857ca2cb | ||
|
|
06c7584fba | ||
|
|
dd605e937f | ||
|
|
d12bcdac9d | ||
|
|
52923c9b55 | ||
|
|
1a7ce06e90 | ||
|
|
34603e7faf | ||
|
|
f7d25950f0 | ||
|
|
0ac6b5077f | ||
|
|
fd2b14c046 | ||
|
|
86f78cd8b6 | ||
|
|
398c1a504c | ||
|
|
5bf40f7b79 | ||
|
|
e76eae612f | ||
|
|
25c3ee407d | ||
|
|
de9ccd8848 | ||
|
|
9cc2e0f2f4 | ||
|
|
1696c1f68d | ||
|
|
2c1d0e2979 | ||
|
|
f1a4c44726 | ||
|
|
382547dcbe | ||
|
|
376dcb8e02 | ||
|
|
ab3f6bcfaa | ||
|
|
04383b974d | ||
|
|
fde811e2f6 | ||
|
|
83a21f75cf | ||
|
|
e9a7b233f6 | ||
|
|
21e4825596 | ||
|
|
622d70a11e | ||
|
|
c26cca3f45 | ||
|
|
b0303e1ac2 | ||
|
|
bdd09f50f8 | ||
|
|
6dc5756d39 | ||
|
|
0120b226ba | ||
|
|
b6af22ec82 | ||
|
|
4870becaeb | ||
|
|
a68d1d35f8 | ||
|
|
a4e70d3e91 | ||
|
|
ba29222851 | ||
|
|
0ff4ede7ca | ||
|
|
7269ee2da9 | ||
|
|
59be73a1e7 | ||
|
|
ce8e54e236 | ||
|
|
155dcff431 | ||
|
|
df27fb873c | ||
|
|
4aa93358fa | ||
|
|
3054a742df | ||
|
|
1fb2f21d1a | ||
|
|
da8a896d86 | ||
|
|
debd9ea2ce | ||
|
|
8ec9f4d2a0 | ||
|
|
4dcdf0834e | ||
|
|
a451fd1fbf | ||
|
|
c7e2b03424 | ||
|
|
6ccbef5862 | ||
|
|
498bca8ae8 | ||
|
|
c3c7648d20 | ||
|
|
fe2e51b047 | ||
|
|
67cda1f54e | ||
|
|
cda3d89983 | ||
|
|
0ae462a82d | ||
|
|
d5ca9a5814 | ||
|
|
72d556a391 | ||
|
|
34bc074441 | ||
|
|
6df67f5607 | ||
|
|
590b192e02 | ||
|
|
67ac59afc8 | ||
|
|
3d5fb40bb0 | ||
|
|
0c483d0c8d | ||
|
|
8804b7aeb7 | ||
|
|
2876efb784 | ||
|
|
762fc5447f | ||
|
|
bd261ffd1f | ||
|
|
ffb17e89ef | ||
|
|
2a08c4547a | ||
|
|
2b68762f5a | ||
|
|
de3ec6c008 | ||
|
|
2890b9648e | ||
|
|
98a0440a56 | ||
|
|
f09c8c4529 | ||
|
|
25d7ea8ad2 | ||
|
|
91cd8890c7 | ||
|
|
87ef365a49 | ||
|
|
d6c28bfaed | ||
|
|
b2ae346484 | ||
|
|
fb6cd55de7 | ||
|
|
c4d4660de3 | ||
|
|
205ae50d3f | ||
|
|
5b8bfc40e0 | ||
|
|
f83eedd440 | ||
|
|
691c439129 | ||
|
|
8f3e81ed53 | ||
|
|
fd8ee0a253 | ||
|
|
509c920d35 | ||
|
|
cef81e71eb | ||
|
|
3ee809655e | ||
|
|
c3cbd6b806 | ||
|
|
fe1e771222 | ||
|
|
694957ce5a | ||
|
|
12949bbc13 | ||
|
|
82e5ff7c6e | ||
|
|
783c16bd61 | ||
|
|
34a3d04af1 | ||
|
|
766ca17c91 | ||
|
|
ffa2c5fda5 | ||
|
|
87ecb782e6 | ||
|
|
2043513f4c | ||
|
|
ef6c3c4289 | ||
|
|
af43e87f38 | ||
|
|
f2fd22c2d4 | ||
|
|
17921bafcb | ||
|
|
36b3cb2c36 | ||
|
|
b93e4692b2 | ||
|
|
30e5436a3e | ||
|
|
9ba96771c3 | ||
|
|
97d9dca26f | ||
|
|
2a70f6f0a2 | ||
|
|
1e532f6606 | ||
|
|
fb60a469c0 | ||
|
|
ac2295137c | ||
|
|
acfe119cb5 | ||
|
|
004a28de81 | ||
|
|
0497868531 | ||
|
|
23569d83b2 | ||
|
|
bef626e2b1 | ||
|
|
b7f094e65e | ||
|
|
6baf9489d1 | ||
|
|
af33784698 | ||
|
|
a5631a5674 | ||
|
|
629b2ac36c | ||
|
|
dd1dc5e819 | ||
|
|
b8ff216f49 | ||
|
|
924f5cdb61 | ||
|
|
86bd0da7d0 | ||
|
|
35240ba66e | ||
|
|
b8a5d398b9 | ||
|
|
85c45051be | ||
|
|
f7c94e977b | ||
|
|
69ada0e26e | ||
|
|
9eaad14e8e | ||
|
|
9f8c805f5d | ||
|
|
46b845e784 | ||
|
|
118764e47d | ||
|
|
bcd50f1a37 | ||
|
|
5aced86de6 | ||
|
|
792780a80c | ||
|
|
5e677b67b5 | ||
|
|
dfa070aab9 | ||
|
|
ba086a4fc0 | ||
|
|
cc299d1b94 | ||
|
|
cbffafb497 | ||
|
|
84668af330 | ||
|
|
013004b592 | ||
|
|
5f5d8c224b | ||
|
|
5887b183ce | ||
|
|
320875ad77 | ||
|
|
4aa358dd86 | ||
|
|
6439d0cd3e | ||
|
|
7953eb170d | ||
|
|
5350fda95d | ||
|
|
5fac326020 | ||
|
|
17e445813b | ||
|
|
a2edd4381e | ||
|
|
2029b8a8f5 | ||
|
|
53c49036ea | ||
|
|
855e0f6f09 | ||
|
|
e4e1d2afa3 | ||
|
|
526afd3ae2 | ||
|
|
31b901aa65 | ||
|
|
a746e39584 | ||
|
|
f4d88039fd | ||
|
|
9f26164d37 | ||
|
|
c7f28e01c8 | ||
|
|
3311953d3f | ||
|
|
ce0653b1c6 | ||
|
|
229e197ac7 | ||
|
|
503c7c0afd | ||
|
|
8e01efee02 | ||
|
|
e09f224a5c | ||
|
|
6eb941ee6c | ||
|
|
7e8dd0322c | ||
|
|
159411f580 | ||
|
|
746cfb3ded | ||
|
|
2227a178ee |
22
.gitignore
vendored
22
.gitignore
vendored
@@ -1,22 +0,0 @@
|
||||
# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore.
|
||||
# Check out http://help.github.com/ignore-files/ for how to set that up.
|
||||
|
||||
debug.log
|
||||
.Gemfile
|
||||
/.bundle
|
||||
/.ruby-version
|
||||
/pkg
|
||||
/dist
|
||||
/doc/rdoc
|
||||
/*/doc
|
||||
/*/test/tmp
|
||||
/activerecord/sqlnet.log
|
||||
/activemodel/test/fixtures/fixture_database.sqlite3
|
||||
/activesupport/test/fixtures/isolation_test
|
||||
/railties/test/500.html
|
||||
/railties/test/fixtures/tmp
|
||||
/railties/test/initializer/root/log
|
||||
/railties/doc
|
||||
/railties/guides/output
|
||||
/railties/tmp
|
||||
/RDOC_MAIN.rdoc
|
||||
28
.travis.yml
28
.travis.yml
@@ -1,28 +0,0 @@
|
||||
script: 'ci/travis.rb'
|
||||
before_install:
|
||||
- gem install bundler
|
||||
rvm:
|
||||
- 1.8.7
|
||||
- 1.9.2
|
||||
- 1.9.3
|
||||
- 2.0.0
|
||||
env:
|
||||
- "GEM=railties"
|
||||
- "GEM=ap,am,amo,ares,as"
|
||||
- "GEM=ar:mysql"
|
||||
- "GEM=ar:mysql2"
|
||||
- "GEM=ar:sqlite3"
|
||||
- "GEM=ar:postgresql"
|
||||
notifications:
|
||||
email: false
|
||||
irc:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
channels:
|
||||
- "irc.freenode.org#rails-contrib"
|
||||
campfire:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
rooms:
|
||||
- secure: "YA1alef1ESHWGFNVwvmVGCkMe4cUy4j+UcNvMUESraceiAfVyRMAovlQBGs6\n9kBRm7DHYBUXYC2ABQoJbQRLDr/1B5JPf/M8+Qd7BKu8tcDC03U01SMHFLpO\naOs/HLXcDxtnnpL07tGVsm0zhMc5N8tq4/L3SHxK7Vi+TacwQzI="
|
||||
bundler_args: --path vendor/bundle
|
||||
@@ -1,4 +0,0 @@
|
||||
--exclude /templates/
|
||||
--quiet
|
||||
act*/lib/**/*.rb
|
||||
railties/lib/**/*.rb
|
||||
65
Gemfile
65
Gemfile
@@ -1,65 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gemspec
|
||||
|
||||
if ENV['AREL']
|
||||
gem 'arel', :path => ENV['AREL']
|
||||
else
|
||||
gem 'arel'
|
||||
end
|
||||
|
||||
gem 'bcrypt-ruby', '~> 3.0.0'
|
||||
gem 'jquery-rails'
|
||||
|
||||
if ENV['JOURNEY']
|
||||
gem 'journey', :path => ENV['JOURNEY']
|
||||
else
|
||||
gem 'journey'
|
||||
end
|
||||
|
||||
# This needs to be with require false to avoid
|
||||
# it being automatically loaded by sprockets
|
||||
gem 'uglifier', '>= 1.0.3', :require => false
|
||||
|
||||
# execjs >= 2.1.0 doesn't work with Ruby 1.8
|
||||
gem 'execjs', '< 2.1.0'
|
||||
|
||||
gem 'rake', '>= 0.8.7'
|
||||
gem 'mocha', '~> 0.14', :require => false
|
||||
|
||||
group :doc do
|
||||
# The current sdoc cannot generate GitHub links due
|
||||
# to a bug, but the PR that fixes it has been there
|
||||
# for some weeks unapplied. As a temporary solution
|
||||
# this is our own fork with the fix.
|
||||
gem 'sdoc', :git => 'git://github.com/fxn/sdoc.git'
|
||||
gem 'RedCloth', '~> 4.2'
|
||||
gem 'w3c_validators'
|
||||
end
|
||||
# AS
|
||||
gem 'memcache-client', '>= 1.8.5'
|
||||
|
||||
# Add your own local bundler stuff
|
||||
instance_eval File.read '.Gemfile' if File.exists? '.Gemfile'
|
||||
|
||||
platforms :mri do
|
||||
group :test do
|
||||
gem 'ruby-prof', '~> 0.11.2' if RUBY_VERSION < '2.0'
|
||||
end
|
||||
end
|
||||
|
||||
platforms :ruby do
|
||||
gem 'yajl-ruby'
|
||||
gem 'nokogiri', '>= 1.4.5', '< 1.6'
|
||||
|
||||
# AR
|
||||
gem 'sqlite3', '~> 1.3.5'
|
||||
|
||||
group :db do
|
||||
gem 'pg', '>= 0.11.0'
|
||||
gem 'mysql', '>= 2.8.1'
|
||||
gem 'mysql2', '>= 0.3.10'
|
||||
end
|
||||
end
|
||||
|
||||
gem 'benchmark-ips'
|
||||
131
Gemfile.lock
131
Gemfile.lock
@@ -1,131 +0,0 @@
|
||||
GIT
|
||||
remote: git://github.com/fxn/sdoc.git
|
||||
revision: 9977ca4ecf83a76d637861f79319b11830d51de5
|
||||
specs:
|
||||
sdoc (0.3.19)
|
||||
json (>= 1.1.3)
|
||||
rdoc (~> 3.10)
|
||||
|
||||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
actionmailer (3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.2)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
rack (~> 1.4.5)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
activemodel (3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
builder (~> 3.2)
|
||||
activerecord (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activeresource (3.2.19)
|
||||
activemodel (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
activesupport (3.2.19)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
rails (3.2.19)
|
||||
actionmailer (= 3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
activerecord (= 3.2.19)
|
||||
activeresource (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.19)
|
||||
railties (3.2.19)
|
||||
actionpack (= 3.2.19)
|
||||
activesupport (= 3.2.19)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
RedCloth (4.2.9)
|
||||
arel (3.0.3)
|
||||
bcrypt-ruby (3.0.1)
|
||||
benchmark-ips (1.2.0)
|
||||
builder (3.2.2)
|
||||
erubis (2.7.0)
|
||||
execjs (2.0.2)
|
||||
i18n (0.6.11)
|
||||
journey (1.0.4)
|
||||
jquery-rails (3.1.0)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.1)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
memcache-client (1.8.5)
|
||||
metaclass (0.0.4)
|
||||
mime-types (1.25.1)
|
||||
mocha (0.14.0)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.10.1)
|
||||
mysql (2.9.1)
|
||||
mysql2 (0.3.15)
|
||||
nokogiri (1.5.11)
|
||||
pg (0.17.1)
|
||||
polyglot (0.3.5)
|
||||
rack (1.4.5)
|
||||
rack-cache (1.2)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rake (10.2.2)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
sqlite3 (1.3.9)
|
||||
thor (0.19.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.41)
|
||||
uglifier (2.5.0)
|
||||
execjs (>= 0.3.0)
|
||||
json (>= 1.8.0)
|
||||
w3c_validators (1.2)
|
||||
json
|
||||
nokogiri
|
||||
yajl-ruby (1.2.0)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
RedCloth (~> 4.2)
|
||||
arel
|
||||
bcrypt-ruby (~> 3.0.0)
|
||||
benchmark-ips
|
||||
execjs (< 2.1.0)
|
||||
journey
|
||||
jquery-rails
|
||||
memcache-client (>= 1.8.5)
|
||||
mocha (~> 0.14)
|
||||
mysql (>= 2.8.1)
|
||||
mysql2 (>= 0.3.10)
|
||||
nokogiri (>= 1.4.5, < 1.6)
|
||||
pg (>= 0.11.0)
|
||||
rails!
|
||||
rake (>= 0.8.7)
|
||||
sdoc!
|
||||
sqlite3 (~> 1.3.5)
|
||||
uglifier (>= 1.0.3)
|
||||
w3c_validators
|
||||
yajl-ruby
|
||||
@@ -1 +0,0 @@
|
||||
3.2.19.github9
|
||||
77
README.rdoc
77
README.rdoc
@@ -1,77 +0,0 @@
|
||||
== Welcome to Rails
|
||||
|
||||
Rails is a web-application framework that includes everything needed to create
|
||||
database-backed web applications according to the {Model-View-Controller (MVC)}[http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller] pattern.
|
||||
|
||||
Understanding the MVC pattern is key to understanding Rails. MVC divides your application
|
||||
into three layers, each with a specific responsibility.
|
||||
|
||||
The View layer is composed of "templates" that are responsible for providing
|
||||
appropriate representations of your application's resources. Templates
|
||||
can come in a variety of formats, but most view templates are \HTML with embedded Ruby
|
||||
code (.erb files).
|
||||
|
||||
The Model layer represents your domain model (such as Account, Product, Person, Post)
|
||||
and encapsulates the business logic that is specific to your application. In Rails,
|
||||
database-backed model classes are derived from ActiveRecord::Base. Active Record allows
|
||||
you to present the data from database rows as objects and embellish these data objects
|
||||
with business logic methods. Although most Rails models are backed by a database, models
|
||||
can also be ordinary Ruby classes, or Ruby classes that implement a set of interfaces as
|
||||
provided by the ActiveModel module. You can read more about Active Record in its
|
||||
{README}[link:/rails/rails/blob/master/activerecord/README.rdoc].
|
||||
|
||||
The Controller layer is responsible for handling incoming HTTP requests and providing a
|
||||
suitable response. Usually this means returning \HTML, but Rails controllers can also
|
||||
generate XML, JSON, PDFs, mobile-specific views, and more. Controllers manipulate models
|
||||
and render view templates in order to generate the appropriate HTTP response.
|
||||
|
||||
In Rails, the Controller and View layers are handled together by Action Pack.
|
||||
These two layers are bundled in a single package due to their heavy interdependence.
|
||||
This is unlike the relationship between Active Record and Action Pack which are
|
||||
independent. Each of these packages can be used independently outside of Rails. You
|
||||
can read more about Action Pack in its {README}[link:/rails/rails/blob/master/actionpack/README.rdoc].
|
||||
|
||||
== Getting Started
|
||||
|
||||
1. Install Rails at the command prompt if you haven't yet:
|
||||
|
||||
gem install rails
|
||||
|
||||
2. At the command prompt, create a new Rails application:
|
||||
|
||||
rails new myapp
|
||||
|
||||
where "myapp" is the application name.
|
||||
|
||||
3. Change directory to +myapp+ and start the web server:
|
||||
|
||||
cd myapp; rails server
|
||||
|
||||
Run with <tt>--help</tt> for options.
|
||||
|
||||
4. Go to http://localhost:3000 and you'll see:
|
||||
|
||||
"Welcome aboard: You're riding Ruby on Rails!"
|
||||
|
||||
5. Follow the guidelines to start developing your application. You may find the following resources handy:
|
||||
|
||||
* The README file created within your application.
|
||||
* The {Getting Started with Rails}[http://guides.rubyonrails.org/getting_started.html].
|
||||
* The {Ruby on Rails Tutorial}[http://railstutorial.org/book].
|
||||
* The {Ruby on Rails Guides}[http://guides.rubyonrails.org].
|
||||
* The {API Documentation}[http://api.rubyonrails.org].
|
||||
|
||||
== Contributing
|
||||
|
||||
We encourage you to contribute to Ruby on Rails! Please check out the {Contributing to Rails
|
||||
guide}[http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html] for guidelines about how
|
||||
to proceed. {Join us}[http://contributors.rubyonrails.org]!
|
||||
|
||||
== Build Status {<img src="https://secure.travis-ci.org/rails/rails.png"/>}[http://travis-ci.org/rails/rails]
|
||||
|
||||
== Dependency Status {<img src="https://gemnasium.com/rails/rails.png?travis"/>}[https://gemnasium.com/rails/rails]
|
||||
|
||||
== License
|
||||
|
||||
Ruby on Rails is released under the MIT license.
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
= Releasing Rails
|
||||
|
||||
In this document, we'll cover the steps necessary to release Rails. Each
|
||||
section contains steps to take during that time before the release. The times
|
||||
suggested in each header are just that: suggestions. However, they should
|
||||
really be considered as minimums.
|
||||
|
||||
== 10 Days before release
|
||||
|
||||
Today is mostly coordination tasks. Here are the things you must do today:
|
||||
|
||||
=== Is the CI green? If not, make it green. (See "Fixing the CI")
|
||||
|
||||
Do not release with a Red CI. You can find the CI status here:
|
||||
|
||||
http://travis-ci.org/#!/rails/rails
|
||||
|
||||
=== Is Sam Ruby happy? If not, make him happy.
|
||||
|
||||
Sam Ruby keeps a test suite that makes sure the code samples in his book (Agile
|
||||
Web Development with Rails) all work. These are valuable integration tests
|
||||
for Rails. You can check the status of his tests here:
|
||||
|
||||
http://intertwingly.net/projects/dashboard.html
|
||||
|
||||
Do not release with Red AWDwR tests.
|
||||
|
||||
=== Are the postgres tests green? If not, make them green
|
||||
|
||||
Currently Travis CI doesn't run the Active Record postgres tests. They are
|
||||
working to resolve this, but in the mean time, it is crucial to ensure that
|
||||
the tests are still green before release.
|
||||
|
||||
=== Do we have any git dependencies? If so, contact those authors.
|
||||
|
||||
Having git dependencies indicates that we depend on unreleased code.
|
||||
Obviously rails cannot be released when it depends on unreleased code.
|
||||
Contact the authors of those particular gems and work out a release date that
|
||||
suits them.
|
||||
|
||||
=== Contact the security team (either Koz or tenderlove)
|
||||
|
||||
Let them know of your plans to release. There may be security issues to be
|
||||
addressed, and that can impact your release date.
|
||||
|
||||
=== Notify implementors.
|
||||
|
||||
Ruby implementors have high stakes in making sure Rails works. Be kind and
|
||||
give them a heads up that Rails will be released soonish.
|
||||
|
||||
Send an email just giving a heads up about the upcoming release to these
|
||||
lists:
|
||||
|
||||
* team@jruby.org
|
||||
* community@rubini.us
|
||||
* rubyonrails-core@googlegroups.com
|
||||
|
||||
Implementors will love you and help you.
|
||||
|
||||
== 3 Days before release
|
||||
|
||||
This is when you should release the release candidate. Here are your tasks
|
||||
for today:
|
||||
|
||||
=== Is the CI green? If not, make it green.
|
||||
|
||||
=== Is Sam Ruby happy? If not, make him happy.
|
||||
|
||||
=== Are the postgres tests green? If not, make them green
|
||||
|
||||
=== Contact the security team. CVE emails must be sent on this day.
|
||||
|
||||
=== Create a release branch.
|
||||
|
||||
From the stable branch, create a release branch. For example, if you're
|
||||
releasing Rails 3.0.10, do this:
|
||||
|
||||
[aaron@higgins rails (3-0-stable)]$ git checkout -b 3-0-10
|
||||
Switched to a new branch '3-0-10'
|
||||
[aaron@higgins rails (3-0-10)]$
|
||||
|
||||
=== Update each CHANGELOG.
|
||||
|
||||
Many times commits are made without the CHANGELOG being updated. You should
|
||||
review the commits since the last release, and fill in any missing information
|
||||
for each CHANGELOG.
|
||||
|
||||
You can review the commits for the 3.0.10 release like this:
|
||||
|
||||
[aaron@higgins rails (3-0-10)]$ git log v3.0.9..
|
||||
|
||||
If you're doing a stable branch release, you should also ensure that all of
|
||||
the CHANGELOG entries in the stable branch are also synced to the master
|
||||
branch.
|
||||
|
||||
=== Update the RAILS_VERSION file to include the RC.
|
||||
|
||||
=== Build and test the gem.
|
||||
|
||||
Run `rake install` to generate the gems and install them locally. Then try
|
||||
generating a new app and ensure that nothing explodes.
|
||||
|
||||
This will stop you from looking silly when you push an RC to rubygems.org and
|
||||
then realise it is broken.
|
||||
|
||||
=== Release the gem.
|
||||
|
||||
IMPORTANT: Due to YAML parse problems on the rubygems.org server, it is safest
|
||||
to use Ruby 1.8 when releasing.
|
||||
|
||||
Run `rake release`. This will populate the gemspecs with data from
|
||||
RAILS_VERSION, commit the changes, tag it, and push the gems to rubygems.org.
|
||||
Here are the commands that `rake release` should use, so you can understand
|
||||
what to do in case anything goes wrong:
|
||||
|
||||
$ rake all:build
|
||||
$ git commit -am'updating RAILS_VERSION'
|
||||
$ git tag -m'tagging rc release' v3.0.10.rc1
|
||||
$ git push
|
||||
$ git push --tags
|
||||
$ for i in $(ls dist); do gem push $i; done
|
||||
|
||||
=== Send Rails release announcements
|
||||
|
||||
Write a release announcement that includes the version, changes, and links to
|
||||
github where people can find the specific commit list. Here are the mailing
|
||||
lists where you should announce:
|
||||
|
||||
* rubyonrails-core@googlegroups.com
|
||||
* rubyonrails-talk@googlegroups.com
|
||||
* ruby-talk@ruby-lang.org
|
||||
|
||||
Use markdown format for your announcement. Remember to ask people to report
|
||||
issues with the release candidate to the rails-core mailing list.
|
||||
|
||||
IMPORTANT: If any users experience regressions when using the release
|
||||
candidate, you *must* postpone the release. Bugfix releases *should not*
|
||||
break existing applications.
|
||||
|
||||
=== Post the announcement to the Rails blog.
|
||||
|
||||
If you used markdown format for your email, you can just paste it in to the
|
||||
blog.
|
||||
|
||||
* http://weblog.rubyonrails.org
|
||||
|
||||
=== Post the announcement to the Rails twitter account.
|
||||
|
||||
== Time between release candidate and actual release
|
||||
|
||||
Check the rails-core mailing list and the github issue list for regressions in
|
||||
the RC.
|
||||
|
||||
If any regressions are found, fix the regressions and repeat the release
|
||||
candidate process. We will not release the final until 72 hours after the
|
||||
last release candidate has been pushed. This means that if users find
|
||||
regressions, the scheduled release date must be postponed.
|
||||
|
||||
When you fix the regressions, do not create a new branch. Fix them on the
|
||||
stable branch, then cherry pick the commit to your release branch. No other
|
||||
commits should be added to the release branch besides regression fixing commits.
|
||||
|
||||
== Day of release
|
||||
|
||||
Many of these steps are the same as for the release candidate, so if you need
|
||||
more explanation on a particular step, so the RC steps.
|
||||
|
||||
Today, do this stuff in this order:
|
||||
|
||||
* Apply security patches to the release branch
|
||||
* Update CHANGELOG with security fixes.
|
||||
* Update RAILS_VERSION to remove the rc
|
||||
* Build and test the gem
|
||||
* Release the gems
|
||||
* Email security lists
|
||||
* Email general announcement lists
|
||||
|
||||
=== Emailing the rails security announce list
|
||||
|
||||
Email the security announce list once for each vulnerability fixed.
|
||||
|
||||
You can do this, or ask the security team to do it.
|
||||
|
||||
Email the security reports to:
|
||||
|
||||
* rubyonrails-security@googlegroups.com
|
||||
* linux-distros@vs.openwall.org
|
||||
|
||||
Be sure to note the security fixes in your announcement along with CVE numbers
|
||||
and links to each patch. Some people may not be able to upgrade right away,
|
||||
so we need to give them the security fixes in patch form.
|
||||
|
||||
* Blog announcements
|
||||
* Twitter announcements
|
||||
* Merge the release branch to the stable branch.
|
||||
* Drink beer (or other cocktail)
|
||||
|
||||
== Misc
|
||||
|
||||
=== Fixing the CI
|
||||
|
||||
There are two simple steps for fixing the CI:
|
||||
|
||||
1. Identify the problem
|
||||
2. Fix it
|
||||
|
||||
Repeat these steps until the CI is green.
|
||||
|
||||
=== Manually trigger docs generation
|
||||
|
||||
We have a post-receive hook in GitHub that calls the docs server on pushes.
|
||||
It triggers generation and publication of edge docs, updates the contrib app,
|
||||
and generates and publishes stable docs if a new stable tag is detected.
|
||||
|
||||
The hook unfortunately is not invoked by tag pushing, so once the new stable
|
||||
tag has been pushed to origin, please run
|
||||
|
||||
rake publish_docs
|
||||
|
||||
You should see something like this:
|
||||
|
||||
Rails master hook tasks scheduled:
|
||||
|
||||
* updates the local checkout
|
||||
* updates Rails Contributors
|
||||
* generates and publishes edge docs
|
||||
|
||||
If a new stable tag is detected it also
|
||||
|
||||
* generates and publishes stable docs
|
||||
|
||||
This needs typically a few minutes.
|
||||
|
||||
Note you do not need to specify the tag, the docs server figures it out.
|
||||
|
||||
Also, don't worry if you call that multiple times or the hook is triggered
|
||||
again by some immediate regular push, if the scripts are running new calls
|
||||
are just queued (in a queue of size 1).
|
||||
198
Rakefile
198
Rakefile
@@ -1,198 +0,0 @@
|
||||
#!/usr/bin/env rake
|
||||
|
||||
require 'rdoc/task'
|
||||
require 'sdoc'
|
||||
require 'net/http'
|
||||
|
||||
$:.unshift File.expand_path('..', __FILE__)
|
||||
require "tasks/release"
|
||||
|
||||
desc "Build gem files for all projects"
|
||||
task :build => "all:build"
|
||||
|
||||
desc "Release all gems to gemcutter and create a tag"
|
||||
task :release => "all:release"
|
||||
|
||||
PROJECTS = %w(activesupport activemodel actionpack actionmailer activeresource activerecord railties)
|
||||
|
||||
desc 'Run all tests by default'
|
||||
task :default => %w(test test:isolated)
|
||||
|
||||
%w(test test:isolated package gem).each do |task_name|
|
||||
desc "Run #{task_name} task for all projects"
|
||||
task task_name do
|
||||
errors = []
|
||||
PROJECTS.each do |project|
|
||||
system(%(cd #{project} && #{$0} #{task_name})) || errors << project
|
||||
end
|
||||
fail("Errors in #{errors.join(', ')}") unless errors.empty?
|
||||
end
|
||||
end
|
||||
|
||||
desc "Smoke-test all projects"
|
||||
task :smoke do
|
||||
(PROJECTS - %w(activerecord)).each do |project|
|
||||
system %(cd #{project} && #{$0} test:isolated)
|
||||
end
|
||||
system %(cd activerecord && #{$0} sqlite3:isolated_test)
|
||||
end
|
||||
|
||||
desc "Install gems for all projects."
|
||||
task :install => :gem do
|
||||
version = File.read("RAILS_VERSION").strip
|
||||
(PROJECTS - ["railties"]).each do |project|
|
||||
puts "INSTALLING #{project}"
|
||||
system("gem install #{project}/pkg/#{project}-#{version}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
system("gem install railties/pkg/railties-#{version}.gem --no-ri --no-rdoc")
|
||||
system("gem install pkg/rails-#{version}.gem --no-ri --no-rdoc")
|
||||
end
|
||||
|
||||
desc "Generate documentation for the Rails framework"
|
||||
RDoc::Task.new do |rdoc|
|
||||
RDOC_MAIN = 'RDOC_MAIN.rdoc'
|
||||
|
||||
# This is a hack.
|
||||
#
|
||||
# Backslashes are needed to prevent RDoc from autolinking "Rails" to the
|
||||
# documentation of the Rails module. On the other hand, as of this
|
||||
# writing README.rdoc is displayed in the front page of the project in
|
||||
# GitHub, where backslashes are shown and look weird.
|
||||
#
|
||||
# The temporary solution is to have a README.rdoc without backslashes for
|
||||
# GitHub, and gsub it to generate the main page of the API.
|
||||
#
|
||||
# Also, relative links in GitHub have to point to blobs, whereas in the API
|
||||
# they need to point to files.
|
||||
#
|
||||
# The idea for the future is to have totally different files, since the
|
||||
# API is no longer a generic entry point to Rails and deserves a
|
||||
# dedicated main page specifically thought as an API entry point.
|
||||
rdoc.before_running_rdoc do
|
||||
rdoc_main = File.read('README.rdoc')
|
||||
|
||||
# The ^(?=\S) assertion prevents code blocks from being processed,
|
||||
# since no autolinking happens there and RDoc displays the backslash
|
||||
# otherwise.
|
||||
rdoc_main.gsub!(/^(?=\S).*?\b(?=Rails)\b/) { "#$&\\" }
|
||||
rdoc_main.gsub!(%r{link:/rails/rails/blob/master/(\w+)/README\.rdoc}, "link:files/\\1/README_rdoc.html")
|
||||
|
||||
# Remove Travis and Gemnasium status images from API pages. Only GitHub
|
||||
# README page gets these images. Travis' https build image is used to avoid
|
||||
# GitHub caching: http://about.travis-ci.org/docs/user/status-images
|
||||
rdoc_main.gsub!(%r{^== (Build|Dependency) Status.*}, '')
|
||||
|
||||
File.open(RDOC_MAIN, 'w') do |f|
|
||||
f.write(rdoc_main)
|
||||
end
|
||||
|
||||
rdoc.rdoc_files.include(RDOC_MAIN)
|
||||
end
|
||||
|
||||
rdoc.rdoc_dir = 'doc/rdoc'
|
||||
rdoc.title = "Ruby on Rails Documentation"
|
||||
|
||||
rdoc.options << '-f' << 'sdoc'
|
||||
rdoc.options << '-T' << 'rails'
|
||||
rdoc.options << '-e' << 'UTF-8'
|
||||
rdoc.options << '-g' # SDoc flag, link methods to GitHub
|
||||
rdoc.options << '-m' << RDOC_MAIN
|
||||
|
||||
rdoc.rdoc_files.include('railties/CHANGELOG.md')
|
||||
rdoc.rdoc_files.include('railties/MIT-LICENSE')
|
||||
rdoc.rdoc_files.include('railties/README.rdoc')
|
||||
rdoc.rdoc_files.include('railties/lib/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('railties/lib/rails/generators/**/templates/**/*.rb')
|
||||
|
||||
rdoc.rdoc_files.include('activerecord/README.rdoc')
|
||||
rdoc.rdoc_files.include('activerecord/CHANGELOG.md')
|
||||
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.rdoc_files.include('activeresource/CHANGELOG.md')
|
||||
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.rdoc_files.include('actionpack/CHANGELOG.md')
|
||||
rdoc.rdoc_files.include('actionpack/lib/abstract_controller/**/*.rb')
|
||||
rdoc.rdoc_files.include('actionpack/lib/action_controller/**/*.rb')
|
||||
rdoc.rdoc_files.include('actionpack/lib/action_dispatch/**/*.rb')
|
||||
rdoc.rdoc_files.include('actionpack/lib/action_view/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('actionpack/lib/action_controller/vendor/*')
|
||||
|
||||
rdoc.rdoc_files.include('actionmailer/README.rdoc')
|
||||
rdoc.rdoc_files.include('actionmailer/CHANGELOG.md')
|
||||
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
|
||||
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/mail_helper.rb')
|
||||
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
|
||||
|
||||
rdoc.rdoc_files.include('activesupport/README.rdoc')
|
||||
rdoc.rdoc_files.include('activesupport/CHANGELOG.md')
|
||||
rdoc.rdoc_files.include('activesupport/lib/active_support/**/*.rb')
|
||||
rdoc.rdoc_files.exclude('activesupport/lib/active_support/vendor/*')
|
||||
|
||||
rdoc.rdoc_files.include('activemodel/README.rdoc')
|
||||
rdoc.rdoc_files.include('activemodel/CHANGELOG.md')
|
||||
rdoc.rdoc_files.include('activemodel/lib/active_model/**/*.rb')
|
||||
end
|
||||
|
||||
# Enhance rdoc task to copy referenced images also
|
||||
task :rdoc do
|
||||
FileUtils.mkdir_p "doc/rdoc/files/examples/"
|
||||
FileUtils.copy "activerecord/examples/associations.png", "doc/rdoc/files/examples/associations.png"
|
||||
end
|
||||
|
||||
desc 'Bump all versions to match version.rb'
|
||||
task :update_versions do
|
||||
require File.dirname(__FILE__) + "/version"
|
||||
|
||||
File.open("RAILS_VERSION", "w") do |f|
|
||||
f.write Rails::VERSION::STRING + "\n"
|
||||
end
|
||||
|
||||
constants = {
|
||||
"activesupport" => "ActiveSupport",
|
||||
"activemodel" => "ActiveModel",
|
||||
"actionpack" => "ActionPack",
|
||||
"actionmailer" => "ActionMailer",
|
||||
"activeresource" => "ActiveResource",
|
||||
"activerecord" => "ActiveRecord",
|
||||
"railties" => "Rails"
|
||||
}
|
||||
|
||||
version_file = File.read("version.rb")
|
||||
|
||||
PROJECTS.each do |project|
|
||||
Dir["#{project}/lib/*/version.rb"].each do |file|
|
||||
File.open(file, "w") do |f|
|
||||
f.write version_file.gsub(/Rails/, constants[project])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# We have a webhook configured in Github that gets invoked after pushes.
|
||||
# This hook triggers the following tasks:
|
||||
#
|
||||
# * updates the local checkout
|
||||
# * updates Rails Contributors
|
||||
# * generates and publishes edge docs
|
||||
# * if there's a new stable tag, generates and publishes stable docs
|
||||
#
|
||||
# Everything is automated and you do NOT need to run this task normally.
|
||||
#
|
||||
# We publish a new version by tagging, and pushing a tag does not trigger
|
||||
# that webhook. Stable docs would be updated by any subsequent regular
|
||||
# push, but if you want that to happen right away just run this.
|
||||
#
|
||||
desc 'Publishes docs, run this AFTER a new stable tag has been pushed'
|
||||
task :publish_docs do
|
||||
Net::HTTP.new('api.rubyonrails.org', 8080).start do |http|
|
||||
request = Net::HTTP::Post.new('/rails-master-hook')
|
||||
response = http.request(request)
|
||||
puts response.body
|
||||
end
|
||||
end
|
||||
282
actionmailer/CHANGELOG
Normal file
282
actionmailer/CHANGELOG
Normal file
@@ -0,0 +1,282 @@
|
||||
*1.3.0* (January 16th, 2007)
|
||||
|
||||
* Make mime version default to 1.0. closes #2323 [ror@andreas-s.net]
|
||||
|
||||
* Make sure quoted-printable text is decoded correctly when only portions of the text are encoded. closes #3154. [jon@siliconcircus.com]
|
||||
|
||||
* Make sure DOS newlines in quoted-printable text are normalized to unix newlines before unquoting. closes #4166 and #4452. [Jamis Buck]
|
||||
|
||||
* Fixed that iconv decoding should catch InvalidEncoding #3153 [jon@siliconcircus.com]
|
||||
|
||||
* Tighten rescue clauses. #5985 [james@grayproductions.net]
|
||||
|
||||
* Automatically included ActionController::UrlWriter, such that URL generation can happen within ActionMailer controllers. [DHH]
|
||||
|
||||
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
|
||||
|
||||
* Resolve action naming collision. #5520 [ssinghi@kreeti.com]
|
||||
|
||||
* ActionMailer::Base documentation rewrite. Closes #4991 [Kevin Clark, Marcel Molina Jr.]
|
||||
|
||||
* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
|
||||
|
||||
* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
|
||||
|
||||
|
||||
*1.2.5* (August 10th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.5
|
||||
|
||||
|
||||
*1.2.4* (August 8th, 2006)
|
||||
|
||||
* Backport of documentation enhancements. [Kevin Clark, Marcel Molina Jr]
|
||||
|
||||
* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.]
|
||||
|
||||
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
|
||||
|
||||
|
||||
*1.2.3* (June 29th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.3
|
||||
|
||||
|
||||
*1.2.2* (June 27th, 2006)
|
||||
|
||||
* Depend on Action Pack 1.12.2
|
||||
|
||||
|
||||
*1.2.1* (April 6th, 2006)
|
||||
|
||||
* Be part of Rails 1.1.1
|
||||
|
||||
|
||||
*1.2.0* (March 27th, 2006)
|
||||
|
||||
* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 [ehalvorsen+rails@runbox.com]
|
||||
|
||||
* Parse content-type apart before using it so that sub-parts of the header can be set correctly #2918 [Jamis Buck]
|
||||
|
||||
* Make custom headers work in subparts #4034 [elan@bluemandrill.com]
|
||||
|
||||
* Template paths with dot chars in them no longer mess up implicit template selection for multipart messages #3332 [Chad Fowler]
|
||||
|
||||
* Make sure anything with content-disposition of "attachment" is passed to the attachment presenter when parsing an email body [Jamis Buck]
|
||||
|
||||
* Make sure TMail#attachments includes anything with content-disposition of "attachment", regardless of content-type [Jamis Buck]
|
||||
|
||||
|
||||
*1.1.5* (December 13th, 2005)
|
||||
|
||||
* Become part of Rails 1.0
|
||||
|
||||
|
||||
*1.1.4* (December 7th, 2005)
|
||||
|
||||
* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
|
||||
|
||||
* Stricter matching for implicitly multipart filenames excludes files ending in unsupported extensions (such as foo.rhtml.bak) and without a two-part content type (such as foo.text.rhtml or foo.text.really.plain.rhtml). #2398 [Dave Burt <dave@burt.id.au>, Jeremy Kemper]
|
||||
|
||||
|
||||
*1.1.3* (November 7th, 2005)
|
||||
|
||||
* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au]
|
||||
|
||||
|
||||
*1.1.2* (October 26th, 2005)
|
||||
|
||||
* Upgraded to Action Pack 1.10.2
|
||||
|
||||
|
||||
*1.1.1* (October 19th, 2005)
|
||||
|
||||
* Upgraded to Action Pack 1.10.1
|
||||
|
||||
|
||||
*1.1.0* (October 16th, 2005)
|
||||
|
||||
* Update and extend documentation (rdoc)
|
||||
|
||||
* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05]
|
||||
|
||||
* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005]
|
||||
|
||||
* Fix vendor require paths to prevent files being required twice
|
||||
|
||||
* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long]
|
||||
|
||||
* Preserve underscores when unquoting message bodies #1930
|
||||
|
||||
* Encode multibyte characters correctly #1894
|
||||
|
||||
* Multipart messages specify a MIME-Version header automatically #2003 [John Long]
|
||||
|
||||
* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
|
||||
|
||||
* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
|
||||
|
||||
* Look at content-location header (if available) to determine filename of attachments #1670
|
||||
|
||||
* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
|
||||
|
||||
* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev]
|
||||
|
||||
|
||||
*1.0.1* (11 July, 2005)
|
||||
|
||||
* Bind to Action Pack 1.9.1
|
||||
|
||||
|
||||
*1.0.0* (6 July, 2005)
|
||||
|
||||
* Avoid adding nil header values #1392
|
||||
|
||||
* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long]
|
||||
|
||||
* Allow for nested parts in multipart mails #1570 [Flurin Egger]
|
||||
|
||||
* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long]
|
||||
|
||||
* Allow template to be explicitly specified #1448 [tuxie@dekadance.se]
|
||||
|
||||
* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger]
|
||||
|
||||
* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
|
||||
|
||||
* Helper support (borrowed from ActionPack)
|
||||
|
||||
* Silently ignore Errno::EINVAL errors when converting text.
|
||||
|
||||
* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com]
|
||||
|
||||
* Nested multipart message parts are correctly processed in TMail::Mail#body
|
||||
|
||||
* BCC headers are removed when sending via SMTP #1402
|
||||
|
||||
* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
|
||||
|
||||
* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com]
|
||||
|
||||
* Support attachments and multipart messages.
|
||||
|
||||
* Added new accessors for the various mail properties.
|
||||
|
||||
* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck]
|
||||
|
||||
* Fix attachments and content-type problems #1276 [Jamis Buck]
|
||||
|
||||
* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck]
|
||||
|
||||
* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck]
|
||||
|
||||
* Added automatic decoding of base64 bodies #1214 [Jamis Buck]
|
||||
|
||||
* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
|
||||
|
||||
* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck]
|
||||
|
||||
|
||||
*0.9.1* (20th April, 2005)
|
||||
|
||||
* Depend on Action Pack 1.8.1
|
||||
|
||||
|
||||
*0.9.0* (19th April, 2005)
|
||||
|
||||
* Added that deliver_* will now return the email that was sent
|
||||
|
||||
* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck]
|
||||
|
||||
* Fixed quoting for all address headers, not just to #955 [Jamis Buck]
|
||||
|
||||
* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net]
|
||||
|
||||
|
||||
*0.8.1* (27th March, 2005)
|
||||
|
||||
* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com]
|
||||
|
||||
* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com]
|
||||
|
||||
* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com]
|
||||
|
||||
* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
|
||||
|
||||
|
||||
*0.8.0* (22th March, 2005)
|
||||
|
||||
* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
|
||||
|
||||
|
||||
*0.7.1* (7th March, 2005)
|
||||
|
||||
* Bind to newest Action Pack (1.5.1)
|
||||
|
||||
|
||||
*0.7.0* (24th February, 2005)
|
||||
|
||||
* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
|
||||
|
||||
def iso_charset(recipient)
|
||||
@recipients = recipient
|
||||
@subject = "testing iso charsets"
|
||||
@from = "system@loudthinking.com"
|
||||
@body = "Nothing to see here."
|
||||
@charset = "iso-8859-1"
|
||||
end
|
||||
|
||||
def unencoded_subject(recipient)
|
||||
@recipients = recipient
|
||||
@subject = "testing unencoded subject"
|
||||
@from = "system@loudthinking.com"
|
||||
@body = "Nothing to see here."
|
||||
@encode_subject = false
|
||||
@charset = "iso-8859-1"
|
||||
end
|
||||
|
||||
|
||||
*0.6.1* (January 18th, 2005)
|
||||
|
||||
* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
|
||||
|
||||
|
||||
*0.6* (January 17th, 2005)
|
||||
|
||||
* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel]
|
||||
|
||||
* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel]
|
||||
|
||||
|
||||
*0.5*
|
||||
|
||||
* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
|
||||
|
||||
def post_notification(recipients, post)
|
||||
@recipients = recipients
|
||||
@from = post.author.email_address_with_name
|
||||
@headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
|
||||
@headers["reply-to"] = "notifications@example.com"
|
||||
@subject = "[#{post.account.name} #{post.title}]"
|
||||
@body["post"] = post
|
||||
end
|
||||
|
||||
*0.4* (5)
|
||||
|
||||
* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten]
|
||||
NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
|
||||
|
||||
* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper]
|
||||
|
||||
* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
|
||||
This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
|
||||
|
||||
* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
|
||||
Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev]
|
||||
|
||||
* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler]
|
||||
|
||||
|
||||
*0.3*
|
||||
|
||||
* First release
|
||||
@@ -1,123 +0,0 @@
|
||||
## Rails 3.2.19 (Jul 2, 2014) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.18 (May 6, 2014) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.17 (Feb 18, 2014) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.16 (Dec 3, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.15 (Oct 16, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
## Rails 3.2.14 (Jul 22, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.13 (Mar 18, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.12 (Feb 11, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.11 (Jan 8, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.10 (Jan 2, 2013) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.9 (Nov 12, 2012) ##
|
||||
|
||||
* The return value from mailer methods is no longer relevant. This fixes a bug,
|
||||
which was introduced with 3.2.9.
|
||||
Backport #8450
|
||||
Fix #8448
|
||||
|
||||
class ExampleMailer < ActionMailer::Base
|
||||
# in 3.2.9, returning a falsy value from a mailer action, prevented the email from beeing sent.
|
||||
# With 3.2.10 the return value is no longer relevant. If you call mail() the email will be sent.
|
||||
def nil_returning_mailer_action
|
||||
mail()
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
|
||||
## Rails 3.2.9 (Nov 12, 2012) ##
|
||||
|
||||
* Do not render views when mail() isn't called.
|
||||
Fix #7761
|
||||
|
||||
*Yves Senn*
|
||||
|
||||
|
||||
## Rails 3.2.8 (Aug 9, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.7 (Jul 26, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.6 (Jun 12, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.5 (Jun 1, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.4 (May 31, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.3 (March 30, 2012) ##
|
||||
|
||||
* Upgrade mail version to 2.4.3 *ML*
|
||||
|
||||
|
||||
## Rails 3.2.2 (March 1, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.1 (January 26, 2012) ##
|
||||
|
||||
* No changes.
|
||||
|
||||
|
||||
## Rails 3.2.0 (January 20, 2012) ##
|
||||
|
||||
* Upgrade mail version to 2.4.0 *ML*
|
||||
|
||||
* Remove Old ActionMailer API *Josh Kalderimis*
|
||||
|
||||
Please check [3-1-stable](https://github.com/rails/rails/blob/3-1-stable/actionmailer/CHANGELOG.md) for previous changes.
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2011 David Heinemeier Hansson
|
||||
Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
|
||||
145
actionmailer/README
Executable file
145
actionmailer/README
Executable file
@@ -0,0 +1,145 @@
|
||||
= Action Mailer -- Easy email delivery and testing
|
||||
|
||||
Action Mailer is a framework for designing email-service layers. These layers
|
||||
are used to consolidate code for sending out forgotten passwords, welcome
|
||||
wishes on signup, invoices for billing, and any other use case that requires
|
||||
a written notification to either a person or another system.
|
||||
|
||||
Additionally, an Action Mailer class can be used to process incoming email,
|
||||
such as allowing a weblog to accept new posts from an email (which could even
|
||||
have been sent from a phone).
|
||||
|
||||
== Sending emails
|
||||
|
||||
The framework works by setting up all the email details, except the body,
|
||||
in methods on the service layer. Subject, recipients, sender, and timestamp
|
||||
are all set up this way. An example of such a method:
|
||||
|
||||
def signed_up(recipient)
|
||||
recipients recipient
|
||||
subject "[Signed up] Welcome #{recipient}"
|
||||
from "system@loudthinking.com"
|
||||
|
||||
body(:recipient => recipient)
|
||||
end
|
||||
|
||||
The body of the email is created by using an Action View template (regular
|
||||
ERb) that has the content of the body hash parameter available as instance variables.
|
||||
So the corresponding body template for the method above could look like this:
|
||||
|
||||
Hello there,
|
||||
|
||||
Mr. <%= @recipient %>
|
||||
|
||||
And if the recipient was given as "david@loudthinking.com", the email
|
||||
generated would look like this:
|
||||
|
||||
Date: Sun, 12 Dec 2004 00:00:00 +0100
|
||||
From: system@loudthinking.com
|
||||
To: david@loudthinking.com
|
||||
Subject: [Signed up] Welcome david@loudthinking.com
|
||||
|
||||
Hello there,
|
||||
|
||||
Mr. david@loudthinking.com
|
||||
|
||||
You never actually call the instance methods like signed_up directly. Instead,
|
||||
you call class methods like deliver_* and create_* that are automatically
|
||||
created for each instance method. So if the signed_up method sat on
|
||||
ApplicationMailer, it would look like this:
|
||||
|
||||
ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing
|
||||
ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email
|
||||
ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work!
|
||||
|
||||
== Receiving emails
|
||||
|
||||
To receive emails, you need to implement a public instance method called receive that takes a
|
||||
tmail object as its single parameter. The Action Mailer framework has a corresponding class method,
|
||||
which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns
|
||||
into the tmail object and calls the receive instance method.
|
||||
|
||||
Example:
|
||||
|
||||
class Mailman < ActionMailer::Base
|
||||
def receive(email)
|
||||
page = Page.find_by_address(email.to.first)
|
||||
page.emails.create(
|
||||
:subject => email.subject, :body => email.body
|
||||
)
|
||||
|
||||
if email.has_attachments?
|
||||
for attachment in email.attachments
|
||||
page.attachments.create({
|
||||
:file => attachment, :description => email.subject
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
This Mailman can be the target for Postfix. In Rails, you would use the runner like this:
|
||||
|
||||
./script/runner 'Mailman.receive(STDIN.read)'
|
||||
|
||||
== Configuration
|
||||
|
||||
The Base class has the full list of configuration options. Here's an example:
|
||||
|
||||
ActionMailer::Base.server_settings = {
|
||||
:address=>'smtp.yourserver.com', # default: localhost
|
||||
:port=>'25', # default: 25
|
||||
:user_name=>'user',
|
||||
:password=>'pass',
|
||||
:authentication=>:plain # :plain, :login or :cram_md5
|
||||
}
|
||||
|
||||
== Dependencies
|
||||
|
||||
Action Mailer requires that the Action Pack is either available to be required immediately
|
||||
or is accessible as a GEM.
|
||||
|
||||
|
||||
== Bundled software
|
||||
|
||||
* tmail 0.10.8 by Minero Aoki released under LGPL
|
||||
Read more on http://i.loveruby.net/en/prog/tmail.html
|
||||
|
||||
* Text::Format 0.63 by Austin Ziegler released under OpenSource
|
||||
Read more on http://www.halostatue.ca/ruby/Text__Format.html
|
||||
|
||||
|
||||
== Download
|
||||
|
||||
The latest version of Action Mailer can be found at
|
||||
|
||||
* http://rubyforge.org/project/showfiles.php?group_id=361
|
||||
|
||||
Documentation can be found at
|
||||
|
||||
* http://actionmailer.rubyonrails.org
|
||||
|
||||
|
||||
== Installation
|
||||
|
||||
You can install Action Mailer with the following command.
|
||||
|
||||
% [sudo] ruby install.rb
|
||||
|
||||
from its distribution directory.
|
||||
|
||||
|
||||
== License
|
||||
|
||||
Action Mailer is released under the MIT license.
|
||||
|
||||
|
||||
== Support
|
||||
|
||||
The Action Mailer homepage is http://www.rubyonrails.org. You can find
|
||||
the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
|
||||
And as Jim from Rake says:
|
||||
|
||||
Feel free to submit commits or feature requests. If you send a patch,
|
||||
remember to update the corresponding unit tests. If fact, I prefer
|
||||
new feature to be submitted in the form of new unit tests.
|
||||
@@ -1,163 +0,0 @@
|
||||
= Action Mailer -- Easy email delivery and testing
|
||||
|
||||
Action Mailer is a framework for designing email-service layers. These layers
|
||||
are used to consolidate code for sending out forgotten passwords, welcome
|
||||
wishes on signup, invoices for billing, and any other use case that requires
|
||||
a written notification to either a person or another system.
|
||||
|
||||
Action Mailer is in essence a wrapper around Action Controller and the
|
||||
Mail gem. It provides a way to make emails using templates in the same
|
||||
way that Action Controller renders views using templates.
|
||||
|
||||
Additionally, an Action Mailer class can be used to process incoming email,
|
||||
such as allowing a blog to accept new posts from an email (which could even
|
||||
have been sent from a phone).
|
||||
|
||||
== Sending emails
|
||||
|
||||
The framework works by initializing any instance variables you want to be
|
||||
available in the email template, followed by a call to +mail+ to deliver
|
||||
the email.
|
||||
|
||||
This can be as simple as:
|
||||
|
||||
class Notifier < ActionMailer::Base
|
||||
delivers_from 'system@loudthinking.com'
|
||||
|
||||
def welcome(recipient)
|
||||
@recipient = recipient
|
||||
mail(:to => recipient,
|
||||
:subject => "[Signed up] Welcome #{recipient}")
|
||||
end
|
||||
end
|
||||
|
||||
The body of the email is created by using an Action View template (regular
|
||||
ERB) that has the instance variables that are declared in the mailer action.
|
||||
|
||||
So the corresponding body template for the method above could look like this:
|
||||
|
||||
Hello there,
|
||||
|
||||
Mr. <%= @recipient %>
|
||||
|
||||
Thank you for signing up!
|
||||
|
||||
And if the recipient was given as "david@loudthinking.com", the email
|
||||
generated would look like this:
|
||||
|
||||
Date: Mon, 25 Jan 2010 22:48:09 +1100
|
||||
From: system@loudthinking.com
|
||||
To: david@loudthinking.com
|
||||
Message-ID: <4b5d84f9dd6a5_7380800b81ac29578@void.loudthinking.com.mail>
|
||||
Subject: [Signed up] Welcome david@loudthinking.com
|
||||
Mime-Version: 1.0
|
||||
Content-Type: text/plain;
|
||||
charset="US-ASCII";
|
||||
Content-Transfer-Encoding: 7bit
|
||||
|
||||
Hello there,
|
||||
|
||||
Mr. david@loudthinking.com
|
||||
|
||||
Thank you for signing up!
|
||||
|
||||
In previous version of Rails you would call <tt>create_method_name</tt> and
|
||||
<tt>deliver_method_name</tt>. Rails 3.0 has a much simpler interface, you
|
||||
simply call the method and optionally call +deliver+ on the return value.
|
||||
|
||||
Calling the method returns a Mail Message object:
|
||||
|
||||
message = Notifier.welcome # => Returns a Mail::Message object
|
||||
message.deliver # => delivers the email
|
||||
|
||||
Or you can just chain the methods together like:
|
||||
|
||||
Notifier.welcome.deliver # Creates the email and sends it immediately
|
||||
|
||||
== Setting defaults
|
||||
|
||||
It is possible to set default values that will be used in every method in your Action Mailer class. To implement this functionality, you just call the public class method <tt>default</tt> which you get for free from ActionMailer::Base. This method accepts a Hash as the parameter. You can use any of the headers e-mail messages has, like <tt>:from</tt> as the key. You can also pass in a string as the key, like "Content-Type", but Action Mailer does this out of the box for you, so you won't need to worry about that. Finally it is also possible to pass in a Proc that will get evaluated when it is needed.
|
||||
|
||||
Note that every value you set with this method will get over written if you use the same key in your mailer method.
|
||||
|
||||
Example:
|
||||
|
||||
class Authenticationmailer < ActionMailer::Base
|
||||
default :from => "awesome@application.com", :subject => Proc.new { "E-mail was generated at #{Time.now}" }
|
||||
.....
|
||||
end
|
||||
|
||||
== Receiving emails
|
||||
|
||||
To receive emails, you need to implement a public instance method called <tt>receive</tt> that takes an
|
||||
email object as its single parameter. The Action Mailer framework has a corresponding class method,
|
||||
which is also called <tt>receive</tt>, that accepts a raw, unprocessed email as a string, which it then turns
|
||||
into the email object and calls the receive instance method.
|
||||
|
||||
Example:
|
||||
|
||||
class Mailman < ActionMailer::Base
|
||||
def receive(email)
|
||||
page = Page.find_by_address(email.to.first)
|
||||
page.emails.create(
|
||||
:subject => email.subject, :body => email.body
|
||||
)
|
||||
|
||||
if email.has_attachments?
|
||||
email.attachments.each do |attachment|
|
||||
page.attachments.create({
|
||||
:file => attachment, :description => email.subject
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
This Mailman can be the target for Postfix or other MTAs. In Rails, you would use the runner in the
|
||||
trivial case like this:
|
||||
|
||||
rails runner 'Mailman.receive(STDIN.read)'
|
||||
|
||||
However, invoking Rails in the runner for each mail to be received is very resource intensive. A single
|
||||
instance of Rails should be run within a daemon, if it is going to be utilized to process more than just
|
||||
a limited number of email.
|
||||
|
||||
== Configuration
|
||||
|
||||
The Base class has the full list of configuration options. Here's an example:
|
||||
|
||||
ActionMailer::Base.smtp_settings = {
|
||||
:address => 'smtp.yourserver.com', # default: localhost
|
||||
:port => '25', # default: 25
|
||||
:user_name => 'user',
|
||||
:password => 'pass',
|
||||
:authentication => :plain # :plain, :login or :cram_md5
|
||||
}
|
||||
|
||||
|
||||
== Download and installation
|
||||
|
||||
The latest version of Action Mailer can be installed with RubyGems:
|
||||
|
||||
% [sudo] gem install actionmailer
|
||||
|
||||
Source code can be downloaded as part of the Rails project on GitHub
|
||||
|
||||
* https://github.com/rails/rails/tree/3-2-stable/actionmailer
|
||||
|
||||
|
||||
== License
|
||||
|
||||
Action Mailer is released under the MIT license.
|
||||
|
||||
|
||||
== Support
|
||||
|
||||
API documentation is at
|
||||
|
||||
* http://api.rubyonrails.org
|
||||
|
||||
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
|
||||
|
||||
* https://github.com/rails/rails/issues
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
#!/usr/bin/env rake
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/packagetask'
|
||||
require 'rubygems/package_task'
|
||||
require 'rake/gempackagetask'
|
||||
require 'rake/contrib/rubyforgepublisher'
|
||||
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
|
||||
|
||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
||||
PKG_NAME = 'actionmailer'
|
||||
PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD
|
||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
||||
|
||||
RELEASE_NAME = "REL #{PKG_VERSION}"
|
||||
|
||||
RUBY_FORGE_PROJECT = "actionmailer"
|
||||
RUBY_FORGE_USER = "webster132"
|
||||
|
||||
desc "Default Task"
|
||||
task :default => [ :test ]
|
||||
@@ -9,28 +23,73 @@ task :default => [ :test ]
|
||||
# Run the unit tests
|
||||
Rake::TestTask.new { |t|
|
||||
t.libs << "test"
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.warning = true
|
||||
t.pattern = 'test/*_test.rb'
|
||||
t.verbose = true
|
||||
t.warning = false
|
||||
}
|
||||
|
||||
namespace :test do
|
||||
task :isolated do
|
||||
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
|
||||
Dir.glob("test/**/*_test.rb").all? do |file|
|
||||
sh(ruby, '-Ilib:test', file)
|
||||
end or raise "Failures"
|
||||
end
|
||||
|
||||
# Genereate the RDoc documentation
|
||||
Rake::RDocTask.new { |rdoc|
|
||||
rdoc.rdoc_dir = 'doc'
|
||||
rdoc.title = "Action Mailer -- Easy email delivery and testing"
|
||||
rdoc.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
||||
rdoc.rdoc_files.include('README', 'CHANGELOG')
|
||||
rdoc.rdoc_files.include('lib/action_mailer.rb')
|
||||
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
|
||||
}
|
||||
|
||||
|
||||
# Create compressed packages
|
||||
spec = Gem::Specification.new do |s|
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.name = PKG_NAME
|
||||
s.summary = "Service layer for easy email delivery and testing."
|
||||
s.description = %q{Makes it trivial to test and deliver emails sent from a single service layer.}
|
||||
s.version = PKG_VERSION
|
||||
|
||||
s.author = "David Heinemeier Hansson"
|
||||
s.email = "david@loudthinking.com"
|
||||
s.rubyforge_project = "actionmailer"
|
||||
s.homepage = "http://www.rubyonrails.org"
|
||||
|
||||
s.add_dependency('actionpack', '= 1.13.0' + PKG_BUILD)
|
||||
|
||||
s.has_rdoc = true
|
||||
s.requirements << 'none'
|
||||
s.require_path = 'lib'
|
||||
s.autorequire = 'action_mailer'
|
||||
|
||||
s.files = [ "Rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
|
||||
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
||||
end
|
||||
|
||||
spec = eval(File.read('actionmailer.gemspec'))
|
||||
|
||||
Gem::PackageTask.new(spec) do |p|
|
||||
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
p.need_tar = true
|
||||
p.need_zip = true
|
||||
end
|
||||
|
||||
desc "Release to gemcutter"
|
||||
task :release => :package do
|
||||
require 'rake/gemcutter'
|
||||
Rake::Gemcutter::Tasks.new(spec).define
|
||||
Rake::Task['gem:push'].invoke
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pgem => [:package] do
|
||||
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
end
|
||||
|
||||
desc "Publish the API documentation"
|
||||
task :pdoc => [:rdoc] do
|
||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload
|
||||
end
|
||||
|
||||
desc "Publish the release files to RubyForge."
|
||||
task :release => [ :package ] do
|
||||
`rubyforge login`
|
||||
|
||||
for ext in %w( gem tgz zip )
|
||||
release_command = "rubyforge add_release #{PKG_NAME} #{PKG_NAME} 'REL #{PKG_VERSION}' pkg/#{PKG_NAME}-#{PKG_VERSION}.#{ext}"
|
||||
puts release_command
|
||||
system(release_command)
|
||||
end
|
||||
end
|
||||
@@ -1,24 +0,0 @@
|
||||
version = File.read(File.expand_path("../../RAILS_VERSION", __FILE__)).strip
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.platform = Gem::Platform::RUBY
|
||||
s.name = 'actionmailer'
|
||||
s.version = version
|
||||
s.summary = 'Email composition, delivery, and receiving framework (part of Rails).'
|
||||
s.description = 'Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments.'
|
||||
|
||||
s.license = 'MIT'
|
||||
|
||||
s.required_ruby_version = '>= 1.8.7'
|
||||
|
||||
s.author = 'David Heinemeier Hansson'
|
||||
s.email = 'david@loudthinking.com'
|
||||
s.homepage = 'http://www.rubyonrails.org'
|
||||
|
||||
s.files = Dir['CHANGELOG.md', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
|
||||
s.require_path = 'lib'
|
||||
s.requirements << 'none'
|
||||
|
||||
s.add_dependency('actionpack', version)
|
||||
s.add_dependency('mail', '~> 2.5.4')
|
||||
end
|
||||
30
actionmailer/install.rb
Normal file
30
actionmailer/install.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require 'rbconfig'
|
||||
require 'find'
|
||||
require 'ftools'
|
||||
|
||||
include Config
|
||||
|
||||
# this was adapted from rdoc's install.rb by way of Log4r
|
||||
|
||||
$sitedir = CONFIG["sitelibdir"]
|
||||
unless $sitedir
|
||||
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
|
||||
$libdir = File.join(CONFIG["libdir"], "ruby", version)
|
||||
$sitedir = $:.find {|x| x =~ /site_ruby/ }
|
||||
if !$sitedir
|
||||
$sitedir = File.join($libdir, "site_ruby")
|
||||
elsif $sitedir !~ Regexp.quote(version)
|
||||
$sitedir = File.join($sitedir, version)
|
||||
end
|
||||
end
|
||||
|
||||
# the acual gruntwork
|
||||
Dir.chdir("lib")
|
||||
|
||||
Find.find("action_mailer", "action_mailer.rb") { |f|
|
||||
if f[-3..-1] == ".rb"
|
||||
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
|
||||
else
|
||||
File::makedirs(File.join($sitedir, *f.split(/\//)))
|
||||
end
|
||||
}
|
||||
53
actionmailer/lib/action_mailer.rb
Normal file → Executable file
53
actionmailer/lib/action_mailer.rb
Normal file → Executable file
@@ -1,5 +1,5 @@
|
||||
#--
|
||||
# Copyright (c) 2004-2011 David Heinemeier Hansson
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
@@ -21,29 +21,30 @@
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#++
|
||||
|
||||
actionpack_path = File.expand_path('../../../actionpack/lib', __FILE__)
|
||||
$:.unshift(actionpack_path) if File.directory?(actionpack_path) && !$:.include?(actionpack_path)
|
||||
|
||||
require 'abstract_controller'
|
||||
require 'action_view'
|
||||
require 'action_mailer/version'
|
||||
|
||||
# Common Active Support usage in Action Mailer
|
||||
require 'active_support/core_ext/class'
|
||||
require 'active_support/core_ext/object/blank'
|
||||
require 'active_support/core_ext/array/uniq_by'
|
||||
require 'active_support/core_ext/module/attr_internal'
|
||||
require 'active_support/core_ext/module/delegation'
|
||||
require 'active_support/core_ext/string/inflections'
|
||||
require 'active_support/lazy_load_hooks'
|
||||
|
||||
module ActionMailer
|
||||
extend ::ActiveSupport::Autoload
|
||||
|
||||
autoload :Collector
|
||||
autoload :Base
|
||||
autoload :DeliveryMethods
|
||||
autoload :MailHelper
|
||||
autoload :TestCase
|
||||
autoload :TestHelper
|
||||
unless defined?(ActionController)
|
||||
begin
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../../actionpack/lib"
|
||||
require 'action_controller'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require_gem 'actionpack', '>= 1.12.5'
|
||||
end
|
||||
end
|
||||
|
||||
$:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
|
||||
|
||||
require 'action_mailer/base'
|
||||
require 'action_mailer/helpers'
|
||||
require 'action_mailer/mail_helper'
|
||||
require 'action_mailer/quoting'
|
||||
require 'tmail'
|
||||
require 'net/smtp'
|
||||
|
||||
ActionMailer::Base.class_eval do
|
||||
include ActionMailer::Quoting
|
||||
include ActionMailer::Helpers
|
||||
|
||||
helper MailHelper
|
||||
end
|
||||
|
||||
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }
|
||||
|
||||
30
actionmailer/lib/action_mailer/adv_attr_accessor.rb
Normal file
30
actionmailer/lib/action_mailer/adv_attr_accessor.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
module ActionMailer
|
||||
module AdvAttrAccessor #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
module ClassMethods #:nodoc:
|
||||
def adv_attr_accessor(*names)
|
||||
names.each do |name|
|
||||
ivar = "@#{name}"
|
||||
|
||||
define_method("#{name}=") do |value|
|
||||
instance_variable_set(ivar, value)
|
||||
end
|
||||
|
||||
define_method(name) do |*parameters|
|
||||
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
|
||||
if parameters.empty?
|
||||
if instance_variables.include?(ivar)
|
||||
instance_variable_get(ivar)
|
||||
end
|
||||
else
|
||||
instance_variable_set(ivar, parameters.first)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,30 +0,0 @@
|
||||
require 'abstract_controller/collector'
|
||||
require 'active_support/core_ext/hash/reverse_merge'
|
||||
require 'active_support/core_ext/array/extract_options'
|
||||
|
||||
module ActionMailer #:nodoc:
|
||||
class Collector
|
||||
include AbstractController::Collector
|
||||
attr_reader :responses
|
||||
|
||||
def initialize(context, &block)
|
||||
@context = context
|
||||
@responses = []
|
||||
@default_render = block
|
||||
end
|
||||
|
||||
def any(*args, &block)
|
||||
options = args.extract_options!
|
||||
raise "You have to supply at least one format" if args.empty?
|
||||
args.each { |type| send(type, options.dup, &block) }
|
||||
end
|
||||
alias :all :any
|
||||
|
||||
def custom(mime, options={})
|
||||
options.reverse_merge!(:content_type => mime.to_s)
|
||||
@context.formats = [mime.to_sym]
|
||||
options[:body] = block_given? ? yield : @default_render.call
|
||||
@responses << options
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,86 +0,0 @@
|
||||
require 'tmpdir'
|
||||
|
||||
module ActionMailer
|
||||
# This module handles everything related to mail delivery, from registering new
|
||||
# delivery methods to configuring the mail object to be sent.
|
||||
module DeliveryMethods
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
class_attribute :delivery_methods, :delivery_method
|
||||
|
||||
# Do not make this inheritable, because we always want it to propagate
|
||||
cattr_accessor :raise_delivery_errors
|
||||
self.raise_delivery_errors = true
|
||||
|
||||
cattr_accessor :perform_deliveries
|
||||
self.perform_deliveries = true
|
||||
|
||||
self.delivery_methods = {}.freeze
|
||||
self.delivery_method = :smtp
|
||||
|
||||
add_delivery_method :smtp, Mail::SMTP,
|
||||
:address => "localhost",
|
||||
:port => 25,
|
||||
:domain => 'localhost.localdomain',
|
||||
:user_name => nil,
|
||||
:password => nil,
|
||||
:authentication => nil,
|
||||
:enable_starttls_auto => true
|
||||
|
||||
add_delivery_method :file, Mail::FileDelivery,
|
||||
:location => defined?(Rails.root) ? "#{Rails.root}/tmp/mails" : "#{Dir.tmpdir}/mails"
|
||||
|
||||
add_delivery_method :sendmail, Mail::Sendmail,
|
||||
:location => '/usr/sbin/sendmail',
|
||||
:arguments => '-i -t'
|
||||
|
||||
add_delivery_method :test, Mail::TestMailer
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Provides a list of emails that have been delivered by Mail::TestMailer
|
||||
delegate :deliveries, :deliveries=, :to => Mail::TestMailer
|
||||
|
||||
# Adds a new delivery method through the given class using the given symbol
|
||||
# as alias and the default options supplied:
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# add_delivery_method :sendmail, Mail::Sendmail,
|
||||
# :location => '/usr/sbin/sendmail',
|
||||
# :arguments => '-i -t'
|
||||
#
|
||||
def add_delivery_method(symbol, klass, default_options={})
|
||||
class_attribute(:"#{symbol}_settings") unless respond_to?(:"#{symbol}_settings")
|
||||
send(:"#{symbol}_settings=", default_options)
|
||||
self.delivery_methods = delivery_methods.merge(symbol.to_sym => klass).freeze
|
||||
end
|
||||
|
||||
def wrap_delivery_behavior(mail, method=nil) #:nodoc:
|
||||
method ||= self.delivery_method
|
||||
mail.delivery_handler = self
|
||||
|
||||
case method
|
||||
when NilClass
|
||||
raise "Delivery method cannot be nil"
|
||||
when Symbol
|
||||
if klass = delivery_methods[method.to_sym]
|
||||
mail.delivery_method(klass, send(:"#{method}_settings"))
|
||||
else
|
||||
raise "Invalid delivery method #{method.inspect}"
|
||||
end
|
||||
else
|
||||
mail.delivery_method(method)
|
||||
end
|
||||
|
||||
mail.perform_deliveries = perform_deliveries
|
||||
mail.raise_delivery_errors = raise_delivery_errors
|
||||
end
|
||||
end
|
||||
|
||||
def wrap_delivery_behavior!(*args) #:nodoc:
|
||||
self.class.wrap_delivery_behavior(message, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
111
actionmailer/lib/action_mailer/helpers.rb
Normal file
111
actionmailer/lib/action_mailer/helpers.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
module ActionMailer
|
||||
module Helpers #:nodoc:
|
||||
def self.included(base) #:nodoc:
|
||||
# Initialize the base module to aggregate its helpers.
|
||||
base.class_inheritable_accessor :master_helper_module
|
||||
base.master_helper_module = Module.new
|
||||
|
||||
# Extend base with class methods to declare helpers.
|
||||
base.extend(ClassMethods)
|
||||
|
||||
base.class_eval do
|
||||
# Wrap inherited to create a new master helper module for subclasses.
|
||||
class << self
|
||||
alias_method_chain :inherited, :helper
|
||||
end
|
||||
|
||||
# Wrap initialize_template_class to extend new template class
|
||||
# instances with the master helper module.
|
||||
alias_method_chain :initialize_template_class, :helper
|
||||
end
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# 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.
|
||||
def add_template_helper(helper_module) #:nodoc:
|
||||
master_helper_module.module_eval "include #{helper_module}"
|
||||
end
|
||||
|
||||
# Declare a helper:
|
||||
# helper :foo
|
||||
# requires 'foo_helper' and includes FooHelper in the template class.
|
||||
# helper FooHelper
|
||||
# includes FooHelper in the template class.
|
||||
# helper { def foo() "#{bar} is the very best" end }
|
||||
# evaluates the block in the template class, adding method #foo.
|
||||
# helper(:three, BlindHelper) { def mice() 'mice' end }
|
||||
# does all three.
|
||||
def helper(*args, &block)
|
||||
args.flatten.each do |arg|
|
||||
case arg
|
||||
when Module
|
||||
add_template_helper(arg)
|
||||
when String, Symbol
|
||||
file_name = arg.to_s.underscore + '_helper'
|
||||
class_name = file_name.camelize
|
||||
|
||||
begin
|
||||
require_dependency(file_name)
|
||||
rescue LoadError => load_error
|
||||
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
|
||||
msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
|
||||
raise LoadError.new(msg).copy_blame!(load_error)
|
||||
end
|
||||
|
||||
add_template_helper(class_name.constantize)
|
||||
else
|
||||
raise ArgumentError, 'helper expects String, Symbol, or Module argument'
|
||||
end
|
||||
end
|
||||
|
||||
# Evaluate block in template class if given.
|
||||
master_helper_module.module_eval(&block) if block_given?
|
||||
end
|
||||
|
||||
# Declare a controller method as a helper. For example,
|
||||
# helper_method :link_to
|
||||
# def link_to(name, options) ... end
|
||||
# makes the link_to controller method available in the view.
|
||||
def helper_method(*methods)
|
||||
methods.flatten.each do |method|
|
||||
master_helper_module.module_eval <<-end_eval
|
||||
def #{method}(*args, &block)
|
||||
controller.send(%(#{method}), *args, &block)
|
||||
end
|
||||
end_eval
|
||||
end
|
||||
end
|
||||
|
||||
# Declare a controller attribute as a helper. For example,
|
||||
# helper_attr :name
|
||||
# attr_accessor :name
|
||||
# makes the name and name= controller methods available in the view.
|
||||
# The is a convenience wrapper for helper_method.
|
||||
def helper_attr(*attrs)
|
||||
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
|
||||
end
|
||||
|
||||
private
|
||||
def inherited_with_helper(child)
|
||||
inherited_without_helper(child)
|
||||
begin
|
||||
child.master_helper_module = Module.new
|
||||
child.master_helper_module.send :include, master_helper_module
|
||||
child.helper child.name.underscore
|
||||
rescue MissingSourceFile => e
|
||||
raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
# Extend the template class instance with our controller's helper module.
|
||||
def initialize_template_class_with_helper(assigns)
|
||||
returning(template = initialize_template_class_without_helper(assigns)) do
|
||||
template.extend self.class.master_helper_module
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,22 +0,0 @@
|
||||
require 'active_support/core_ext/array/wrap'
|
||||
|
||||
module ActionMailer
|
||||
class LogSubscriber < ActiveSupport::LogSubscriber
|
||||
def deliver(event)
|
||||
recipients = Array.wrap(event.payload[:to]).join(', ')
|
||||
info("\nSent mail to #{recipients} (#{format_duration(event.duration)})")
|
||||
debug(event.payload[:mail])
|
||||
end
|
||||
|
||||
def receive(event)
|
||||
info("\nReceived mail (#{format_duration(event.duration)})")
|
||||
debug(event.payload[:mail])
|
||||
end
|
||||
|
||||
def logger
|
||||
ActionMailer::Base.logger
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionMailer::LogSubscriber.attach_to :action_mailer
|
||||
@@ -1,56 +1,19 @@
|
||||
module ActionMailer
|
||||
module MailHelper
|
||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
||||
# each line, and wrapped at 72 columns.
|
||||
def block_format(text)
|
||||
formatted = text.split(/\n\r\n/).collect { |paragraph|
|
||||
format_paragraph(paragraph)
|
||||
}.join("\n")
|
||||
require 'text/format'
|
||||
|
||||
# Make list points stand on their own line
|
||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
module MailHelper
|
||||
# Uses Text::Format to take the text and format it, indented two spaces for
|
||||
# each line, and wrapped at 72 columns.
|
||||
def block_format(text)
|
||||
formatted = text.split(/\n\r\n/).collect { |paragraph|
|
||||
Text::Format.new(
|
||||
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
|
||||
).format
|
||||
}.join("\n")
|
||||
|
||||
# Make list points stand on their own line
|
||||
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
|
||||
|
||||
formatted
|
||||
end
|
||||
|
||||
# Access the mailer instance.
|
||||
def mailer
|
||||
@_controller
|
||||
end
|
||||
|
||||
# Access the message instance.
|
||||
def message
|
||||
@_message
|
||||
end
|
||||
|
||||
# Access the message attachments list.
|
||||
def attachments
|
||||
@_message.attachments
|
||||
end
|
||||
|
||||
# Returns +text+ wrapped at +len+ columns and indented +indent+ spaces.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# my_text = "Here is a sample text with more than 40 characters"
|
||||
#
|
||||
# format_paragraph(my_text, 25, 4)
|
||||
# # => " Here is a sample text with\n more than 40 characters"
|
||||
def format_paragraph(text, len = 72, indent = 2)
|
||||
sentences = [[]]
|
||||
|
||||
text.split.each do |word|
|
||||
if (sentences.last + [word]).join(' ').length > len
|
||||
sentences << [word]
|
||||
else
|
||||
sentences.last << word
|
||||
end
|
||||
end
|
||||
|
||||
sentences.map { |sentence|
|
||||
"#{" " * indent}#{sentence.join(' ')}"
|
||||
}.join "\n"
|
||||
end
|
||||
formatted
|
||||
end
|
||||
end
|
||||
|
||||
113
actionmailer/lib/action_mailer/part.rb
Normal file
113
actionmailer/lib/action_mailer/part.rb
Normal file
@@ -0,0 +1,113 @@
|
||||
require 'action_mailer/adv_attr_accessor'
|
||||
require 'action_mailer/part_container'
|
||||
require 'action_mailer/utils'
|
||||
|
||||
module ActionMailer
|
||||
# Represents a subpart of an email message. It shares many similar
|
||||
# attributes of ActionMailer::Base. Although you can create parts manually
|
||||
# and add them to the #parts list of the mailer, it is easier
|
||||
# to use the helper methods in ActionMailer::PartContainer.
|
||||
class Part
|
||||
include ActionMailer::AdvAttrAccessor
|
||||
include ActionMailer::PartContainer
|
||||
|
||||
# Represents the body of the part, as a string. This should not be a
|
||||
# Hash (like ActionMailer::Base), but if you want a template to be rendered
|
||||
# into the body of a subpart you can do it with the mailer's #render method
|
||||
# and assign the result here.
|
||||
adv_attr_accessor :body
|
||||
|
||||
# Specify the charset for this subpart. By default, it will be the charset
|
||||
# of the containing part or mailer.
|
||||
adv_attr_accessor :charset
|
||||
|
||||
# The content disposition of this part, typically either "inline" or
|
||||
# "attachment".
|
||||
adv_attr_accessor :content_disposition
|
||||
|
||||
# The content type of the part.
|
||||
adv_attr_accessor :content_type
|
||||
|
||||
# The filename to use for this subpart (usually for attachments).
|
||||
adv_attr_accessor :filename
|
||||
|
||||
# Accessor for specifying additional headers to include with this part.
|
||||
adv_attr_accessor :headers
|
||||
|
||||
# The transfer encoding to use for this subpart, like "base64" or
|
||||
# "quoted-printable".
|
||||
adv_attr_accessor :transfer_encoding
|
||||
|
||||
# Create a new part from the given +params+ hash. The valid params keys
|
||||
# correspond to the accessors.
|
||||
def initialize(params)
|
||||
@content_type = params[:content_type]
|
||||
@content_disposition = params[:disposition] || "inline"
|
||||
@charset = params[:charset]
|
||||
@body = params[:body]
|
||||
@filename = params[:filename]
|
||||
@transfer_encoding = params[:transfer_encoding] || "quoted-printable"
|
||||
@headers = params[:headers] || {}
|
||||
@parts = []
|
||||
end
|
||||
|
||||
# Convert the part to a mail object which can be included in the parts
|
||||
# list of another mail object.
|
||||
def to_mail(defaults)
|
||||
part = TMail::Mail.new
|
||||
|
||||
real_content_type, ctype_attrs = parse_content_type(defaults)
|
||||
|
||||
if @parts.empty?
|
||||
part.content_transfer_encoding = transfer_encoding || "quoted-printable"
|
||||
case (transfer_encoding || "").downcase
|
||||
when "base64" then
|
||||
part.body = TMail::Base64.folding_encode(body)
|
||||
when "quoted-printable"
|
||||
part.body = [Utils.normalize_new_lines(body)].pack("M*")
|
||||
else
|
||||
part.body = body
|
||||
end
|
||||
|
||||
# Always set the content_type after setting the body and or parts!
|
||||
# Also don't set filename and name when there is none (like in
|
||||
# non-attachment parts)
|
||||
if content_disposition == "attachment"
|
||||
ctype_attrs.delete "charset"
|
||||
part.set_content_type(real_content_type, nil,
|
||||
squish("name" => filename).merge(ctype_attrs))
|
||||
part.set_content_disposition(content_disposition,
|
||||
squish("filename" => filename).merge(ctype_attrs))
|
||||
else
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
part.set_content_disposition(content_disposition)
|
||||
end
|
||||
else
|
||||
if String === body
|
||||
part = TMail::Mail.new
|
||||
part.body = body
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
||||
part.set_content_disposition "inline"
|
||||
m.parts << part
|
||||
end
|
||||
|
||||
@parts.each do |p|
|
||||
prt = (TMail::Mail === p ? p : p.to_mail(defaults))
|
||||
part.parts << prt
|
||||
end
|
||||
|
||||
part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
|
||||
end
|
||||
|
||||
headers.each { |k,v| part[k] = v }
|
||||
|
||||
part
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def squish(values={})
|
||||
values.delete_if { |k,v| v.nil? }
|
||||
end
|
||||
end
|
||||
end
|
||||
51
actionmailer/lib/action_mailer/part_container.rb
Normal file
51
actionmailer/lib/action_mailer/part_container.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
module ActionMailer
|
||||
# Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
|
||||
# in common. Using these helpers you can easily add subparts or attachments
|
||||
# to your message:
|
||||
#
|
||||
# def my_mail_message(...)
|
||||
# ...
|
||||
# part "text/plain" do |p|
|
||||
# p.body "hello, world"
|
||||
# p.transfer_encoding "base64"
|
||||
# end
|
||||
#
|
||||
# attachment "image/jpg" do |a|
|
||||
# a.body = File.read("hello.jpg")
|
||||
# a.filename = "hello.jpg"
|
||||
# end
|
||||
# end
|
||||
module PartContainer
|
||||
# The list of subparts of this container
|
||||
attr_reader :parts
|
||||
|
||||
# Add a part to a multipart message, with the given content-type. The
|
||||
# part itself is yielded to the block so that other properties (charset,
|
||||
# body, headers, etc.) can be set on it.
|
||||
def part(params)
|
||||
params = {:content_type => params} if String === params
|
||||
part = Part.new(params)
|
||||
yield part if block_given?
|
||||
@parts << part
|
||||
end
|
||||
|
||||
# Add an attachment to a multipart message. This is simply a part with the
|
||||
# content-disposition set to "attachment".
|
||||
def attachment(params, &block)
|
||||
params = { :content_type => params } if String === params
|
||||
params = { :disposition => "attachment",
|
||||
:transfer_encoding => "base64" }.merge(params)
|
||||
part(params, &block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_content_type(defaults=nil)
|
||||
return [defaults && defaults.content_type, {}] if content_type.blank?
|
||||
ctype, *attrs = content_type.split(/;\s*/)
|
||||
attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
|
||||
[ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
59
actionmailer/lib/action_mailer/quoting.rb
Normal file
59
actionmailer/lib/action_mailer/quoting.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
module ActionMailer
|
||||
module Quoting #:nodoc:
|
||||
# Convert the given text into quoted printable format, with an instruction
|
||||
# that the text be eventually interpreted in the given charset.
|
||||
def quoted_printable(text, charset)
|
||||
text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
|
||||
gsub( / /, "_" )
|
||||
"=?#{charset}?Q?#{text}?="
|
||||
end
|
||||
|
||||
# Convert the given character to quoted printable format, taking into
|
||||
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
||||
def quoted_printable_encode(character)
|
||||
result = ""
|
||||
character.each_byte { |b| result << "=%02x" % b }
|
||||
result
|
||||
end
|
||||
|
||||
# A quick-and-dirty regexp for determining whether a string contains any
|
||||
# characters that need escaping.
|
||||
if !defined?(CHARS_NEEDING_QUOTING)
|
||||
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
|
||||
end
|
||||
|
||||
# Quote the given text if it contains any "illegal" characters
|
||||
def quote_if_necessary(text, charset)
|
||||
(text =~ CHARS_NEEDING_QUOTING) ?
|
||||
quoted_printable(text, charset) :
|
||||
text
|
||||
end
|
||||
|
||||
# Quote any of the given strings if they contain any "illegal" characters
|
||||
def quote_any_if_necessary(charset, *args)
|
||||
args.map { |v| quote_if_necessary(v, charset) }
|
||||
end
|
||||
|
||||
# Quote the given address if it needs to be. The address may be a
|
||||
# regular email address, or it can be a phrase followed by an address in
|
||||
# brackets. The phrase is the only part that will be quoted, and only if
|
||||
# it needs to be. This allows extended characters to be used in the
|
||||
# "to", "from", "cc", and "bcc" headers.
|
||||
def quote_address_if_necessary(address, charset)
|
||||
if Array === address
|
||||
address.map { |a| quote_address_if_necessary(a, charset) }
|
||||
elsif address =~ /^(\S.*)\s+(<.*>)$/
|
||||
address = $2
|
||||
phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
|
||||
"\"#{phrase}\" #{address}"
|
||||
else
|
||||
address
|
||||
end
|
||||
end
|
||||
|
||||
# Quote any of the given addresses, if they need to be.
|
||||
def quote_any_address_if_necessary(charset, *args)
|
||||
args.map { |v| quote_address_if_necessary(v, charset) }
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,44 +0,0 @@
|
||||
require "action_mailer"
|
||||
require "rails"
|
||||
require "abstract_controller/railties/routes_helpers"
|
||||
|
||||
module ActionMailer
|
||||
class Railtie < Rails::Railtie
|
||||
config.action_mailer = ActiveSupport::OrderedOptions.new
|
||||
|
||||
initializer "action_mailer.logger" do
|
||||
ActiveSupport.on_load(:action_mailer) { self.logger ||= Rails.logger }
|
||||
end
|
||||
|
||||
initializer "action_mailer.set_configs" do |app|
|
||||
paths = app.config.paths
|
||||
options = app.config.action_mailer
|
||||
|
||||
options.assets_dir ||= paths["public"].first
|
||||
options.javascripts_dir ||= paths["public/javascripts"].first
|
||||
options.stylesheets_dir ||= paths["public/stylesheets"].first
|
||||
|
||||
# make sure readers methods get compiled
|
||||
options.asset_path ||= app.config.asset_path
|
||||
options.asset_host ||= app.config.asset_host
|
||||
options.relative_url_root ||= app.config.relative_url_root
|
||||
|
||||
ActiveSupport.on_load(:action_mailer) do
|
||||
include AbstractController::UrlFor
|
||||
extend ::AbstractController::Railties::RoutesHelpers.with(app.routes)
|
||||
include app.routes.mounted_helpers
|
||||
|
||||
register_interceptors(options.delete(:interceptors))
|
||||
register_observers(options.delete(:observers))
|
||||
|
||||
options.each { |k,v| send("#{k}=", v) }
|
||||
end
|
||||
end
|
||||
|
||||
initializer "action_mailer.compile_config_methods" do
|
||||
ActiveSupport.on_load(:action_mailer) do
|
||||
config.compile_methods! if config.respond_to?(:compile_methods!)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,82 +0,0 @@
|
||||
require 'active_support/core_ext/class/attribute'
|
||||
|
||||
module ActionMailer
|
||||
class NonInferrableMailerError < ::StandardError
|
||||
def initialize(name)
|
||||
super "Unable to determine the mailer to test from #{name}. " +
|
||||
"You'll need to specify it using tests YourMailer in your " +
|
||||
"test case definition"
|
||||
end
|
||||
end
|
||||
|
||||
class TestCase < ActiveSupport::TestCase
|
||||
module Behavior
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include TestHelper
|
||||
|
||||
included do
|
||||
class_attribute :_mailer_class
|
||||
setup :initialize_test_deliveries
|
||||
setup :set_expected_mail
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
def tests(mailer)
|
||||
case mailer
|
||||
when String, Symbol
|
||||
self._mailer_class = mailer.to_s.camelize.constantize
|
||||
when Module
|
||||
self._mailer_class = mailer
|
||||
else
|
||||
raise NonInferrableMailerError.new(mailer)
|
||||
end
|
||||
end
|
||||
|
||||
def mailer_class
|
||||
if mailer = self._mailer_class
|
||||
mailer
|
||||
else
|
||||
tests determine_default_mailer(name)
|
||||
end
|
||||
end
|
||||
|
||||
def determine_default_mailer(name)
|
||||
name.sub(/Test$/, '').constantize
|
||||
rescue NameError
|
||||
raise NonInferrableMailerError.new(name)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def initialize_test_deliveries
|
||||
ActionMailer::Base.delivery_method = :test
|
||||
ActionMailer::Base.perform_deliveries = true
|
||||
ActionMailer::Base.deliveries.clear
|
||||
end
|
||||
|
||||
def set_expected_mail
|
||||
@expected = Mail.new
|
||||
@expected.content_type ["text", "plain", { "charset" => charset }]
|
||||
@expected.mime_version = '1.0'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def charset
|
||||
"UTF-8"
|
||||
end
|
||||
|
||||
def encode(subject)
|
||||
Mail::Encodings.q_value_encode(subject, charset)
|
||||
end
|
||||
|
||||
def read_fixture(action)
|
||||
IO.readlines(File.join(Rails.root, 'test', 'fixtures', self.class.mailer_class.name.underscore, action))
|
||||
end
|
||||
end
|
||||
|
||||
include Behavior
|
||||
end
|
||||
end
|
||||
@@ -1,61 +0,0 @@
|
||||
module ActionMailer
|
||||
module TestHelper
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
# Asserts that the number of emails sent matches the given number.
|
||||
#
|
||||
# def test_emails
|
||||
# assert_emails 0
|
||||
# ContactMailer.deliver_contact
|
||||
# assert_emails 1
|
||||
# ContactMailer.deliver_contact
|
||||
# assert_emails 2
|
||||
# end
|
||||
#
|
||||
# If a block is passed, that block should cause the specified number of emails to be sent.
|
||||
#
|
||||
# def test_emails_again
|
||||
# assert_emails 1 do
|
||||
# ContactMailer.deliver_contact
|
||||
# end
|
||||
#
|
||||
# assert_emails 2 do
|
||||
# ContactMailer.deliver_contact
|
||||
# ContactMailer.deliver_contact
|
||||
# end
|
||||
# end
|
||||
def assert_emails(number)
|
||||
if block_given?
|
||||
original_count = ActionMailer::Base.deliveries.size
|
||||
yield
|
||||
new_count = ActionMailer::Base.deliveries.size
|
||||
assert_equal original_count + number, new_count, "#{number} emails expected, but #{new_count - original_count} were sent"
|
||||
else
|
||||
assert_equal number, ActionMailer::Base.deliveries.size
|
||||
end
|
||||
end
|
||||
|
||||
# Assert that no emails have been sent.
|
||||
#
|
||||
# def test_emails
|
||||
# assert_no_emails
|
||||
# ContactMailer.deliver_contact
|
||||
# assert_emails 1
|
||||
# end
|
||||
#
|
||||
# If a block is passed, that block should not cause any emails to be sent.
|
||||
#
|
||||
# def test_emails_again
|
||||
# assert_no_emails do
|
||||
# # No emails should be sent from this block
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Note: This assertion is simply a shortcut for:
|
||||
#
|
||||
# assert_emails 0
|
||||
def assert_no_emails(&block)
|
||||
assert_emails 0, &block
|
||||
end
|
||||
end
|
||||
end
|
||||
8
actionmailer/lib/action_mailer/utils.rb
Normal file
8
actionmailer/lib/action_mailer/utils.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
module ActionMailer
|
||||
module Utils #:nodoc:
|
||||
def normalize_new_lines(text)
|
||||
text.to_s.gsub(/\r\n?/, "\n")
|
||||
end
|
||||
module_function :normalize_new_lines
|
||||
end
|
||||
end
|
||||
1466
actionmailer/lib/action_mailer/vendor/text/format.rb
vendored
Executable file
1466
actionmailer/lib/action_mailer/vendor/text/format.rb
vendored
Executable file
File diff suppressed because it is too large
Load Diff
3
actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
Executable file
3
actionmailer/lib/action_mailer/vendor/tmail.rb
vendored
Executable file
@@ -0,0 +1,3 @@
|
||||
require 'tmail/info'
|
||||
require 'tmail/mail'
|
||||
require 'tmail/mailbox'
|
||||
242
actionmailer/lib/action_mailer/vendor/tmail/address.rb
vendored
Executable file
242
actionmailer/lib/action_mailer/vendor/tmail/address.rb
vendored
Executable file
@@ -0,0 +1,242 @@
|
||||
#
|
||||
# address.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/encode'
|
||||
require 'tmail/parser'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Address
|
||||
|
||||
include TextUtils
|
||||
|
||||
def Address.parse( str )
|
||||
Parser.parse :ADDRESS, str
|
||||
end
|
||||
|
||||
def address_group?
|
||||
false
|
||||
end
|
||||
|
||||
def initialize( local, domain )
|
||||
if domain
|
||||
domain.each do |s|
|
||||
raise SyntaxError, 'empty word in domain' if s.empty?
|
||||
end
|
||||
end
|
||||
@local = local
|
||||
@domain = domain
|
||||
@name = nil
|
||||
@routes = []
|
||||
end
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def name=( str )
|
||||
@name = str
|
||||
@name = nil if str and str.empty?
|
||||
end
|
||||
|
||||
alias phrase name
|
||||
alias phrase= name=
|
||||
|
||||
attr_reader :routes
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{address()}>"
|
||||
end
|
||||
|
||||
def local
|
||||
return nil unless @local
|
||||
return '""' if @local.size == 1 and @local[0].empty?
|
||||
@local.map {|i| quote_atom(i) }.join('.')
|
||||
end
|
||||
|
||||
def domain
|
||||
return nil unless @domain
|
||||
join_domain(@domain)
|
||||
end
|
||||
|
||||
def spec
|
||||
s = self.local
|
||||
d = self.domain
|
||||
if s and d
|
||||
s + '@' + d
|
||||
else
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
alias address spec
|
||||
|
||||
|
||||
def ==( other )
|
||||
other.respond_to? :spec and self.spec == other.spec
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@local.hash ^ @domain.hash
|
||||
end
|
||||
|
||||
def dup
|
||||
obj = self.class.new(@local.dup, @domain.dup)
|
||||
obj.name = @name.dup if @name
|
||||
obj.routes.replace @routes
|
||||
obj
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
||||
unless @local
|
||||
strategy.meta '<>' # empty return-path
|
||||
return
|
||||
end
|
||||
|
||||
spec_p = (not @name and @routes.empty?)
|
||||
if @name
|
||||
strategy.phrase @name
|
||||
strategy.space
|
||||
end
|
||||
tmp = spec_p ? '' : '<'
|
||||
unless @routes.empty?
|
||||
tmp << @routes.map {|i| '@' + i }.join(',') << ':'
|
||||
end
|
||||
tmp << self.spec
|
||||
tmp << '>' unless spec_p
|
||||
strategy.meta tmp
|
||||
strategy.lwsp ''
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddressGroup
|
||||
|
||||
include Enumerable
|
||||
|
||||
def address_group?
|
||||
true
|
||||
end
|
||||
|
||||
def initialize( name, addrs )
|
||||
@name = name
|
||||
@addresses = addrs
|
||||
end
|
||||
|
||||
attr_reader :name
|
||||
|
||||
def ==( other )
|
||||
other.respond_to? :to_a and @addresses == other.to_a
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
map {|i| i.hash }.hash
|
||||
end
|
||||
|
||||
def []( idx )
|
||||
@addresses[idx]
|
||||
end
|
||||
|
||||
def size
|
||||
@addresses.size
|
||||
end
|
||||
|
||||
def empty?
|
||||
@addresses.empty?
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
@addresses.each(&block)
|
||||
end
|
||||
|
||||
def to_a
|
||||
@addresses.dup
|
||||
end
|
||||
|
||||
alias to_ary to_a
|
||||
|
||||
def include?( a )
|
||||
@addresses.include? a
|
||||
end
|
||||
|
||||
def flatten
|
||||
set = []
|
||||
@addresses.each do |a|
|
||||
if a.respond_to? :flatten
|
||||
set.concat a.flatten
|
||||
else
|
||||
set.push a
|
||||
end
|
||||
end
|
||||
set
|
||||
end
|
||||
|
||||
def each_address( &block )
|
||||
flatten.each(&block)
|
||||
end
|
||||
|
||||
def add( a )
|
||||
@addresses.push a
|
||||
end
|
||||
|
||||
alias push add
|
||||
|
||||
def delete( a )
|
||||
@addresses.delete a
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
||||
strategy.phrase @name
|
||||
strategy.meta ':'
|
||||
strategy.space
|
||||
first = true
|
||||
each do |mbox|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
end
|
||||
strategy.space
|
||||
mbox.accept strategy
|
||||
end
|
||||
strategy.meta ';'
|
||||
strategy.lwsp ''
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
39
actionmailer/lib/action_mailer/vendor/tmail/attachments.rb
vendored
Normal file
39
actionmailer/lib/action_mailer/vendor/tmail/attachments.rb
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
require 'stringio'
|
||||
|
||||
module TMail
|
||||
class Attachment < StringIO
|
||||
attr_accessor :original_filename, :content_type
|
||||
end
|
||||
|
||||
class Mail
|
||||
def has_attachments?
|
||||
multipart? && parts.any? { |part| attachment?(part) }
|
||||
end
|
||||
|
||||
def attachment?(part)
|
||||
(part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
|
||||
part.header['content-type'].main_type != "text"
|
||||
end
|
||||
|
||||
def attachments
|
||||
if multipart?
|
||||
parts.collect { |part|
|
||||
if attachment?(part)
|
||||
content = part.body # unquoted automatically by TMail#body
|
||||
file_name = (part['content-location'] &&
|
||||
part['content-location'].body) ||
|
||||
part.sub_header("content-type", "name") ||
|
||||
part.sub_header("content-disposition", "filename")
|
||||
|
||||
next if file_name.blank? || content.blank?
|
||||
|
||||
attachment = Attachment.new(content)
|
||||
attachment.original_filename = file_name.strip
|
||||
attachment.content_type = part.content_type
|
||||
attachment
|
||||
end
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
71
actionmailer/lib/action_mailer/vendor/tmail/base64.rb
vendored
Executable file
71
actionmailer/lib/action_mailer/vendor/tmail/base64.rb
vendored
Executable file
@@ -0,0 +1,71 @@
|
||||
#
|
||||
# base64.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
module Base64
|
||||
|
||||
module_function
|
||||
|
||||
def rb_folding_encode( str, eol = "\n", limit = 60 )
|
||||
[str].pack('m')
|
||||
end
|
||||
|
||||
def rb_encode( str )
|
||||
[str].pack('m').tr( "\r\n", '' )
|
||||
end
|
||||
|
||||
def rb_decode( str, strict = false )
|
||||
str.unpack('m')
|
||||
end
|
||||
|
||||
begin
|
||||
require 'tmail/base64.so'
|
||||
alias folding_encode c_folding_encode
|
||||
alias encode c_encode
|
||||
alias decode c_decode
|
||||
class << self
|
||||
alias folding_encode c_folding_encode
|
||||
alias encode c_encode
|
||||
alias decode c_decode
|
||||
end
|
||||
rescue LoadError
|
||||
alias folding_encode rb_folding_encode
|
||||
alias encode rb_encode
|
||||
alias decode rb_decode
|
||||
class << self
|
||||
alias folding_encode rb_folding_encode
|
||||
alias encode rb_encode
|
||||
alias decode rb_decode
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
69
actionmailer/lib/action_mailer/vendor/tmail/config.rb
vendored
Executable file
69
actionmailer/lib/action_mailer/vendor/tmail/config.rb
vendored
Executable file
@@ -0,0 +1,69 @@
|
||||
#
|
||||
# config.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
class Config
|
||||
|
||||
def initialize( strict )
|
||||
@strict_parse = strict
|
||||
@strict_base64decode = strict
|
||||
end
|
||||
|
||||
def strict_parse?
|
||||
@strict_parse
|
||||
end
|
||||
|
||||
attr_writer :strict_parse
|
||||
|
||||
def strict_base64decode?
|
||||
@strict_base64decode
|
||||
end
|
||||
|
||||
attr_writer :strict_base64decode
|
||||
|
||||
def new_body_port( mail )
|
||||
StringPort.new
|
||||
end
|
||||
|
||||
alias new_preamble_port new_body_port
|
||||
alias new_part_port new_body_port
|
||||
|
||||
end
|
||||
|
||||
DEFAULT_CONFIG = Config.new(false)
|
||||
DEFAULT_STRICT_CONFIG = Config.new(true)
|
||||
|
||||
def Config.to_config( arg )
|
||||
return DEFAULT_STRICT_CONFIG if arg == true
|
||||
return DEFAULT_CONFIG if arg == false
|
||||
arg or DEFAULT_CONFIG
|
||||
end
|
||||
|
||||
end
|
||||
467
actionmailer/lib/action_mailer/vendor/tmail/encode.rb
vendored
Executable file
467
actionmailer/lib/action_mailer/vendor/tmail/encode.rb
vendored
Executable file
@@ -0,0 +1,467 @@
|
||||
#
|
||||
# encode.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'nkf'
|
||||
require 'tmail/base64.rb'
|
||||
require 'tmail/stringio'
|
||||
require 'tmail/utils'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
module StrategyInterface
|
||||
|
||||
def create_dest( obj )
|
||||
case obj
|
||||
when nil
|
||||
StringOutput.new
|
||||
when String
|
||||
StringOutput.new(obj)
|
||||
when IO, StringOutput
|
||||
obj
|
||||
else
|
||||
raise TypeError, 'cannot handle this type of object for dest'
|
||||
end
|
||||
end
|
||||
module_function :create_dest
|
||||
|
||||
def encoded( eol = "\r\n", charset = 'j', dest = nil )
|
||||
accept_strategy Encoder, eol, charset, dest
|
||||
end
|
||||
|
||||
def decoded( eol = "\n", charset = 'e', dest = nil )
|
||||
accept_strategy Decoder, eol, charset, dest
|
||||
end
|
||||
|
||||
alias to_s decoded
|
||||
|
||||
def accept_strategy( klass, eol, charset, dest = nil )
|
||||
dest ||= ''
|
||||
accept klass.new(create_dest(dest), charset, eol)
|
||||
dest
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### MIME B encoding decoder
|
||||
###
|
||||
|
||||
class Decoder
|
||||
|
||||
include TextUtils
|
||||
|
||||
encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
|
||||
ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
|
||||
|
||||
OUTPUT_ENCODING = {
|
||||
'EUC' => 'e',
|
||||
'SJIS' => 's',
|
||||
}
|
||||
|
||||
def self.decode( str, encoding = nil )
|
||||
encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
|
||||
opt = '-m' + encoding
|
||||
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
|
||||
end
|
||||
|
||||
def initialize( dest, encoding = nil, eol = "\n" )
|
||||
@f = StrategyInterface.create_dest(dest)
|
||||
@encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
|
||||
@eol = eol
|
||||
end
|
||||
|
||||
def decode( str )
|
||||
self.class.decode(str, @encoding)
|
||||
end
|
||||
private :decode
|
||||
|
||||
def terminate
|
||||
end
|
||||
|
||||
def header_line( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def header_name( nm )
|
||||
@f << nm << ': '
|
||||
end
|
||||
|
||||
def header_body( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def space
|
||||
@f << ' '
|
||||
end
|
||||
|
||||
alias spc space
|
||||
|
||||
def lwsp( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
def meta( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
def text( str )
|
||||
@f << decode(str)
|
||||
end
|
||||
|
||||
def phrase( str )
|
||||
@f << quote_phrase(decode(str))
|
||||
end
|
||||
|
||||
def kv_pair( k, v )
|
||||
@f << k << '=' << v
|
||||
end
|
||||
|
||||
def puts( str = nil )
|
||||
@f << str if str
|
||||
@f << @eol
|
||||
end
|
||||
|
||||
def write( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### MIME B-encoding encoder
|
||||
###
|
||||
|
||||
#
|
||||
# FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
|
||||
#
|
||||
class Encoder
|
||||
|
||||
include TextUtils
|
||||
|
||||
BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
|
||||
|
||||
def Encoder.encode( str )
|
||||
e = new()
|
||||
e.header_body str
|
||||
e.terminate
|
||||
e.dest.string
|
||||
end
|
||||
|
||||
SPACER = "\t"
|
||||
MAX_LINE_LEN = 70
|
||||
|
||||
OPTIONS = {
|
||||
'EUC' => '-Ej -m0',
|
||||
'SJIS' => '-Sj -m0',
|
||||
'UTF8' => nil, # FIXME
|
||||
'NONE' => nil
|
||||
}
|
||||
|
||||
def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
|
||||
@f = StrategyInterface.create_dest(dest)
|
||||
@opt = OPTIONS[$KCODE]
|
||||
@eol = eol
|
||||
reset
|
||||
end
|
||||
|
||||
def normalize_encoding( str )
|
||||
if @opt
|
||||
then NKF.nkf(@opt, str)
|
||||
else str
|
||||
end
|
||||
end
|
||||
|
||||
def reset
|
||||
@text = ''
|
||||
@lwsp = ''
|
||||
@curlen = 0
|
||||
end
|
||||
|
||||
def terminate
|
||||
add_lwsp ''
|
||||
reset
|
||||
end
|
||||
|
||||
def dest
|
||||
@f
|
||||
end
|
||||
|
||||
def puts( str = nil )
|
||||
@f << str if str
|
||||
@f << @eol
|
||||
end
|
||||
|
||||
def write( str )
|
||||
@f << str
|
||||
end
|
||||
|
||||
#
|
||||
# add
|
||||
#
|
||||
|
||||
def header_line( line )
|
||||
scanadd line
|
||||
end
|
||||
|
||||
def header_name( name )
|
||||
add_text name.split(/-/).map {|i| i.capitalize }.join('-')
|
||||
add_text ':'
|
||||
add_lwsp ' '
|
||||
end
|
||||
|
||||
def header_body( str )
|
||||
scanadd normalize_encoding(str)
|
||||
end
|
||||
|
||||
def space
|
||||
add_lwsp ' '
|
||||
end
|
||||
|
||||
alias spc space
|
||||
|
||||
def lwsp( str )
|
||||
add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
|
||||
end
|
||||
|
||||
def meta( str )
|
||||
add_text str
|
||||
end
|
||||
|
||||
def text( str )
|
||||
scanadd normalize_encoding(str)
|
||||
end
|
||||
|
||||
def phrase( str )
|
||||
str = normalize_encoding(str)
|
||||
if CONTROL_CHAR === str
|
||||
scanadd str
|
||||
else
|
||||
add_text quote_phrase(str)
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: implement line folding
|
||||
#
|
||||
def kv_pair( k, v )
|
||||
return if v.nil?
|
||||
v = normalize_encoding(v)
|
||||
if token_safe?(v)
|
||||
add_text k + '=' + v
|
||||
elsif not CONTROL_CHAR === v
|
||||
add_text k + '=' + quote_token(v)
|
||||
else
|
||||
# apply RFC2231 encoding
|
||||
kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
|
||||
add_text kv
|
||||
end
|
||||
end
|
||||
|
||||
def encode_value( str )
|
||||
str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scanadd( str, force = false )
|
||||
types = ''
|
||||
strs = []
|
||||
|
||||
until str.empty?
|
||||
if m = /\A[^\e\t\r\n ]+/.match(str)
|
||||
types << (force ? 'j' : 'a')
|
||||
strs.push m[0]
|
||||
|
||||
elsif m = /\A[\t\r\n ]+/.match(str)
|
||||
types << 's'
|
||||
strs.push m[0]
|
||||
|
||||
elsif m = /\A\e../.match(str)
|
||||
esc = m[0]
|
||||
str = m.post_match
|
||||
if esc != "\e(B" and m = /\A[^\e]+/.match(str)
|
||||
types << 'j'
|
||||
strs.push m[0]
|
||||
end
|
||||
|
||||
else
|
||||
raise 'TMail FATAL: encoder scan fail'
|
||||
end
|
||||
(str = m.post_match) unless m.nil?
|
||||
end
|
||||
|
||||
do_encode types, strs
|
||||
end
|
||||
|
||||
def do_encode( types, strs )
|
||||
#
|
||||
# result : (A|E)(S(A|E))*
|
||||
# E : W(SW)*
|
||||
# W : (J|A)+ but must contain J # (J|A)*J(J|A)*
|
||||
# A : <<A character string not to be encoded>>
|
||||
# J : <<A character string to be encoded>>
|
||||
# S : <<LWSP>>
|
||||
#
|
||||
# An encoding unit is `E'.
|
||||
# Input (parameter `types') is (J|A)(J|A|S)*(J|A)
|
||||
#
|
||||
if BENCODE_DEBUG
|
||||
puts
|
||||
puts '-- do_encode ------------'
|
||||
puts types.split(//).join(' ')
|
||||
p strs
|
||||
end
|
||||
|
||||
e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
|
||||
|
||||
while m = e.match(types)
|
||||
pre = m.pre_match
|
||||
concat_A_S pre, strs[0, pre.size] unless pre.empty?
|
||||
concat_E m[0], strs[m.begin(0) ... m.end(0)]
|
||||
types = m.post_match
|
||||
strs.slice! 0, m.end(0)
|
||||
end
|
||||
concat_A_S types, strs
|
||||
end
|
||||
|
||||
def concat_A_S( types, strs )
|
||||
i = 0
|
||||
types.each_byte do |t|
|
||||
case t
|
||||
when ?a then add_text strs[i]
|
||||
when ?s then add_lwsp strs[i]
|
||||
else
|
||||
raise "TMail FATAL: unknown flag: #{t.chr}"
|
||||
end
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
METHOD_ID = {
|
||||
?j => :extract_J,
|
||||
?e => :extract_E,
|
||||
?a => :extract_A,
|
||||
?s => :extract_S
|
||||
}
|
||||
|
||||
def concat_E( types, strs )
|
||||
if BENCODE_DEBUG
|
||||
puts '---- concat_E'
|
||||
puts "types=#{types.split(//).join(' ')}"
|
||||
puts "strs =#{strs.inspect}"
|
||||
end
|
||||
|
||||
flush() unless @text.empty?
|
||||
|
||||
chunk = ''
|
||||
strs.each_with_index do |s,i|
|
||||
mid = METHOD_ID[types[i]]
|
||||
until s.empty?
|
||||
unless c = __send__(mid, chunk.size, s)
|
||||
add_with_encode chunk unless chunk.empty?
|
||||
flush
|
||||
chunk = ''
|
||||
fold
|
||||
c = __send__(mid, 0, s)
|
||||
raise 'TMail FATAL: extract fail' unless c
|
||||
end
|
||||
chunk << c
|
||||
end
|
||||
end
|
||||
add_with_encode chunk unless chunk.empty?
|
||||
end
|
||||
|
||||
def extract_J( chunksize, str )
|
||||
size = max_bytes(chunksize, str.size) - 6
|
||||
size = (size % 2 == 0) ? (size) : (size - 1)
|
||||
return nil if size <= 0
|
||||
"\e$B#{str.slice!(0, size)}\e(B"
|
||||
end
|
||||
|
||||
def extract_A( chunksize, str )
|
||||
size = max_bytes(chunksize, str.size)
|
||||
return nil if size <= 0
|
||||
str.slice!(0, size)
|
||||
end
|
||||
|
||||
alias extract_S extract_A
|
||||
|
||||
def max_bytes( chunksize, ssize )
|
||||
(restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
|
||||
end
|
||||
|
||||
#
|
||||
# free length buffer
|
||||
#
|
||||
|
||||
def add_text( str )
|
||||
@text << str
|
||||
# puts '---- text -------------------------------------'
|
||||
# puts "+ #{str.inspect}"
|
||||
# puts "txt >>>#{@text.inspect}<<<"
|
||||
end
|
||||
|
||||
def add_with_encode( str )
|
||||
@text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
|
||||
end
|
||||
|
||||
def add_lwsp( lwsp )
|
||||
# puts '---- lwsp -------------------------------------'
|
||||
# puts "+ #{lwsp.inspect}"
|
||||
fold if restsize() <= 0
|
||||
flush
|
||||
@lwsp = lwsp
|
||||
end
|
||||
|
||||
def flush
|
||||
# puts '---- flush ----'
|
||||
# puts "spc >>>#{@lwsp.inspect}<<<"
|
||||
# puts "txt >>>#{@text.inspect}<<<"
|
||||
@f << @lwsp << @text
|
||||
@curlen += (@lwsp.size + @text.size)
|
||||
@text = ''
|
||||
@lwsp = ''
|
||||
end
|
||||
|
||||
def fold
|
||||
# puts '---- fold ----'
|
||||
@f << @eol
|
||||
@curlen = 0
|
||||
@lwsp = SPACER
|
||||
end
|
||||
|
||||
def restsize
|
||||
MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
552
actionmailer/lib/action_mailer/vendor/tmail/facade.rb
vendored
Executable file
552
actionmailer/lib/action_mailer/vendor/tmail/facade.rb
vendored
Executable file
@@ -0,0 +1,552 @@
|
||||
#
|
||||
# facade.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/utils'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
def header_string( name, default = nil )
|
||||
h = @header[name.downcase] or return default
|
||||
h.to_s
|
||||
end
|
||||
|
||||
###
|
||||
### attributes
|
||||
###
|
||||
|
||||
include TextUtils
|
||||
|
||||
def set_string_array_attr( key, strs )
|
||||
strs.flatten!
|
||||
if strs.empty?
|
||||
@header.delete key.downcase
|
||||
else
|
||||
store key, strs.join(', ')
|
||||
end
|
||||
strs
|
||||
end
|
||||
private :set_string_array_attr
|
||||
|
||||
def set_string_attr( key, str )
|
||||
if str
|
||||
store key, str
|
||||
else
|
||||
@header.delete key.downcase
|
||||
end
|
||||
str
|
||||
end
|
||||
private :set_string_attr
|
||||
|
||||
def set_addrfield( name, arg )
|
||||
if arg
|
||||
h = HeaderField.internal_new(name, @config)
|
||||
h.addrs.replace [arg].flatten
|
||||
@header[name] = h
|
||||
else
|
||||
@header.delete name
|
||||
end
|
||||
arg
|
||||
end
|
||||
private :set_addrfield
|
||||
|
||||
def addrs2specs( addrs )
|
||||
return nil unless addrs
|
||||
list = addrs.map {|addr|
|
||||
if addr.address_group?
|
||||
then addr.map {|a| a.spec }
|
||||
else addr.spec
|
||||
end
|
||||
}.flatten
|
||||
return nil if list.empty?
|
||||
list
|
||||
end
|
||||
private :addrs2specs
|
||||
|
||||
|
||||
#
|
||||
# date time
|
||||
#
|
||||
|
||||
def date( default = nil )
|
||||
if h = @header['date']
|
||||
h.date
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def date=( time )
|
||||
if time
|
||||
store 'Date', time2str(time)
|
||||
else
|
||||
@header.delete 'date'
|
||||
end
|
||||
time
|
||||
end
|
||||
|
||||
def strftime( fmt, default = nil )
|
||||
if t = date
|
||||
t.strftime(fmt)
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# destination
|
||||
#
|
||||
|
||||
def to_addrs( default = nil )
|
||||
if h = @header['to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def cc_addrs( default = nil )
|
||||
if h = @header['cc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def bcc_addrs( default = nil )
|
||||
if h = @header['bcc']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def to_addrs=( arg )
|
||||
set_addrfield 'to', arg
|
||||
end
|
||||
|
||||
def cc_addrs=( arg )
|
||||
set_addrfield 'cc', arg
|
||||
end
|
||||
|
||||
def bcc_addrs=( arg )
|
||||
set_addrfield 'bcc', arg
|
||||
end
|
||||
|
||||
def to( default = nil )
|
||||
addrs2specs(to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def cc( default = nil )
|
||||
addrs2specs(cc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def bcc( default = nil )
|
||||
addrs2specs(bcc_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def to=( *strs )
|
||||
set_string_array_attr 'To', strs
|
||||
end
|
||||
|
||||
def cc=( *strs )
|
||||
set_string_array_attr 'Cc', strs
|
||||
end
|
||||
|
||||
def bcc=( *strs )
|
||||
set_string_array_attr 'Bcc', strs
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# originator
|
||||
#
|
||||
|
||||
def from_addrs( default = nil )
|
||||
if h = @header['from']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def from_addrs=( arg )
|
||||
set_addrfield 'from', arg
|
||||
end
|
||||
|
||||
def from( default = nil )
|
||||
addrs2specs(from_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def from=( *strs )
|
||||
set_string_array_attr 'From', strs
|
||||
end
|
||||
|
||||
def friendly_from( default = nil )
|
||||
h = @header['from']
|
||||
a, = h.addrs
|
||||
return default unless a
|
||||
return a.phrase if a.phrase
|
||||
return h.comments.join(' ') unless h.comments.empty?
|
||||
a.spec
|
||||
end
|
||||
|
||||
|
||||
def reply_to_addrs( default = nil )
|
||||
if h = @header['reply-to']
|
||||
h.addrs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def reply_to_addrs=( arg )
|
||||
set_addrfield 'reply-to', arg
|
||||
end
|
||||
|
||||
def reply_to( default = nil )
|
||||
addrs2specs(reply_to_addrs(nil)) || default
|
||||
end
|
||||
|
||||
def reply_to=( *strs )
|
||||
set_string_array_attr 'Reply-To', strs
|
||||
end
|
||||
|
||||
|
||||
def sender_addr( default = nil )
|
||||
f = @header['sender'] or return default
|
||||
f.addr or return default
|
||||
end
|
||||
|
||||
def sender_addr=( addr )
|
||||
if addr
|
||||
h = HeaderField.internal_new('sender', @config)
|
||||
h.addr = addr
|
||||
@header['sender'] = h
|
||||
else
|
||||
@header.delete 'sender'
|
||||
end
|
||||
addr
|
||||
end
|
||||
|
||||
def sender( default )
|
||||
f = @header['sender'] or return default
|
||||
a = f.addr or return default
|
||||
a.spec
|
||||
end
|
||||
|
||||
def sender=( str )
|
||||
set_string_attr 'Sender', str
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# subject
|
||||
#
|
||||
|
||||
def subject( default = nil )
|
||||
if h = @header['subject']
|
||||
h.body
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
alias quoted_subject subject
|
||||
|
||||
def subject=( str )
|
||||
set_string_attr 'Subject', str
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# identity & threading
|
||||
#
|
||||
|
||||
def message_id( default = nil )
|
||||
if h = @header['message-id']
|
||||
h.id || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def message_id=( str )
|
||||
set_string_attr 'Message-Id', str
|
||||
end
|
||||
|
||||
def in_reply_to( default = nil )
|
||||
if h = @header['in-reply-to']
|
||||
h.ids
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def in_reply_to=( *idstrs )
|
||||
set_string_array_attr 'In-Reply-To', idstrs
|
||||
end
|
||||
|
||||
def references( default = nil )
|
||||
if h = @header['references']
|
||||
h.refs
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def references=( *strs )
|
||||
set_string_array_attr 'References', strs
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# MIME headers
|
||||
#
|
||||
|
||||
def mime_version( default = nil )
|
||||
if h = @header['mime-version']
|
||||
h.version || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def mime_version=( m, opt = nil )
|
||||
if opt
|
||||
if h = @header['mime-version']
|
||||
h.major = m
|
||||
h.minor = opt
|
||||
else
|
||||
store 'Mime-Version', "#{m}.#{opt}"
|
||||
end
|
||||
else
|
||||
store 'Mime-Version', m
|
||||
end
|
||||
m
|
||||
end
|
||||
|
||||
|
||||
def content_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.content_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def main_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.main_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def sub_type( default = nil )
|
||||
if h = @header['content-type']
|
||||
h.sub_type || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def set_content_type( str, sub = nil, param = nil )
|
||||
if sub
|
||||
main, sub = str, sub
|
||||
else
|
||||
main, sub = str.split(%r</>, 2)
|
||||
raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
|
||||
end
|
||||
if h = @header['content-type']
|
||||
h.main_type = main
|
||||
h.sub_type = sub
|
||||
h.params.clear
|
||||
else
|
||||
store 'Content-Type', "#{main}/#{sub}"
|
||||
end
|
||||
@header['content-type'].params.replace param if param
|
||||
|
||||
str
|
||||
end
|
||||
|
||||
alias content_type= set_content_type
|
||||
|
||||
def type_param( name, default = nil )
|
||||
if h = @header['content-type']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset( default = nil )
|
||||
if h = @header['content-type']
|
||||
h['charset'] or default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def charset=( str )
|
||||
if str
|
||||
if h = @header[ 'content-type' ]
|
||||
h['charset'] = str
|
||||
else
|
||||
store 'Content-Type', "text/plain; charset=#{str}"
|
||||
end
|
||||
end
|
||||
str
|
||||
end
|
||||
|
||||
|
||||
def transfer_encoding( default = nil )
|
||||
if h = @header['content-transfer-encoding']
|
||||
h.encoding || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
def transfer_encoding=( str )
|
||||
set_string_attr 'Content-Transfer-Encoding', str
|
||||
end
|
||||
|
||||
alias encoding transfer_encoding
|
||||
alias encoding= transfer_encoding=
|
||||
alias content_transfer_encoding transfer_encoding
|
||||
alias content_transfer_encoding= transfer_encoding=
|
||||
|
||||
|
||||
def disposition( default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias content_disposition disposition
|
||||
|
||||
def set_disposition( str, params = nil )
|
||||
if h = @header['content-disposition']
|
||||
h.disposition = str
|
||||
h.params.clear
|
||||
else
|
||||
store('Content-Disposition', str)
|
||||
h = @header['content-disposition']
|
||||
end
|
||||
h.params.replace params if params
|
||||
end
|
||||
|
||||
alias disposition= set_disposition
|
||||
alias set_content_disposition set_disposition
|
||||
alias content_disposition= set_disposition
|
||||
|
||||
def disposition_param( name, default = nil )
|
||||
if h = @header['content-disposition']
|
||||
h[name] || default
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### utils
|
||||
###
|
||||
|
||||
def create_reply
|
||||
mail = TMail::Mail.parse('')
|
||||
mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
|
||||
mail.to_addrs = reply_addresses([])
|
||||
mail.in_reply_to = [message_id(nil)].compact
|
||||
mail.references = references([]) + [message_id(nil)].compact
|
||||
mail.mime_version = '1.0'
|
||||
mail
|
||||
end
|
||||
|
||||
|
||||
def base64_encode
|
||||
store 'Content-Transfer-Encoding', 'Base64'
|
||||
self.body = Base64.folding_encode(self.body)
|
||||
end
|
||||
|
||||
def base64_decode
|
||||
if /base64/i === self.transfer_encoding('')
|
||||
store 'Content-Transfer-Encoding', '8bit'
|
||||
self.body = Base64.decode(self.body, @config.strict_base64decode?)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def destinations( default = nil )
|
||||
ret = []
|
||||
%w( to cc bcc ).each do |nm|
|
||||
if h = @header[nm]
|
||||
h.addrs.each {|i| ret.push i.address }
|
||||
end
|
||||
end
|
||||
ret.empty? ? default : ret
|
||||
end
|
||||
|
||||
def each_destination( &block )
|
||||
destinations([]).each do |i|
|
||||
if Address === i
|
||||
yield i
|
||||
else
|
||||
i.each(&block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
alias each_dest each_destination
|
||||
|
||||
|
||||
def reply_addresses( default = nil )
|
||||
reply_to_addrs(nil) or from_addrs(nil) or default
|
||||
end
|
||||
|
||||
def error_reply_addresses( default = nil )
|
||||
if s = sender(nil)
|
||||
[s]
|
||||
else
|
||||
from_addrs(default)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def multipart?
|
||||
main_type('').downcase == 'multipart'
|
||||
end
|
||||
|
||||
end # class Mail
|
||||
|
||||
end # module TMail
|
||||
914
actionmailer/lib/action_mailer/vendor/tmail/header.rb
vendored
Executable file
914
actionmailer/lib/action_mailer/vendor/tmail/header.rb
vendored
Executable file
@@ -0,0 +1,914 @@
|
||||
#
|
||||
# header.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/encode'
|
||||
require 'tmail/address'
|
||||
require 'tmail/parser'
|
||||
require 'tmail/config'
|
||||
require 'tmail/utils'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class HeaderField
|
||||
|
||||
include TextUtils
|
||||
|
||||
class << self
|
||||
|
||||
alias newobj new
|
||||
|
||||
def new( name, body, conf = DEFAULT_CONFIG )
|
||||
klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
|
||||
klass.newobj body, conf
|
||||
end
|
||||
|
||||
def new_from_port( port, name, conf = DEFAULT_CONFIG )
|
||||
re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
|
||||
str = nil
|
||||
port.ropen {|f|
|
||||
f.each do |line|
|
||||
if m = re.match(line) then str = m.post_match.strip
|
||||
elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
|
||||
elsif /\A-*\s*\z/ === line then break
|
||||
elsif str then break
|
||||
end
|
||||
end
|
||||
}
|
||||
new(name, str, Config.to_config(conf))
|
||||
end
|
||||
|
||||
def internal_new( name, conf )
|
||||
FNAME_TO_CLASS[name].newobj('', conf, true)
|
||||
end
|
||||
|
||||
end # class << self
|
||||
|
||||
def initialize( body, conf, intern = false )
|
||||
@body = body
|
||||
@config = conf
|
||||
|
||||
@illegal = false
|
||||
@parsed = false
|
||||
if intern
|
||||
@parsed = true
|
||||
parse_init
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@body.inspect}>"
|
||||
end
|
||||
|
||||
def illegal?
|
||||
@illegal
|
||||
end
|
||||
|
||||
def empty?
|
||||
ensure_parsed
|
||||
return true if @illegal
|
||||
isempty?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_parsed
|
||||
return if @parsed
|
||||
@parsed = true
|
||||
parse
|
||||
end
|
||||
|
||||
# defabstract parse
|
||||
# end
|
||||
|
||||
def clear_parse_status
|
||||
@parsed = false
|
||||
@illegal = false
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def body
|
||||
ensure_parsed
|
||||
v = Decoder.new(s = '')
|
||||
do_accept v
|
||||
v.terminate
|
||||
s
|
||||
end
|
||||
|
||||
def body=( str )
|
||||
@body = str
|
||||
clear_parse_status
|
||||
end
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def accept( strategy, dummy1 = nil, dummy2 = nil )
|
||||
ensure_parsed
|
||||
do_accept strategy
|
||||
strategy.terminate
|
||||
end
|
||||
|
||||
# abstract do_accept
|
||||
|
||||
end
|
||||
|
||||
|
||||
class UnstructuredHeader < HeaderField
|
||||
|
||||
def body
|
||||
ensure_parsed
|
||||
@body
|
||||
end
|
||||
|
||||
def body=( arg )
|
||||
ensure_parsed
|
||||
@body = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_init
|
||||
end
|
||||
|
||||
def parse
|
||||
@body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @body
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.text @body
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class StructuredHeader < HeaderField
|
||||
|
||||
def comments
|
||||
ensure_parsed
|
||||
@comments
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse
|
||||
save = nil
|
||||
|
||||
begin
|
||||
parse_init
|
||||
do_parse
|
||||
rescue SyntaxError
|
||||
if not save and mime_encoded? @body
|
||||
save = @body
|
||||
@body = Decoder.decode(save)
|
||||
retry
|
||||
elsif save
|
||||
@body = save
|
||||
end
|
||||
|
||||
@illegal = true
|
||||
raise if @config.strict_parse?
|
||||
end
|
||||
end
|
||||
|
||||
def parse_init
|
||||
@comments = []
|
||||
init
|
||||
end
|
||||
|
||||
def do_parse
|
||||
obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
|
||||
set obj if obj
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class DateTimeHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :DATETIME
|
||||
|
||||
def date
|
||||
ensure_parsed
|
||||
@date
|
||||
end
|
||||
|
||||
def date=( arg )
|
||||
ensure_parsed
|
||||
@date = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@date = nil
|
||||
end
|
||||
|
||||
def set( t )
|
||||
@date = t
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @date
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta time2str(@date)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddressHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :MADDRESS
|
||||
|
||||
def addrs
|
||||
ensure_parsed
|
||||
@addrs
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@addrs = []
|
||||
end
|
||||
|
||||
def set( a )
|
||||
@addrs = a
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@addrs.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@addrs.each do |a|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
strategy.space
|
||||
end
|
||||
a.accept strategy
|
||||
end
|
||||
|
||||
@comments.each do |c|
|
||||
strategy.space
|
||||
strategy.meta '('
|
||||
strategy.text c
|
||||
strategy.meta ')'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReturnPathHeader < AddressHeader
|
||||
|
||||
PARSE_TYPE = :RETPATH
|
||||
|
||||
def addr
|
||||
addrs()[0]
|
||||
end
|
||||
|
||||
def spec
|
||||
a = addr() or return nil
|
||||
a.spec
|
||||
end
|
||||
|
||||
def routes
|
||||
a = addr() or return nil
|
||||
a.routes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_accept( strategy )
|
||||
a = addr()
|
||||
|
||||
strategy.meta '<'
|
||||
unless a.routes.empty?
|
||||
strategy.meta a.routes.map {|i| '@' + i }.join(',')
|
||||
strategy.meta ':'
|
||||
end
|
||||
spec = a.spec
|
||||
strategy.meta spec if spec
|
||||
strategy.meta '>'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class SingleAddressHeader < AddressHeader
|
||||
|
||||
def addr
|
||||
addrs()[0]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def do_accept( strategy )
|
||||
a = addr()
|
||||
a.accept strategy
|
||||
@comments.each do |c|
|
||||
strategy.space
|
||||
strategy.meta '('
|
||||
strategy.text c
|
||||
strategy.meta ')'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MessageIdHeader < StructuredHeader
|
||||
|
||||
def id
|
||||
ensure_parsed
|
||||
@id
|
||||
end
|
||||
|
||||
def id=( arg )
|
||||
ensure_parsed
|
||||
@id = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@id = nil
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @id
|
||||
end
|
||||
|
||||
def do_parse
|
||||
@id = @body.slice(MESSAGE_ID) or
|
||||
raise SyntaxError, "wrong Message-ID format: #{@body}"
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReferencesHeader < StructuredHeader
|
||||
|
||||
def refs
|
||||
ensure_parsed
|
||||
@refs
|
||||
end
|
||||
|
||||
def each_id
|
||||
self.refs.each do |i|
|
||||
yield i if MESSAGE_ID === i
|
||||
end
|
||||
end
|
||||
|
||||
def ids
|
||||
ensure_parsed
|
||||
@ids
|
||||
end
|
||||
|
||||
def each_phrase
|
||||
self.refs.each do |i|
|
||||
yield i unless MESSAGE_ID === i
|
||||
end
|
||||
end
|
||||
|
||||
def phrases
|
||||
ret = []
|
||||
each_phrase {|i| ret.push i }
|
||||
ret
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@refs = []
|
||||
@ids = []
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@ids.empty?
|
||||
end
|
||||
|
||||
def do_parse
|
||||
str = @body
|
||||
while m = MESSAGE_ID.match(str)
|
||||
pre = m.pre_match.strip
|
||||
@refs.push pre unless pre.empty?
|
||||
@refs.push s = m[0]
|
||||
@ids.push s
|
||||
str = m.post_match
|
||||
end
|
||||
str = str.strip
|
||||
@refs.push str unless str.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@ids.each do |i|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.space
|
||||
end
|
||||
strategy.meta i
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ReceivedHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :RECEIVED
|
||||
|
||||
def from
|
||||
ensure_parsed
|
||||
@from
|
||||
end
|
||||
|
||||
def from=( arg )
|
||||
ensure_parsed
|
||||
@from = arg
|
||||
end
|
||||
|
||||
def by
|
||||
ensure_parsed
|
||||
@by
|
||||
end
|
||||
|
||||
def by=( arg )
|
||||
ensure_parsed
|
||||
@by = arg
|
||||
end
|
||||
|
||||
def via
|
||||
ensure_parsed
|
||||
@via
|
||||
end
|
||||
|
||||
def via=( arg )
|
||||
ensure_parsed
|
||||
@via = arg
|
||||
end
|
||||
|
||||
def with
|
||||
ensure_parsed
|
||||
@with
|
||||
end
|
||||
|
||||
def id
|
||||
ensure_parsed
|
||||
@id
|
||||
end
|
||||
|
||||
def id=( arg )
|
||||
ensure_parsed
|
||||
@id = arg
|
||||
end
|
||||
|
||||
def _for
|
||||
ensure_parsed
|
||||
@_for
|
||||
end
|
||||
|
||||
def _for=( arg )
|
||||
ensure_parsed
|
||||
@_for = arg
|
||||
end
|
||||
|
||||
def date
|
||||
ensure_parsed
|
||||
@date
|
||||
end
|
||||
|
||||
def date=( arg )
|
||||
ensure_parsed
|
||||
@date = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@from = @by = @via = @with = @id = @_for = nil
|
||||
@with = []
|
||||
@date = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@from, @by, @via, @with, @id, @_for, @date = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@with.empty? and not (@from or @by or @via or @id or @_for or @date)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
list = []
|
||||
list.push 'from ' + @from if @from
|
||||
list.push 'by ' + @by if @by
|
||||
list.push 'via ' + @via if @via
|
||||
@with.each do |i|
|
||||
list.push 'with ' + i
|
||||
end
|
||||
list.push 'id ' + @id if @id
|
||||
list.push 'for <' + @_for + '>' if @_for
|
||||
|
||||
first = true
|
||||
list.each do |i|
|
||||
strategy.space unless first
|
||||
strategy.meta i
|
||||
first = false
|
||||
end
|
||||
if @date
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.meta time2str(@date)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class KeywordsHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :KEYWORDS
|
||||
|
||||
def keys
|
||||
ensure_parsed
|
||||
@keys
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@keys = []
|
||||
end
|
||||
|
||||
def set( a )
|
||||
@keys = a
|
||||
end
|
||||
|
||||
def isempty?
|
||||
@keys.empty?
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
first = true
|
||||
@keys.each do |i|
|
||||
if first
|
||||
first = false
|
||||
else
|
||||
strategy.meta ','
|
||||
end
|
||||
strategy.meta i
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class EncryptedHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :ENCRYPTED
|
||||
|
||||
def encrypter
|
||||
ensure_parsed
|
||||
@encrypter
|
||||
end
|
||||
|
||||
def encrypter=( arg )
|
||||
ensure_parsed
|
||||
@encrypter = arg
|
||||
end
|
||||
|
||||
def keyword
|
||||
ensure_parsed
|
||||
@keyword
|
||||
end
|
||||
|
||||
def keyword=( arg )
|
||||
ensure_parsed
|
||||
@keyword = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@encrypter = nil
|
||||
@keyword = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@encrypter, @keyword = args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@encrypter or @keyword)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
if @key
|
||||
strategy.meta @encrypter + ','
|
||||
strategy.space
|
||||
strategy.meta @keyword
|
||||
else
|
||||
strategy.meta @encrypter
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MimeVersionHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :MIMEVERSION
|
||||
|
||||
def major
|
||||
ensure_parsed
|
||||
@major
|
||||
end
|
||||
|
||||
def major=( arg )
|
||||
ensure_parsed
|
||||
@major = arg
|
||||
end
|
||||
|
||||
def minor
|
||||
ensure_parsed
|
||||
@minor
|
||||
end
|
||||
|
||||
def minor=( arg )
|
||||
ensure_parsed
|
||||
@minor = arg
|
||||
end
|
||||
|
||||
def version
|
||||
sprintf('%d.%d', major, minor)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@major = nil
|
||||
@minor = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@major, @minor = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@major or @minor)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta sprintf('%d.%d', @major, @minor)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentTypeHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CTYPE
|
||||
|
||||
def main_type
|
||||
ensure_parsed
|
||||
@main
|
||||
end
|
||||
|
||||
def main_type=( arg )
|
||||
ensure_parsed
|
||||
@main = arg.downcase
|
||||
end
|
||||
|
||||
def sub_type
|
||||
ensure_parsed
|
||||
@sub
|
||||
end
|
||||
|
||||
def sub_type=( arg )
|
||||
ensure_parsed
|
||||
@sub = arg.downcase
|
||||
end
|
||||
|
||||
def content_type
|
||||
ensure_parsed
|
||||
@sub ? sprintf('%s/%s', @main, @sub) : @main
|
||||
end
|
||||
|
||||
def params
|
||||
ensure_parsed
|
||||
@params
|
||||
end
|
||||
|
||||
def []( key )
|
||||
ensure_parsed
|
||||
@params and @params[key]
|
||||
end
|
||||
|
||||
def []=( key, val )
|
||||
ensure_parsed
|
||||
(@params ||= {})[key] = val
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@main = @sub = @params = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@main, @sub, @params = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not (@main or @sub)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
if @sub
|
||||
strategy.meta sprintf('%s/%s', @main, @sub)
|
||||
else
|
||||
strategy.meta @main
|
||||
end
|
||||
@params.each do |k,v|
|
||||
if v
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.kv_pair k, v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentTransferEncodingHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CENCODING
|
||||
|
||||
def encoding
|
||||
ensure_parsed
|
||||
@encoding
|
||||
end
|
||||
|
||||
def encoding=( arg )
|
||||
ensure_parsed
|
||||
@encoding = arg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@encoding = nil
|
||||
end
|
||||
|
||||
def set( s )
|
||||
@encoding = s
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @encoding
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @encoding.capitalize
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class ContentDispositionHeader < StructuredHeader
|
||||
|
||||
PARSE_TYPE = :CDISPOSITION
|
||||
|
||||
def disposition
|
||||
ensure_parsed
|
||||
@disposition
|
||||
end
|
||||
|
||||
def disposition=( str )
|
||||
ensure_parsed
|
||||
@disposition = str.downcase
|
||||
end
|
||||
|
||||
def params
|
||||
ensure_parsed
|
||||
@params
|
||||
end
|
||||
|
||||
def []( key )
|
||||
ensure_parsed
|
||||
@params and @params[key]
|
||||
end
|
||||
|
||||
def []=( key, val )
|
||||
ensure_parsed
|
||||
(@params ||= {})[key] = val
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def init
|
||||
@disposition = @params = nil
|
||||
end
|
||||
|
||||
def set( args )
|
||||
@disposition, @params = *args
|
||||
end
|
||||
|
||||
def isempty?
|
||||
not @disposition and (not @params or @params.empty?)
|
||||
end
|
||||
|
||||
def do_accept( strategy )
|
||||
strategy.meta @disposition
|
||||
@params.each do |k,v|
|
||||
strategy.meta ';'
|
||||
strategy.space
|
||||
strategy.kv_pair k, v
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class HeaderField # redefine
|
||||
|
||||
FNAME_TO_CLASS = {
|
||||
'date' => DateTimeHeader,
|
||||
'resent-date' => DateTimeHeader,
|
||||
'to' => AddressHeader,
|
||||
'cc' => AddressHeader,
|
||||
'bcc' => AddressHeader,
|
||||
'from' => AddressHeader,
|
||||
'reply-to' => AddressHeader,
|
||||
'resent-to' => AddressHeader,
|
||||
'resent-cc' => AddressHeader,
|
||||
'resent-bcc' => AddressHeader,
|
||||
'resent-from' => AddressHeader,
|
||||
'resent-reply-to' => AddressHeader,
|
||||
'sender' => SingleAddressHeader,
|
||||
'resent-sender' => SingleAddressHeader,
|
||||
'return-path' => ReturnPathHeader,
|
||||
'message-id' => MessageIdHeader,
|
||||
'resent-message-id' => MessageIdHeader,
|
||||
'in-reply-to' => ReferencesHeader,
|
||||
'received' => ReceivedHeader,
|
||||
'references' => ReferencesHeader,
|
||||
'keywords' => KeywordsHeader,
|
||||
'encrypted' => EncryptedHeader,
|
||||
'mime-version' => MimeVersionHeader,
|
||||
'content-type' => ContentTypeHeader,
|
||||
'content-transfer-encoding' => ContentTransferEncodingHeader,
|
||||
'content-disposition' => ContentDispositionHeader,
|
||||
'content-id' => MessageIdHeader,
|
||||
'subject' => UnstructuredHeader,
|
||||
'comments' => UnstructuredHeader,
|
||||
'content-description' => UnstructuredHeader
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
35
actionmailer/lib/action_mailer/vendor/tmail/info.rb
vendored
Executable file
35
actionmailer/lib/action_mailer/vendor/tmail/info.rb
vendored
Executable file
@@ -0,0 +1,35 @@
|
||||
#
|
||||
# info.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
Version = '0.10.7'
|
||||
Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
|
||||
|
||||
end
|
||||
1
actionmailer/lib/action_mailer/vendor/tmail/loader.rb
vendored
Executable file
1
actionmailer/lib/action_mailer/vendor/tmail/loader.rb
vendored
Executable file
@@ -0,0 +1 @@
|
||||
require 'tmail/mailbox'
|
||||
447
actionmailer/lib/action_mailer/vendor/tmail/mail.rb
vendored
Executable file
447
actionmailer/lib/action_mailer/vendor/tmail/mail.rb
vendored
Executable file
@@ -0,0 +1,447 @@
|
||||
#
|
||||
# mail.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/facade'
|
||||
require 'tmail/encode'
|
||||
require 'tmail/header'
|
||||
require 'tmail/port'
|
||||
require 'tmail/config'
|
||||
require 'tmail/utils'
|
||||
require 'tmail/attachments'
|
||||
require 'tmail/quoting'
|
||||
require 'socket'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
class << self
|
||||
def load( fname )
|
||||
new(FilePort.new(fname))
|
||||
end
|
||||
|
||||
alias load_from load
|
||||
alias loadfrom load
|
||||
|
||||
def parse( str )
|
||||
new(StringPort.new(str))
|
||||
end
|
||||
end
|
||||
|
||||
def initialize( port = nil, conf = DEFAULT_CONFIG )
|
||||
@port = port || StringPort.new
|
||||
@config = Config.to_config(conf)
|
||||
|
||||
@header = {}
|
||||
@body_port = nil
|
||||
@body_parsed = false
|
||||
@epilogue = ''
|
||||
@parts = []
|
||||
|
||||
@port.ropen {|f|
|
||||
parse_header f
|
||||
parse_body f unless @port.reproducible?
|
||||
}
|
||||
end
|
||||
|
||||
attr_reader :port
|
||||
|
||||
def inspect
|
||||
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
|
||||
end
|
||||
|
||||
#
|
||||
# to_s interfaces
|
||||
#
|
||||
|
||||
public
|
||||
|
||||
include StrategyInterface
|
||||
|
||||
def write_back( eol = "\n", charset = 'e' )
|
||||
parse_body
|
||||
@port.wopen {|stream| encoded eol, charset, stream }
|
||||
end
|
||||
|
||||
def accept( strategy )
|
||||
with_multipart_encoding(strategy) {
|
||||
ordered_each do |name, field|
|
||||
next if field.empty?
|
||||
strategy.header_name canonical(name)
|
||||
field.accept strategy
|
||||
strategy.puts
|
||||
end
|
||||
strategy.puts
|
||||
body_port().ropen {|r|
|
||||
strategy.write r.read
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def canonical( name )
|
||||
name.split(/-/).map {|s| s.capitalize }.join('-')
|
||||
end
|
||||
|
||||
def with_multipart_encoding( strategy )
|
||||
if parts().empty? # DO NOT USE @parts
|
||||
yield
|
||||
|
||||
else
|
||||
bound = ::TMail.new_boundary
|
||||
if @header.key? 'content-type'
|
||||
@header['content-type'].params['boundary'] = bound
|
||||
else
|
||||
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
|
||||
end
|
||||
|
||||
yield
|
||||
|
||||
parts().each do |tm|
|
||||
strategy.puts
|
||||
strategy.puts '--' + bound
|
||||
tm.accept strategy
|
||||
end
|
||||
strategy.puts
|
||||
strategy.puts '--' + bound + '--'
|
||||
strategy.write epilogue()
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
### header
|
||||
###
|
||||
|
||||
public
|
||||
|
||||
ALLOW_MULTIPLE = {
|
||||
'received' => true,
|
||||
'resent-date' => true,
|
||||
'resent-from' => true,
|
||||
'resent-sender' => true,
|
||||
'resent-to' => true,
|
||||
'resent-cc' => true,
|
||||
'resent-bcc' => true,
|
||||
'resent-message-id' => true,
|
||||
'comments' => true,
|
||||
'keywords' => true
|
||||
}
|
||||
USE_ARRAY = ALLOW_MULTIPLE
|
||||
|
||||
def header
|
||||
@header.dup
|
||||
end
|
||||
|
||||
def []( key )
|
||||
@header[key.downcase]
|
||||
end
|
||||
|
||||
def sub_header(key, param)
|
||||
(hdr = self[key]) ? hdr[param] : nil
|
||||
end
|
||||
|
||||
alias fetch []
|
||||
|
||||
def []=( key, val )
|
||||
dkey = key.downcase
|
||||
|
||||
if val.nil?
|
||||
@header.delete dkey
|
||||
return nil
|
||||
end
|
||||
|
||||
case val
|
||||
when String
|
||||
header = new_hf(key, val)
|
||||
when HeaderField
|
||||
;
|
||||
when Array
|
||||
ALLOW_MULTIPLE.include? dkey or
|
||||
raise ArgumentError, "#{key}: Header must not be multiple"
|
||||
@header[dkey] = val
|
||||
return val
|
||||
else
|
||||
header = new_hf(key, val.to_s)
|
||||
end
|
||||
if ALLOW_MULTIPLE.include? dkey
|
||||
(@header[dkey] ||= []).push header
|
||||
else
|
||||
@header[dkey] = header
|
||||
end
|
||||
|
||||
val
|
||||
end
|
||||
|
||||
alias store []=
|
||||
|
||||
def each_header
|
||||
@header.each do |key, val|
|
||||
[val].flatten.each {|v| yield key, v }
|
||||
end
|
||||
end
|
||||
|
||||
alias each_pair each_header
|
||||
|
||||
def each_header_name( &block )
|
||||
@header.each_key(&block)
|
||||
end
|
||||
|
||||
alias each_key each_header_name
|
||||
|
||||
def each_field( &block )
|
||||
@header.values.flatten.each(&block)
|
||||
end
|
||||
|
||||
alias each_value each_field
|
||||
|
||||
FIELD_ORDER = %w(
|
||||
return-path received
|
||||
resent-date resent-from resent-sender resent-to
|
||||
resent-cc resent-bcc resent-message-id
|
||||
date from sender reply-to to cc bcc
|
||||
message-id in-reply-to references
|
||||
subject comments keywords
|
||||
mime-version content-type content-transfer-encoding
|
||||
content-disposition content-description
|
||||
)
|
||||
|
||||
def ordered_each
|
||||
list = @header.keys
|
||||
FIELD_ORDER.each do |name|
|
||||
if list.delete(name)
|
||||
[@header[name]].flatten.each {|v| yield name, v }
|
||||
end
|
||||
end
|
||||
list.each do |name|
|
||||
[@header[name]].flatten.each {|v| yield name, v }
|
||||
end
|
||||
end
|
||||
|
||||
def clear
|
||||
@header.clear
|
||||
end
|
||||
|
||||
def delete( key )
|
||||
@header.delete key.downcase
|
||||
end
|
||||
|
||||
def delete_if
|
||||
@header.delete_if do |key,val|
|
||||
if Array === val
|
||||
val.delete_if {|v| yield key, v }
|
||||
val.empty?
|
||||
else
|
||||
yield key, val
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def keys
|
||||
@header.keys
|
||||
end
|
||||
|
||||
def key?( key )
|
||||
@header.key? key.downcase
|
||||
end
|
||||
|
||||
def values_at( *args )
|
||||
args.map {|k| @header[k.downcase] }.flatten
|
||||
end
|
||||
|
||||
alias indexes values_at
|
||||
alias indices values_at
|
||||
|
||||
private
|
||||
|
||||
def parse_header( f )
|
||||
name = field = nil
|
||||
unixfrom = nil
|
||||
|
||||
while line = f.gets
|
||||
case line
|
||||
when /\A[ \t]/ # continue from prev line
|
||||
raise SyntaxError, 'mail is began by space' unless field
|
||||
field << ' ' << line.strip
|
||||
|
||||
when /\A([^\: \t]+):\s*/ # new header line
|
||||
add_hf name, field if field
|
||||
name = $1
|
||||
field = $' #.strip
|
||||
|
||||
when /\A\-*\s*\z/ # end of header
|
||||
add_hf name, field if field
|
||||
name = field = nil
|
||||
break
|
||||
|
||||
when /\AFrom (\S+)/
|
||||
unixfrom = $1
|
||||
|
||||
when /^charset=.*/
|
||||
|
||||
else
|
||||
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
|
||||
end
|
||||
end
|
||||
add_hf name, field if name
|
||||
|
||||
if unixfrom
|
||||
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
|
||||
end
|
||||
end
|
||||
|
||||
def add_hf( name, field )
|
||||
key = name.downcase
|
||||
field = new_hf(name, field)
|
||||
|
||||
if ALLOW_MULTIPLE.include? key
|
||||
(@header[key] ||= []).push field
|
||||
else
|
||||
@header[key] = field
|
||||
end
|
||||
end
|
||||
|
||||
def new_hf( name, field )
|
||||
HeaderField.new(name, field, @config)
|
||||
end
|
||||
|
||||
###
|
||||
### body
|
||||
###
|
||||
|
||||
public
|
||||
|
||||
def body_port
|
||||
parse_body
|
||||
@body_port
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
body_port().ropen {|f| f.each(&block) }
|
||||
end
|
||||
|
||||
def quoted_body
|
||||
parse_body
|
||||
@body_port.ropen {|f|
|
||||
return f.read
|
||||
}
|
||||
end
|
||||
|
||||
def body=( str )
|
||||
parse_body
|
||||
@body_port.wopen {|f| f.write str }
|
||||
str
|
||||
end
|
||||
|
||||
alias preamble body
|
||||
alias preamble= body=
|
||||
|
||||
def epilogue
|
||||
parse_body
|
||||
@epilogue.dup
|
||||
end
|
||||
|
||||
def epilogue=( str )
|
||||
parse_body
|
||||
@epilogue = str
|
||||
str
|
||||
end
|
||||
|
||||
def parts
|
||||
parse_body
|
||||
@parts
|
||||
end
|
||||
|
||||
def each_part( &block )
|
||||
parts().each(&block)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_body( f = nil )
|
||||
return if @body_parsed
|
||||
if f
|
||||
parse_body_0 f
|
||||
else
|
||||
@port.ropen {|f|
|
||||
skip_header f
|
||||
parse_body_0 f
|
||||
}
|
||||
end
|
||||
@body_parsed = true
|
||||
end
|
||||
|
||||
def skip_header( f )
|
||||
while line = f.gets
|
||||
return if /\A[\r\n]*\z/ === line
|
||||
end
|
||||
end
|
||||
|
||||
def parse_body_0( f )
|
||||
if multipart?
|
||||
read_multipart f
|
||||
else
|
||||
@body_port = @config.new_body_port(self)
|
||||
@body_port.wopen {|w|
|
||||
w.write f.read
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def read_multipart( src )
|
||||
bound = @header['content-type'].params['boundary']
|
||||
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
|
||||
lastbound = "--#{bound}--"
|
||||
|
||||
ports = [ @config.new_preamble_port(self) ]
|
||||
begin
|
||||
f = ports.last.wopen
|
||||
while line = src.gets
|
||||
if is_sep === line
|
||||
f.close
|
||||
break if line.strip == lastbound
|
||||
ports.push @config.new_part_port(self)
|
||||
f = ports.last.wopen
|
||||
else
|
||||
f << line
|
||||
end
|
||||
end
|
||||
@epilogue = (src.read || '')
|
||||
ensure
|
||||
f.close if f and not f.closed?
|
||||
end
|
||||
|
||||
@body_port = ports.shift
|
||||
@parts = ports.map {|p| self.class.new(p, @config) }
|
||||
end
|
||||
|
||||
end # class Mail
|
||||
|
||||
end # module TMail
|
||||
433
actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb
vendored
Executable file
433
actionmailer/lib/action_mailer/vendor/tmail/mailbox.rb
vendored
Executable file
@@ -0,0 +1,433 @@
|
||||
#
|
||||
# mailbox.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/port'
|
||||
require 'socket'
|
||||
require 'mutex_m'
|
||||
|
||||
|
||||
unless [].respond_to?(:sort_by)
|
||||
module Enumerable#:nodoc:
|
||||
def sort_by
|
||||
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class MhMailbox
|
||||
|
||||
PORT_CLASS = MhPort
|
||||
|
||||
def initialize( dir )
|
||||
edir = File.expand_path(dir)
|
||||
raise ArgumentError, "not directory: #{dir}"\
|
||||
unless FileTest.directory? edir
|
||||
@dirname = edir
|
||||
@last_file = nil
|
||||
@last_atime = nil
|
||||
end
|
||||
|
||||
def directory
|
||||
@dirname
|
||||
end
|
||||
|
||||
alias dirname directory
|
||||
|
||||
attr_accessor :last_atime
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@dirname}>"
|
||||
end
|
||||
|
||||
def close
|
||||
end
|
||||
|
||||
def new_port
|
||||
PORT_CLASS.new(next_file_name())
|
||||
end
|
||||
|
||||
def each_port
|
||||
mail_files().each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port
|
||||
mail_files().reverse_each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
# old #each_mail returns Port
|
||||
#def each_mail
|
||||
# each_port do |port|
|
||||
# yield Mail.new(port)
|
||||
# end
|
||||
#end
|
||||
|
||||
def each_new_port( mtime = nil, &block )
|
||||
mtime ||= @last_atime
|
||||
return each_port(&block) unless mtime
|
||||
return unless File.mtime(@dirname) >= mtime
|
||||
|
||||
mail_files().each do |path|
|
||||
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
|
||||
end
|
||||
@last_atime = Time.now
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mail_files
|
||||
Dir.entries(@dirname)\
|
||||
.select {|s| /\A\d+\z/ === s }\
|
||||
.map {|s| s.to_i }\
|
||||
.sort\
|
||||
.map {|i| "#{@dirname}/#{i}" }\
|
||||
.select {|path| FileTest.file? path }
|
||||
end
|
||||
|
||||
def next_file_name
|
||||
unless n = @last_file
|
||||
n = 0
|
||||
Dir.entries(@dirname)\
|
||||
.select {|s| /\A\d+\z/ === s }\
|
||||
.map {|s| s.to_i }.sort\
|
||||
.each do |i|
|
||||
next unless FileTest.file? "#{@dirname}/#{i}"
|
||||
n = i
|
||||
end
|
||||
end
|
||||
begin
|
||||
n += 1
|
||||
end while FileTest.exist? "#{@dirname}/#{n}"
|
||||
@last_file = n
|
||||
|
||||
"#{@dirname}/#{n}"
|
||||
end
|
||||
|
||||
end # MhMailbox
|
||||
|
||||
MhLoader = MhMailbox
|
||||
|
||||
|
||||
class UNIXMbox
|
||||
|
||||
def UNIXMbox.lock( fname )
|
||||
begin
|
||||
f = File.open(fname)
|
||||
f.flock File::LOCK_EX
|
||||
yield f
|
||||
ensure
|
||||
f.flock File::LOCK_UN
|
||||
f.close if f and not f.closed?
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
alias newobj new
|
||||
end
|
||||
|
||||
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
|
||||
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
||||
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
||||
end
|
||||
|
||||
def UNIXMbox.static_new( fname, dir, readonly = false )
|
||||
newobj(fname, dir, readonly, true)
|
||||
end
|
||||
|
||||
def initialize( fname, mhdir, readonly, static )
|
||||
@filename = fname
|
||||
@readonly = readonly
|
||||
@closed = false
|
||||
|
||||
Dir.mkdir mhdir
|
||||
@real = MhMailbox.new(mhdir)
|
||||
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
|
||||
ObjectSpace.define_finalizer self, @finalizer
|
||||
end
|
||||
|
||||
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
|
||||
lambda {
|
||||
if writeback_p
|
||||
lock(mboxfile) {|f|
|
||||
mh.each_port do |port|
|
||||
f.puts create_from_line(port)
|
||||
port.ropen {|r|
|
||||
f.puts r.read
|
||||
}
|
||||
end
|
||||
}
|
||||
end
|
||||
if cleanup_p
|
||||
Dir.foreach(mh.dirname) do |fname|
|
||||
next if /\A\.\.?\z/ === fname
|
||||
File.unlink "#{mh.dirname}/#{fname}"
|
||||
end
|
||||
Dir.rmdir mh.dirname
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# make _From line
|
||||
def UNIXMbox.create_from_line( port )
|
||||
sprintf 'From %s %s',
|
||||
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
||||
end
|
||||
|
||||
def UNIXMbox.fromaddr
|
||||
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
||||
HeaderField.new_from_port(port, 'From') or return 'nobody'
|
||||
a = h.addrs[0] or return 'nobody'
|
||||
a.spec
|
||||
end
|
||||
private_class_method :fromaddr
|
||||
|
||||
def close
|
||||
return if @closed
|
||||
|
||||
ObjectSpace.undefine_finalizer self
|
||||
@finalizer.call
|
||||
@finalizer = nil
|
||||
@real = nil
|
||||
@closed = true
|
||||
@updated = nil
|
||||
end
|
||||
|
||||
def each_port( &block )
|
||||
close_check
|
||||
update
|
||||
@real.each_port(&block)
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port( &block )
|
||||
close_check
|
||||
update
|
||||
@real.reverse_each_port(&block)
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
# old #each_mail returns Port
|
||||
#def each_mail( &block )
|
||||
# each_port do |port|
|
||||
# yield Mail.new(port)
|
||||
# end
|
||||
#end
|
||||
|
||||
def each_new_port( mtime = nil )
|
||||
close_check
|
||||
update
|
||||
@real.each_new_port(mtime) {|p| yield p }
|
||||
end
|
||||
|
||||
def new_port
|
||||
close_check
|
||||
@real.new_port
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def close_check
|
||||
@closed and raise ArgumentError, 'accessing already closed mbox'
|
||||
end
|
||||
|
||||
def update
|
||||
return if FileTest.zero?(@filename)
|
||||
return if @updated and File.mtime(@filename) < @updated
|
||||
w = nil
|
||||
port = nil
|
||||
time = nil
|
||||
UNIXMbox.lock(@filename) {|f|
|
||||
begin
|
||||
f.each do |line|
|
||||
if /\AFrom / === line
|
||||
w.close if w
|
||||
File.utime time, time, port.filename if time
|
||||
|
||||
port = @real.new_port
|
||||
w = port.wopen
|
||||
time = fromline2time(line)
|
||||
else
|
||||
w.print line if w
|
||||
end
|
||||
end
|
||||
ensure
|
||||
if w and not w.closed?
|
||||
w.close
|
||||
File.utime time, time, port.filename if time
|
||||
end
|
||||
end
|
||||
f.truncate(0) unless @readonly
|
||||
@updated = Time.now
|
||||
}
|
||||
end
|
||||
|
||||
def fromline2time( line )
|
||||
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
|
||||
or return nil
|
||||
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
|
||||
end
|
||||
|
||||
end # UNIXMbox
|
||||
|
||||
MboxLoader = UNIXMbox
|
||||
|
||||
|
||||
class Maildir
|
||||
|
||||
extend Mutex_m
|
||||
|
||||
PORT_CLASS = MaildirPort
|
||||
|
||||
@seq = 0
|
||||
def Maildir.unique_number
|
||||
synchronize {
|
||||
@seq += 1
|
||||
return @seq
|
||||
}
|
||||
end
|
||||
|
||||
def initialize( dir = nil )
|
||||
@dirname = dir || ENV['MAILDIR']
|
||||
raise ArgumentError, "not directory: #{@dirname}"\
|
||||
unless FileTest.directory? @dirname
|
||||
@new = "#{@dirname}/new"
|
||||
@tmp = "#{@dirname}/tmp"
|
||||
@cur = "#{@dirname}/cur"
|
||||
end
|
||||
|
||||
def directory
|
||||
@dirname
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@dirname}>"
|
||||
end
|
||||
|
||||
def close
|
||||
end
|
||||
|
||||
def each_port
|
||||
mail_files(@cur).each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
end
|
||||
|
||||
alias each each_port
|
||||
|
||||
def reverse_each_port
|
||||
mail_files(@cur).reverse_each do |path|
|
||||
yield PORT_CLASS.new(path)
|
||||
end
|
||||
end
|
||||
|
||||
alias reverse_each reverse_each_port
|
||||
|
||||
def new_port
|
||||
fname = nil
|
||||
tmpfname = nil
|
||||
newfname = nil
|
||||
|
||||
begin
|
||||
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
|
||||
|
||||
tmpfname = "#{@tmp}/#{fname}"
|
||||
newfname = "#{@new}/#{fname}"
|
||||
end while FileTest.exist? tmpfname
|
||||
|
||||
if block_given?
|
||||
File.open(tmpfname, 'w') {|f| yield f }
|
||||
File.rename tmpfname, newfname
|
||||
PORT_CLASS.new(newfname)
|
||||
else
|
||||
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
|
||||
PORT_CLASS.new(tmpfname)
|
||||
end
|
||||
end
|
||||
|
||||
def each_new_port
|
||||
mail_files(@new).each do |path|
|
||||
dest = @cur + '/' + File.basename(path)
|
||||
File.rename path, dest
|
||||
yield PORT_CLASS.new(dest)
|
||||
end
|
||||
|
||||
check_tmp
|
||||
end
|
||||
|
||||
TOO_OLD = 60 * 60 * 36 # 36 hour
|
||||
|
||||
def check_tmp
|
||||
old = Time.now.to_i - TOO_OLD
|
||||
|
||||
each_filename(@tmp) do |full, fname|
|
||||
if FileTest.file? full and
|
||||
File.stat(full).mtime.to_i < old
|
||||
File.unlink full
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mail_files( dir )
|
||||
Dir.entries(dir)\
|
||||
.select {|s| s[0] != ?. }\
|
||||
.sort_by {|s| s.slice(/\A\d+/).to_i }\
|
||||
.map {|s| "#{dir}/#{s}" }\
|
||||
.select {|path| FileTest.file? path }
|
||||
end
|
||||
|
||||
def each_filename( dir )
|
||||
Dir.foreach(dir) do |fname|
|
||||
path = "#{dir}/#{fname}"
|
||||
if fname[0] != ?. and FileTest.file? path
|
||||
yield path, fname
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end # Maildir
|
||||
|
||||
MaildirLoader = Maildir
|
||||
|
||||
end # module TMail
|
||||
1
actionmailer/lib/action_mailer/vendor/tmail/mbox.rb
vendored
Executable file
1
actionmailer/lib/action_mailer/vendor/tmail/mbox.rb
vendored
Executable file
@@ -0,0 +1 @@
|
||||
require 'tmail/mailbox'
|
||||
280
actionmailer/lib/action_mailer/vendor/tmail/net.rb
vendored
Executable file
280
actionmailer/lib/action_mailer/vendor/tmail/net.rb
vendored
Executable file
@@ -0,0 +1,280 @@
|
||||
#
|
||||
# net.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'nkf'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Mail
|
||||
|
||||
def send_to( smtp )
|
||||
do_send_to(smtp) do
|
||||
ready_to_send
|
||||
end
|
||||
end
|
||||
|
||||
def send_text_to( smtp )
|
||||
do_send_to(smtp) do
|
||||
ready_to_send
|
||||
mime_encode
|
||||
end
|
||||
end
|
||||
|
||||
def do_send_to( smtp )
|
||||
from = from_address or raise ArgumentError, 'no from address'
|
||||
(dests = destinations).empty? and raise ArgumentError, 'no receipient'
|
||||
yield
|
||||
send_to_0 smtp, from, dests
|
||||
end
|
||||
private :do_send_to
|
||||
|
||||
def send_to_0( smtp, from, to )
|
||||
smtp.ready(from, to) do |f|
|
||||
encoded "\r\n", 'j', f, ''
|
||||
end
|
||||
end
|
||||
|
||||
def ready_to_send
|
||||
delete_no_send_fields
|
||||
add_message_id
|
||||
add_date
|
||||
end
|
||||
|
||||
NOSEND_FIELDS = %w(
|
||||
received
|
||||
bcc
|
||||
)
|
||||
|
||||
def delete_no_send_fields
|
||||
NOSEND_FIELDS.each do |nm|
|
||||
delete nm
|
||||
end
|
||||
delete_if {|n,v| v.empty? }
|
||||
end
|
||||
|
||||
def add_message_id( fqdn = nil )
|
||||
self.message_id = ::TMail::new_message_id(fqdn)
|
||||
end
|
||||
|
||||
def add_date
|
||||
self.date = Time.now
|
||||
end
|
||||
|
||||
def mime_encode
|
||||
if parts.empty?
|
||||
mime_encode_singlepart
|
||||
else
|
||||
mime_encode_multipart true
|
||||
end
|
||||
end
|
||||
|
||||
def mime_encode_singlepart
|
||||
self.mime_version = '1.0'
|
||||
b = body
|
||||
if NKF.guess(b) != NKF::BINARY
|
||||
mime_encode_text b
|
||||
else
|
||||
mime_encode_binary b
|
||||
end
|
||||
end
|
||||
|
||||
def mime_encode_text( body )
|
||||
self.body = NKF.nkf('-j -m0', body)
|
||||
self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
||||
self.encoding = '7bit'
|
||||
end
|
||||
|
||||
def mime_encode_binary( body )
|
||||
self.body = [body].pack('m')
|
||||
self.set_content_type 'application', 'octet-stream'
|
||||
self.encoding = 'Base64'
|
||||
end
|
||||
|
||||
def mime_encode_multipart( top = true )
|
||||
self.mime_version = '1.0' if top
|
||||
self.set_content_type 'multipart', 'mixed'
|
||||
e = encoding(nil)
|
||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
||||
raise ArgumentError,
|
||||
'using C.T.Encoding with multipart mail is not permitted'
|
||||
end
|
||||
end
|
||||
|
||||
def create_empty_mail
|
||||
self.class.new(StringPort.new(''), @config)
|
||||
end
|
||||
|
||||
def create_reply
|
||||
setup_reply create_empty_mail()
|
||||
end
|
||||
|
||||
def setup_reply( m )
|
||||
if tmp = reply_addresses(nil)
|
||||
m.to_addrs = tmp
|
||||
end
|
||||
|
||||
mid = message_id(nil)
|
||||
tmp = references(nil) || []
|
||||
tmp.push mid if mid
|
||||
m.in_reply_to = [mid] if mid
|
||||
m.references = tmp unless tmp.empty?
|
||||
m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
|
||||
|
||||
m
|
||||
end
|
||||
|
||||
def create_forward
|
||||
setup_forward create_empty_mail()
|
||||
end
|
||||
|
||||
def setup_forward( mail )
|
||||
m = Mail.new(StringPort.new(''))
|
||||
m.body = decoded
|
||||
m.set_content_type 'message', 'rfc822'
|
||||
m.encoding = encoding('7bit')
|
||||
mail.parts.push m
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class DeleteFields
|
||||
|
||||
NOSEND_FIELDS = %w(
|
||||
received
|
||||
bcc
|
||||
)
|
||||
|
||||
def initialize( nosend = nil, delempty = true )
|
||||
@no_send_fields = nosend || NOSEND_FIELDS.dup
|
||||
@delete_empty_fields = delempty
|
||||
end
|
||||
|
||||
attr :no_send_fields
|
||||
attr :delete_empty_fields, true
|
||||
|
||||
def exec( mail )
|
||||
@no_send_fields.each do |nm|
|
||||
delete nm
|
||||
end
|
||||
delete_if {|n,v| v.empty? } if @delete_empty_fields
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddMessageId
|
||||
|
||||
def initialize( fqdn = nil )
|
||||
@fqdn = fqdn
|
||||
end
|
||||
|
||||
attr :fqdn, true
|
||||
|
||||
def exec( mail )
|
||||
mail.message_id = ::TMail::new_msgid(@fqdn)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class AddDate
|
||||
|
||||
def exec( mail )
|
||||
mail.date = Time.now
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MimeEncodeAuto
|
||||
|
||||
def initialize( s = nil, m = nil )
|
||||
@singlepart_composer = s || MimeEncodeSingle.new
|
||||
@multipart_composer = m || MimeEncodeMulti.new
|
||||
end
|
||||
|
||||
attr :singlepart_composer
|
||||
attr :multipart_composer
|
||||
|
||||
def exec( mail )
|
||||
if mail._builtin_multipart?
|
||||
then @multipart_composer
|
||||
else @singlepart_composer end.exec mail
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MimeEncodeSingle
|
||||
|
||||
def exec( mail )
|
||||
mail.mime_version = '1.0'
|
||||
b = mail.body
|
||||
if NKF.guess(b) != NKF::BINARY
|
||||
on_text b
|
||||
else
|
||||
on_binary b
|
||||
end
|
||||
end
|
||||
|
||||
def on_text( body )
|
||||
mail.body = NKF.nkf('-j -m0', body)
|
||||
mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
|
||||
mail.encoding = '7bit'
|
||||
end
|
||||
|
||||
def on_binary( body )
|
||||
mail.body = [body].pack('m')
|
||||
mail.set_content_type 'application', 'octet-stream'
|
||||
mail.encoding = 'Base64'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MimeEncodeMulti
|
||||
|
||||
def exec( mail, top = true )
|
||||
mail.mime_version = '1.0' if top
|
||||
mail.set_content_type 'multipart', 'mixed'
|
||||
e = encoding(nil)
|
||||
if e and not /\A(?:7bit|8bit|binary)\z/i === e
|
||||
raise ArgumentError,
|
||||
'using C.T.Encoding with multipart mail is not permitted'
|
||||
end
|
||||
mail.parts.each do |m|
|
||||
exec m, false if m._builtin_multipart?
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
135
actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb
vendored
Executable file
135
actionmailer/lib/action_mailer/vendor/tmail/obsolete.rb
vendored
Executable file
@@ -0,0 +1,135 @@
|
||||
#
|
||||
# obsolete.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
# mail.rb
|
||||
class Mail
|
||||
alias include? key?
|
||||
alias has_key? key?
|
||||
|
||||
def values
|
||||
ret = []
|
||||
each_field {|v| ret.push v }
|
||||
ret
|
||||
end
|
||||
|
||||
def value?( val )
|
||||
HeaderField === val or return false
|
||||
|
||||
[ @header[val.name.downcase] ].flatten.include? val
|
||||
end
|
||||
|
||||
alias has_value? value?
|
||||
end
|
||||
|
||||
|
||||
# facade.rb
|
||||
class Mail
|
||||
def from_addr( default = nil )
|
||||
addr, = from_addrs(nil)
|
||||
addr || default
|
||||
end
|
||||
|
||||
def from_address( default = nil )
|
||||
if a = from_addr(nil)
|
||||
a.spec
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias from_address= from_addrs=
|
||||
|
||||
def from_phrase( default = nil )
|
||||
if a = from_addr(nil)
|
||||
a.phrase
|
||||
else
|
||||
default
|
||||
end
|
||||
end
|
||||
|
||||
alias msgid message_id
|
||||
alias msgid= message_id=
|
||||
|
||||
alias each_dest each_destination
|
||||
end
|
||||
|
||||
|
||||
# address.rb
|
||||
class Address
|
||||
alias route routes
|
||||
alias addr spec
|
||||
|
||||
def spec=( str )
|
||||
@local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
|
||||
end
|
||||
|
||||
alias addr= spec=
|
||||
alias address= spec=
|
||||
end
|
||||
|
||||
|
||||
# mbox.rb
|
||||
class MhMailbox
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
class UNIXMbox
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
class Maildir
|
||||
alias new_mail new_port
|
||||
alias each_mail each_port
|
||||
alias each_newmail each_new_port
|
||||
end
|
||||
|
||||
|
||||
# utils.rb
|
||||
extend TextUtils
|
||||
|
||||
class << self
|
||||
alias msgid? message_id?
|
||||
alias boundary new_boundary
|
||||
alias msgid new_message_id
|
||||
alias new_msgid new_message_id
|
||||
end
|
||||
|
||||
def Mail.boundary
|
||||
::TMail.new_boundary
|
||||
end
|
||||
|
||||
def Mail.msgid
|
||||
::TMail.new_message_id
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
1522
actionmailer/lib/action_mailer/vendor/tmail/parser.rb
vendored
Executable file
1522
actionmailer/lib/action_mailer/vendor/tmail/parser.rb
vendored
Executable file
File diff suppressed because it is too large
Load Diff
377
actionmailer/lib/action_mailer/vendor/tmail/port.rb
vendored
Executable file
377
actionmailer/lib/action_mailer/vendor/tmail/port.rb
vendored
Executable file
@@ -0,0 +1,377 @@
|
||||
#
|
||||
# port.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/stringio'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Port
|
||||
def reproducible?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### FilePort
|
||||
###
|
||||
|
||||
class FilePort < Port
|
||||
|
||||
def initialize( fname )
|
||||
@filename = File.expand_path(fname)
|
||||
super()
|
||||
end
|
||||
|
||||
attr_reader :filename
|
||||
|
||||
alias ident filename
|
||||
|
||||
def ==( other )
|
||||
other.respond_to?(:filename) and @filename == other.filename
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@filename.hash
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@filename}>"
|
||||
end
|
||||
|
||||
def reproducible?
|
||||
true
|
||||
end
|
||||
|
||||
def size
|
||||
File.size @filename
|
||||
end
|
||||
|
||||
|
||||
def ropen( &block )
|
||||
File.open(@filename, &block)
|
||||
end
|
||||
|
||||
def wopen( &block )
|
||||
File.open(@filename, 'w', &block)
|
||||
end
|
||||
|
||||
def aopen( &block )
|
||||
File.open(@filename, 'a', &block)
|
||||
end
|
||||
|
||||
|
||||
def read_all
|
||||
ropen {|f|
|
||||
return f.read
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
def remove
|
||||
File.unlink @filename
|
||||
end
|
||||
|
||||
def move_to( port )
|
||||
begin
|
||||
File.link @filename, port.filename
|
||||
rescue Errno::EXDEV
|
||||
copy_to port
|
||||
end
|
||||
File.unlink @filename
|
||||
end
|
||||
|
||||
alias mv move_to
|
||||
|
||||
def copy_to( port )
|
||||
if FilePort === port
|
||||
copy_file @filename, port.filename
|
||||
else
|
||||
File.open(@filename) {|r|
|
||||
port.wopen {|w|
|
||||
while s = r.sysread(4096)
|
||||
w.write << s
|
||||
end
|
||||
} }
|
||||
end
|
||||
end
|
||||
|
||||
alias cp copy_to
|
||||
|
||||
private
|
||||
|
||||
# from fileutils.rb
|
||||
def copy_file( src, dest )
|
||||
st = r = w = nil
|
||||
|
||||
File.open(src, 'rb') {|r|
|
||||
File.open(dest, 'wb') {|w|
|
||||
st = r.stat
|
||||
begin
|
||||
while true
|
||||
w.write r.sysread(st.blksize)
|
||||
end
|
||||
rescue EOFError
|
||||
end
|
||||
} }
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
module MailFlags
|
||||
|
||||
def seen=( b )
|
||||
set_status 'S', b
|
||||
end
|
||||
|
||||
def seen?
|
||||
get_status 'S'
|
||||
end
|
||||
|
||||
def replied=( b )
|
||||
set_status 'R', b
|
||||
end
|
||||
|
||||
def replied?
|
||||
get_status 'R'
|
||||
end
|
||||
|
||||
def flagged=( b )
|
||||
set_status 'F', b
|
||||
end
|
||||
|
||||
def flagged?
|
||||
get_status 'F'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def procinfostr( str, tag, true_p )
|
||||
a = str.upcase.split(//)
|
||||
a.push true_p ? tag : nil
|
||||
a.delete tag unless true_p
|
||||
a.compact.sort.join('').squeeze
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MhPort < FilePort
|
||||
|
||||
include MailFlags
|
||||
|
||||
private
|
||||
|
||||
def set_status( tag, flag )
|
||||
begin
|
||||
tmpfile = @filename + '.tmailtmp.' + $$.to_s
|
||||
File.open(tmpfile, 'w') {|f|
|
||||
write_status f, tag, flag
|
||||
}
|
||||
File.unlink @filename
|
||||
File.link tmpfile, @filename
|
||||
ensure
|
||||
File.unlink tmpfile
|
||||
end
|
||||
end
|
||||
|
||||
def write_status( f, tag, flag )
|
||||
stat = ''
|
||||
File.open(@filename) {|r|
|
||||
while line = r.gets
|
||||
if line.strip.empty?
|
||||
break
|
||||
elsif m = /\AX-TMail-Status:/i.match(line)
|
||||
stat = m.post_match.strip
|
||||
else
|
||||
f.print line
|
||||
end
|
||||
end
|
||||
|
||||
s = procinfostr(stat, tag, flag)
|
||||
f.puts 'X-TMail-Status: ' + s unless s.empty?
|
||||
f.puts
|
||||
|
||||
while s = r.read(2048)
|
||||
f.write s
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def get_status( tag )
|
||||
File.foreach(@filename) {|line|
|
||||
return false if line.strip.empty?
|
||||
if m = /\AX-TMail-Status:/i.match(line)
|
||||
return m.post_match.strip.include?(tag[0])
|
||||
end
|
||||
}
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class MaildirPort < FilePort
|
||||
|
||||
def move_to_new
|
||||
new = replace_dir(@filename, 'new')
|
||||
File.rename @filename, new
|
||||
@filename = new
|
||||
end
|
||||
|
||||
def move_to_cur
|
||||
new = replace_dir(@filename, 'cur')
|
||||
File.rename @filename, new
|
||||
@filename = new
|
||||
end
|
||||
|
||||
def replace_dir( path, dir )
|
||||
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
|
||||
end
|
||||
private :replace_dir
|
||||
|
||||
|
||||
include MailFlags
|
||||
|
||||
private
|
||||
|
||||
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
|
||||
|
||||
def set_status( tag, flag )
|
||||
if m = MAIL_FILE.match(File.basename(@filename))
|
||||
s, uniq, type, info, = m.to_a
|
||||
return if type and type != '2' # do not change anything
|
||||
newname = File.dirname(@filename) + '/' +
|
||||
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
|
||||
else
|
||||
newname = @filename + ':2,' + tag
|
||||
end
|
||||
|
||||
File.link @filename, newname
|
||||
File.unlink @filename
|
||||
@filename = newname
|
||||
end
|
||||
|
||||
def get_status( tag )
|
||||
m = MAIL_FILE.match(File.basename(@filename)) or return false
|
||||
m[2] == '2' and m[3].to_s.include?(tag[0])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
###
|
||||
### StringPort
|
||||
###
|
||||
|
||||
class StringPort < Port
|
||||
|
||||
def initialize( str = '' )
|
||||
@buffer = str
|
||||
super()
|
||||
end
|
||||
|
||||
def string
|
||||
@buffer
|
||||
end
|
||||
|
||||
def to_s
|
||||
@buffer.dup
|
||||
end
|
||||
|
||||
alias read_all to_s
|
||||
|
||||
def size
|
||||
@buffer.size
|
||||
end
|
||||
|
||||
def ==( other )
|
||||
StringPort === other and @buffer.equal? other.string
|
||||
end
|
||||
|
||||
alias eql? ==
|
||||
|
||||
def hash
|
||||
@buffer.object_id.hash
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
|
||||
end
|
||||
|
||||
def reproducible?
|
||||
true
|
||||
end
|
||||
|
||||
def ropen( &block )
|
||||
@buffer or raise Errno::ENOENT, "#{inspect} is already removed"
|
||||
StringInput.open(@buffer, &block)
|
||||
end
|
||||
|
||||
def wopen( &block )
|
||||
@buffer = ''
|
||||
StringOutput.new(@buffer, &block)
|
||||
end
|
||||
|
||||
def aopen( &block )
|
||||
@buffer ||= ''
|
||||
StringOutput.new(@buffer, &block)
|
||||
end
|
||||
|
||||
def remove
|
||||
@buffer = nil
|
||||
end
|
||||
|
||||
alias rm remove
|
||||
|
||||
def copy_to( port )
|
||||
port.wopen {|f|
|
||||
f.write @buffer
|
||||
}
|
||||
end
|
||||
|
||||
alias cp copy_to
|
||||
|
||||
def move_to( port )
|
||||
if StringPort === port
|
||||
str = @buffer
|
||||
port.instance_eval { @buffer = str }
|
||||
else
|
||||
copy_to port
|
||||
end
|
||||
remove
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
131
actionmailer/lib/action_mailer/vendor/tmail/quoting.rb
vendored
Normal file
131
actionmailer/lib/action_mailer/vendor/tmail/quoting.rb
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
module TMail
|
||||
class Mail
|
||||
def subject(to_charset = 'utf-8')
|
||||
Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
|
||||
end
|
||||
|
||||
def unquoted_body(to_charset = 'utf-8')
|
||||
from_charset = sub_header("content-type", "charset")
|
||||
case (content_transfer_encoding || "7bit").downcase
|
||||
when "quoted-printable"
|
||||
Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
|
||||
to_charset, from_charset, true)
|
||||
when "base64"
|
||||
Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
|
||||
from_charset)
|
||||
when "7bit", "8bit"
|
||||
Unquoter.convert_to(quoted_body, to_charset, from_charset)
|
||||
when "binary"
|
||||
quoted_body
|
||||
else
|
||||
quoted_body
|
||||
end
|
||||
end
|
||||
|
||||
def body(to_charset = 'utf-8', &block)
|
||||
attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
|
||||
|
||||
if multipart?
|
||||
parts.collect { |part|
|
||||
header = part["content-type"]
|
||||
|
||||
if part.multipart?
|
||||
part.body(to_charset, &attachment_presenter)
|
||||
elsif header.nil?
|
||||
""
|
||||
elsif !attachment?(part)
|
||||
part.unquoted_body(to_charset)
|
||||
else
|
||||
attachment_presenter.call(header["name"] || "(unnamed)")
|
||||
end
|
||||
}.join
|
||||
else
|
||||
unquoted_body(to_charset)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Unquoter
|
||||
class << self
|
||||
def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
|
||||
return "" if text.nil?
|
||||
text.gsub(/(.*?)(?:(?:=\?(.*?)\?(.)\?(.*?)\?=)|$)/) do
|
||||
before = $1
|
||||
from_charset = $2
|
||||
quoting_method = $3
|
||||
text = $4
|
||||
|
||||
before = convert_to(before, to_charset, from_charset) if before.length > 0
|
||||
before + case quoting_method
|
||||
when "q", "Q" then
|
||||
unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
|
||||
when "b", "B" then
|
||||
unquote_base64_and_convert_to(text, to_charset, from_charset)
|
||||
when nil then
|
||||
# will be nil at the end of the string, due to the nature of
|
||||
# the regex used.
|
||||
""
|
||||
else
|
||||
raise "unknown quoting method #{quoting_method.inspect}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
|
||||
text = text.gsub(/_/, " ") unless preserve_underscores
|
||||
text = text.gsub(/\r\n|\r/, "\n") # normalize newlines
|
||||
convert_to(text.unpack("M*").first, to, from)
|
||||
end
|
||||
|
||||
def unquote_base64_and_convert_to(text, to, from)
|
||||
convert_to(Base64.decode(text).first, to, from)
|
||||
end
|
||||
|
||||
begin
|
||||
require 'iconv'
|
||||
def convert_to(text, to, from)
|
||||
return text unless to && from
|
||||
text ? Iconv.iconv(to, from, text).first : ""
|
||||
rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
|
||||
# the 'from' parameter specifies a charset other than what the text
|
||||
# actually is...not much we can do in this case but just return the
|
||||
# unconverted text.
|
||||
#
|
||||
# Ditto if either parameter represents an unknown charset, like
|
||||
# X-UNKNOWN.
|
||||
text
|
||||
end
|
||||
rescue LoadError
|
||||
# Not providing quoting support
|
||||
def convert_to(text, to, from)
|
||||
warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
|
||||
text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
require 'test/unit'
|
||||
|
||||
class TC_Unquoter < Test::Unit::TestCase
|
||||
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
|
||||
end
|
||||
end
|
||||
41
actionmailer/lib/action_mailer/vendor/tmail/scanner.rb
vendored
Executable file
41
actionmailer/lib/action_mailer/vendor/tmail/scanner.rb
vendored
Executable file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# scanner.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/utils'
|
||||
|
||||
module TMail
|
||||
require 'tmail/scanner_r.rb'
|
||||
begin
|
||||
raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
|
||||
require 'tmail/scanner_c.so'
|
||||
Scanner = Scanner_C
|
||||
rescue LoadError
|
||||
Scanner = Scanner_R
|
||||
end
|
||||
end
|
||||
263
actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb
vendored
Executable file
263
actionmailer/lib/action_mailer/vendor/tmail/scanner_r.rb
vendored
Executable file
@@ -0,0 +1,263 @@
|
||||
#
|
||||
# scanner_r.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
require 'tmail/config'
|
||||
|
||||
|
||||
module TMail
|
||||
|
||||
class Scanner_R
|
||||
|
||||
Version = '0.10.7'
|
||||
Version.freeze
|
||||
|
||||
MIME_HEADERS = {
|
||||
:CTYPE => true,
|
||||
:CENCODING => true,
|
||||
:CDISPOSITION => true
|
||||
}
|
||||
|
||||
alnum = 'a-zA-Z0-9'
|
||||
atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
|
||||
tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
|
||||
|
||||
atomchars = alnum + Regexp.quote(atomsyms)
|
||||
tokenchars = alnum + Regexp.quote(tokensyms)
|
||||
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
|
||||
|
||||
eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
|
||||
sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
|
||||
utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
|
||||
|
||||
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
|
||||
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
|
||||
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
|
||||
|
||||
quoted_without_iso2022 = /\A[^\\"]+/n
|
||||
domlit_without_iso2022 = /\A[^\\\]]+/n
|
||||
comment_without_iso2022 = /\A[^\\()]+/n
|
||||
|
||||
PATTERN_TABLE = {}
|
||||
PATTERN_TABLE['EUC'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
|
||||
quoted_with_iso2022,
|
||||
domlit_with_iso2022,
|
||||
comment_with_iso2022
|
||||
]
|
||||
PATTERN_TABLE['SJIS'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
|
||||
quoted_with_iso2022,
|
||||
domlit_with_iso2022,
|
||||
comment_with_iso2022
|
||||
]
|
||||
PATTERN_TABLE['UTF8'] =
|
||||
[
|
||||
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
|
||||
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
|
||||
quoted_without_iso2022,
|
||||
domlit_without_iso2022,
|
||||
comment_without_iso2022
|
||||
]
|
||||
PATTERN_TABLE['NONE'] =
|
||||
[
|
||||
/\A[#{atomchars}]+/n,
|
||||
/\A[#{tokenchars}]+/n,
|
||||
quoted_without_iso2022,
|
||||
domlit_without_iso2022,
|
||||
comment_without_iso2022
|
||||
]
|
||||
|
||||
|
||||
def initialize( str, scantype, comments )
|
||||
init_scanner str
|
||||
@comments = comments || []
|
||||
@debug = false
|
||||
|
||||
# fix scanner mode
|
||||
@received = (scantype == :RECEIVED)
|
||||
@is_mime_header = MIME_HEADERS[scantype]
|
||||
|
||||
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
|
||||
@word_re = (MIME_HEADERS[scantype] ? token : atom)
|
||||
end
|
||||
|
||||
attr_accessor :debug
|
||||
|
||||
def scan( &block )
|
||||
if @debug
|
||||
scan_main do |arr|
|
||||
s, v = arr
|
||||
printf "%7d %-10s %s\n",
|
||||
rest_size(),
|
||||
s.respond_to?(:id2name) ? s.id2name : s.inspect,
|
||||
v.inspect
|
||||
yield arr
|
||||
end
|
||||
else
|
||||
scan_main(&block)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
RECV_TOKEN = {
|
||||
'from' => :FROM,
|
||||
'by' => :BY,
|
||||
'via' => :VIA,
|
||||
'with' => :WITH,
|
||||
'id' => :ID,
|
||||
'for' => :FOR
|
||||
}
|
||||
|
||||
def scan_main
|
||||
until eof?
|
||||
if skip(/\A[\n\r\t ]+/n) # LWSP
|
||||
break if eof?
|
||||
end
|
||||
|
||||
if s = readstr(@word_re)
|
||||
if @is_mime_header
|
||||
yield :TOKEN, s
|
||||
else
|
||||
# atom
|
||||
if /\A\d+\z/ === s
|
||||
yield :DIGIT, s
|
||||
elsif @received
|
||||
yield RECV_TOKEN[s.downcase] || :ATOM, s
|
||||
else
|
||||
yield :ATOM, s
|
||||
end
|
||||
end
|
||||
|
||||
elsif skip(/\A"/)
|
||||
yield :QUOTED, scan_quoted_word()
|
||||
|
||||
elsif skip(/\A\[/)
|
||||
yield :DOMLIT, scan_domain_literal()
|
||||
|
||||
elsif skip(/\A\(/)
|
||||
@comments.push scan_comment()
|
||||
|
||||
else
|
||||
c = readchar()
|
||||
yield c, c
|
||||
end
|
||||
end
|
||||
|
||||
yield false, '$'
|
||||
end
|
||||
|
||||
def scan_quoted_word
|
||||
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
|
||||
end
|
||||
|
||||
def scan_domain_literal
|
||||
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
|
||||
end
|
||||
|
||||
def scan_qstr( pattern, terminal, type )
|
||||
result = ''
|
||||
until eof?
|
||||
if s = readstr(pattern) then result << s
|
||||
elsif skip(terminal) then return result
|
||||
elsif skip(/\A\\/) then result << readchar()
|
||||
else
|
||||
raise "TMail FATAL: not match in #{type}"
|
||||
end
|
||||
end
|
||||
scan_error! "found unterminated #{type}"
|
||||
end
|
||||
|
||||
def scan_comment
|
||||
result = ''
|
||||
nest = 1
|
||||
content = @comment_re
|
||||
|
||||
until eof?
|
||||
if s = readstr(content) then result << s
|
||||
elsif skip(/\A\)/) then nest -= 1
|
||||
return result if nest == 0
|
||||
result << ')'
|
||||
elsif skip(/\A\(/) then nest += 1
|
||||
result << '('
|
||||
elsif skip(/\A\\/) then result << readchar()
|
||||
else
|
||||
raise 'TMail FATAL: not match in comment'
|
||||
end
|
||||
end
|
||||
scan_error! 'found unterminated comment'
|
||||
end
|
||||
|
||||
# string scanner
|
||||
|
||||
def init_scanner( str )
|
||||
@src = str
|
||||
end
|
||||
|
||||
def eof?
|
||||
@src.empty?
|
||||
end
|
||||
|
||||
def rest_size
|
||||
@src.size
|
||||
end
|
||||
|
||||
def readstr( re )
|
||||
if m = re.match(@src)
|
||||
@src = m.post_match
|
||||
m[0]
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def readchar
|
||||
readstr(/\A./)
|
||||
end
|
||||
|
||||
def skip( re )
|
||||
if m = re.match(@src)
|
||||
@src = m.post_match
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def scan_error!( msg )
|
||||
raise SyntaxError, msg
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end # module TMail
|
||||
277
actionmailer/lib/action_mailer/vendor/tmail/stringio.rb
vendored
Executable file
277
actionmailer/lib/action_mailer/vendor/tmail/stringio.rb
vendored
Executable file
@@ -0,0 +1,277 @@
|
||||
#
|
||||
# stringio.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
class StringInput#:nodoc:
|
||||
|
||||
include Enumerable
|
||||
|
||||
class << self
|
||||
|
||||
def new( str )
|
||||
if block_given?
|
||||
begin
|
||||
f = super
|
||||
yield f
|
||||
ensure
|
||||
f.close if f
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias open new
|
||||
|
||||
end
|
||||
|
||||
def initialize( str )
|
||||
@src = str
|
||||
@pos = 0
|
||||
@closed = false
|
||||
@lineno = 0
|
||||
end
|
||||
|
||||
attr_reader :lineno
|
||||
|
||||
def string
|
||||
@src
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
|
||||
end
|
||||
|
||||
def close
|
||||
stream_check!
|
||||
@pos = nil
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
def pos
|
||||
stream_check!
|
||||
[@pos, @src.size].min
|
||||
end
|
||||
|
||||
alias tell pos
|
||||
|
||||
def seek( offset, whence = IO::SEEK_SET )
|
||||
stream_check!
|
||||
case whence
|
||||
when IO::SEEK_SET
|
||||
@pos = offset
|
||||
when IO::SEEK_CUR
|
||||
@pos += offset
|
||||
when IO::SEEK_END
|
||||
@pos = @src.size - offset
|
||||
else
|
||||
raise ArgumentError, "unknown seek flag: #{whence}"
|
||||
end
|
||||
@pos = 0 if @pos < 0
|
||||
@pos = [@pos, @src.size + 1].min
|
||||
offset
|
||||
end
|
||||
|
||||
def rewind
|
||||
stream_check!
|
||||
@pos = 0
|
||||
end
|
||||
|
||||
def eof?
|
||||
stream_check!
|
||||
@pos > @src.size
|
||||
end
|
||||
|
||||
def each( &block )
|
||||
stream_check!
|
||||
begin
|
||||
@src.each(&block)
|
||||
ensure
|
||||
@pos = 0
|
||||
end
|
||||
end
|
||||
|
||||
def gets
|
||||
stream_check!
|
||||
if idx = @src.index(?\n, @pos)
|
||||
idx += 1 # "\n".size
|
||||
line = @src[ @pos ... idx ]
|
||||
@pos = idx
|
||||
@pos += 1 if @pos == @src.size
|
||||
else
|
||||
line = @src[ @pos .. -1 ]
|
||||
@pos = @src.size + 1
|
||||
end
|
||||
@lineno += 1
|
||||
|
||||
line
|
||||
end
|
||||
|
||||
def getc
|
||||
stream_check!
|
||||
ch = @src[@pos]
|
||||
@pos += 1
|
||||
@pos += 1 if @pos == @src.size
|
||||
ch
|
||||
end
|
||||
|
||||
def read( len = nil )
|
||||
stream_check!
|
||||
return read_all unless len
|
||||
str = @src[@pos, len]
|
||||
@pos += len
|
||||
@pos += 1 if @pos == @src.size
|
||||
str
|
||||
end
|
||||
|
||||
alias sysread read
|
||||
|
||||
def read_all
|
||||
stream_check!
|
||||
return nil if eof?
|
||||
rest = @src[@pos ... @src.size]
|
||||
@pos = @src.size + 1
|
||||
rest
|
||||
end
|
||||
|
||||
def stream_check!
|
||||
@closed and raise IOError, 'closed stream'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
class StringOutput#:nodoc:
|
||||
|
||||
class << self
|
||||
|
||||
def new( str = '' )
|
||||
if block_given?
|
||||
begin
|
||||
f = super
|
||||
yield f
|
||||
ensure
|
||||
f.close if f
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
alias open new
|
||||
|
||||
end
|
||||
|
||||
def initialize( str = '' )
|
||||
@dest = str
|
||||
@closed = false
|
||||
end
|
||||
|
||||
def close
|
||||
@closed = true
|
||||
end
|
||||
|
||||
def closed?
|
||||
@closed
|
||||
end
|
||||
|
||||
def string
|
||||
@dest
|
||||
end
|
||||
|
||||
alias value string
|
||||
alias to_str string
|
||||
|
||||
def size
|
||||
@dest.size
|
||||
end
|
||||
|
||||
alias pos size
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
|
||||
end
|
||||
|
||||
def print( *args )
|
||||
stream_check!
|
||||
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
|
||||
args.each do |s|
|
||||
raise ArgumentError, 'nil not allowed' if s.nil?
|
||||
@dest << s.to_s
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def puts( *args )
|
||||
stream_check!
|
||||
args.each do |str|
|
||||
@dest << (s = str.to_s)
|
||||
@dest << "\n" unless s[-1] == ?\n
|
||||
end
|
||||
@dest << "\n" if args.empty?
|
||||
nil
|
||||
end
|
||||
|
||||
def putc( ch )
|
||||
stream_check!
|
||||
@dest << ch.chr
|
||||
nil
|
||||
end
|
||||
|
||||
def printf( *args )
|
||||
stream_check!
|
||||
@dest << sprintf(*args)
|
||||
nil
|
||||
end
|
||||
|
||||
def write( str )
|
||||
stream_check!
|
||||
s = str.to_s
|
||||
@dest << s
|
||||
s.size
|
||||
end
|
||||
|
||||
alias syswrite write
|
||||
|
||||
def <<( str )
|
||||
stream_check!
|
||||
@dest << str.to_s
|
||||
self
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stream_check!
|
||||
@closed and raise IOError, 'closed stream'
|
||||
end
|
||||
|
||||
end
|
||||
1
actionmailer/lib/action_mailer/vendor/tmail/tmail.rb
vendored
Executable file
1
actionmailer/lib/action_mailer/vendor/tmail/tmail.rb
vendored
Executable file
@@ -0,0 +1 @@
|
||||
require 'tmail'
|
||||
238
actionmailer/lib/action_mailer/vendor/tmail/utils.rb
vendored
Executable file
238
actionmailer/lib/action_mailer/vendor/tmail/utils.rb
vendored
Executable file
@@ -0,0 +1,238 @@
|
||||
#
|
||||
# utils.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
#
|
||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
||||
# with permission of Minero Aoki.
|
||||
#++
|
||||
|
||||
module TMail
|
||||
|
||||
class SyntaxError < StandardError; end
|
||||
|
||||
|
||||
def TMail.new_boundary
|
||||
'mimepart_' + random_tag
|
||||
end
|
||||
|
||||
def TMail.new_message_id( fqdn = nil )
|
||||
fqdn ||= ::Socket.gethostname
|
||||
"<#{random_tag()}@#{fqdn}.tmail>"
|
||||
end
|
||||
|
||||
def TMail.random_tag
|
||||
@uniq += 1
|
||||
t = Time.now
|
||||
sprintf('%x%x_%x%x%d%x',
|
||||
t.to_i, t.tv_usec,
|
||||
$$, Thread.current.object_id, @uniq, rand(255))
|
||||
end
|
||||
private_class_method :random_tag
|
||||
|
||||
@uniq = 0
|
||||
|
||||
|
||||
module TextUtils
|
||||
|
||||
aspecial = '()<>[]:;.\\,"'
|
||||
tspecial = '()<>[];:\\,"/?='
|
||||
lwsp = " \t\r\n"
|
||||
control = '\x00-\x1f\x7f-\xff'
|
||||
|
||||
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
|
||||
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
|
||||
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
|
||||
CONTROL_CHAR = /[#{control}]/n
|
||||
|
||||
def atom_safe?( str )
|
||||
not ATOM_UNSAFE === str
|
||||
end
|
||||
|
||||
def quote_atom( str )
|
||||
(ATOM_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
def quote_phrase( str )
|
||||
(PHRASE_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
def token_safe?( str )
|
||||
not TOKEN_UNSAFE === str
|
||||
end
|
||||
|
||||
def quote_token( str )
|
||||
(TOKEN_UNSAFE === str) ? dquote(str) : str
|
||||
end
|
||||
|
||||
def dquote( str )
|
||||
'"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
|
||||
end
|
||||
private :dquote
|
||||
|
||||
|
||||
def join_domain( arr )
|
||||
arr.map {|i|
|
||||
if /\A\[.*\]\z/ === i
|
||||
i
|
||||
else
|
||||
quote_atom(i)
|
||||
end
|
||||
}.join('.')
|
||||
end
|
||||
|
||||
|
||||
ZONESTR_TABLE = {
|
||||
'jst' => 9 * 60,
|
||||
'eet' => 2 * 60,
|
||||
'bst' => 1 * 60,
|
||||
'met' => 1 * 60,
|
||||
'gmt' => 0,
|
||||
'utc' => 0,
|
||||
'ut' => 0,
|
||||
'nst' => -(3 * 60 + 30),
|
||||
'ast' => -4 * 60,
|
||||
'edt' => -4 * 60,
|
||||
'est' => -5 * 60,
|
||||
'cdt' => -5 * 60,
|
||||
'cst' => -6 * 60,
|
||||
'mdt' => -6 * 60,
|
||||
'mst' => -7 * 60,
|
||||
'pdt' => -7 * 60,
|
||||
'pst' => -8 * 60,
|
||||
'a' => -1 * 60,
|
||||
'b' => -2 * 60,
|
||||
'c' => -3 * 60,
|
||||
'd' => -4 * 60,
|
||||
'e' => -5 * 60,
|
||||
'f' => -6 * 60,
|
||||
'g' => -7 * 60,
|
||||
'h' => -8 * 60,
|
||||
'i' => -9 * 60,
|
||||
# j not use
|
||||
'k' => -10 * 60,
|
||||
'l' => -11 * 60,
|
||||
'm' => -12 * 60,
|
||||
'n' => 1 * 60,
|
||||
'o' => 2 * 60,
|
||||
'p' => 3 * 60,
|
||||
'q' => 4 * 60,
|
||||
'r' => 5 * 60,
|
||||
's' => 6 * 60,
|
||||
't' => 7 * 60,
|
||||
'u' => 8 * 60,
|
||||
'v' => 9 * 60,
|
||||
'w' => 10 * 60,
|
||||
'x' => 11 * 60,
|
||||
'y' => 12 * 60,
|
||||
'z' => 0 * 60
|
||||
}
|
||||
|
||||
def timezone_string_to_unixtime( str )
|
||||
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
|
||||
sec = (m[2].to_i * 60 + m[3].to_i) * 60
|
||||
m[1] == '-' ? -sec : sec
|
||||
else
|
||||
min = ZONESTR_TABLE[str.downcase] or
|
||||
raise SyntaxError, "wrong timezone format '#{str}'"
|
||||
min * 60
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
|
||||
MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
|
||||
Jul Aug Sep Oct Nov Dec TMailBUG )
|
||||
|
||||
def time2str( tm )
|
||||
# [ruby-list:7928]
|
||||
gmt = Time.at(tm.to_i)
|
||||
gmt.gmtime
|
||||
offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
|
||||
|
||||
# DO NOT USE strftime: setlocale() breaks it
|
||||
sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
|
||||
WDAY[tm.wday], tm.mday, MONTH[tm.month],
|
||||
tm.year, tm.hour, tm.min, tm.sec,
|
||||
*(offset / 60).divmod(60)
|
||||
end
|
||||
|
||||
|
||||
MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
|
||||
|
||||
def message_id?( str )
|
||||
MESSAGE_ID === str
|
||||
end
|
||||
|
||||
|
||||
MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
|
||||
|
||||
def mime_encoded?( str )
|
||||
MIME_ENCODED === str
|
||||
end
|
||||
|
||||
|
||||
def decode_params( hash )
|
||||
new = Hash.new
|
||||
encoded = nil
|
||||
hash.each do |key, value|
|
||||
if m = /\*(?:(\d+)\*)?\z/.match(key)
|
||||
((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
|
||||
else
|
||||
new[key] = to_kcode(value)
|
||||
end
|
||||
end
|
||||
if encoded
|
||||
encoded.each do |key, strings|
|
||||
new[key] = decode_RFC2231(strings.join(''))
|
||||
end
|
||||
end
|
||||
|
||||
new
|
||||
end
|
||||
|
||||
NKF_FLAGS = {
|
||||
'EUC' => '-e -m',
|
||||
'SJIS' => '-s -m'
|
||||
}
|
||||
|
||||
def to_kcode( str )
|
||||
flag = NKF_FLAGS[$KCODE] or return str
|
||||
NKF.nkf(flag, str)
|
||||
end
|
||||
|
||||
RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
|
||||
|
||||
def decode_RFC2231( str )
|
||||
m = RFC2231_ENCODED.match(str) or return str
|
||||
begin
|
||||
NKF.nkf(NKF_FLAGS[$KCODE],
|
||||
m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
|
||||
rescue
|
||||
m.post_match.gsub(/%[\da-f]{2}/in, "")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,10 +1,9 @@
|
||||
module ActionMailer
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 3
|
||||
MINOR = 2
|
||||
TINY = 19
|
||||
PRE = nil
|
||||
MAJOR = 1
|
||||
MINOR = 3
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
Description:
|
||||
============
|
||||
Stubs out a new mailer and its views. Pass the mailer name, either
|
||||
CamelCased or under_scored, and an optional list of emails as arguments.
|
||||
|
||||
This generates a mailer class in app/mailers and invokes your template
|
||||
engine and test framework generators.
|
||||
|
||||
Example:
|
||||
========
|
||||
rails generate mailer Notifications signup forgot_password invoice
|
||||
|
||||
creates a Notifications mailer class, views, test, and fixtures:
|
||||
Mailer: app/mailers/notifications.rb
|
||||
Views: app/views/notifications/signup.erb [...]
|
||||
Test: test/functional/notifications_test.rb
|
||||
Fixtures: test/fixtures/notifications/signup [...]
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
module Rails
|
||||
module Generators
|
||||
class MailerGenerator < NamedBase
|
||||
source_root File.expand_path("../templates", __FILE__)
|
||||
|
||||
argument :actions, :type => :array, :default => [], :banner => "method method"
|
||||
check_class_collision
|
||||
|
||||
def create_mailer_file
|
||||
template "mailer.rb", File.join('app/mailers', class_path, "#{file_name}.rb")
|
||||
end
|
||||
|
||||
hook_for :template_engine, :test_framework
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,18 +0,0 @@
|
||||
<% module_namespacing do -%>
|
||||
class <%= class_name %> < ActionMailer::Base
|
||||
default <%= key_value :from, '"from@example.com"' %>
|
||||
<% actions.each do |action| -%>
|
||||
|
||||
# Subject can be set in your I18n file at config/locales/en.yml
|
||||
# with the following lookup:
|
||||
#
|
||||
# en.<%= file_path.gsub("/",".") %>.<%= action %>.subject
|
||||
#
|
||||
def <%= action %>
|
||||
@greeting = "Hi"
|
||||
|
||||
mail <%= key_value :to, '"to@example.org"' %>
|
||||
end
|
||||
<% end -%>
|
||||
end
|
||||
<% end -%>
|
||||
@@ -1,49 +1,13 @@
|
||||
# Pathname has a warning, so require it first while silencing
|
||||
# warnings to shut it up.
|
||||
#
|
||||
# Also, in 1.9, Bundler creates warnings due to overriding
|
||||
# Rubygems methods
|
||||
begin
|
||||
old, $VERBOSE = $VERBOSE, nil
|
||||
require 'pathname'
|
||||
require File.expand_path('../../../load_paths', __FILE__)
|
||||
ensure
|
||||
$VERBOSE = old
|
||||
end
|
||||
|
||||
require 'active_support/core_ext/kernel/reporting'
|
||||
|
||||
require 'active_support/core_ext/string/encoding'
|
||||
if "ruby".encoding_aware?
|
||||
# These are the normal settings that will be set up by Railties
|
||||
# TODO: Have these tests support other combinations of these values
|
||||
silence_warnings do
|
||||
Encoding.default_internal = "UTF-8"
|
||||
Encoding.default_external = "UTF-8"
|
||||
end
|
||||
end
|
||||
|
||||
lib = File.expand_path("#{File.dirname(__FILE__)}/../lib")
|
||||
$:.unshift(lib) unless $:.include?('lib') || $:.include?(lib)
|
||||
|
||||
require 'test/unit'
|
||||
require 'action_mailer'
|
||||
require 'action_mailer/test_case'
|
||||
|
||||
silence_warnings do
|
||||
# These external dependencies have warnings :/
|
||||
require 'mail'
|
||||
end
|
||||
$:.unshift "#{File.dirname(__FILE__)}/../lib"
|
||||
require 'action_mailer'
|
||||
|
||||
# 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 }
|
||||
|
||||
FIXTURE_LOAD_PATH = File.expand_path('fixtures', File.dirname(__FILE__))
|
||||
ActionMailer::Base.view_paths = FIXTURE_LOAD_PATH
|
||||
$:.unshift "#{File.dirname(__FILE__)}/fixtures/helpers"
|
||||
ActionMailer::Base.template_root = "#{File.dirname(__FILE__)}/fixtures"
|
||||
|
||||
class MockSMTP
|
||||
def self.deliveries
|
||||
@@ -57,25 +21,10 @@ class MockSMTP
|
||||
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
|
||||
def self.start(*args)
|
||||
yield MockSMTP.new
|
||||
end
|
||||
end
|
||||
|
||||
def set_delivery_method(method)
|
||||
@old_delivery_method = ActionMailer::Base.delivery_method
|
||||
ActionMailer::Base.delivery_method = method
|
||||
end
|
||||
|
||||
def restore_delivery_method
|
||||
ActionMailer::Base.delivery_method = @old_delivery_method
|
||||
end
|
||||
|
||||
ActiveSupport::Deprecation.silenced = true
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require 'action_controller'
|
||||
|
||||
class AssetHostMailer < ActionMailer::Base
|
||||
def email_with_asset
|
||||
mail :to => 'test@localhost',
|
||||
: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.clear
|
||||
AssetHostMailer.configure do |c|
|
||||
c.asset_host = "http://www.example.com"
|
||||
c.assets_dir = ''
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
restore_delivery_method
|
||||
end
|
||||
|
||||
def test_asset_host_as_string
|
||||
mail = AssetHostMailer.email_with_asset
|
||||
assert_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_asset_host_as_one_argument_proc
|
||||
AssetHostMailer.config.asset_host = Proc.new { |source|
|
||||
if source.starts_with?('/images')
|
||||
"http://images.example.com"
|
||||
else
|
||||
"http://assets.example.com"
|
||||
end
|
||||
}
|
||||
mail = AssetHostMailer.email_with_asset
|
||||
assert_equal %Q{<img alt="Somelogo" src="http://images.example.com/images/somelogo.png" />}, mail.body.to_s.strip
|
||||
end
|
||||
|
||||
def test_asset_host_as_two_argument_proc
|
||||
ActionController::Base.config.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.email_with_asset }
|
||||
assert_equal %Q{<img alt="Somelogo" src="http://www.example.com/images/somelogo.png" />}, mail.body.to_s.strip
|
||||
end
|
||||
end
|
||||
@@ -1,614 +0,0 @@
|
||||
# encoding: utf-8
|
||||
require 'abstract_unit'
|
||||
require 'active_support/time'
|
||||
|
||||
require 'mailers/base_mailer'
|
||||
require 'mailers/proc_mailer'
|
||||
require 'mailers/asset_mailer'
|
||||
|
||||
class BaseTest < ActiveSupport::TestCase
|
||||
def teardown
|
||||
ActionMailer::Base.asset_host = nil
|
||||
ActionMailer::Base.assets_dir = nil
|
||||
end
|
||||
|
||||
test "method call to mail does not raise error" do
|
||||
assert_nothing_raised { BaseMailer.welcome }
|
||||
end
|
||||
|
||||
# Basic mail usage without block
|
||||
test "mail() should set the headers of the mail message" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal(['system@test.lindsaar.net'], email.to)
|
||||
assert_equal(['jose@test.plataformatec.com'], email.from)
|
||||
assert_equal('The first email on new API!', email.subject)
|
||||
end
|
||||
|
||||
test "mail() with from overwrites the class level default" do
|
||||
email = BaseMailer.welcome(:from => 'someone@example.com',
|
||||
:to => 'another@example.org')
|
||||
assert_equal(['someone@example.com'], email.from)
|
||||
assert_equal(['another@example.org'], email.to)
|
||||
end
|
||||
|
||||
test "mail() with bcc, cc, content_type, charset, mime_version, reply_to and date" do
|
||||
time = Time.now.beginning_of_day.to_datetime
|
||||
email = BaseMailer.welcome(:bcc => 'bcc@test.lindsaar.net',
|
||||
:cc => 'cc@test.lindsaar.net',
|
||||
:content_type => 'multipart/mixed',
|
||||
:charset => 'iso-8559-1',
|
||||
:mime_version => '2.0',
|
||||
:reply_to => 'reply-to@test.lindsaar.net',
|
||||
:date => time)
|
||||
assert_equal(['bcc@test.lindsaar.net'], email.bcc)
|
||||
assert_equal(['cc@test.lindsaar.net'], email.cc)
|
||||
assert_equal('multipart/mixed; charset=iso-8559-1', email.content_type)
|
||||
assert_equal('iso-8559-1', email.charset)
|
||||
assert_equal('2.0', email.mime_version)
|
||||
assert_equal(['reply-to@test.lindsaar.net'], email.reply_to)
|
||||
assert_equal(time, email.date)
|
||||
end
|
||||
|
||||
test "mail() renders the template using the method being processed" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("Welcome", email.body.encoded)
|
||||
end
|
||||
|
||||
test "can pass in :body to the mail method hash" do
|
||||
email = BaseMailer.welcome(:body => "Hello there")
|
||||
assert_equal("text/plain", email.mime_type)
|
||||
assert_equal("Hello there", email.body.encoded)
|
||||
end
|
||||
|
||||
test "should set template content type if mail has only one part" do
|
||||
mail = BaseMailer.html_only
|
||||
assert_equal('text/html', mail.mime_type)
|
||||
mail = BaseMailer.plain_text_only
|
||||
assert_equal('text/plain', mail.mime_type)
|
||||
end
|
||||
|
||||
# Custom headers
|
||||
test "custom headers" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("Not SPAM", email['X-SPAM'].decoded)
|
||||
end
|
||||
|
||||
test "can pass random headers in as a hash to mail" do
|
||||
hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
|
||||
'In-Reply-To' => '1234@mikel.me.com' }
|
||||
mail = BaseMailer.welcome(hash)
|
||||
assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
|
||||
assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
|
||||
end
|
||||
|
||||
test "can pass random headers in as a hash to headers" do
|
||||
hash = {'X-Special-Domain-Specific-Header' => "SecretValue",
|
||||
'In-Reply-To' => '1234@mikel.me.com' }
|
||||
mail = BaseMailer.welcome_with_headers(hash)
|
||||
assert_equal('SecretValue', mail['X-Special-Domain-Specific-Header'].decoded)
|
||||
assert_equal('1234@mikel.me.com', mail['In-Reply-To'].decoded)
|
||||
end
|
||||
|
||||
# Attachments
|
||||
test "attachment with content" do
|
||||
email = BaseMailer.attachment_with_content
|
||||
assert_equal(1, email.attachments.length)
|
||||
assert_equal('invoice.pdf', email.attachments[0].filename)
|
||||
assert_equal('This is test File content', email.attachments['invoice.pdf'].decoded)
|
||||
end
|
||||
|
||||
test "attachment gets content type from filename" do
|
||||
email = BaseMailer.attachment_with_content
|
||||
assert_equal('invoice.pdf', email.attachments[0].filename)
|
||||
end
|
||||
|
||||
test "attachment with hash" do
|
||||
email = BaseMailer.attachment_with_hash
|
||||
assert_equal(1, email.attachments.length)
|
||||
assert_equal('invoice.jpg', email.attachments[0].filename)
|
||||
expected = "\312\213\254\232)b"
|
||||
expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding)
|
||||
assert_equal expected, email.attachments['invoice.jpg'].decoded
|
||||
end
|
||||
|
||||
test "attachment with hash using default mail encoding" do
|
||||
email = BaseMailer.attachment_with_hash_default_encoding
|
||||
assert_equal(1, email.attachments.length)
|
||||
assert_equal('invoice.jpg', email.attachments[0].filename)
|
||||
expected = "\312\213\254\232)b"
|
||||
expected.force_encoding(Encoding::BINARY) if '1.9'.respond_to?(:force_encoding)
|
||||
assert_equal expected, email.attachments['invoice.jpg'].decoded
|
||||
end
|
||||
|
||||
test "sets mime type to multipart/mixed when attachment is included" do
|
||||
email = BaseMailer.attachment_with_content
|
||||
assert_equal(1, email.attachments.length)
|
||||
assert_equal("multipart/mixed", email.mime_type)
|
||||
end
|
||||
|
||||
test "adds the rendered template as part" do
|
||||
email = BaseMailer.attachment_with_content
|
||||
assert_equal(2, email.parts.length)
|
||||
assert_equal("multipart/mixed", email.mime_type)
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("Attachment with content", email.parts[0].body.encoded)
|
||||
assert_equal("application/pdf", email.parts[1].mime_type)
|
||||
assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "adds the given :body as part" do
|
||||
email = BaseMailer.attachment_with_content(:body => "I'm the eggman")
|
||||
assert_equal(2, email.parts.length)
|
||||
assert_equal("multipart/mixed", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("I'm the eggman", email.parts[0].body.encoded)
|
||||
assert_equal("application/pdf", email.parts[1].mime_type)
|
||||
assert_equal("VGhpcyBpcyB0ZXN0IEZpbGUgY29udGVudA==\r\n", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "can embed an inline attachment" do
|
||||
email = BaseMailer.inline_attachment
|
||||
# Need to call #encoded to force the JIT sort on parts
|
||||
email.encoded
|
||||
assert_equal(2, email.parts.length)
|
||||
assert_equal("multipart/related", email.mime_type)
|
||||
assert_equal("multipart/alternative", email.parts[0].mime_type)
|
||||
assert_equal("text/plain", email.parts[0].parts[0].mime_type)
|
||||
assert_equal("text/html", email.parts[0].parts[1].mime_type)
|
||||
assert_equal("logo.png", email.parts[1].filename)
|
||||
end
|
||||
|
||||
# Defaults values
|
||||
test "uses default charset from class" do
|
||||
with_default BaseMailer, :charset => "US-ASCII" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("US-ASCII", email.charset)
|
||||
|
||||
email = BaseMailer.welcome(:charset => "iso-8559-1")
|
||||
assert_equal("iso-8559-1", email.charset)
|
||||
end
|
||||
end
|
||||
|
||||
test "uses default content type from class" do
|
||||
with_default BaseMailer, :content_type => "text/html" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("text/html", email.mime_type)
|
||||
|
||||
email = BaseMailer.welcome(:content_type => "text/plain")
|
||||
assert_equal("text/plain", email.mime_type)
|
||||
end
|
||||
end
|
||||
|
||||
test "uses default mime version from class" do
|
||||
with_default BaseMailer, :mime_version => "2.0" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("2.0", email.mime_version)
|
||||
|
||||
email = BaseMailer.welcome(:mime_version => "1.0")
|
||||
assert_equal("1.0", email.mime_version)
|
||||
end
|
||||
end
|
||||
|
||||
test "uses random default headers from class" do
|
||||
with_default BaseMailer, "X-Custom" => "Custom" do
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("Custom", email["X-Custom"].decoded)
|
||||
end
|
||||
end
|
||||
|
||||
test "subject gets default from I18n" do
|
||||
BaseMailer.default :subject => nil
|
||||
email = BaseMailer.welcome(:subject => nil)
|
||||
assert_equal "Welcome", email.subject
|
||||
|
||||
I18n.backend.store_translations('en', :base_mailer => {:welcome => {:subject => "New Subject!"}})
|
||||
email = BaseMailer.welcome(:subject => nil)
|
||||
assert_equal "New Subject!", email.subject
|
||||
end
|
||||
|
||||
test "translations are scoped properly" do
|
||||
I18n.backend.store_translations('en', :base_mailer => {:email_with_translations => {:greet_user => "Hello %{name}!"}})
|
||||
email = BaseMailer.email_with_translations
|
||||
assert_equal 'Hello lifo!', email.body.encoded
|
||||
end
|
||||
|
||||
# Implicit multipart
|
||||
test "implicit multipart" do
|
||||
email = BaseMailer.implicit_multipart
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("TEXT Implicit Multipart", email.parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("HTML Implicit Multipart", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "implicit multipart with sort order" do
|
||||
order = ["text/html", "text/plain"]
|
||||
with_default BaseMailer, :parts_order => order do
|
||||
email = BaseMailer.implicit_multipart
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("text/plain", email.parts[1].mime_type)
|
||||
|
||||
email = BaseMailer.implicit_multipart(:parts_order => order.reverse)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
end
|
||||
end
|
||||
|
||||
test "implicit multipart with attachments creates nested parts" do
|
||||
email = BaseMailer.implicit_multipart(:attachments => true)
|
||||
assert_equal("application/pdf", email.parts[0].mime_type)
|
||||
assert_equal("multipart/alternative", email.parts[1].mime_type)
|
||||
assert_equal("text/plain", email.parts[1].parts[0].mime_type)
|
||||
assert_equal("TEXT Implicit Multipart", email.parts[1].parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].parts[1].mime_type)
|
||||
assert_equal("HTML Implicit Multipart", email.parts[1].parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "implicit multipart with attachments and sort order" do
|
||||
order = ["text/html", "text/plain"]
|
||||
with_default BaseMailer, :parts_order => order do
|
||||
email = BaseMailer.implicit_multipart(:attachments => true)
|
||||
assert_equal("application/pdf", email.parts[0].mime_type)
|
||||
assert_equal("multipart/alternative", email.parts[1].mime_type)
|
||||
assert_equal("text/plain", email.parts[1].parts[1].mime_type)
|
||||
assert_equal("text/html", email.parts[1].parts[0].mime_type)
|
||||
end
|
||||
end
|
||||
|
||||
test "implicit multipart with default locale" do
|
||||
email = BaseMailer.implicit_with_locale
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("Implicit with locale TEXT", email.parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("Implicit with locale EN HTML", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "implicit multipart with other locale" do
|
||||
swap I18n, :locale => :pl do
|
||||
email = BaseMailer.implicit_with_locale
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("Implicit with locale PL TEXT", email.parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("Implicit with locale HTML", email.parts[1].body.encoded)
|
||||
end
|
||||
end
|
||||
|
||||
test "implicit multipart with several view paths uses the first one with template" do
|
||||
old = BaseMailer.view_paths
|
||||
begin
|
||||
BaseMailer.view_paths = [File.join(FIXTURE_LOAD_PATH, "another.path")] + old.dup
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("Welcome from another path", email.body.encoded)
|
||||
ensure
|
||||
BaseMailer.view_paths = old
|
||||
end
|
||||
end
|
||||
|
||||
test "implicit multipart with inexistent templates uses the next view path" do
|
||||
old = BaseMailer.view_paths
|
||||
begin
|
||||
BaseMailer.view_paths = [File.join(FIXTURE_LOAD_PATH, "unknown")] + old.dup
|
||||
email = BaseMailer.welcome
|
||||
assert_equal("Welcome", email.body.encoded)
|
||||
ensure
|
||||
BaseMailer.view_paths = old
|
||||
end
|
||||
end
|
||||
|
||||
# Explicit multipart
|
||||
test "explicit multipart" do
|
||||
email = BaseMailer.explicit_multipart
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("TEXT Explicit Multipart", email.parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("HTML Explicit Multipart", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart have a boundary" do
|
||||
mail = BaseMailer.explicit_multipart
|
||||
assert_not_nil(mail.content_type_parameters[:boundary])
|
||||
end
|
||||
|
||||
test "explicit multipart does not sort order" do
|
||||
order = ["text/html", "text/plain"]
|
||||
with_default BaseMailer, :parts_order => order do
|
||||
email = BaseMailer.explicit_multipart
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
|
||||
email = BaseMailer.explicit_multipart(:parts_order => order.reverse)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
end
|
||||
end
|
||||
|
||||
test "explicit multipart with attachments creates nested parts" do
|
||||
email = BaseMailer.explicit_multipart(:attachments => true)
|
||||
assert_equal("application/pdf", email.parts[0].mime_type)
|
||||
assert_equal("multipart/alternative", email.parts[1].mime_type)
|
||||
assert_equal("text/plain", email.parts[1].parts[0].mime_type)
|
||||
assert_equal("TEXT Explicit Multipart", email.parts[1].parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].parts[1].mime_type)
|
||||
assert_equal("HTML Explicit Multipart", email.parts[1].parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart with templates" do
|
||||
email = BaseMailer.explicit_multipart_templates
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("HTML Explicit Multipart Templates", email.parts[0].body.encoded)
|
||||
assert_equal("text/plain", email.parts[1].mime_type)
|
||||
assert_equal("TEXT Explicit Multipart Templates", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart with format.any" do
|
||||
email = BaseMailer.explicit_multipart_with_any
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("Format with any!", email.parts[0].body.encoded)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("Format with any!", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
test "explicit multipart with format(Hash)" do
|
||||
email = BaseMailer.explicit_multipart_with_options(true)
|
||||
email.ready_to_send!
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/plain", email.parts[0].mime_type)
|
||||
assert_equal("base64", email.parts[0].content_transfer_encoding)
|
||||
assert_equal("text/html", email.parts[1].mime_type)
|
||||
assert_equal("7bit", email.parts[1].content_transfer_encoding)
|
||||
end
|
||||
|
||||
test "explicit multipart with one part is rendered as body and options are merged" do
|
||||
email = BaseMailer.explicit_multipart_with_options
|
||||
assert_equal(0, email.parts.size)
|
||||
assert_equal("text/plain", email.mime_type)
|
||||
assert_equal("base64", email.content_transfer_encoding)
|
||||
end
|
||||
|
||||
test "explicit multipart with one template has the expected format" do
|
||||
email = BaseMailer.explicit_multipart_with_one_template
|
||||
assert_equal(2, email.parts.size)
|
||||
assert_equal("multipart/alternative", email.mime_type)
|
||||
assert_equal("text/html", email.parts[0].mime_type)
|
||||
assert_equal("[:html]", email.parts[0].body.encoded)
|
||||
assert_equal("text/plain", email.parts[1].mime_type)
|
||||
assert_equal("[:text]", email.parts[1].body.encoded)
|
||||
end
|
||||
|
||||
# Class level API with method missing
|
||||
test "should respond to action methods" do
|
||||
assert_respond_to BaseMailer, :welcome
|
||||
assert_respond_to BaseMailer, :implicit_multipart
|
||||
assert !BaseMailer.respond_to?(:mail)
|
||||
assert !BaseMailer.respond_to?(:headers)
|
||||
end
|
||||
|
||||
test "calling just the action should return the generated mail object" do
|
||||
BaseMailer.deliveries.clear
|
||||
email = BaseMailer.welcome
|
||||
assert_equal(0, BaseMailer.deliveries.length)
|
||||
assert_equal('The first email on new API!', email.subject)
|
||||
end
|
||||
|
||||
test "calling deliver on the action should deliver the mail object" do
|
||||
BaseMailer.deliveries.clear
|
||||
BaseMailer.expects(:deliver_mail).once
|
||||
mail = BaseMailer.welcome.deliver
|
||||
assert_instance_of Mail::Message, mail
|
||||
end
|
||||
|
||||
test "calling deliver on the action should increment the deliveries collection if using the test mailer" do
|
||||
BaseMailer.delivery_method = :test
|
||||
BaseMailer.deliveries.clear
|
||||
BaseMailer.welcome.deliver
|
||||
assert_equal(1, BaseMailer.deliveries.length)
|
||||
end
|
||||
|
||||
test "calling deliver, ActionMailer should yield back to mail to let it call :do_delivery on itself" do
|
||||
mail = Mail::Message.new
|
||||
mail.expects(:do_delivery).once
|
||||
BaseMailer.expects(:welcome).returns(mail)
|
||||
BaseMailer.welcome.deliver
|
||||
end
|
||||
|
||||
# Rendering
|
||||
test "you can specify a different template for implicit render" do
|
||||
mail = BaseMailer.implicit_different_template('implicit_multipart').deliver
|
||||
assert_equal("HTML Implicit Multipart", mail.html_part.body.decoded)
|
||||
assert_equal("TEXT Implicit Multipart", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "you can specify a different template for explicit render" do
|
||||
mail = BaseMailer.explicit_different_template('explicit_multipart_templates').deliver
|
||||
assert_equal("HTML Explicit Multipart Templates", mail.html_part.body.decoded)
|
||||
assert_equal("TEXT Explicit Multipart Templates", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "you can specify a different layout" do
|
||||
mail = BaseMailer.different_layout('different_layout').deliver
|
||||
assert_equal("HTML -- HTML", mail.html_part.body.decoded)
|
||||
assert_equal("PLAIN -- PLAIN", mail.text_part.body.decoded)
|
||||
end
|
||||
|
||||
test "you can specify the template path for implicit lookup" do
|
||||
mail = BaseMailer.welcome_from_another_path('another.path/base_mailer').deliver
|
||||
assert_equal("Welcome from another path", mail.body.encoded)
|
||||
|
||||
mail = BaseMailer.welcome_from_another_path(['unknown/invalid', 'another.path/base_mailer']).deliver
|
||||
assert_equal("Welcome from another path", mail.body.encoded)
|
||||
end
|
||||
|
||||
test "assets tags should use ActionMailer's asset_host settings" do
|
||||
ActionMailer::Base.config.asset_host = "http://global.com"
|
||||
ActionMailer::Base.config.assets_dir = "global/"
|
||||
|
||||
mail = AssetMailer.welcome
|
||||
|
||||
assert_equal(%{<img alt="Dummy" src="http://global.com/images/dummy.png" />}, mail.body.to_s.strip)
|
||||
end
|
||||
|
||||
test "assets tags should use a Mailer's asset_host settings when available" do
|
||||
ActionMailer::Base.config.asset_host = "global.com"
|
||||
ActionMailer::Base.config.assets_dir = "global/"
|
||||
|
||||
AssetMailer.asset_host = "http://local.com"
|
||||
|
||||
mail = AssetMailer.welcome
|
||||
|
||||
assert_equal(%{<img alt="Dummy" src="http://local.com/images/dummy.png" />}, mail.body.to_s.strip)
|
||||
end
|
||||
|
||||
test 'the view is not rendered when mail was never called' do
|
||||
mail = BaseMailer.without_mail_call
|
||||
assert_equal('', mail.body.to_s.strip)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test 'the return value of mailer methods is not relevant' do
|
||||
mail = BaseMailer.with_nil_as_return_value
|
||||
assert_equal('Welcome', mail.body.to_s.strip)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
# Before and After hooks
|
||||
|
||||
class MyObserver
|
||||
def self.delivered_email(mail)
|
||||
end
|
||||
end
|
||||
|
||||
class MySecondObserver
|
||||
def self.delivered_email(mail)
|
||||
end
|
||||
end
|
||||
|
||||
test "you can register an observer to the mail object that gets informed on email delivery" do
|
||||
ActionMailer::Base.register_observer(MyObserver)
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test "you can register an observer using its stringified name to the mail object that gets informed on email delivery" do
|
||||
ActionMailer::Base.register_observer("BaseTest::MyObserver")
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test "you can register multiple observers to the mail object that both get informed on email delivery" do
|
||||
ActionMailer::Base.register_observers("BaseTest::MyObserver", MySecondObserver)
|
||||
mail = BaseMailer.welcome
|
||||
MyObserver.expects(:delivered_email).with(mail)
|
||||
MySecondObserver.expects(:delivered_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
class MyInterceptor
|
||||
def self.delivering_email(mail)
|
||||
end
|
||||
end
|
||||
|
||||
class MySecondInterceptor
|
||||
def self.delivering_email(mail)
|
||||
end
|
||||
end
|
||||
|
||||
test "you can register an interceptor to the mail object that gets passed the mail object before delivery" do
|
||||
ActionMailer::Base.register_interceptor(MyInterceptor)
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test "you can register an interceptor using its stringified name to the mail object that gets passed the mail object before delivery" do
|
||||
ActionMailer::Base.register_interceptor("BaseTest::MyInterceptor")
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test "you can register multiple interceptors to the mail object that both get passed the mail object before delivery" do
|
||||
ActionMailer::Base.register_interceptors("BaseTest::MyInterceptor", MySecondInterceptor)
|
||||
mail = BaseMailer.welcome
|
||||
MyInterceptor.expects(:delivering_email).with(mail)
|
||||
MySecondInterceptor.expects(:delivering_email).with(mail)
|
||||
mail.deliver
|
||||
end
|
||||
|
||||
test "being able to put proc's into the defaults hash and they get evaluated on mail sending" do
|
||||
mail1 = ProcMailer.welcome
|
||||
yesterday = 1.day.ago
|
||||
Time.stubs(:now).returns(yesterday)
|
||||
mail2 = ProcMailer.welcome
|
||||
assert(mail1['X-Proc-Method'].to_s.to_i > mail2['X-Proc-Method'].to_s.to_i)
|
||||
end
|
||||
|
||||
test "we can call other defined methods on the class as needed" do
|
||||
mail = ProcMailer.welcome
|
||||
assert_equal("Thanks for signing up this afternoon", mail.subject)
|
||||
end
|
||||
|
||||
test "action methods should be refreshed after defining new method" do
|
||||
class FooMailer < ActionMailer::Base
|
||||
# this triggers action_methods
|
||||
self.respond_to?(:foo)
|
||||
|
||||
def notify
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal ["notify"], FooMailer.action_methods
|
||||
end
|
||||
|
||||
test "mailer can be anonymous" do
|
||||
mailer = Class.new(ActionMailer::Base) do
|
||||
def welcome
|
||||
mail
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal "anonymous", mailer.mailer_name
|
||||
|
||||
assert_equal "Welcome", mailer.welcome.subject
|
||||
assert_equal "Anonymous mailer body", mailer.welcome.body.encoded.strip
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Execute the block setting the given values and restoring old values after
|
||||
# the block is executed.
|
||||
def swap(klass, new_values)
|
||||
old_values = {}
|
||||
new_values.each do |key, value|
|
||||
old_values[key] = klass.send key
|
||||
klass.send :"#{key}=", value
|
||||
end
|
||||
yield
|
||||
ensure
|
||||
old_values.each do |key, value|
|
||||
klass.send :"#{key}=", value
|
||||
end
|
||||
end
|
||||
|
||||
def with_default(klass, new_values)
|
||||
old = klass.default_params
|
||||
klass.default(new_values)
|
||||
yield
|
||||
ensure
|
||||
klass.default_params = old
|
||||
end
|
||||
end
|
||||
@@ -1,172 +0,0 @@
|
||||
require 'abstract_unit'
|
||||
require 'mail'
|
||||
|
||||
class MyCustomDelivery
|
||||
end
|
||||
|
||||
class BogusDelivery
|
||||
def initialize(*)
|
||||
end
|
||||
|
||||
def deliver!(mail)
|
||||
raise "failed"
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultsDeliveryMethodsTest < ActiveSupport::TestCase
|
||||
test "default smtp settings" do
|
||||
settings = { :address => "localhost",
|
||||
:port => 25,
|
||||
:domain => 'localhost.localdomain',
|
||||
:user_name => nil,
|
||||
:password => nil,
|
||||
:authentication => nil,
|
||||
:enable_starttls_auto => true }
|
||||
assert_equal settings, ActionMailer::Base.smtp_settings
|
||||
end
|
||||
|
||||
test "default file delivery settings" do
|
||||
settings = {:location => "#{Dir.tmpdir}/mails"}
|
||||
assert_equal settings, ActionMailer::Base.file_settings
|
||||
end
|
||||
|
||||
test "default sendmail settings" do
|
||||
settings = {:location => '/usr/sbin/sendmail',
|
||||
:arguments => '-i -t'}
|
||||
assert_equal settings, ActionMailer::Base.sendmail_settings
|
||||
end
|
||||
end
|
||||
|
||||
class CustomDeliveryMethodsTest < ActiveSupport::TestCase
|
||||
def setup
|
||||
@old_delivery_method = ActionMailer::Base.delivery_method
|
||||
ActionMailer::Base.add_delivery_method :custom, MyCustomDelivery
|
||||
end
|
||||
|
||||
def teardown
|
||||
ActionMailer::Base.delivery_method = @old_delivery_method
|
||||
new = ActionMailer::Base.delivery_methods.dup
|
||||
new.delete(:custom)
|
||||
ActionMailer::Base.delivery_methods = new
|
||||
end
|
||||
|
||||
test "allow to add custom delivery method" do
|
||||
ActionMailer::Base.delivery_method = :custom
|
||||
assert_equal :custom, ActionMailer::Base.delivery_method
|
||||
end
|
||||
|
||||
test "allow to customize custom settings" do
|
||||
ActionMailer::Base.custom_settings = { :foo => :bar }
|
||||
assert_equal Hash[:foo => :bar], ActionMailer::Base.custom_settings
|
||||
end
|
||||
|
||||
test "respond to custom settings" do
|
||||
assert_respond_to ActionMailer::Base, :custom_settings
|
||||
assert_respond_to ActionMailer::Base, :custom_settings=
|
||||
end
|
||||
|
||||
test "does not respond to unknown settings" do
|
||||
assert_raise NoMethodError do
|
||||
ActionMailer::Base.another_settings
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MailDeliveryTest < ActiveSupport::TestCase
|
||||
class DeliveryMailer < ActionMailer::Base
|
||||
DEFAULT_HEADERS = {
|
||||
:to => 'mikel@test.lindsaar.net',
|
||||
:from => 'jose@test.plataformatec.com'
|
||||
}
|
||||
|
||||
def welcome(hash={})
|
||||
mail(DEFAULT_HEADERS.merge(hash))
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
ActionMailer::Base.delivery_method = :smtp
|
||||
end
|
||||
|
||||
def teardown
|
||||
DeliveryMailer.delivery_method = :smtp
|
||||
DeliveryMailer.perform_deliveries = true
|
||||
DeliveryMailer.raise_delivery_errors = true
|
||||
end
|
||||
|
||||
test "ActionMailer should be told when Mail gets delivered" do
|
||||
DeliveryMailer.deliveries.clear
|
||||
DeliveryMailer.expects(:deliver_mail).once
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
|
||||
test "delivery method can be customized per instance" do
|
||||
email = DeliveryMailer.welcome.deliver
|
||||
assert_instance_of Mail::SMTP, email.delivery_method
|
||||
email = DeliveryMailer.welcome(:delivery_method => :test).deliver
|
||||
assert_instance_of Mail::TestMailer, email.delivery_method
|
||||
end
|
||||
|
||||
test "delivery method can be customized in subclasses not changing the parent" do
|
||||
DeliveryMailer.delivery_method = :test
|
||||
assert_equal :smtp, ActionMailer::Base.delivery_method
|
||||
$BREAK = true
|
||||
email = DeliveryMailer.welcome.deliver
|
||||
assert_instance_of Mail::TestMailer, email.delivery_method
|
||||
end
|
||||
|
||||
test "non registered delivery methods raises errors" do
|
||||
DeliveryMailer.delivery_method = :unknown
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
end
|
||||
|
||||
test "does not perform deliveries if requested" do
|
||||
DeliveryMailer.perform_deliveries = false
|
||||
DeliveryMailer.deliveries.clear
|
||||
Mail::Message.any_instance.expects(:deliver!).never
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
|
||||
test "does not append the deliveries collection if told not to perform the delivery" do
|
||||
DeliveryMailer.perform_deliveries = false
|
||||
DeliveryMailer.deliveries.clear
|
||||
DeliveryMailer.welcome.deliver
|
||||
assert_equal(0, DeliveryMailer.deliveries.length)
|
||||
end
|
||||
|
||||
test "raise errors on bogus deliveries" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.deliveries.clear
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
end
|
||||
|
||||
test "does not increment the deliveries collection on error" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.deliveries.clear
|
||||
assert_raise RuntimeError do
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
assert_equal(0, DeliveryMailer.deliveries.length)
|
||||
end
|
||||
|
||||
test "does not raise errors on bogus deliveries if set" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.raise_delivery_errors = false
|
||||
assert_nothing_raised do
|
||||
DeliveryMailer.welcome.deliver
|
||||
end
|
||||
end
|
||||
|
||||
test "does not increment the deliveries collection on bogus deliveries" do
|
||||
DeliveryMailer.delivery_method = BogusDelivery
|
||||
DeliveryMailer.raise_delivery_errors = false
|
||||
DeliveryMailer.deliveries.clear
|
||||
DeliveryMailer.welcome.deliver
|
||||
assert_equal(0, DeliveryMailer.deliveries.length)
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1 +0,0 @@
|
||||
Anonymous mailer body
|
||||
@@ -1 +0,0 @@
|
||||
Welcome from another path
|
||||
@@ -1 +0,0 @@
|
||||
<%= image_tag "somelogo.png" %>
|
||||
@@ -1 +0,0 @@
|
||||
<%= image_tag "dummy.png" %>
|
||||
BIN
actionmailer/test/fixtures/attachments/foo.jpg
vendored
BIN
actionmailer/test/fixtures/attachments/foo.jpg
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 KiB |
BIN
actionmailer/test/fixtures/attachments/test.jpg
vendored
BIN
actionmailer/test/fixtures/attachments/test.jpg
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 2.0 KiB |
@@ -1 +0,0 @@
|
||||
Inside
|
||||
@@ -1 +0,0 @@
|
||||
text/html multipart
|
||||
@@ -1 +0,0 @@
|
||||
text/plain multipart
|
||||
@@ -1 +0,0 @@
|
||||
Attachment with content
|
||||
@@ -1 +0,0 @@
|
||||
HTML
|
||||
@@ -1 +0,0 @@
|
||||
PLAIN
|
||||
@@ -1 +0,0 @@
|
||||
body_text
|
||||
@@ -1 +0,0 @@
|
||||
<%= t('.greet_user', :name => 'lifo') %>
|
||||
@@ -1 +0,0 @@
|
||||
HTML Explicit Multipart Templates
|
||||
@@ -1 +0,0 @@
|
||||
TEXT Explicit Multipart Templates
|
||||
@@ -1 +0,0 @@
|
||||
<%= self.formats.inspect %>
|
||||
@@ -1 +0,0 @@
|
||||
<h1>Testing</h1>
|
||||
@@ -1 +0,0 @@
|
||||
HTML Implicit Multipart
|
||||
@@ -1 +0,0 @@
|
||||
TEXT Implicit Multipart
|
||||
@@ -1 +0,0 @@
|
||||
Implicit with locale EN HTML
|
||||
@@ -1 +0,0 @@
|
||||
Implicit with locale HTML
|
||||
@@ -1 +0,0 @@
|
||||
Implicit with locale PL TEXT
|
||||
@@ -1 +0,0 @@
|
||||
Implicit with locale TEXT
|
||||
@@ -1,5 +0,0 @@
|
||||
<h1>Inline Image</h1>
|
||||
|
||||
<%= image_tag attachments['logo.png'].url %>
|
||||
|
||||
<p>This is an image that is inline</p>
|
||||
@@ -1,4 +0,0 @@
|
||||
Inline Image
|
||||
|
||||
No image for you
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Testing
|
||||
@@ -1 +0,0 @@
|
||||
Welcome
|
||||
@@ -1 +0,0 @@
|
||||
<% raise 'the template should not be rendered' %>
|
||||
@@ -1 +0,0 @@
|
||||
You logged out
|
||||
@@ -1 +0,0 @@
|
||||
We do not spam
|
||||
1
actionmailer/test/fixtures/helper_mailer/use_helper.rhtml
vendored
Normal file
1
actionmailer/test/fixtures/helper_mailer/use_helper.rhtml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello, <%= person_name %>. Thanks for registering!
|
||||
1
actionmailer/test/fixtures/helper_mailer/use_helper_method.rhtml
vendored
Normal file
1
actionmailer/test/fixtures/helper_mailer/use_helper_method.rhtml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
This message brought to you by <%= name_of_the_mailer_class %>.
|
||||
5
actionmailer/test/fixtures/helper_mailer/use_mail_helper.rhtml
vendored
Normal file
5
actionmailer/test/fixtures/helper_mailer/use_mail_helper.rhtml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
From "Romeo and Juliet":
|
||||
|
||||
<%= block_format @text %>
|
||||
|
||||
Good ol' Shakespeare.
|
||||
1
actionmailer/test/fixtures/helper_mailer/use_test_helper.rhtml
vendored
Normal file
1
actionmailer/test/fixtures/helper_mailer/use_test_helper.rhtml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
So, <%= test_format(@text) %>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user