Compare commits

..

112 Commits

Author SHA1 Message Date
David Heinemeier Hansson
8382493551 RAILS_ROOT, please
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@4199 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-04-07 18:29:43 +00:00
David Heinemeier Hansson
a60ecc1571 Fixed that boot.rb would set RAILS_GEM_VERSION twice, not respect an uncommented RAILS_GEM_VERSION line, and not use require_gem [DHH] Added rake rails:update:configs to update config/boot.rb from the latest (also included in rake rails:update) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@4198 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-04-07 18:22:16 +00:00
David Heinemeier Hansson
2120651dde Fixed that boot.rb would set RAILS_GEM_VERSION twice, not respect an uncommented RAILS_GEM_VERSION line, and not use require_gem [DHH] Added rake rails:update:configs to update config/boot.rb from the latest (also included in rake rails:update) [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@4197 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-04-07 18:21:52 +00:00
David Heinemeier Hansson
e1deb6acca Now where did that stable go
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@4195 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-04-07 16:39:58 +00:00
David Heinemeier Hansson
2f4093690b Tagged and branches 1.1
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@4091 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-03-28 05:22:14 +00:00
Jeremy Kemper
ec20838381 r3847@asus: jeremy | 2006-02-26 15:26:53 -0800
Apply [3674] to stable. Closes #3591.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3676 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-02-26 23:26:47 +00:00
Jeremy Kemper
fba571a50a r3846@asus: jeremy | 2006-02-26 15:24:58 -0800
silence test/unit whining DefaultsTest is empty


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3675 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-02-26 23:26:42 +00:00
Jeremy Kemper
c049ccd786 r3730@asus: jeremy | 2006-02-09 11:42:57 -0800
Apply [3559] to stable. Closes #3581.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3560 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-02-09 19:43:29 +00:00
Jeremy Kemper
f13ddd3eb8 r3724@asus: jeremy | 2006-02-09 10:13:41 -0800
Apply [3554] to stable. PostgreSQL: correctly parse negative integer column defaults. Closes #3776.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3555 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-02-09 18:14:22 +00:00
Nicholas Seckar
4ae6f74612 Show a meaningful error when the DB2 adapter cannot be loaded due to missing dependencies.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3461 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-01-22 06:30:52 +00:00
Marcel Molina
f999ab089d Fix case of VERSION option in migration docs. Closes #3521.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3427 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2006-01-18 03:31:14 +00:00
Scott Barron
16a31ccd7d Apply [3359] to stable branch.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3360 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-30 18:50:29 +00:00
Florian Weber
cf448036b6 Fixed that saving a model with multiple habtm associations would only save the first one.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3332 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-21 16:56:32 +00:00
Thomas Fuchs
37e0d5de89 Update stable to scriptaculous 1.5 final
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3300 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 18:41:10 +00:00
Jeremy Kemper
e8c7d7af48 r3465@asus: jeremy | 2005-12-13 10:15:33 -0800
Apply [3298] to stable.  Don't used defined? on a scoped constant since it results in a const_missing call.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3299 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 18:37:32 +00:00
Sam Stephenson
cad37c3eaf Update to Prototype 1.4.0 final
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3297 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 17:51:17 +00:00
Jeremy Kemper
da66198fcd r3458@asus: jeremy | 2005-12-13 09:33:50 -0800
Roll back [3245] on stable.  References #3116.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3296 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 17:34:09 +00:00
Jeremy Kemper
2670371690 r3457@asus: jeremy | 2005-12-13 08:52:39 -0800
Apply [3293] to stable.  MySQL: allow encoding option for mysql.rb driver.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3295 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 17:34:00 +00:00
David Heinemeier Hansson
5da86f8c27 Made ready for 1.0 launch
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3292 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 16:41:44 +00:00
Marcel Molina
11598ac77b Apply [3290] to stable. Fix typo in benchmarker usage string.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3291 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 02:33:56 +00:00
Marcel Molina
eeb00ea334 Apply [3288] to stable. Fix typo in profiler usage string.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3289 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-13 02:26:15 +00:00
Marcel Molina
d5bfd181d7 Remove duplicate entry from chagnelogs.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3278 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-11 05:09:52 +00:00
Marcel Molina
921c5a7fdc Apply [3276] to stable. Update instructions on how to find and install generators. Closes #3172.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3277 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-11 05:07:39 +00:00
Jeremy Kemper
4ef417cf96 r3435@asus: jeremy | 2005-12-10 13:40:43 -0800
Apply [3274] to stable.  MySQL: fixes for the bundled mysql.rb driver.  Closes #3160.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3275 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-10 21:41:16 +00:00
David Heinemeier Hansson
65e93a523f Close h1 with h1 not h2 #3150
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3273 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-10 19:58:02 +00:00
Jeremy Kemper
2018b2a8d2 r3429@asus: jeremy | 2005-12-09 16:55:16 -0800
Apply [3270] SQLServer rollup to stable.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3271 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-10 01:44:03 +00:00
Jeremy Kemper
596fe49697 r3421@asus: jeremy | 2005-12-09 15:42:40 -0800
Apply [3265] to stable.  Name vendor/generators source differently from lib/generators source.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3268 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 23:43:20 +00:00
Jeremy Kemper
4e2e1a1bc6 r3420@asus: jeremy | 2005-12-09 15:38:30 -0800
Apply [3264] to stable.  MySQL: ensure that @config is set.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3267 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 23:43:16 +00:00
Jeremy Kemper
36619f2084 r3419@asus: jeremy | 2005-12-09 15:36:50 -0800
Apply [3165] to stable. MySQL, PostgreSQL: reconnect! also reconfigures the connection. Otherwise, the connection 'loses' its settings if it times out and is reconnected. References #2978.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3266 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 23:43:09 +00:00
Jeremy Kemper
d2b0cc7bf4 r3416@asus: jeremy | 2005-12-09 15:07:21 -0800
Apply [3262] to stable.  Generator looks in vendor/generators also.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3263 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 23:07:28 +00:00
Jeremy Kemper
eb79743b13 r3413@asus: jeremy | 2005-12-09 15:03:42 -0800
Apply [3260] to stable.  Fix bundled mysql.rb to correctly check for PROTO_41.  Fixed scramble41 with nil password.  Fixed change_user with PROTO_41.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3261 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 23:03:47 +00:00
Jeremy Kemper
256d28d330 r3410@asus: jeremy | 2005-12-09 10:48:32 -0800
Apply [3258] to stable.  Fix shebang handling for empty files.  Closes #2927.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3259 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 18:48:38 +00:00
Jeremy Kemper
1b4470c82f r3407@asus: jeremy | 2005-12-09 10:39:00 -0800
Apply [3256] to stable.  Generator copies files in binary mode.  Closes #3156.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3257 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-09 18:39:15 +00:00
Jeremy Kemper
69e4c3c474 r3404@asus: jeremy | 2005-12-08 15:24:44 -0800
Apply [3254] to stable.  Fix some test failures due to MySQL assumptions.  References #3149.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3255 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 23:24:52 +00:00
Jeremy Kemper
94a25200a1 r3401@asus: jeremy | 2005-12-07 23:30:11 -0800
Apply [3252] to stable.  Oracle: active? performs a select instead of a commit.  Closes #3133.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3253 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 07:30:18 +00:00
David Heinemeier Hansson
93bd3b00e1 Prepare versions for release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3250 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 05:56:23 +00:00
David Heinemeier Hansson
57e992dc0f Remove insignificant classes from docs
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3248 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 05:43:54 +00:00
Jeremy Kemper
a9f4205b21 r3392@asus: jeremy | 2005-12-07 21:10:42 -0800
Apply [3246] to stable.  MySQL: more robust test for nullified result hashes.  Closes #3124.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3247 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 05:11:38 +00:00
Jeremy Kemper
8a19d148b7 r3389@asus: jeremy | 2005-12-07 20:52:39 -0800
Apply [3244] to stable.  SQLite: find database file when RAILS_ROOT is a symlink.  Closes #3116.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3245 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:52:51 +00:00
Jeremy Kemper
bd9b27df8b r3387@asus: jeremy | 2005-12-07 20:48:42 -0800
Apply [3242] to stable.  Reloading an instance refreshes its aggregations as well as its associations.  Closes #3024.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3243 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:48:50 +00:00
David Heinemeier Hansson
c6120acc00 Fixed that using :include together with :conditions array in Base.find would cause NoMethodError #2887 [Paul Hammmond]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3241 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:46:40 +00:00
Jeremy Kemper
60d68aab53 r3379@asus: jeremy | 2005-12-07 20:42:31 -0800
Don't warn on keep_flash in test.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3239 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:42:54 +00:00
Jeremy Kemper
67cbb0f04c r3378@asus: jeremy | 2005-12-07 20:41:47 -0800
Apply [3237] to stable.  More robust relative url root discovery for SCGI compatibility.  This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint.  Closes #3070.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3238 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:42:47 +00:00
Jeremy Kemper
0bb6521649 r3375@asus: jeremy | 2005-12-07 20:36:13 -0800
Apply [3235] to stable.  PostgreSQL: more robust sequence name discovery.  Closes #3087.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3236 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:36:22 +00:00
Jeremy Kemper
966b027c71 r3372@asus: jeremy | 2005-12-07 20:25:20 -0800
Apply [3233] to stable.  Oracle: use syntax compatible with Oracle 8.  Closes #3131.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3234 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 04:25:36 +00:00
David Heinemeier Hansson
2c5d342809 Warn people not to change boot.rb
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3232 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 03:54:53 +00:00
David Heinemeier Hansson
d45ca84858 Fixed RDoc warnings [DHH]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3229 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-08 03:23:54 +00:00
Jeremy Kemper
142304b79d r3363@asus: jeremy | 2005-12-06 22:26:27 -0800
Apply #428 changesets to stable: r3000, r3001, r3002, r3025, r3045, r3096, r3148, r3152, r3165, r3189, r3195, r3205, r3216, r3219, r3221, r3222.  Closes #428.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3226 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-07 06:26:54 +00:00
David Heinemeier Hansson
2deefbade0 Shouldnt overwrite Base documentation
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3225 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-06 02:46:18 +00:00
Sam Stephenson
66cd1330a5 Add builtin/ to the gemspec. Closes #3047.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3224 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-06 00:32:57 +00:00
Marcel Molina
cf656ec1f7 Apply [3207] to stable. Fix docs for text_area_tag. Closes #3083.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3208 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-02 04:41:03 +00:00
Jeremy Kemper
1c86c6f4d9 r3339@asus: jeremy | 2005-12-01 17:03:16 -0800
Apply [3202] to stable.  Firebird: updated for FireRuby 0.4.0.  Closes #3009.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3204 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-12-02 01:04:54 +00:00
Sam Stephenson
14c2334c84 Bring stable lighttpd server script in-line with trunk
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3199 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-30 19:26:23 +00:00
Marcel Molina
50c5b2817f Apply [3196] to stable. Make load_fixtures include csv fixtures. Closes #3053.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3197 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-28 23:37:51 +00:00
Nicholas Seckar
786726692f Apply freeze gems fix to stable
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3192 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-28 00:56:23 +00:00
Florian Weber
b762e0141c Made test_update_attributes_after_push_without_duplicate_join_table_rows pass with Oracle Adapter.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3187 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 12:54:08 +00:00
Jeremy Kemper
6865b66621 r3315@asus: jeremy | 2005-11-23 23:13:48 -0800
Apply [3184] to stable.  SQLServer: resolve column aliasing/quoting collision when using limit or offset in an eager find.  Closes #2974.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3185 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 07:16:44 +00:00
Jeremy Kemper
1dc4783707 r3313@asus: jeremy | 2005-11-23 23:03:36 -0800
Apply [3182] to stable.  Reloading a model doesn't lose track of its connection.  Closes #2996.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3183 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 07:06:33 +00:00
Jeremy Kemper
98811479f0 r3307@asus: jeremy | 2005-11-23 17:52:14 -0800
Apply [3110] to stable.  PostgreSQL: the purge_test_database Rake task shouldn't explicitly specify the template0 template when creating a fresh test database.  References #2964.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3180 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 01:55:39 +00:00
Jeremy Kemper
2b4dd8c6e7 r3306@asus: jeremy | 2005-11-23 17:39:24 -0800
Apply [3100], [3108] to stable.  Makes new ActiveRecordStore sessions work correctly with components.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3179 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 01:55:32 +00:00
Jeremy Kemper
3fcec37473 r3305@asus: jeremy | 2005-11-23 17:33:51 -0800
Apply [3151] to stable.  Don't put flash in session if sessions are disabled.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3178 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 01:55:27 +00:00
Jeremy Kemper
15839d56d0 r3303@asus: jeremy | 2005-11-23 17:14:31 -0800
Apply [3051], [3052], [3053], [3059] to stable.  Brings the Firebird adapter to 1.0.  References #1874.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3177 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-24 01:17:30 +00:00
Jeremy Kemper
9c05146f4b r3296@asus: jeremy | 2005-11-23 13:38:56 -0800
Apply [3170] to stable.  Credit ticket author.  References #2888.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3173 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-23 21:41:57 +00:00
Jeremy Kemper
a766ae6d84 r3285@asus: jeremy | 2005-11-22 13:33:04 -0800
Apply [3162] to stable.  Model generator: correct relative path to test_helper in unit test.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3172 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-23 21:41:48 +00:00
Jeremy Kemper
cc4deb1610 r3269@asus: jeremy | 2005-11-21 04:46:40 -0800
Apply [3148] to stable.  Simpler Mysql load test.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3171 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-23 21:41:42 +00:00
Florian Weber
b87294c671 Fixed bug where using update_attribute after pushing a record to a habtm association of the object caused duplicate rows in the join table. #2888
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3168 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-23 21:14:47 +00:00
Thomas Fuchs
7d6e3d0857 Make javascript_include_tag :default behave correctly with application.js, fixes #2986
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3164 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-22 23:18:53 +00:00
Marcel Molina
4906b32450 Apply [3159] to stable. Remove superfluous check for pluralize_table_names (use table_name which does this check when it's set).
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3160 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-22 09:06:04 +00:00
Marcel Molina
541da098f0 Apply [3157] to stable. Make ActionController's render honor the :locals option when rendering a :file. Closes #1665.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3158 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-22 08:41:59 +00:00
Marcel Molina
7bf86f2c57 Revert [3140]. Behavior of introducing Kernel.binding causes breakage.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3143 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-21 09:38:03 +00:00
Jeremy Kemper
81f3a923d7 r3258@asus: jeremy | 2005-11-21 00:33:59 -0800
Apply [3139] to stable.  Use Kernel.binding rather than binding to allow columns of that name.  Closes #2973.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3140 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-21 08:36:13 +00:00
Marcel Molina
4a9847f987 Apply [3136] to stable. Make the db_schema_dump task honor the SCHEMA environment variable if present the way db_schema_import does. Closes #2931.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3138 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-21 08:04:44 +00:00
Sam Stephenson
743fb60a8a Strip out trailing &_= for raw post bodies #2868
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3137 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-21 08:04:28 +00:00
Marcel Molina
6329724293 Apply [3120] to stable. Make help for the console command more explicit about how to specify the desired environment in which to run the console. Closes #2911.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3121 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-21 05:35:54 +00:00
Marcel Molina
78e95faec5 Apply #3111 to stable. Correct docs for automatic layout assignment. Closes #2610.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3112 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-20 18:55:26 +00:00
Marcel Molina
371fa97ccc Apply [3105] to stable. Add tasks to create, drop and rebuild the MySQL and PostgreSQL test databases.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3106 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-20 07:51:27 +00:00
Jeremy Kemper
1aac21ca24 r3201@asus: jeremy | 2005-11-19 21:31:47 -0800
Apply [3103] to stable.  Document request.env and request.host.  Strip trailing whitespace.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3104 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-20 05:33:46 +00:00
Jeremy Kemper
54977e0364 r3199@asus: jeremy | 2005-11-19 21:13:22 -0800
Apply [3101] to stable.  Allows generator to specify migrations directory.  Closes #2960.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3102 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-20 05:15:18 +00:00
Jeremy Kemper
3f2541b4ff r3190@asus: jeremy | 2005-11-19 20:20:48 -0800
Apply [3098] to stable.  Introducing the session_migration generator.  Creates an add_session_table migration.  Closes #2958.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3099 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-20 04:26:21 +00:00
Jeremy Kemper
bed4fec60e r3178@asus: jeremy | 2005-11-19 01:59:47 -0800
Apply [3094] to stable.  Use query methods rather than readers in boolean tests for [3092].  References #2949.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3095 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-19 10:01:34 +00:00
Jeremy Kemper
fe09fa278e r3174@asus: jeremy | 2005-11-19 01:53:00 -0800
Apply [3092] to stable.  Correct boolean handling in generated reader methods.  Closes #2945.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3093 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-19 09:54:55 +00:00
Sam Stephenson
d4039f5246 Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument)
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3091 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-19 06:59:07 +00:00
Jeremy Kemper
b8ea04f8e8 r3167@asus: jeremy | 2005-11-18 22:47:31 -0800
Apply [3089] to stable.  Don't generate read methods for columns whose names are not valid ruby method names.  Closes #2946.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3090 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-19 06:49:18 +00:00
Jeremy Kemper
beddd9680e r3157@asus: jeremy | 2005-11-17 21:27:39 -0800
Apply [3081] to stable.  Eliminate nil from newly generated logfiles.  Closes #2927.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3088 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-18 21:22:09 +00:00
Sam Stephenson
dcaa871695 Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3079 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-18 00:53:11 +00:00
Jeremy Kemper
9796a587b8 r3149@asus: jeremy | 2005-11-17 12:47:36 -0800
Apply [3075] to stable.  Document :force option to create_table.  Closes #2921.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3077 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 20:49:31 +00:00
Jeremy Kemper
de5980289c r3148@asus: jeremy | 2005-11-17 12:45:44 -0800
Apply [3074] to stable.  Don't add the same conditions twice in has_one finder sql.  Closes #2916.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3076 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 20:49:24 +00:00
Tobias Lütke
0023cb1b8c stable merge of 3032 -- Updated docs for in_place_editor by Justin Palmer
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3071 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 05:04:11 +00:00
Tobias Lütke
37664eb90c stable merge of [3069]
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3070 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 04:59:13 +00:00
Marcel Molina
d212f03093 Apply [3067] to stable. Update from LGPL to MIT license as per Minero Aoki's permission.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3068 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 00:53:29 +00:00
Marcel Molina
afcd3ebcbb Apply [3065] to stable. Rename Version constant to VERSION. Closes #2802.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3066 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-17 00:38:59 +00:00
Jeremy Kemper
b35d05f594 r3130@asus: jeremy | 2005-11-16 14:37:47 -0800
Apply [3063] to stable.  Remove CHANGELOG from apidoc Rake task since it isn't included with new apps anymore.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3064 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-16 22:39:01 +00:00
Jeremy Kemper
1ec7fb609d r3120@asus: jeremy | 2005-11-16 13:34:45 -0800
Apply [3060] to stable.  Remove the unused, slow response_dump and session_dump variables from error pages.  Closes #1222.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3061 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-16 21:36:12 +00:00
Nicholas Seckar
113c815c6a Change Inflector#constantize to use eval instead of const_get
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3050 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-16 01:17:28 +00:00
Jeremy Kemper
9decb1a46f r3077@asus: jeremy | 2005-11-14 14:28:21 -0800
Apply [3035] to stable.  SQLServer: insert uses given primary key value if not nil rather than SELECT @@IDENTITY.  Closes #2866.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3037 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 22:29:27 +00:00
Thomas Fuchs
49de64e0a4 Update stable to latest script.aculo.us
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3034 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 20:50:49 +00:00
Jeremy Kemper
58c7f2a744 r3073@asus: jeremy | 2005-11-13 23:42:32 -0800
Apply [3021] to stable.  Correct documentation for Base.delete_all.  Closes #1568.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3022 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 07:43:03 +00:00
Jeremy Kemper
e24096d616 r3070@asus: jeremy | 2005-11-13 20:54:50 -0800
Apply [3019] to stable.  Oracle: test case for column default parsing.  Closes #2788.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3020 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 04:55:18 +00:00
Jeremy Kemper
902cb21124 r3066@asus: jeremy | 2005-11-13 20:24:18 -0800
Apply [3017] to stable.  Update documentation for Migrations.  Closes #2861.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3018 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 04:24:52 +00:00
Jeremy Kemper
e01febbe68 r3063@asus: jeremy | 2005-11-13 16:13:51 -0800
Apply [3015] to stable.  Update documentation for render :file.  Closes #2858.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3016 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 00:14:37 +00:00
Jeremy Kemper
364f6d645a r3058@asus: jeremy | 2005-11-13 16:09:17 -0800
Apply [3013] to stable.  Note that the ruby-memcache bindings are required to use the memcache store.  Closes #2857.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3014 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-14 00:09:42 +00:00
Jeremy Kemper
971b40f59f r3055@asus: jeremy | 2005-11-13 14:48:48 -0800
Apply [3009] to stable:  Reapply [2942] which was elided by [2997].  References #2788.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3010 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 22:49:19 +00:00
Jeremy Kemper
af0ad4d50b r3053@asus: jeremy | 2005-11-13 14:37:39 -0800
Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb.  Closes #2855.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@3008 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 22:38:39 +00:00
Jeremy Kemper
f0b5630dce r3037@asus: jeremy | 2005-11-13 00:11:26 -0800
Apply [2998] to stable.  PostgreSQL: last_insert_id uses select_value rather than using @connection.exec directly.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2999 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 08:12:22 +00:00
Jeremy Kemper
532d4e8782 r3033@asus: jeremy | 2005-11-12 23:27:13 -0800
Apply [2996] to stable.  Much faster Oracle column reflection.  Closes #2848.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2997 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 07:29:02 +00:00
Jeremy Kemper
b970d2055a r3027@asus: jeremy | 2005-11-12 22:37:45 -0800
Apply [2994] to stable.  PostgreSQL: correct the sequence discovery fallback query.  References #2594.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2995 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 06:38:35 +00:00
Jeremy Kemper
9403f471b4 r3022@asus: jeremy | 2005-11-12 18:40:42 -0800
merge to stable
 r3024@asus:  jeremy | 2005-11-12 19:00:49 -0800
 Apply [2992] to stable.  SQLite: the clone_structure_to_test and purge_test_database Rake tasks should always use the test environment.  References #2846.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2993 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 03:01:19 +00:00
Jeremy Kemper
17836f4819 r3008@asus (orig r2978): david | 2005-11-11 01:50:42 -0800
Make sure that legacy db tasks also reference :database for SQLite (closes #2830) [kazuhiko@fdiary.net]
 r3009@asus (orig r2979):  david | 2005-11-11 02:02:44 -0800
 Changelogging
 r3011@asus (orig r2981):  bitsweat | 2005-11-11 10:49:01 -0800
 Include the Enumerable module in ActiveRecord::Errors.
 r3012@asus (orig r2982):  bitsweat | 2005-11-11 15:45:02 -0800
 SQLServer: don't report limits for unsupported field types.  Closes #2835.
 r3014@asus (orig r2984):  minam | 2005-11-11 21:09:05 -0800
 Make Validations#create! use the current scope
 
 r3015@asus (orig r2985):  bitsweat | 2005-11-12 03:59:54 -0800
  r4325@asus:  jeremy | 2005-11-12 03:57:46 -0800
  PostgreSQL: correctly discover custom primary key sequences.  PostgreSQL: smarter sequence name defaults, stricter last_insert_id, warn on pk without sequence.  Base.reset_sequence_name analogous to reset_table_name (mostly useful for testing).  Base.define_attr_method allows nil values.  References #2594.
 
 r3017@asus (orig r2987):  david | 2005-11-12 08:26:23 -0800
 Pulled auto-starting browser: More pain than gain
 r3019@asus (orig r2989):  bitsweat | 2005-11-12 14:28:38 -0800
 PostgreSQL: min_messages = warning for AR tests.
 r3020@asus (orig r2990):  bitsweat | 2005-11-12 17:12:48 -0800
 SQLite: the clone_structure_to_test Rake task should always use the test environment.  References #2846.


git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2991 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-13 01:16:59 +00:00
Jamis Buck
e823ef8336 merge from trunk (include Enumerable in AR::Errors, make Validations#create! use current scope)
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2988 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-12 17:41:59 +00:00
David Heinemeier Hansson
ce6545e574 Pulled auto-starting browser: More pain than gain
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2986 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-12 16:26:12 +00:00
Jeremy Kemper
e8086859c5 Merge [2982] to stable. SQLServer: don't report limits for unsupported field types. References #2835.
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2983 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-12 02:56:11 +00:00
David Heinemeier Hansson
e0ef631055 Added stable branch to prepare for 1.0 release
git-svn-id: http://svn-commit.rubyonrails.org/rails/branches/stable@2980 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
2005-11-11 10:07:24 +00:00
2595 changed files with 90036 additions and 230512 deletions

21
.gitignore vendored
View File

@@ -1,21 +0,0 @@
pkg
.bundle
debug.log
doc/rdoc
activemodel/doc
activeresource/doc
activerecord/doc
activerecord/sqlnet.log
actionpack/doc
actionmailer/doc
activesupport/doc
activemodel/test/fixtures/fixture_database.sqlite3
actionpack/test/tmp
activesupport/test/fixtures/isolation_test
dist
railties/test/500.html
railties/test/fixtures/tmp
railties/test/initializer/root/log
railties/doc
railties/guides/output
railties/tmp

View File

@@ -1,25 +0,0 @@
script: 'ci/travis.rb'
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
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: "CGWvthGkBKNnTnk9YSmf9AXKoiRI33fCl5D3jU4nx3cOPu6kv2R9nMjt9EAo\nOuS4Q85qNSf4VNQ2cUPNiNYSWQ+XiTfivKvDUw/QW9r1FejYyeWarMsSBWA+\n0fADjF1M2dkDIVLgYPfwoXEv7l+j654F1KLKB69F0F/netwP9CQ="
bundler_args: --path vendor/bundle

View File

@@ -1,3 +0,0 @@
--exclude /templates/
--quiet
act*/lib/**/*.rb

53
Gemfile
View File

@@ -1,53 +0,0 @@
source 'http://rubygems.org'
gemspec
gem "rake", ">= 0.8.7"
gem 'mocha', '>= 0.13.0', :require => false
gem "pry"
group :doc do
gem "rdoc", "~> 3.4"
gem "horo", "= 1.0.3"
gem "RedCloth", "~> 4.2" if RUBY_VERSION < "1.9.3"
end
# for perf tests
gem "faker"
gem "rbench"
gem "addressable"
# AS
gem "memcache-client", ">= 1.8.5"
platforms :ruby do
gem 'json'
gem 'yajl-ruby'
gem "nokogiri", ">= 1.4.4"
# AR
gem "sqlite3", "~> 1.3.3"
group :db do
gem "pg", ">= 0.9.0"
gem "mysql", ">= 2.8.1"
gem "mysql2", :git => "git://github.com/brianmario/mysql2.git", :branch => "0.2.x"
end
end
env :AREL do
gem "arel", :path => ENV['AREL']
end
# gems that are necessary for ActiveRecord tests with Oracle database
if ENV['ORACLE_ENHANCED_PATH'] || ENV['ORACLE_ENHANCED']
platforms :ruby do
gem 'ruby-oci8', ">= 2.0.4"
end
if ENV['ORACLE_ENHANCED_PATH']
gem 'activerecord-oracle_enhanced-adapter', :path => ENV['ORACLE_ENHANCED_PATH']
else
gem "activerecord-oracle_enhanced-adapter", :git => "git://github.com/rsim/oracle-enhanced.git"
end
end

View File

@@ -1,124 +0,0 @@
GIT
remote: git://github.com/brianmario/mysql2.git
revision: 3c7548851f5bf124eb23307286ef95d61172ac4b
branch: 0.2.x
specs:
mysql2 (0.2.22)
PATH
remote: .
specs:
actionmailer (3.0.20)
actionpack (= 3.0.20)
mail (~> 2.2)
actionpack (3.0.20)
activemodel (= 3.0.20)
activesupport (= 3.0.20)
builder (~> 3.2.0)
erubis (~> 2.7.0)
i18n (~> 0.6.0)
rack (~> 1.4.1)
rack-mount (~> 0.6.14)
rack-test (~> 0.6.1)
tzinfo (~> 0.3.23)
activemodel (3.0.20)
activesupport (= 3.0.20)
builder (~> 3.2.0)
i18n (~> 0.6.0)
activerecord (3.0.20)
activemodel (= 3.0.20)
activesupport (= 3.0.20)
arel (~> 2.0.10)
tzinfo (~> 0.3.23)
activeresource (3.0.20)
activemodel (= 3.0.20)
activesupport (= 3.0.20)
activesupport (3.0.20)
rails (3.0.20)
actionmailer (= 3.0.20)
actionpack (= 3.0.20)
activerecord (= 3.0.20)
activeresource (= 3.0.20)
activesupport (= 3.0.20)
bundler (~> 1.0)
railties (= 3.0.20)
railties (3.0.20)
actionpack (= 3.0.20)
activesupport (= 3.0.20)
rake (>= 0.8.7)
rdoc (~> 3.4)
thor (~> 0.18)
GEM
remote: http://rubygems.org/
specs:
addressable (2.3.6)
arel (2.0.10)
builder (3.2.2)
coderay (1.1.0)
erubis (2.7.0)
faker (1.3.0)
i18n (~> 0.5)
horo (1.0.3)
rdoc (>= 2.5)
i18n (0.6.9)
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)
method_source (0.8.2)
mime-types (1.25.1)
mini_portile (0.5.3)
mocha (1.0.0)
metaclass (~> 0.0.1)
mysql (2.9.1)
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
pg (0.17.1)
polyglot (0.3.4)
pry (0.9.12.6)
coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
rack (1.4.5)
rack-mount (0.6.14)
rack (>= 1.0.0)
rack-test (0.6.2)
rack (>= 1.0)
rake (10.2.2)
rbench (0.2.3)
rdoc (3.12.2)
json (~> 1.4)
slop (3.5.0)
sqlite3 (1.3.9)
thor (0.19.1)
treetop (1.4.15)
polyglot
polyglot (>= 0.3.1)
tzinfo (0.3.39)
yajl-ruby (1.2.0)
PLATFORMS
ruby
DEPENDENCIES
addressable
arel
faker
horo (= 1.0.3)
json
memcache-client (>= 1.8.5)
mocha (>= 0.13.0)
mysql (>= 2.8.1)
mysql2!
nokogiri (>= 1.4.4)
pg (>= 0.9.0)
pry
rails!
rake (>= 0.8.7)
rbench
rdoc (~> 3.4)
sqlite3 (~> 1.3.3)
yajl-ruby

View File

@@ -1 +0,0 @@
3.0.20

View File

@@ -1,68 +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-Control pattern.
This pattern splits the view (also called the presentation) into "dumb"
templates that are primarily responsible for inserting pre-built data in between
HTML tags. The model contains the "smart" domain objects (such as Account,
Product, Person, Post) that holds all the business logic and knows how to
persist themselves to a database. The controller handles the incoming requests
(such as Save New Account, Update Product, Show Post) by manipulating the model
and directing data to the view.
In \Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in its
{README}[link:files/activerecord/README_rdoc.html].
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
\Rails. You can read more about Action Pack in its
{README}[link:files/actionpack/README_rdoc.html].
== 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 can 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/getting_started.html].
* 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_rails.html] for guidelines about how
to proceed. {Join us}[http://contributors.rubyonrails.org]!
== License
Ruby on \Rails is released under the MIT license.

161
Rakefile
View File

@@ -1,161 +0,0 @@
gem 'rdoc', '>= 2.5.10'
require 'rdoc'
require 'rake'
require 'rdoc/task'
$:.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"
# RDoc skips some files in the Rails tree due to its binary? predicate. This is a quick
# hack for edge docs, until we decide which is the correct way to address this issue.
# If not fixed in RDoc itself, via an option or something, we should probably move this
# to railties and use it also in doc:rails.
def hijack_rdoc!
require "rdoc/parser"
class << RDoc::Parser
def binary?(file)
s = File.read(file, 1024) or return false
if s[0, 2] == Marshal.dump('')[0, 2] then
true
elsif file =~ /erb\.rb$/ then
false
elsif s.index("\x00") then # ORIGINAL is s.scan(/<%|%>/).length >= 4 || s.index("\x00")
true
elsif 0.respond_to? :fdiv then
s.count("^ -~\t\r\n").fdiv(s.size) > 0.3
else # HACK 1.8.6
(s.count("^ -~\t\r\n").to_f / s.size) > 0.3
end
end
end
end
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|
hijack_rdoc!
rdoc.rdoc_dir = 'doc/rdoc'
rdoc.title = "Ruby on Rails Documentation"
rdoc.options << '-f' << 'horo'
rdoc.options << '-c' << 'utf-8'
rdoc.options << '-m' << 'README.rdoc'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('railties/CHANGELOG')
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/*')
rdoc.rdoc_files.include('activerecord/README.rdoc')
rdoc.rdoc_files.include('activerecord/CHANGELOG')
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')
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')
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')
rdoc.rdoc_files.include('actionmailer/lib/action_mailer/base.rb')
rdoc.rdoc_files.exclude('actionmailer/lib/action_mailer/vendor/*')
rdoc.rdoc_files.include('activesupport/README.rdoc')
rdoc.rdoc_files.include('activesupport/CHANGELOG')
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')
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

View File

@@ -1,261 +1,9 @@
## Rails 3.0.20 (unreleased)
## Rails 3.0.19 (Jan 8, 2013)
* No changes.
## Rails 3.0.18 (Jan 2, 2013)
* No changes.
## Rails 3.0.17 (Aug 9, 2012)
* No changes.
## Rails 3.0.16 (Jul 26, 2012)
* No changes.
## Rails 3.0.14 (Jun 12, 2012)
* No changes.
* Rails 3.0.13 (May 31, 2012)
* No changes.
*Rails 3.0.10 (August 16, 2011)*
*No changes.
*Rails 3.0.9 (June 16, 2011)*
*No changes.
*Rails 3.0.8 (June 7, 2011)*
* Mail dependency increased to 2.2.19
*Rails 3.0.7 (April 18, 2011)*
* remove AM delegating register_observer and register_interceptor to Mail [Josh Kalderimis]
*Rails 3.0.6 (April 5, 2011)
* Don't allow i18n to change the minor version, version now set to ~> 0.5.0 [Santiago Pastorino]
*Rails 3.0.5 (February 26, 2011)*
* No changes.
*Rails 3.0.4 (February 8, 2011)*
* No changes.
*Rails 3.0.3 (November 16, 2010)*
* No changes.
*Rails 3.0.2 (November 15, 2010)*
* No changes.
*Rails 3.0.1 (October 15, 2010)*
* No Changes.
*Rails 3.0.0 (August 29, 2010)*
* subject is automatically looked up on I18n using mailer_name and action_name as scope as in t(".subject") [JK]
* Changed encoding behaviour of mail, so updated tests in actionmailer and bumped mail version to 2.2.1 [ML]
* Added ability to pass Proc objects to the defaults hash [ML]
* Removed all quoting.rb type files from ActionMailer and put Mail 2.2.0 in instead [ML]
* Lot of updates to various test cases that now work better with the new Mail and so have different expectations
* Added interceptors and observers from Mail [ML]
ActionMailer::Base.register_interceptor calls Mail.register_interceptor
ActionMailer::Base.register_observer calls Mail.register_observer
* Mail::Part now no longer has nil as a default charset, it is always set to something, and defaults to UTF-8
* Added explict setting of charset in set_fields! method to make sure Mail has the user defined default
* Removed quoting.rb and refactored for Mail to take responsibility of all quoting and auto encoding requirements for the header.
* Fixed several tests which had incorrect encoding.
* Changed all utf-8 to UTF-8 for consistency
* Whole new API added with tests. See base.rb for full details. Old API is deprecated.
* The Mail::Message class has helped methods for all the field types that return 'common' defaults for the common use case, so to get the subject, mail.subject will give you a string, mail.date will give you a DateTime object, mail.from will give you an array of address specs (mikel@test.lindsaar.net) etc. If you want to access the field object itself, call mail[:field_name] which will return the field object you want, which you can then chain, like mail[:from].formatted
* Mail#content_type now returns the content_type field as a string. If you want the mime type of a mail, then you call Mail#mime_type (eg, text/plain), if you want the parameters of the content type field, you call Mail#content_type_parameters which gives you a hash, eg {'format' => 'flowed', 'charset' => 'utf-8'}
* ActionMailer::Base :default_implicit_parts_order now is in the sequence of the order you want, no reversing of ordering takes place. The default order now is text/plain, then text/enriched, then text/html and then any other part that is not one of these three.
* Mail does not have "quoted_body", "quoted_subject" etc. All of these are accessed via body.encoded, subject.encoded etc
* Every object in a Mail object returns an object, never a string. So Mail.body returns a Mail::Body class object, need to call #encoded or #decoded to get the string you want.
* Mail::Message#set_content_type does not exist, it is simply Mail::Message#content_type
* Every mail message gets a unique message_id unless you specify one, had to change all the tests that check for equality with expected.encoded == actual.encoded to first replace their message_ids with control values
* Mail now has a proper concept of parts, remove the ActionMailer::Part and ActionMailer::PartContainer classes
* Calling #encoded on any object returns it as a string ready to go into the output stream of an email, this means it includes the \r\n at the end of the lines and the object is pre-wrapped with \r\n\t if it is a header field. Also, the "encoded" value includes the field name if it is a header field.
* Attachments are only the actual attachment, with filename etc. A part contains an attachment. The part has the content_type etc. So attachments.last.content_type is invalid. But parts.last.content_type
* There is no idea of a "sub_head" in Mail. A part is just a Message with some extra functionality, so it just has a "header" like a normal mail message
*2.3.2 [Final] (March 15, 2009)*
* Fixed that ActionMailer should send correctly formatted Return-Path in MAIL FROM for SMTP #1842 [Matt Jones]
* Fixed RFC-2045 quoted-printable bug #1421 [squadette]
* Fixed that no body charset would be set when there are attachments present #740 [Paweł Kondzior]
*2.2.1 [RC2] (November 14th, 2008)*
* Turn on STARTTLS if it is available in Net::SMTP (added in Ruby 1.8.7) and the SMTP server supports it (This is required for Gmail's SMTP server) #1336 [Grant Hollingworth]
*2.2.0 [RC1] (October 24th, 2008)*
* Add layout functionality to mailers [Pratik Naik]
Mailer layouts behaves just like controller layouts, except layout names need to
have '_mailer' postfix for them to be automatically picked up.
*2.1.0 (May 31st, 2008)*
* Fixed that a return-path header would be ignored #7572 [joost]
* Less verbose mail logging: just recipients for :info log level; the whole email for :debug only. #8000 [iaddict, Tarmo Tänav]
* Updated TMail to version 1.2.1 [Mikel Lindsaar]
* Fixed that you don't have to call super in ActionMailer::TestCase#setup #10406 [jamesgolick]
*2.0.2* (December 16th, 2007)
* Included in Rails 2.0.2
*2.0.1* (December 7th, 2007)
* Update ActionMailer so it treats ActionView the same way that ActionController does. Closes #10244 [Rick Olson]
* Pass the template_root as an array as ActionView's view_path
* Request templates with the "#{mailer_name}/#{action}" as opposed to just "#{action}"
* Fixed that partials would be broken when using text.plain.erb as the extension #10130 [java]
* Update README to use new smtp settings configuration API. Closes #10060 [psq]
* Allow ActionMailer subclasses to individually set their delivery method (so two subclasses can have different delivery methods) #10033 [Zach Dennis]
* Update TMail to v1.1.0. Use an updated version of TMail if available. [Mikel Lindsaar]
* Introduce a new base test class for testing Mailers. ActionMailer::TestCase [Michael Koziarski]
* Fix silent failure of rxml templates. #9879 [jstewart]
* Fix attachment decoding when using the TMail C extension. #7861 [orangechicken]
* Increase mail delivery test coverage. #8692 [Kamal Fariz Mahyuddin]
* Register alternative template engines using ActionMailer::Base.register_template_extension('haml'). #7534 [cwd, Josh Peek]
* Only load ActionController::UrlWriter if ActionController is present [Rick Olson]
* Make sure parsed emails recognized attachments nested inside multipart parts. #6714 [Jamis Buck]
* Allow mailer actions named send by using __send__ internally. #6467 [iGEL]
* Add assert_emails and assert_no_emails to test the number of emails delivered. #6479 [Jonathan Viney]
# Assert total number of emails delivered:
assert_emails 0
ContactMailer.deliver_contact
assert_emails 1
# Assert number of emails delivered within a block:
assert_emails 1 do
post :signup, :name => 'Jonathan'
end
*1.3.3* (March 12th, 2007)
* Depend on Action Pack 1.13.3
*1.3.2* (February 5th, 2007)
* Deprecate server_settings renaming it to smtp_settings, add sendmail_settings to allow you to override the arguments to and location of the sendmail executable. [Michael Koziarski]
*1.3.1* (January 16th, 2007)
* Depend on Action Pack 1.13.1
*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. [David Heinemeier Hansson]
* Replace Reloadable with Reloadable::Deprecated. [Nicholas Seckar]
* Mailer template root applies to a class and its subclasses rather than acting globally. #5555 [somekool@gmail.com]
* 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.]
* Correct spurious documentation example code which results in a SyntaxError. [Marcel Molina Jr.]
*1.2.1* (April 6th, 2006)
*1.2.1* (April 6th, 2005)
* Be part of Rails 1.1.1
*1.2.0* (March 27th, 2006)
*1.2.0* (March 27th, 2005)
* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 [ehalvorsen+rails@runbox.com]
@@ -389,7 +137,7 @@
* 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]
* 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]
@@ -428,7 +176,7 @@
@body = "Nothing to see here."
@charset = "iso-8859-1"
end
def unencoded_subject(recipient)
@recipients = recipient
@subject = "testing unencoded subject"
@@ -437,7 +185,7 @@
@encode_subject = false
@charset = "iso-8859-1"
end
*0.6.1* (January 18th, 2005)

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2010 David Heinemeier Hansson
Copyright (c) 2004 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

148
actionmailer/README Executable file
View File

@@ -0,0 +1,148 @@
= 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, welcoming
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://actionmailer.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.
For other information, feel free to ask on the ruby-talk mailing list (which
is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.

View File

@@ -1,147 +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 weblog 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
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
== Receiving emails
To receive emails, you need to implement a public instance method called <tt>receive</tt> that takes a
tmail 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 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 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
* http://github.com/rails/rails/tree/master/actionmailer/
== License
Action Mailer is released under the MIT license.
== Support
API documentation is at
* http://api.rubyonrails.com
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets

207
actionmailer/Rakefile Normal file → Executable file
View File

@@ -1,7 +1,21 @@
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,177 @@ 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"
# 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.12.1' + 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
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end
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
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
if RUBY_FORGE_PROJECT then
require 'net/http'
require 'open-uri'
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
project_data = open(project_uri) { |data| data.read }
group_id = project_data[/[?&]group_id=(\d+)/, 1]
raise "Couldn't get group id" unless group_id
# This echos password to shell which is a bit sucky
if ENV["RUBY_FORGE_PASSWORD"]
password = ENV["RUBY_FORGE_PASSWORD"]
else
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
password = STDIN.gets.chomp
end
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
data = [
"login=1",
"form_loginname=#{RUBY_FORGE_USER}",
"form_pw=#{password}"
].join("&")
http.post("/account/login.php", data)
end
cookie = login_response["set-cookie"]
raise "Login failed" unless cookie
headers = { "Cookie" => cookie }
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
release_data = open(release_uri, headers) { |data| data.read }
package_id = release_data[/[?&]package_id=(\d+)/, 1]
raise "Couldn't get package id" unless package_id
first_file = true
release_id = ""
files.each do |filename|
basename = File.basename(filename)
file_ext = File.extname(filename)
file_data = File.open(filename, "rb") { |file| file.read }
puts "Releasing #{basename}..."
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
type_map = {
".zip" => "3000",
".tgz" => "3110",
".gz" => "3110",
".gem" => "1400"
}; type_map.default = "9999"
type = type_map[file_ext]
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
query_hash = if first_file then
{
"group_id" => group_id,
"package_id" => package_id,
"release_name" => RELEASE_NAME,
"release_date" => release_date,
"type_id" => type,
"processor_id" => "8000", # Any
"release_notes" => "",
"release_changes" => "",
"preformatted" => "1",
"submit" => "1"
}
else
{
"group_id" => group_id,
"release_id" => release_id,
"package_id" => package_id,
"step2" => "1",
"type_id" => type,
"processor_id" => "8000", # Any
"submit" => "Add This File"
}
end
query = "?" + query_hash.map do |(name, value)|
[name, URI.encode(value)].join("=")
end.join("&")
data = [
"--" + boundary,
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
"Content-Type: application/octet-stream",
"Content-Transfer-Encoding: binary",
"", file_data, ""
].join("\x0D\x0A")
release_headers = headers.merge(
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
)
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
http.post(target + query, data, release_headers)
end
if first_file then
release_id = release_response.body[/release_id=(\d+)/, 1]
raise("Couldn't get release id") unless release_id
end
first_file = false
end
end
end
spec = eval(File.read('actionmailer.gemspec'))
Gem::PackageTask.new(spec) do |p|
p.gem_spec = spec
end
desc "Release to gemcutter"
task :release => :package do
require 'rake/gemcutter'
Rake::Gemcutter::Tasks.new(spec).define
Rake::Task['gem:push'].invoke
end

View File

@@ -1,22 +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.required_ruby_version = '>= 1.8.7'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
s.rubyforge_project = 'actionmailer'
s.files = Dir['CHANGELOG', 'README.rdoc', 'MIT-LICENSE', 'lib/**/*']
s.require_path = 'lib'
s.requirements << 'none'
s.add_dependency('actionpack', version)
s.add_dependency('mail', '~> 2.2')
end

30
actionmailer/install.rb Normal file
View 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
}

57
actionmailer/lib/action_mailer.rb Normal file → Executable file
View File

@@ -1,5 +1,5 @@
#--
# Copyright (c) 2004-2010 David Heinemeier Hansson
# Copyright (c) 2004 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,32 +21,31 @@
# 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 :AdvAttrAccessor
autoload :Collector
autoload :Base
autoload :DeliveryMethods
autoload :DeprecatedApi
autoload :MailHelper
autoload :OldApi
autoload :TestCase
autoload :TestHelper
begin
require 'action_controller'
rescue LoadError
begin
require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
rescue LoadError
require 'rubygems'
require_gem 'actionpack', '>= 1.9.1'
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) }

View File

@@ -1,25 +1,30 @@
module ActionMailer
module AdvAttrAccessor #:nodoc:
def adv_attr_accessor(*names)
names.each do |name|
ivar = "@#{name}"
def self.append_features(base)
super
base.extend(ClassMethods)
end
class_eval <<-ACCESSORS, __FILE__, __LINE__ + 1
def #{name}=(value)
#{ivar} = value
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
def #{name}(*args)
raise ArgumentError, "expected 0 or 1 parameters" unless args.length <= 1
if args.empty?
#{ivar} if instance_variable_names.include?(#{ivar.inspect})
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
#{ivar} = args.first
instance_variable_set(ivar, parameters.first)
end
end
ACCESSORS
self.protected_instance_variables << ivar if self.respond_to?(:protected_instance_variables)
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -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.freeze_formats([mime.to_sym])
options[:body] = block_given? ? yield : @default_render.call
@responses << options
end
end
end

View File

@@ -1,86 +0,0 @@
require 'tmpdir'
module ActionMailer
# This modules handles everything related to the 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

View File

@@ -1,147 +0,0 @@
require 'active_support/core_ext/object/try'
module ActionMailer
# This is the API which is deprecated and is going to be removed on Rails 3.1 release.
# Part of the old API will be deprecated after 3.1, for a smoother deprecation process.
# Check those in OldApi instead.
module DeprecatedApi #:nodoc:
extend ActiveSupport::Concern
included do
[:charset, :content_type, :mime_version, :implicit_parts_order].each do |method|
class_eval <<-FILE, __FILE__, __LINE__ + 1
def self.default_#{method}
@@default_#{method}
end
def self.default_#{method}=(value)
ActiveSupport::Deprecation.warn "ActionMailer::Base.default_#{method}=value is deprecated, " <<
"use default :#{method} => value instead"
@@default_#{method} = value
end
@@default_#{method} = nil
FILE
end
end
module ClassMethods
# Deliver the given mail object directly. This can be used to deliver
# a preconstructed mail object, like:
#
# email = MyMailer.create_some_mail(parameters)
# email.set_some_obscure_header "frobnicate"
# MyMailer.deliver(email)
def deliver(mail, show_warning=true)
if show_warning
ActiveSupport::Deprecation.warn "#{self}.deliver is deprecated, call " <<
"deliver in the mailer instance instead", caller[0,2]
end
raise "no mail object available for delivery!" unless mail
wrap_delivery_behavior(mail)
mail.deliver
mail
end
def template_root
self.view_paths && self.view_paths.first
end
def template_root=(root)
ActiveSupport::Deprecation.warn "template_root= is deprecated, use prepend_view_path instead", caller[0,2]
self.view_paths = ActionView::Base.process_view_paths(root)
end
def respond_to?(method_symbol, include_private = false)
matches_dynamic_method?(method_symbol) || super
end
def method_missing(method_symbol, *parameters)
if match = matches_dynamic_method?(method_symbol)
case match[1]
when 'create'
ActiveSupport::Deprecation.warn "#{self}.create_#{match[2]} is deprecated, " <<
"use #{self}.#{match[2]} instead", caller[0,2]
new(match[2], *parameters).message
when 'deliver'
ActiveSupport::Deprecation.warn "#{self}.deliver_#{match[2]} is deprecated, " <<
"use #{self}.#{match[2]}.deliver instead", caller[0,2]
new(match[2], *parameters).message.deliver
else super
end
else
super
end
end
private
def matches_dynamic_method?(method_name)
method_name = method_name.to_s
/^(create|deliver)_([_a-z]\w*)/.match(method_name) || /^(new)$/.match(method_name)
end
end
# Delivers a Mail object. By default, it delivers the cached mail
# object (from the <tt>create!</tt> method). If no cached mail object exists, and
# no alternate has been given as the parameter, this will fail.
def deliver!(mail = @_message)
ActiveSupport::Deprecation.warn "Calling deliver in the AM::Base object is deprecated, " <<
"please call deliver in the Mail instance", caller[0,2]
self.class.deliver(mail, false)
end
alias :deliver :deliver!
def render(*args)
options = args.last.is_a?(Hash) ? args.last : {}
if file = options[:file] and !file.index("/")
ActiveSupport::Deprecation.warn("render :file is deprecated except for absolute paths. " \
"Please use render :template instead")
options[:prefix] = _prefix
end
if options[:body].is_a?(Hash)
ActiveSupport::Deprecation.warn(':body in render deprecated. Please use instance ' <<
'variables as assigns instead', caller[0,1])
options[:body].each { |k,v| instance_variable_set(:"@#{k}", v) }
end
super
end
# Render a message but does not set it as mail body. Useful for rendering
# data for part and attachments.
#
# Examples:
#
# render_message "special_message"
# render_message :template => "special_message"
# render_message :inline => "<%= 'Hi!' %>"
#
def render_message(*args)
ActiveSupport::Deprecation.warn "render_message is deprecated, use render instead", caller[0,2]
render(*args)
end
private
def initialize_defaults(*)
@charset ||= self.class.default_charset.try(:dup)
@content_type ||= self.class.default_content_type.try(:dup)
@implicit_parts_order ||= self.class.default_implicit_parts_order.try(:dup)
@mime_version ||= self.class.default_mime_version.try(:dup)
super
end
def create_parts
if @body.is_a?(Hash) && !@body.empty?
ActiveSupport::Deprecation.warn "Giving a hash to body is deprecated, please use instance variables instead", caller[0,2]
@body.each { |k, v| instance_variable_set(:"@#{k}", v) }
end
super
end
end
end

View File

@@ -0,0 +1,115 @@
module ActionMailer
module Helpers #:nodoc:
def self.append_features(base) #:nodoc:
super
# 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 :inherited_without_helper, :inherited
alias_method :inherited, :inherited_with_helper
end
# Wrap initialize_template_class to extend new template class
# instances with the master helper module.
alias_method :initialize_template_class_without_helper, :initialize_template_class
alias_method :initialize_template_class, :initialize_template_class_with_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

View File

@@ -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} (%1.fms)" % event.duration)
debug(event.payload[:mail])
end
def receive(event)
info("\nReceived mail (%.1fms)" % event.duration)
debug(event.payload[:mail])
end
def logger
ActionMailer::Base.logger
end
end
end
ActionMailer::LogSubscriber.attach_to :action_mailer

View File

@@ -1,49 +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|
simple_format(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
private
def simple_format(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

View File

@@ -1,259 +0,0 @@
require 'active_support/concern'
require 'active_support/core_ext/object/try'
require 'active_support/core_ext/object/blank'
module ActionMailer
module OldApi #:nodoc:
extend ActiveSupport::Concern
included do
extend ActionMailer::AdvAttrAccessor
@@protected_instance_variables = %w(@parts)
cattr_reader :protected_instance_variables
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
# Specify the CC addresses for the message.
adv_attr_accessor :cc
# Specify the charset to use for the message. This defaults to the
# +default_charset+ specified for ActionMailer::Base.
adv_attr_accessor :charset
# Specify the content type for the message. This defaults to <tt>text/plain</tt>
# in most cases, but can be automatically set in some situations.
adv_attr_accessor :content_type
# Specify the from address for the message.
adv_attr_accessor :from
# Specify the address (if different than the "from" address) to direct
# replies to this message.
adv_attr_accessor :reply_to
# Specify the order in which parts should be sorted, based on content-type.
# This defaults to the value for the +default_implicit_parts_order+.
adv_attr_accessor :implicit_parts_order
# Defaults to "1.0", but may be explicitly given if needed.
adv_attr_accessor :mime_version
# The recipient addresses for the message, either as a string (for a single
# address) or an array (for multiple addresses).
adv_attr_accessor :recipients
# The date on which the message was sent. If not set (the default), the
# header will be set by the delivery agent.
adv_attr_accessor :sent_on
# Specify the subject of the message.
adv_attr_accessor :subject
# Specify the template name to use for current message. This is the "base"
# template name, without the extension or directory, and may be used to
# have multiple mailer methods share the same template.
adv_attr_accessor :template
# Override the mailer name, which defaults to an inflected version of the
# mailer's class name. If you want to use a template in a non-standard
# location, you can use this to specify that location.
adv_attr_accessor :mailer_name
# Define the body of the message. This is either a Hash (in which case it
# specifies the variables to pass to the template when it is rendered),
# or a string, in which case it specifies the actual text of the message.
adv_attr_accessor :body
# Alias controller_path to mailer_name so render :partial in views work.
alias :controller_path :mailer_name
end
def process(method_name, *args)
initialize_defaults(method_name)
super
unless @mail_was_called
create_parts
create_mail
end
@_message
end
# 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
if custom_headers = params.delete(:headers)
params.merge!(custom_headers)
end
part = Mail::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[:content] ||= params.delete(:data) || params.delete(:body)
if params[:filename]
params = normalize_file_hash(params)
else
params = normalize_nonfile_hash(params)
end
part(params, &block)
end
protected
def normalize_nonfile_hash(params)
content_disposition = "attachment;"
mime_type = params.delete(:mime_type)
if content_type = params.delete(:content_type)
content_type = "#{mime_type || content_type};"
end
params[:body] = params.delete(:data) if params[:data]
{ :content_type => content_type,
:content_disposition => content_disposition }.merge(params)
end
def normalize_file_hash(params)
filename = File.basename(params.delete(:filename))
content_disposition = "attachment; filename=\"#{File.basename(filename)}\""
mime_type = params.delete(:mime_type)
if (content_type = params.delete(:content_type)) && (content_type !~ /filename=/)
content_type = "#{mime_type || content_type}; filename=\"#{filename}\""
end
params[:body] = params.delete(:data) if params[:data]
{ :content_type => content_type,
:content_disposition => content_disposition }.merge(params)
end
def create_mail
m = @_message
set_fields!({:subject => subject, :to => recipients, :from => from,
:bcc => bcc, :cc => cc, :reply_to => reply_to}, charset)
m.mime_version = mime_version unless mime_version.nil?
m.date = sent_on.to_time rescue sent_on if sent_on
@headers.each { |k, v| m[k] = v }
real_content_type, ctype_attrs = parse_content_type
main_type, sub_type = split_content_type(real_content_type)
if @parts.size == 1 && @parts.first.parts.empty?
m.content_type([main_type, sub_type, ctype_attrs])
m.body = @parts.first.body.encoded
else
@parts.each do |p|
m.add_part(p)
end
m.body.set_sort_order(@implicit_parts_order)
m.body.sort_parts!
if real_content_type =~ /multipart/
ctype_attrs.delete "charset"
m.content_type([main_type, sub_type, ctype_attrs])
end
end
wrap_delivery_behavior!
m.content_transfer_encoding = '8bit' unless m.body.only_us_ascii?
@_message
end
# Set up the default values for the various instance variables of this
# mailer. Subclasses may override this method to provide different
# defaults.
def initialize_defaults(method_name)
@charset ||= self.class.default[:charset].try(:dup)
@content_type ||= self.class.default[:content_type].try(:dup)
@implicit_parts_order ||= self.class.default[:parts_order].try(:dup)
@mime_version ||= self.class.default[:mime_version].try(:dup)
@mailer_name ||= self.class.mailer_name.dup
@template ||= method_name
@mail_was_called = false
@parts ||= []
@headers ||= {}
@sent_on ||= Time.now
@body ||= {}
end
def create_parts
if String === @body
@parts.unshift create_inline_part(@body)
elsif @parts.empty? || @parts.all? { |p| p.content_disposition =~ /^attachment/ }
lookup_context.find_all(@template, @mailer_name).each do |template|
self.formats = template.formats
@parts << create_inline_part(render(:template => template), template.mime_type)
end
if @parts.size > 1
@content_type = "multipart/alternative" if @content_type !~ /^multipart/
end
# If this is a multipart e-mail add the mime_version if it is not
# already set.
@mime_version ||= "1.0" if !@parts.empty?
end
end
def create_inline_part(body, mime_type=nil)
ct = mime_type || "text/plain"
main_type, sub_type = split_content_type(ct.to_s)
Mail::Part.new(
:content_type => [main_type, sub_type, {:charset => charset}],
:content_disposition => "inline",
:body => body
)
end
def set_fields!(headers, charset) #:nodoc:
m = @_message
m.charset = charset
m.subject ||= headers.delete(:subject) if headers[:subject]
m.to ||= headers.delete(:to) if headers[:to]
m.from ||= headers.delete(:from) if headers[:from]
m.cc ||= headers.delete(:cc) if headers[:cc]
m.bcc ||= headers.delete(:bcc) if headers[:bcc]
m.reply_to ||= headers.delete(:reply_to) if headers[:reply_to]
end
def split_content_type(ct)
ct.to_s.split("/")
end
def parse_content_type(defaults=nil)
if @content_type.blank?
[ nil, {} ]
else
ctype, *attrs = @content_type.split(/;\s*/)
attrs = attrs.inject({}) { |h,s| k,v = s.split(/\=/, 2); h[k] = v; h }
[ctype, {"charset" => @charset}.merge(attrs)]
end
end
end
end

View 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

View 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

View 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

View File

@@ -1,30 +0,0 @@
require "action_mailer"
require "rails"
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.to_a.first
options.javascripts_dir ||= paths.public.javascripts.to_a.first
options.stylesheets_dir ||= paths.public.stylesheets.to_a.first
ActiveSupport.on_load(:action_mailer) do
include app.routes.url_helpers
register_interceptors(options.delete(:interceptors))
register_observers(options.delete(:observers))
options.each { |k,v| send("#{k}=", v) }
end
end
end
end

View File

@@ -1,76 +0,0 @@
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
module ClassMethods
def tests(mailer)
write_inheritable_attribute(:mailer_class, mailer)
end
def mailer_class
if mailer = read_inheritable_attribute(: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
module InstanceMethods
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
included do
setup :initialize_test_deliveries
setup :set_expected_mail
end
end
include Behavior
end
end

View File

@@ -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

View File

@@ -1,34 +0,0 @@
module Mail
class Message
def set_content_type(*args)
ActiveSupport::Deprecation.warn('Message#set_content_type is deprecated, please just call ' <<
'Message#content_type with the same arguments', caller[0,2])
content_type(*args)
end
alias :old_transfer_encoding :transfer_encoding
def transfer_encoding(value = nil)
if value
ActiveSupport::Deprecation.warn('Message#transfer_encoding is deprecated, please call ' <<
'Message#content_transfer_encoding with the same arguments', caller[0,2])
content_transfer_encoding(value)
else
old_transfer_encoding
end
end
def transfer_encoding=(value)
ActiveSupport::Deprecation.warn('Message#transfer_encoding= is deprecated, please call ' <<
'Message#content_transfer_encoding= with the same arguments', caller[0,2])
self.content_transfer_encoding = value
end
def original_filename
ActiveSupport::Deprecation.warn('Message#original_filename is deprecated, ' <<
'please call Message#filename', caller[0,2])
filename
end
end
end

View 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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
require 'tmail/info'
require 'tmail/mail'
require 'tmail/mailbox'

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -0,0 +1 @@
require 'tmail/mailbox'

View 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

View 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

View File

@@ -0,0 +1 @@
require 'tmail/mailbox'

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -0,0 +1,125 @@
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?
if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
from_charset = $1
quoting_method = $2
text = $3
case quoting_method.upcase
when "Q" then
unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
when "B" then
unquote_base64_and_convert_to(text, to_charset, from_charset)
else
raise "unknown quoting method #{quoting_method.inspect}"
end
else
convert_to(text, to_charset, from_charset)
end
end
def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
text = text.gsub(/_/, " ") unless preserve_underscores
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, 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

View 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

View 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

View 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

View File

@@ -0,0 +1 @@
require 'tmail'

View 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

View File

@@ -1,10 +1,9 @@
module ActionMailer
module VERSION #:nodoc:
MAJOR = 3
MINOR = 0
TINY = 20
PRE = nil
MAJOR = 1
MINOR = 2
TINY = 1
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View File

@@ -1,15 +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 [...]

View File

@@ -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

View File

@@ -1,16 +0,0 @@
class <%= class_name %> < ActionMailer::Base
default :from => "from@example.com"
<% for action in actions -%>
# 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 :to => "to@example.org"
end
<% end -%>
end

View File

@@ -1,79 +0,0 @@
# 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
silence_warnings do
# These external dependencies have warnings :/
require 'mail'
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'
# 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
class MockSMTP
def self.deliveries
@@deliveries
end
def initialize
@@deliveries = []
end
def sendmail(mail, from, to)
@@deliveries << [mail, from, to]
end
def start(*args)
yield self
end
end
class Net::SMTP
def self.new(*args)
MockSMTP.new
end
end
def 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

View File

@@ -1,605 +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
# TODO Add some tests for implicity layout render and url helpers
# so we can get rid of old base tests altogether with old base.
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 "deprecated non-String custom headers" do
email = assert_deprecated { BaseMailer.welcome_with_fixnum_header }
assert_equal("2", email['X-SPAM-COUNT'].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
skip "failed already"
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 "render :file uses render :template semantics and is deprecated" do
mail = nil
assert_deprecated { mail = BaseMailer.implicit_different_template_with_file('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
# 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
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

View File

@@ -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

View File

@@ -1 +0,0 @@
Welcome from another path

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -1 +0,0 @@
Inside

View File

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

View File

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

View File

@@ -1 +0,0 @@
Attachment with content

View File

@@ -1 +0,0 @@
HTML

View File

@@ -1 +0,0 @@
PLAIN

View File

@@ -1 +0,0 @@
body_text

View File

@@ -1 +0,0 @@
<%= t('.greet_user', :name => 'lifo') %>

View File

@@ -1 +0,0 @@
HTML Explicit Multipart Templates

View File

@@ -1 +0,0 @@
TEXT Explicit Multipart Templates

View File

@@ -1 +0,0 @@
<%= self.formats.inspect %>

View File

@@ -1 +0,0 @@
<h1>Testing</h1>

View File

@@ -1 +0,0 @@
HTML Implicit Multipart

View File

@@ -1 +0,0 @@
TEXT Implicit Multipart

View File

@@ -1 +0,0 @@
Implicit with locale EN HTML

View File

@@ -1 +0,0 @@
Implicit with locale HTML

View File

@@ -1 +0,0 @@
Implicit with locale PL TEXT

View File

@@ -1 +0,0 @@
Implicit with locale TEXT

View File

@@ -1,5 +0,0 @@
<h1>Inline Image</h1>
<%= image_tag attachments['logo.png'].url %>
<p>This is an image that is inline</p>

View File

@@ -1,4 +0,0 @@
Inline Image
No image for you

View File

@@ -1 +0,0 @@
Testing

View File

@@ -1 +0,0 @@
Welcome

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
module TestHelper
def test_format(text)
"<em><strong><small>#{text}</small></strong></em>"
end
end

View File

@@ -1,4 +0,0 @@
Hello there,
Mr. <%= @recipient %>. Be greeted, new member!

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