mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-18 16:20:12 +01:00
The login_controller functional tests failed because the logic for should_expire_sessions was back to front. Fixed and tests pass now.
Deleted vendor/rails in preparation to freeze to Rails 1.1.2 git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@254 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
edcc065659
commit
01ac255778
925 changed files with 3 additions and 108890 deletions
|
|
@ -12,7 +12,7 @@ class LoginController < ApplicationController
|
||||||
# If checkbox on login page checked, we don't expire the session after 1 hour
|
# If checkbox on login page checked, we don't expire the session after 1 hour
|
||||||
# of inactivity
|
# of inactivity
|
||||||
session['noexpiry'] = params['user_noexpiry']
|
session['noexpiry'] = params['user_noexpiry']
|
||||||
msg = (should_expire_sessions?) ? "will not expire." : "will expire after 1 hour of inactivity."
|
msg = (should_expire_sessions?) ? "will expire after 1 hour of inactivity." : "will not expire."
|
||||||
flash['notice'] = "Login successful: session #{msg}"
|
flash['notice'] = "Login successful: session #{msg}"
|
||||||
redirect_back_or_default :controller => "todo", :action => "list"
|
redirect_back_or_default :controller => "todo", :action => "list"
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
* Homepage: http://www.rousette.org.uk/projects/
|
* Homepage: http://www.rousette.org.uk/projects/
|
||||||
* Author: bsag (http://www.rousette.org.uk/)
|
* Author: bsag (http://www.rousette.org.uk/)
|
||||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard (for great installation tutorials on Windows XP), Jim Strupp, Eric Lesh, Damien Cirotteau
|
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard (for great installation tutorials on Windows XP), Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley
|
||||||
* Version: 1.04
|
* Version: 1.04
|
||||||
* Copyright: (cc) 2004-2006 rousette.org.uk
|
* Copyright: (cc) 2004-2006 rousette.org.uk
|
||||||
* License: GNU GPL
|
* License: GNU GPL
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
* Trac (for bug reports): http://dev.rousette.org.uk/report/6
|
* Trac (for bug reports): http://dev.rousette.org.uk/report/6
|
||||||
* Wiki (more info on installation): http://dev.rousette.org.uk/wiki
|
* Wiki (more info on installation): http://dev.rousette.org.uk/wiki
|
||||||
* Author: bsag (http://www.rousette.org.uk/)
|
* Author: bsag (http://www.rousette.org.uk/)
|
||||||
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard (for great installation tutorials on Windows XP), Jim Strupp, Eric Lesh, Damien Cirotteau
|
* Contributors: Nicholas Lee, Lolindrath, Jim Ray, Arnaud Limbourg, Timothy Martens, Luke Melia, John Leonard (for great installation tutorials on Windows XP), Jim Strupp, Eric Lesh, Damien Cirotteau, Janet Riley
|
||||||
* Version: 1.041
|
* Version: 1.041
|
||||||
* Copyright: (cc) 2004-2006 rousette.org.uk
|
* Copyright: (cc) 2004-2006 rousette.org.uk
|
||||||
* License: GNU GPL
|
* License: GNU GPL
|
||||||
|
|
|
||||||
0
tracks/vendor/rails/REVISION_4091
vendored
0
tracks/vendor/rails/REVISION_4091
vendored
228
tracks/vendor/rails/actionmailer/CHANGELOG
vendored
228
tracks/vendor/rails/actionmailer/CHANGELOG
vendored
|
|
@ -1,228 +0,0 @@
|
||||||
*1.2.0* (March 27th, 2005)
|
|
||||||
|
|
||||||
* Nil charset caused subject line to be improperly quoted in implicitly multipart messages #2662 [ehalvorsen+rails@runbox.com]
|
|
||||||
|
|
||||||
* Parse content-type apart before using it so that sub-parts of the header can be set correctly #2918 [Jamis Buck]
|
|
||||||
|
|
||||||
* Make custom headers work in subparts #4034 [elan@bluemandrill.com]
|
|
||||||
|
|
||||||
* Template paths with dot chars in them no longer mess up implicit template selection for multipart messages #3332 [Chad Fowler]
|
|
||||||
|
|
||||||
* Make sure anything with content-disposition of "attachment" is passed to the attachment presenter when parsing an email body [Jamis Buck]
|
|
||||||
|
|
||||||
* Make sure TMail#attachments includes anything with content-disposition of "attachment", regardless of content-type [Jamis Buck]
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.5* (December 13th, 2005)
|
|
||||||
|
|
||||||
* Become part of Rails 1.0
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.4* (December 7th, 2005)
|
|
||||||
|
|
||||||
* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
|
|
||||||
|
|
||||||
* Stricter matching for implicitly multipart filenames excludes files ending in unsupported extensions (such as foo.rhtml.bak) and without a two-part content type (such as foo.text.rhtml or foo.text.really.plain.rhtml). #2398 [Dave Burt <dave@burt.id.au>, Jeremy Kemper]
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.3* (November 7th, 2005)
|
|
||||||
|
|
||||||
* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au]
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.2* (October 26th, 2005)
|
|
||||||
|
|
||||||
* Upgraded to Action Pack 1.10.2
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.1* (October 19th, 2005)
|
|
||||||
|
|
||||||
* Upgraded to Action Pack 1.10.1
|
|
||||||
|
|
||||||
|
|
||||||
*1.1.0* (October 16th, 2005)
|
|
||||||
|
|
||||||
* Update and extend documentation (rdoc)
|
|
||||||
|
|
||||||
* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05]
|
|
||||||
|
|
||||||
* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005]
|
|
||||||
|
|
||||||
* Fix vendor require paths to prevent files being required twice
|
|
||||||
|
|
||||||
* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long]
|
|
||||||
|
|
||||||
* Preserve underscores when unquoting message bodies #1930
|
|
||||||
|
|
||||||
* Encode multibyte characters correctly #1894
|
|
||||||
|
|
||||||
* Multipart messages specify a MIME-Version header automatically #2003 [John Long]
|
|
||||||
|
|
||||||
* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
|
|
||||||
|
|
||||||
* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
|
|
||||||
|
|
||||||
* Look at content-location header (if available) to determine filename of attachments #1670
|
|
||||||
|
|
||||||
* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
|
|
||||||
|
|
||||||
* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev]
|
|
||||||
|
|
||||||
|
|
||||||
*1.0.1* (11 July, 2005)
|
|
||||||
|
|
||||||
* Bind to Action Pack 1.9.1
|
|
||||||
|
|
||||||
|
|
||||||
*1.0.0* (6 July, 2005)
|
|
||||||
|
|
||||||
* Avoid adding nil header values #1392
|
|
||||||
|
|
||||||
* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long]
|
|
||||||
|
|
||||||
* Allow for nested parts in multipart mails #1570 [Flurin Egger]
|
|
||||||
|
|
||||||
* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long]
|
|
||||||
|
|
||||||
* Allow template to be explicitly specified #1448 [tuxie@dekadance.se]
|
|
||||||
|
|
||||||
* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger]
|
|
||||||
|
|
||||||
* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
|
|
||||||
|
|
||||||
* Helper support (borrowed from ActionPack)
|
|
||||||
|
|
||||||
* Silently ignore Errno::EINVAL errors when converting text.
|
|
||||||
|
|
||||||
* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Nested multipart message parts are correctly processed in TMail::Mail#body
|
|
||||||
|
|
||||||
* BCC headers are removed when sending via SMTP #1402
|
|
||||||
|
|
||||||
* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
|
|
||||||
|
|
||||||
* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Support attachments and multipart messages.
|
|
||||||
|
|
||||||
* Added new accessors for the various mail properties.
|
|
||||||
|
|
||||||
* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fix attachments and content-type problems #1276 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added automatic decoding of base64 bodies #1214 [Jamis Buck]
|
|
||||||
|
|
||||||
* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
|
|
||||||
|
|
||||||
* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck]
|
|
||||||
|
|
||||||
|
|
||||||
*0.9.1* (20th April, 2005)
|
|
||||||
|
|
||||||
* Depend on Action Pack 1.8.1
|
|
||||||
|
|
||||||
|
|
||||||
*0.9.0* (19th April, 2005)
|
|
||||||
|
|
||||||
* Added that deliver_* will now return the email that was sent
|
|
||||||
|
|
||||||
* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed quoting for all address headers, not just to #955 [Jamis Buck]
|
|
||||||
|
|
||||||
* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net]
|
|
||||||
|
|
||||||
|
|
||||||
*0.8.1* (27th March, 2005)
|
|
||||||
|
|
||||||
* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com]
|
|
||||||
|
|
||||||
* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
|
|
||||||
|
|
||||||
|
|
||||||
*0.8.0* (22th March, 2005)
|
|
||||||
|
|
||||||
* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
|
|
||||||
|
|
||||||
|
|
||||||
*0.7.1* (7th March, 2005)
|
|
||||||
|
|
||||||
* Bind to newest Action Pack (1.5.1)
|
|
||||||
|
|
||||||
|
|
||||||
*0.7.0* (24th February, 2005)
|
|
||||||
|
|
||||||
* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
|
|
||||||
|
|
||||||
def iso_charset(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing iso charsets"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
def unencoded_subject(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing unencoded subject"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@encode_subject = false
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
*0.6.1* (January 18th, 2005)
|
|
||||||
|
|
||||||
* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
|
|
||||||
|
|
||||||
|
|
||||||
*0.6* (January 17th, 2005)
|
|
||||||
|
|
||||||
* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel]
|
|
||||||
|
|
||||||
* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel]
|
|
||||||
|
|
||||||
|
|
||||||
*0.5*
|
|
||||||
|
|
||||||
* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
|
|
||||||
|
|
||||||
def post_notification(recipients, post)
|
|
||||||
@recipients = recipients
|
|
||||||
@from = post.author.email_address_with_name
|
|
||||||
@headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
|
|
||||||
@headers["reply-to"] = "notifications@example.com"
|
|
||||||
@subject = "[#{post.account.name} #{post.title}]"
|
|
||||||
@body["post"] = post
|
|
||||||
end
|
|
||||||
|
|
||||||
*0.4* (5)
|
|
||||||
|
|
||||||
* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten]
|
|
||||||
NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
|
|
||||||
|
|
||||||
* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper]
|
|
||||||
|
|
||||||
* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
|
|
||||||
This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
|
|
||||||
|
|
||||||
* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
|
|
||||||
Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev]
|
|
||||||
|
|
||||||
* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler]
|
|
||||||
|
|
||||||
|
|
||||||
*0.3*
|
|
||||||
|
|
||||||
* First release
|
|
||||||
21
tracks/vendor/rails/actionmailer/MIT-LICENSE
vendored
21
tracks/vendor/rails/actionmailer/MIT-LICENSE
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
"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.
|
|
||||||
|
|
||||||
148
tracks/vendor/rails/actionmailer/README
vendored
148
tracks/vendor/rails/actionmailer/README
vendored
|
|
@ -1,148 +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, 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.
|
|
||||||
199
tracks/vendor/rails/actionmailer/Rakefile
vendored
199
tracks/vendor/rails/actionmailer/Rakefile
vendored
|
|
@ -1,199 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'rake'
|
|
||||||
require 'rake/testtask'
|
|
||||||
require 'rake/rdoctask'
|
|
||||||
require 'rake/packagetask'
|
|
||||||
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 ]
|
|
||||||
|
|
||||||
# Run the unit tests
|
|
||||||
Rake::TestTask.new { |t|
|
|
||||||
t.libs << "test"
|
|
||||||
t.pattern = 'test/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
t.warning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# 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.0' + PKG_BUILD)
|
|
||||||
|
|
||||||
s.has_rdoc = true
|
|
||||||
s.requirements << 'none'
|
|
||||||
s.require_path = 'lib'
|
|
||||||
s.autorequire = 'action_mailer'
|
|
||||||
|
|
||||||
s.files = [ "rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
|
|
||||||
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
|
||||||
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
30
tracks/vendor/rails/actionmailer/install.rb
vendored
30
tracks/vendor/rails/actionmailer/install.rb
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
#--
|
|
||||||
# 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
|
|
||||||
# "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.
|
|
||||||
#++
|
|
||||||
|
|
||||||
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) }
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module AdvAttrAccessor #:nodoc:
|
|
||||||
def self.append_features(base)
|
|
||||||
super
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods #:nodoc:
|
|
||||||
def adv_attr_accessor(*names)
|
|
||||||
names.each do |name|
|
|
||||||
ivar = "@#{name}"
|
|
||||||
|
|
||||||
define_method("#{name}=") do |value|
|
|
||||||
instance_variable_set(ivar, value)
|
|
||||||
end
|
|
||||||
|
|
||||||
define_method(name) do |*parameters|
|
|
||||||
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
|
|
||||||
if parameters.empty?
|
|
||||||
if instance_variables.include?(ivar)
|
|
||||||
instance_variable_get(ivar)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
instance_variable_set(ivar, parameters.first)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,464 +0,0 @@
|
||||||
require 'action_mailer/adv_attr_accessor'
|
|
||||||
require 'action_mailer/part'
|
|
||||||
require 'action_mailer/part_container'
|
|
||||||
require 'action_mailer/utils'
|
|
||||||
require 'tmail/net'
|
|
||||||
|
|
||||||
module ActionMailer #:nodoc:
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# class ApplicationMailer < ActionMailer::Base
|
|
||||||
# # Set up properties
|
|
||||||
# # Properties can also be specified via accessor methods
|
|
||||||
# # (i.e. self.subject = "foo") and instance variables (@subject = "foo").
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# body { "account" => recipient }
|
|
||||||
# from "system@example.com"
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # explicitly specify multipart messages
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
#
|
|
||||||
# part :content_type => "text/html",
|
|
||||||
# :body => render_message("signup-as-html", :account => recipient)
|
|
||||||
#
|
|
||||||
# part "text/plain" do |p|
|
|
||||||
# p.body = render_message("signup-as-plain", :account => recipient)
|
|
||||||
# p.transfer_encoding = "base64"
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # attachments
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
#
|
|
||||||
# attachment :content_type => "image/jpeg",
|
|
||||||
# :body => File.read("an-image.jpg")
|
|
||||||
#
|
|
||||||
# attachment "application/pdf" do |a|
|
|
||||||
# a.body = generate_your_pdf_here()
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # implicitly multipart messages
|
|
||||||
# def signup_notification(recipient)
|
|
||||||
# recipients recipient.email_address_with_name
|
|
||||||
# subject "New account information"
|
|
||||||
# from "system@example.com"
|
|
||||||
# body(:account => "recipient")
|
|
||||||
#
|
|
||||||
# # ActionMailer will automatically detect and use multipart templates,
|
|
||||||
# # where each template is named after the name of the action, followed
|
|
||||||
# # by the content type. Each such detected template will be added as
|
|
||||||
# # a separate part to the message.
|
|
||||||
# #
|
|
||||||
# # for example, if the following templates existed:
|
|
||||||
# # * signup_notification.text.plain.rhtml
|
|
||||||
# # * signup_notification.text.html.rhtml
|
|
||||||
# # * signup_notification.text.xml.rxml
|
|
||||||
# # * signup_notification.text.x-yaml.rhtml
|
|
||||||
# #
|
|
||||||
# # Each would be rendered and added as a separate part to the message,
|
|
||||||
# # with the corresponding content type. The same body hash is passed to
|
|
||||||
# # each template.
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# # After this, post_notification will look for "templates/application_mailer/post_notification.rhtml"
|
|
||||||
# ApplicationMailer.template_root = "templates"
|
|
||||||
#
|
|
||||||
# ApplicationMailer.create_comment_notification(david, hello_world) # => a tmail object
|
|
||||||
# ApplicationMailer.deliver_comment_notification(david, hello_world) # sends the email
|
|
||||||
#
|
|
||||||
# = Configuration options
|
|
||||||
#
|
|
||||||
# These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
|
|
||||||
#
|
|
||||||
# * <tt>template_root</tt> - template root determines the base from which template references will be made.
|
|
||||||
#
|
|
||||||
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
|
|
||||||
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
|
|
||||||
#
|
|
||||||
# * <tt>server_settings</tt> - Allows detailed configuration of the server:
|
|
||||||
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
|
|
||||||
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
|
|
||||||
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
|
|
||||||
# * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
|
|
||||||
# * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
|
|
||||||
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
|
|
||||||
# This is a symbol and one of :plain, :login, :cram_md5
|
|
||||||
#
|
|
||||||
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
|
|
||||||
#
|
|
||||||
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
|
|
||||||
# Sendmail is assumed to be present at "/usr/sbin/sendmail".
|
|
||||||
#
|
|
||||||
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
|
|
||||||
# but this can be turned off to help functional testing.
|
|
||||||
#
|
|
||||||
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
|
|
||||||
# for unit and functional testing.
|
|
||||||
#
|
|
||||||
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
|
|
||||||
# pick a different charset from inside a method with <tt>@charset</tt>.
|
|
||||||
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
|
|
||||||
# can also pick a different content type from inside a method with <tt>@content_type</tt>.
|
|
||||||
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
|
|
||||||
# can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
|
|
||||||
# use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
|
|
||||||
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
|
|
||||||
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
|
|
||||||
# ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client
|
|
||||||
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
|
|
||||||
# <tt>@implicit_parts_order</tt>.
|
|
||||||
class Base
|
|
||||||
include AdvAttrAccessor, PartContainer
|
|
||||||
|
|
||||||
# Action Mailer subclasses should be reloaded by the dispatcher in Rails
|
|
||||||
# when Dependencies.mechanism = :load.
|
|
||||||
include Reloadable::Subclasses
|
|
||||||
|
|
||||||
private_class_method :new #:nodoc:
|
|
||||||
|
|
||||||
cattr_accessor :template_root
|
|
||||||
cattr_accessor :logger
|
|
||||||
|
|
||||||
@@server_settings = {
|
|
||||||
:address => "localhost",
|
|
||||||
:port => 25,
|
|
||||||
:domain => 'localhost.localdomain',
|
|
||||||
:user_name => nil,
|
|
||||||
:password => nil,
|
|
||||||
:authentication => nil
|
|
||||||
}
|
|
||||||
cattr_accessor :server_settings
|
|
||||||
|
|
||||||
@@raise_delivery_errors = true
|
|
||||||
cattr_accessor :raise_delivery_errors
|
|
||||||
|
|
||||||
@@delivery_method = :smtp
|
|
||||||
cattr_accessor :delivery_method
|
|
||||||
|
|
||||||
@@perform_deliveries = true
|
|
||||||
cattr_accessor :perform_deliveries
|
|
||||||
|
|
||||||
@@deliveries = []
|
|
||||||
cattr_accessor :deliveries
|
|
||||||
|
|
||||||
@@default_charset = "utf-8"
|
|
||||||
cattr_accessor :default_charset
|
|
||||||
|
|
||||||
@@default_content_type = "text/plain"
|
|
||||||
cattr_accessor :default_content_type
|
|
||||||
|
|
||||||
@@default_mime_version = nil
|
|
||||||
cattr_accessor :default_mime_version
|
|
||||||
|
|
||||||
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
|
|
||||||
cattr_accessor :default_implicit_parts_order
|
|
||||||
|
|
||||||
# Specify the BCC addresses for the message
|
|
||||||
adv_attr_accessor :bcc
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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 additional headers to be added to the message.
|
|
||||||
adv_attr_accessor :headers
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
# The mail object instance referenced by this mailer.
|
|
||||||
attr_reader :mail
|
|
||||||
|
|
||||||
class << self
|
|
||||||
def method_missing(method_symbol, *parameters)#:nodoc:
|
|
||||||
case method_symbol.id2name
|
|
||||||
when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
|
|
||||||
when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
|
|
||||||
when "new" then nil
|
|
||||||
else super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Receives a raw email, parses it into an email object, decodes it,
|
|
||||||
# instantiates a new mailer, and passes the email object to the mailer
|
|
||||||
# object's #receive method. If you want your mailer to be able to
|
|
||||||
# process incoming messages, you'll need to implement a #receive
|
|
||||||
# method that accepts the email object as a parameter:
|
|
||||||
#
|
|
||||||
# class MyMailer < ActionMailer::Base
|
|
||||||
# def receive(mail)
|
|
||||||
# ...
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
def receive(raw_email)
|
|
||||||
logger.info "Received mail:\n #{raw_email}" unless logger.nil?
|
|
||||||
mail = TMail::Mail.parse(raw_email)
|
|
||||||
mail.base64_decode
|
|
||||||
new.receive(mail)
|
|
||||||
end
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
new.deliver!(mail)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
|
|
||||||
# will be initialized according to the named method. If not, the mailer will
|
|
||||||
# remain uninitialized (useful when you only need to invoke the "receive"
|
|
||||||
# method, for instance).
|
|
||||||
def initialize(method_name=nil, *parameters) #:nodoc:
|
|
||||||
create!(method_name, *parameters) if method_name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Initialize the mailer via the given +method_name+. The body will be
|
|
||||||
# rendered and a new TMail::Mail object created.
|
|
||||||
def create!(method_name, *parameters) #:nodoc:
|
|
||||||
initialize_defaults(method_name)
|
|
||||||
send(method_name, *parameters)
|
|
||||||
|
|
||||||
# If an explicit, textual body has not been set, we check assumptions.
|
|
||||||
unless String === @body
|
|
||||||
# First, we look to see if there are any likely templates that match,
|
|
||||||
# which include the content-type in their file name (i.e.,
|
|
||||||
# "the_template_file.text.html.rhtml", etc.). Only do this if parts
|
|
||||||
# have not already been specified manually.
|
|
||||||
if @parts.empty?
|
|
||||||
templates = Dir.glob("#{template_path}/#{@template}.*")
|
|
||||||
templates.each do |path|
|
|
||||||
# TODO: don't hardcode rhtml|rxml
|
|
||||||
basename = File.basename(path)
|
|
||||||
next unless md = /^([^\.]+)\.([^\.]+\.[^\+]+)\.(rhtml|rxml)$/.match(basename)
|
|
||||||
template_name = basename
|
|
||||||
content_type = md.captures[1].gsub('.', '/')
|
|
||||||
@parts << Part.new(:content_type => content_type,
|
|
||||||
:disposition => "inline", :charset => charset,
|
|
||||||
:body => render_message(template_name, @body))
|
|
||||||
end
|
|
||||||
unless @parts.empty?
|
|
||||||
@content_type = "multipart/alternative"
|
|
||||||
@parts = sort_parts(@parts, @implicit_parts_order)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Then, if there were such templates, we check to see if we ought to
|
|
||||||
# also render a "normal" template (without the content type). If a
|
|
||||||
# normal template exists (or if there were no implicit parts) we render
|
|
||||||
# it.
|
|
||||||
template_exists = @parts.empty?
|
|
||||||
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
|
|
||||||
@body = render_message(@template, @body) if template_exists
|
|
||||||
|
|
||||||
# Finally, if there are other message parts and a textual body exists,
|
|
||||||
# we shift it onto the front of the parts and set the body to nil (so
|
|
||||||
# that create_mail doesn't try to render it in addition to the parts).
|
|
||||||
if !@parts.empty? && String === @body
|
|
||||||
@parts.unshift Part.new(:charset => charset, :body => @body)
|
|
||||||
@body = nil
|
|
||||||
end
|
|
||||||
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?
|
|
||||||
|
|
||||||
# build the mail object itself
|
|
||||||
@mail = create_mail
|
|
||||||
end
|
|
||||||
|
|
||||||
# Delivers a TMail::Mail object. By default, it delivers the cached mail
|
|
||||||
# object (from the #create! method). If no cached mail object exists, and
|
|
||||||
# no alternate has been given as the parameter, this will fail.
|
|
||||||
def deliver!(mail = @mail)
|
|
||||||
raise "no mail object available for delivery!" unless mail
|
|
||||||
logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
|
|
||||||
|
|
||||||
begin
|
|
||||||
send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
|
|
||||||
rescue Object => e
|
|
||||||
raise e if raise_delivery_errors
|
|
||||||
end
|
|
||||||
|
|
||||||
return mail
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
# 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 ||= @@default_charset.dup
|
|
||||||
@content_type ||= @@default_content_type.dup
|
|
||||||
@implicit_parts_order ||= @@default_implicit_parts_order.dup
|
|
||||||
@template ||= method_name
|
|
||||||
@mailer_name ||= Inflector.underscore(self.class.name)
|
|
||||||
@parts ||= []
|
|
||||||
@headers ||= {}
|
|
||||||
@body ||= {}
|
|
||||||
@mime_version = @@default_mime_version.dup if @@default_mime_version
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_message(method_name, body)
|
|
||||||
render :file => method_name, :body => body
|
|
||||||
end
|
|
||||||
|
|
||||||
def render(opts)
|
|
||||||
body = opts.delete(:body)
|
|
||||||
initialize_template_class(body).render(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_path
|
|
||||||
"#{template_root}/#{mailer_name}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_template_class(assigns)
|
|
||||||
ActionView::Base.new(template_path, assigns, self)
|
|
||||||
end
|
|
||||||
|
|
||||||
def sort_parts(parts, order = [])
|
|
||||||
order = order.collect { |s| s.downcase }
|
|
||||||
|
|
||||||
parts = parts.sort do |a, b|
|
|
||||||
a_ct = a.content_type.downcase
|
|
||||||
b_ct = b.content_type.downcase
|
|
||||||
|
|
||||||
a_in = order.include? a_ct
|
|
||||||
b_in = order.include? b_ct
|
|
||||||
|
|
||||||
s = case
|
|
||||||
when a_in && b_in
|
|
||||||
order.index(a_ct) <=> order.index(b_ct)
|
|
||||||
when a_in
|
|
||||||
-1
|
|
||||||
when b_in
|
|
||||||
1
|
|
||||||
else
|
|
||||||
a_ct <=> b_ct
|
|
||||||
end
|
|
||||||
|
|
||||||
# reverse the ordering because parts that come last are displayed
|
|
||||||
# first in mail clients
|
|
||||||
(s * -1)
|
|
||||||
end
|
|
||||||
|
|
||||||
parts
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_mail
|
|
||||||
m = TMail::Mail.new
|
|
||||||
|
|
||||||
m.subject, = quote_any_if_necessary(charset, subject)
|
|
||||||
m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
|
|
||||||
m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
|
|
||||||
m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
if @parts.empty?
|
|
||||||
m.set_content_type(real_content_type, nil, ctype_attrs)
|
|
||||||
m.body = Utils.normalize_new_lines(body)
|
|
||||||
else
|
|
||||||
if String === body
|
|
||||||
part = TMail::Mail.new
|
|
||||||
part.body = Utils.normalize_new_lines(body)
|
|
||||||
part.set_content_type(real_content_type, nil, ctype_attrs)
|
|
||||||
part.set_content_disposition "inline"
|
|
||||||
m.parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
@parts.each do |p|
|
|
||||||
part = (TMail::Mail === p ? p : p.to_mail(self))
|
|
||||||
m.parts << part
|
|
||||||
end
|
|
||||||
|
|
||||||
if real_content_type =~ /multipart/
|
|
||||||
ctype_attrs.delete "charset"
|
|
||||||
m.set_content_type(real_content_type, nil, ctype_attrs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@mail = m
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_smtp(mail)
|
|
||||||
destinations = mail.destinations
|
|
||||||
mail.ready_to_send
|
|
||||||
|
|
||||||
Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
|
|
||||||
server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
|
|
||||||
smtp.sendmail(mail.encoded, mail.from, destinations)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_sendmail(mail)
|
|
||||||
IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
|
|
||||||
sm.print(mail.encoded.gsub(/\r/, ''))
|
|
||||||
sm.flush
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_delivery_test(mail)
|
|
||||||
deliveries << mail
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
require 'text/format'
|
|
||||||
|
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module Quoting #:nodoc:
|
|
||||||
# Convert the given text into quoted printable format, with an instruction
|
|
||||||
# that the text be eventually interpreted in the given charset.
|
|
||||||
def quoted_printable(text, charset)
|
|
||||||
text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
|
|
||||||
gsub( / /, "_" )
|
|
||||||
"=?#{charset}?Q?#{text}?="
|
|
||||||
end
|
|
||||||
|
|
||||||
# Convert the given character to quoted printable format, taking into
|
|
||||||
# account multi-byte characters (if executing with $KCODE="u", for instance)
|
|
||||||
def quoted_printable_encode(character)
|
|
||||||
result = ""
|
|
||||||
character.each_byte { |b| result << "=%02x" % b }
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
# A quick-and-dirty regexp for determining whether a string contains any
|
|
||||||
# characters that need escaping.
|
|
||||||
if !defined?(CHARS_NEEDING_QUOTING)
|
|
||||||
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote the given text if it contains any "illegal" characters
|
|
||||||
def quote_if_necessary(text, charset)
|
|
||||||
(text =~ CHARS_NEEDING_QUOTING) ?
|
|
||||||
quoted_printable(text, charset) :
|
|
||||||
text
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote any of the given strings if they contain any "illegal" characters
|
|
||||||
def quote_any_if_necessary(charset, *args)
|
|
||||||
args.map { |v| quote_if_necessary(v, charset) }
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote the given address if it needs to be. The address may be a
|
|
||||||
# regular email address, or it can be a phrase followed by an address in
|
|
||||||
# brackets. The phrase is the only part that will be quoted, and only if
|
|
||||||
# it needs to be. This allows extended characters to be used in the
|
|
||||||
# "to", "from", "cc", and "bcc" headers.
|
|
||||||
def quote_address_if_necessary(address, charset)
|
|
||||||
if Array === address
|
|
||||||
address.map { |a| quote_address_if_necessary(a, charset) }
|
|
||||||
elsif address =~ /^(\S.*)\s+(<.*>)$/
|
|
||||||
address = $2
|
|
||||||
phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
|
|
||||||
"\"#{phrase}\" #{address}"
|
|
||||||
else
|
|
||||||
address
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Quote any of the given addresses, if they need to be.
|
|
||||||
def quote_any_address_if_necessary(charset, *args)
|
|
||||||
args.map { |v| quote_address_if_necessary(v, charset) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
@ -1,3 +0,0 @@
|
||||||
require 'tmail/info'
|
|
||||||
require 'tmail/mail'
|
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,242 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,467 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,552 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,914 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#
|
|
||||||
# info.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
Version = '0.10.7'
|
|
||||||
Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,447 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,433 +0,0 @@
|
||||||
#
|
|
||||||
# mailbox.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
require 'tmail/port'
|
|
||||||
require 'socket'
|
|
||||||
require 'mutex_m'
|
|
||||||
|
|
||||||
|
|
||||||
unless [].respond_to?(:sort_by)
|
|
||||||
module Enumerable#:nodoc:
|
|
||||||
def sort_by
|
|
||||||
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class MhMailbox
|
|
||||||
|
|
||||||
PORT_CLASS = MhPort
|
|
||||||
|
|
||||||
def initialize( dir )
|
|
||||||
edir = File.expand_path(dir)
|
|
||||||
raise ArgumentError, "not directory: #{dir}"\
|
|
||||||
unless FileTest.directory? edir
|
|
||||||
@dirname = edir
|
|
||||||
@last_file = nil
|
|
||||||
@last_atime = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def directory
|
|
||||||
@dirname
|
|
||||||
end
|
|
||||||
|
|
||||||
alias dirname directory
|
|
||||||
|
|
||||||
attr_accessor :last_atime
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{@dirname}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
PORT_CLASS.new(next_file_name())
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port
|
|
||||||
mail_files().each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port
|
|
||||||
mail_files().reverse_each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
# old #each_mail returns Port
|
|
||||||
#def each_mail
|
|
||||||
# each_port do |port|
|
|
||||||
# yield Mail.new(port)
|
|
||||||
# end
|
|
||||||
#end
|
|
||||||
|
|
||||||
def each_new_port( mtime = nil, &block )
|
|
||||||
mtime ||= @last_atime
|
|
||||||
return each_port(&block) unless mtime
|
|
||||||
return unless File.mtime(@dirname) >= mtime
|
|
||||||
|
|
||||||
mail_files().each do |path|
|
|
||||||
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
|
|
||||||
end
|
|
||||||
@last_atime = Time.now
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def mail_files
|
|
||||||
Dir.entries(@dirname)\
|
|
||||||
.select {|s| /\A\d+\z/ === s }\
|
|
||||||
.map {|s| s.to_i }\
|
|
||||||
.sort\
|
|
||||||
.map {|i| "#{@dirname}/#{i}" }\
|
|
||||||
.select {|path| FileTest.file? path }
|
|
||||||
end
|
|
||||||
|
|
||||||
def next_file_name
|
|
||||||
unless n = @last_file
|
|
||||||
n = 0
|
|
||||||
Dir.entries(@dirname)\
|
|
||||||
.select {|s| /\A\d+\z/ === s }\
|
|
||||||
.map {|s| s.to_i }.sort\
|
|
||||||
.each do |i|
|
|
||||||
next unless FileTest.file? "#{@dirname}/#{i}"
|
|
||||||
n = i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
begin
|
|
||||||
n += 1
|
|
||||||
end while FileTest.exist? "#{@dirname}/#{n}"
|
|
||||||
@last_file = n
|
|
||||||
|
|
||||||
"#{@dirname}/#{n}"
|
|
||||||
end
|
|
||||||
|
|
||||||
end # MhMailbox
|
|
||||||
|
|
||||||
MhLoader = MhMailbox
|
|
||||||
|
|
||||||
|
|
||||||
class UNIXMbox
|
|
||||||
|
|
||||||
def UNIXMbox.lock( fname )
|
|
||||||
begin
|
|
||||||
f = File.open(fname)
|
|
||||||
f.flock File::LOCK_EX
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.flock File::LOCK_UN
|
|
||||||
f.close if f and not f.closed?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class << self
|
|
||||||
alias newobj new
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
|
|
||||||
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
|
|
||||||
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.static_new( fname, dir, readonly = false )
|
|
||||||
newobj(fname, dir, readonly, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( fname, mhdir, readonly, static )
|
|
||||||
@filename = fname
|
|
||||||
@readonly = readonly
|
|
||||||
@closed = false
|
|
||||||
|
|
||||||
Dir.mkdir mhdir
|
|
||||||
@real = MhMailbox.new(mhdir)
|
|
||||||
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
|
|
||||||
ObjectSpace.define_finalizer self, @finalizer
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
|
|
||||||
lambda {
|
|
||||||
if writeback_p
|
|
||||||
lock(mboxfile) {|f|
|
|
||||||
mh.each_port do |port|
|
|
||||||
f.puts create_from_line(port)
|
|
||||||
port.ropen {|r|
|
|
||||||
f.puts r.read
|
|
||||||
}
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
if cleanup_p
|
|
||||||
Dir.foreach(mh.dirname) do |fname|
|
|
||||||
next if /\A\.\.?\z/ === fname
|
|
||||||
File.unlink "#{mh.dirname}/#{fname}"
|
|
||||||
end
|
|
||||||
Dir.rmdir mh.dirname
|
|
||||||
end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# make _From line
|
|
||||||
def UNIXMbox.create_from_line( port )
|
|
||||||
sprintf 'From %s %s',
|
|
||||||
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
|
|
||||||
end
|
|
||||||
|
|
||||||
def UNIXMbox.fromaddr
|
|
||||||
h = HeaderField.new_from_port(port, 'Return-Path') ||
|
|
||||||
HeaderField.new_from_port(port, 'From') or return 'nobody'
|
|
||||||
a = h.addrs[0] or return 'nobody'
|
|
||||||
a.spec
|
|
||||||
end
|
|
||||||
private_class_method :fromaddr
|
|
||||||
|
|
||||||
def close
|
|
||||||
return if @closed
|
|
||||||
|
|
||||||
ObjectSpace.undefine_finalizer self
|
|
||||||
@finalizer.call
|
|
||||||
@finalizer = nil
|
|
||||||
@real = nil
|
|
||||||
@closed = true
|
|
||||||
@updated = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port( &block )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.each_port(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port( &block )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.reverse_each_port(&block)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
# old #each_mail returns Port
|
|
||||||
#def each_mail( &block )
|
|
||||||
# each_port do |port|
|
|
||||||
# yield Mail.new(port)
|
|
||||||
# end
|
|
||||||
#end
|
|
||||||
|
|
||||||
def each_new_port( mtime = nil )
|
|
||||||
close_check
|
|
||||||
update
|
|
||||||
@real.each_new_port(mtime) {|p| yield p }
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
close_check
|
|
||||||
@real.new_port
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def close_check
|
|
||||||
@closed and raise ArgumentError, 'accessing already closed mbox'
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
return if FileTest.zero?(@filename)
|
|
||||||
return if @updated and File.mtime(@filename) < @updated
|
|
||||||
w = nil
|
|
||||||
port = nil
|
|
||||||
time = nil
|
|
||||||
UNIXMbox.lock(@filename) {|f|
|
|
||||||
begin
|
|
||||||
f.each do |line|
|
|
||||||
if /\AFrom / === line
|
|
||||||
w.close if w
|
|
||||||
File.utime time, time, port.filename if time
|
|
||||||
|
|
||||||
port = @real.new_port
|
|
||||||
w = port.wopen
|
|
||||||
time = fromline2time(line)
|
|
||||||
else
|
|
||||||
w.print line if w
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
if w and not w.closed?
|
|
||||||
w.close
|
|
||||||
File.utime time, time, port.filename if time
|
|
||||||
end
|
|
||||||
end
|
|
||||||
f.truncate(0) unless @readonly
|
|
||||||
@updated = Time.now
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def fromline2time( line )
|
|
||||||
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
|
|
||||||
or return nil
|
|
||||||
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
|
|
||||||
end
|
|
||||||
|
|
||||||
end # UNIXMbox
|
|
||||||
|
|
||||||
MboxLoader = UNIXMbox
|
|
||||||
|
|
||||||
|
|
||||||
class Maildir
|
|
||||||
|
|
||||||
extend Mutex_m
|
|
||||||
|
|
||||||
PORT_CLASS = MaildirPort
|
|
||||||
|
|
||||||
@seq = 0
|
|
||||||
def Maildir.unique_number
|
|
||||||
synchronize {
|
|
||||||
@seq += 1
|
|
||||||
return @seq
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( dir = nil )
|
|
||||||
@dirname = dir || ENV['MAILDIR']
|
|
||||||
raise ArgumentError, "not directory: #{@dirname}"\
|
|
||||||
unless FileTest.directory? @dirname
|
|
||||||
@new = "#{@dirname}/new"
|
|
||||||
@tmp = "#{@dirname}/tmp"
|
|
||||||
@cur = "#{@dirname}/cur"
|
|
||||||
end
|
|
||||||
|
|
||||||
def directory
|
|
||||||
@dirname
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class} #{@dirname}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_port
|
|
||||||
mail_files(@cur).each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias each each_port
|
|
||||||
|
|
||||||
def reverse_each_port
|
|
||||||
mail_files(@cur).reverse_each do |path|
|
|
||||||
yield PORT_CLASS.new(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias reverse_each reverse_each_port
|
|
||||||
|
|
||||||
def new_port
|
|
||||||
fname = nil
|
|
||||||
tmpfname = nil
|
|
||||||
newfname = nil
|
|
||||||
|
|
||||||
begin
|
|
||||||
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
|
|
||||||
|
|
||||||
tmpfname = "#{@tmp}/#{fname}"
|
|
||||||
newfname = "#{@new}/#{fname}"
|
|
||||||
end while FileTest.exist? tmpfname
|
|
||||||
|
|
||||||
if block_given?
|
|
||||||
File.open(tmpfname, 'w') {|f| yield f }
|
|
||||||
File.rename tmpfname, newfname
|
|
||||||
PORT_CLASS.new(newfname)
|
|
||||||
else
|
|
||||||
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
|
|
||||||
PORT_CLASS.new(tmpfname)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_new_port
|
|
||||||
mail_files(@new).each do |path|
|
|
||||||
dest = @cur + '/' + File.basename(path)
|
|
||||||
File.rename path, dest
|
|
||||||
yield PORT_CLASS.new(dest)
|
|
||||||
end
|
|
||||||
|
|
||||||
check_tmp
|
|
||||||
end
|
|
||||||
|
|
||||||
TOO_OLD = 60 * 60 * 36 # 36 hour
|
|
||||||
|
|
||||||
def check_tmp
|
|
||||||
old = Time.now.to_i - TOO_OLD
|
|
||||||
|
|
||||||
each_filename(@tmp) do |full, fname|
|
|
||||||
if FileTest.file? full and
|
|
||||||
File.stat(full).mtime.to_i < old
|
|
||||||
File.unlink full
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def mail_files( dir )
|
|
||||||
Dir.entries(dir)\
|
|
||||||
.select {|s| s[0] != ?. }\
|
|
||||||
.sort_by {|s| s.slice(/\A\d+/).to_i }\
|
|
||||||
.map {|s| "#{dir}/#{s}" }\
|
|
||||||
.select {|path| FileTest.file? path }
|
|
||||||
end
|
|
||||||
|
|
||||||
def each_filename( dir )
|
|
||||||
Dir.foreach(dir) do |fname|
|
|
||||||
path = "#{dir}/#{fname}"
|
|
||||||
if fname[0] != ?. and FileTest.file? path
|
|
||||||
yield path, fname
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end # Maildir
|
|
||||||
|
|
||||||
MaildirLoader = Maildir
|
|
||||||
|
|
||||||
end # module TMail
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail/mailbox'
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
@ -1,377 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,263 +0,0 @@
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
@ -1,277 +0,0 @@
|
||||||
#
|
|
||||||
# stringio.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
class StringInput#:nodoc:
|
|
||||||
|
|
||||||
include Enumerable
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def new( str )
|
|
||||||
if block_given?
|
|
||||||
begin
|
|
||||||
f = super
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.close if f
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias open new
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( str )
|
|
||||||
@src = str
|
|
||||||
@pos = 0
|
|
||||||
@closed = false
|
|
||||||
@lineno = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
attr_reader :lineno
|
|
||||||
|
|
||||||
def string
|
|
||||||
@src
|
|
||||||
end
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
stream_check!
|
|
||||||
@pos = nil
|
|
||||||
@closed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def closed?
|
|
||||||
@closed
|
|
||||||
end
|
|
||||||
|
|
||||||
def pos
|
|
||||||
stream_check!
|
|
||||||
[@pos, @src.size].min
|
|
||||||
end
|
|
||||||
|
|
||||||
alias tell pos
|
|
||||||
|
|
||||||
def seek( offset, whence = IO::SEEK_SET )
|
|
||||||
stream_check!
|
|
||||||
case whence
|
|
||||||
when IO::SEEK_SET
|
|
||||||
@pos = offset
|
|
||||||
when IO::SEEK_CUR
|
|
||||||
@pos += offset
|
|
||||||
when IO::SEEK_END
|
|
||||||
@pos = @src.size - offset
|
|
||||||
else
|
|
||||||
raise ArgumentError, "unknown seek flag: #{whence}"
|
|
||||||
end
|
|
||||||
@pos = 0 if @pos < 0
|
|
||||||
@pos = [@pos, @src.size + 1].min
|
|
||||||
offset
|
|
||||||
end
|
|
||||||
|
|
||||||
def rewind
|
|
||||||
stream_check!
|
|
||||||
@pos = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
def eof?
|
|
||||||
stream_check!
|
|
||||||
@pos > @src.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def each( &block )
|
|
||||||
stream_check!
|
|
||||||
begin
|
|
||||||
@src.each(&block)
|
|
||||||
ensure
|
|
||||||
@pos = 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def gets
|
|
||||||
stream_check!
|
|
||||||
if idx = @src.index(?\n, @pos)
|
|
||||||
idx += 1 # "\n".size
|
|
||||||
line = @src[ @pos ... idx ]
|
|
||||||
@pos = idx
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
else
|
|
||||||
line = @src[ @pos .. -1 ]
|
|
||||||
@pos = @src.size + 1
|
|
||||||
end
|
|
||||||
@lineno += 1
|
|
||||||
|
|
||||||
line
|
|
||||||
end
|
|
||||||
|
|
||||||
def getc
|
|
||||||
stream_check!
|
|
||||||
ch = @src[@pos]
|
|
||||||
@pos += 1
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
ch
|
|
||||||
end
|
|
||||||
|
|
||||||
def read( len = nil )
|
|
||||||
stream_check!
|
|
||||||
return read_all unless len
|
|
||||||
str = @src[@pos, len]
|
|
||||||
@pos += len
|
|
||||||
@pos += 1 if @pos == @src.size
|
|
||||||
str
|
|
||||||
end
|
|
||||||
|
|
||||||
alias sysread read
|
|
||||||
|
|
||||||
def read_all
|
|
||||||
stream_check!
|
|
||||||
return nil if eof?
|
|
||||||
rest = @src[@pos ... @src.size]
|
|
||||||
@pos = @src.size + 1
|
|
||||||
rest
|
|
||||||
end
|
|
||||||
|
|
||||||
def stream_check!
|
|
||||||
@closed and raise IOError, 'closed stream'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class StringOutput#:nodoc:
|
|
||||||
|
|
||||||
class << self
|
|
||||||
|
|
||||||
def new( str = '' )
|
|
||||||
if block_given?
|
|
||||||
begin
|
|
||||||
f = super
|
|
||||||
yield f
|
|
||||||
ensure
|
|
||||||
f.close if f
|
|
||||||
end
|
|
||||||
else
|
|
||||||
super
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
alias open new
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize( str = '' )
|
|
||||||
@dest = str
|
|
||||||
@closed = false
|
|
||||||
end
|
|
||||||
|
|
||||||
def close
|
|
||||||
@closed = true
|
|
||||||
end
|
|
||||||
|
|
||||||
def closed?
|
|
||||||
@closed
|
|
||||||
end
|
|
||||||
|
|
||||||
def string
|
|
||||||
@dest
|
|
||||||
end
|
|
||||||
|
|
||||||
alias value string
|
|
||||||
alias to_str string
|
|
||||||
|
|
||||||
def size
|
|
||||||
@dest.size
|
|
||||||
end
|
|
||||||
|
|
||||||
alias pos size
|
|
||||||
|
|
||||||
def inspect
|
|
||||||
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def print( *args )
|
|
||||||
stream_check!
|
|
||||||
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
|
|
||||||
args.each do |s|
|
|
||||||
raise ArgumentError, 'nil not allowed' if s.nil?
|
|
||||||
@dest << s.to_s
|
|
||||||
end
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def puts( *args )
|
|
||||||
stream_check!
|
|
||||||
args.each do |str|
|
|
||||||
@dest << (s = str.to_s)
|
|
||||||
@dest << "\n" unless s[-1] == ?\n
|
|
||||||
end
|
|
||||||
@dest << "\n" if args.empty?
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def putc( ch )
|
|
||||||
stream_check!
|
|
||||||
@dest << ch.chr
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def printf( *args )
|
|
||||||
stream_check!
|
|
||||||
@dest << sprintf(*args)
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def write( str )
|
|
||||||
stream_check!
|
|
||||||
s = str.to_s
|
|
||||||
@dest << s
|
|
||||||
s.size
|
|
||||||
end
|
|
||||||
|
|
||||||
alias syswrite write
|
|
||||||
|
|
||||||
def <<( str )
|
|
||||||
stream_check!
|
|
||||||
@dest << str.to_s
|
|
||||||
self
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def stream_check!
|
|
||||||
@closed and raise IOError, 'closed stream'
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require 'tmail'
|
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
#
|
|
||||||
# utils.rb
|
|
||||||
#
|
|
||||||
#--
|
|
||||||
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
# a copy of this software and associated documentation files (the
|
|
||||||
# "Software"), to deal in the Software without restriction, including
|
|
||||||
# without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
# permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
# the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be
|
|
||||||
# included in all copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
#
|
|
||||||
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
|
|
||||||
# with permission of Minero Aoki.
|
|
||||||
#++
|
|
||||||
|
|
||||||
module TMail
|
|
||||||
|
|
||||||
class SyntaxError < StandardError; end
|
|
||||||
|
|
||||||
|
|
||||||
def TMail.new_boundary
|
|
||||||
'mimepart_' + random_tag
|
|
||||||
end
|
|
||||||
|
|
||||||
def TMail.new_message_id( fqdn = nil )
|
|
||||||
fqdn ||= ::Socket.gethostname
|
|
||||||
"<#{random_tag()}@#{fqdn}.tmail>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def TMail.random_tag
|
|
||||||
@uniq += 1
|
|
||||||
t = Time.now
|
|
||||||
sprintf('%x%x_%x%x%d%x',
|
|
||||||
t.to_i, t.tv_usec,
|
|
||||||
$$, Thread.current.object_id, @uniq, rand(255))
|
|
||||||
end
|
|
||||||
private_class_method :random_tag
|
|
||||||
|
|
||||||
@uniq = 0
|
|
||||||
|
|
||||||
|
|
||||||
module TextUtils
|
|
||||||
|
|
||||||
aspecial = '()<>[]:;.\\,"'
|
|
||||||
tspecial = '()<>[];:\\,"/?='
|
|
||||||
lwsp = " \t\r\n"
|
|
||||||
control = '\x00-\x1f\x7f-\xff'
|
|
||||||
|
|
||||||
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
|
|
||||||
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
|
|
||||||
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
|
|
||||||
CONTROL_CHAR = /[#{control}]/n
|
|
||||||
|
|
||||||
def atom_safe?( str )
|
|
||||||
not ATOM_UNSAFE === str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_atom( str )
|
|
||||||
(ATOM_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_phrase( str )
|
|
||||||
(PHRASE_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def token_safe?( str )
|
|
||||||
not TOKEN_UNSAFE === str
|
|
||||||
end
|
|
||||||
|
|
||||||
def quote_token( str )
|
|
||||||
(TOKEN_UNSAFE === str) ? dquote(str) : str
|
|
||||||
end
|
|
||||||
|
|
||||||
def dquote( str )
|
|
||||||
'"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
|
|
||||||
end
|
|
||||||
private :dquote
|
|
||||||
|
|
||||||
|
|
||||||
def join_domain( arr )
|
|
||||||
arr.map {|i|
|
|
||||||
if /\A\[.*\]\z/ === i
|
|
||||||
i
|
|
||||||
else
|
|
||||||
quote_atom(i)
|
|
||||||
end
|
|
||||||
}.join('.')
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
ZONESTR_TABLE = {
|
|
||||||
'jst' => 9 * 60,
|
|
||||||
'eet' => 2 * 60,
|
|
||||||
'bst' => 1 * 60,
|
|
||||||
'met' => 1 * 60,
|
|
||||||
'gmt' => 0,
|
|
||||||
'utc' => 0,
|
|
||||||
'ut' => 0,
|
|
||||||
'nst' => -(3 * 60 + 30),
|
|
||||||
'ast' => -4 * 60,
|
|
||||||
'edt' => -4 * 60,
|
|
||||||
'est' => -5 * 60,
|
|
||||||
'cdt' => -5 * 60,
|
|
||||||
'cst' => -6 * 60,
|
|
||||||
'mdt' => -6 * 60,
|
|
||||||
'mst' => -7 * 60,
|
|
||||||
'pdt' => -7 * 60,
|
|
||||||
'pst' => -8 * 60,
|
|
||||||
'a' => -1 * 60,
|
|
||||||
'b' => -2 * 60,
|
|
||||||
'c' => -3 * 60,
|
|
||||||
'd' => -4 * 60,
|
|
||||||
'e' => -5 * 60,
|
|
||||||
'f' => -6 * 60,
|
|
||||||
'g' => -7 * 60,
|
|
||||||
'h' => -8 * 60,
|
|
||||||
'i' => -9 * 60,
|
|
||||||
# j not use
|
|
||||||
'k' => -10 * 60,
|
|
||||||
'l' => -11 * 60,
|
|
||||||
'm' => -12 * 60,
|
|
||||||
'n' => 1 * 60,
|
|
||||||
'o' => 2 * 60,
|
|
||||||
'p' => 3 * 60,
|
|
||||||
'q' => 4 * 60,
|
|
||||||
'r' => 5 * 60,
|
|
||||||
's' => 6 * 60,
|
|
||||||
't' => 7 * 60,
|
|
||||||
'u' => 8 * 60,
|
|
||||||
'v' => 9 * 60,
|
|
||||||
'w' => 10 * 60,
|
|
||||||
'x' => 11 * 60,
|
|
||||||
'y' => 12 * 60,
|
|
||||||
'z' => 0 * 60
|
|
||||||
}
|
|
||||||
|
|
||||||
def timezone_string_to_unixtime( str )
|
|
||||||
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
|
|
||||||
sec = (m[2].to_i * 60 + m[3].to_i) * 60
|
|
||||||
m[1] == '-' ? -sec : sec
|
|
||||||
else
|
|
||||||
min = ZONESTR_TABLE[str.downcase] or
|
|
||||||
raise SyntaxError, "wrong timezone format '#{str}'"
|
|
||||||
min * 60
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
|
|
||||||
MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
|
|
||||||
Jul Aug Sep Oct Nov Dec TMailBUG )
|
|
||||||
|
|
||||||
def time2str( tm )
|
|
||||||
# [ruby-list:7928]
|
|
||||||
gmt = Time.at(tm.to_i)
|
|
||||||
gmt.gmtime
|
|
||||||
offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
|
|
||||||
|
|
||||||
# DO NOT USE strftime: setlocale() breaks it
|
|
||||||
sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
|
|
||||||
WDAY[tm.wday], tm.mday, MONTH[tm.month],
|
|
||||||
tm.year, tm.hour, tm.min, tm.sec,
|
|
||||||
*(offset / 60).divmod(60)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
|
|
||||||
|
|
||||||
def message_id?( str )
|
|
||||||
MESSAGE_ID === str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
|
|
||||||
|
|
||||||
def mime_encoded?( str )
|
|
||||||
MIME_ENCODED === str
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def decode_params( hash )
|
|
||||||
new = Hash.new
|
|
||||||
encoded = nil
|
|
||||||
hash.each do |key, value|
|
|
||||||
if m = /\*(?:(\d+)\*)?\z/.match(key)
|
|
||||||
((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
|
|
||||||
else
|
|
||||||
new[key] = to_kcode(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if encoded
|
|
||||||
encoded.each do |key, strings|
|
|
||||||
new[key] = decode_RFC2231(strings.join(''))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
new
|
|
||||||
end
|
|
||||||
|
|
||||||
NKF_FLAGS = {
|
|
||||||
'EUC' => '-e -m',
|
|
||||||
'SJIS' => '-s -m'
|
|
||||||
}
|
|
||||||
|
|
||||||
def to_kcode( str )
|
|
||||||
flag = NKF_FLAGS[$KCODE] or return str
|
|
||||||
NKF.nkf(flag, str)
|
|
||||||
end
|
|
||||||
|
|
||||||
RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
|
|
||||||
|
|
||||||
def decode_RFC2231( str )
|
|
||||||
m = RFC2231_ENCODED.match(str) or return str
|
|
||||||
begin
|
|
||||||
NKF.nkf(NKF_FLAGS[$KCODE],
|
|
||||||
m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
|
|
||||||
rescue
|
|
||||||
m.post_match.gsub(/%[\da-f]{2}/in, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module ActionMailer
|
|
||||||
module VERSION #:nodoc:
|
|
||||||
MAJOR = 1
|
|
||||||
MINOR = 2
|
|
||||||
TINY = 0
|
|
||||||
|
|
||||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Hello, <%= person_name %>. Thanks for registering!
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
This message brought to you by <%= name_of_the_mailer_class %>.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
From "Romeo and Juliet":
|
|
||||||
|
|
||||||
<%= block_format @text %>
|
|
||||||
|
|
||||||
Good ol' Shakespeare.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
So, <%= test_format(@text) %>
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
module TestHelper
|
|
||||||
def test_format(text)
|
|
||||||
"<em><strong><small>#{text}</small></strong></em>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Have a lovely picture, from me. Enjoy!
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
From jamis_buck@byu.edu Mon May 2 16:07:05 2005
|
|
||||||
Mime-Version: 1.0 (Apple Message framework v622)
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=EUC-KR;
|
|
||||||
format=flowed
|
|
||||||
To: willard15georgina@jamis.backpackit.com
|
|
||||||
From: Jamis Buck <jamis@37signals.com>
|
|
||||||
Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?=
|
|
||||||
Date: Mon, 2 May 2005 16:07:05 -0600
|
|
||||||
|
|
||||||
tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
|
|
||||||
wLogSmFtaXPA1LTPtNku
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
Content-Type: text/plain; charset=X-UNKNOWN
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
From xxx@xxxx.com Wed Apr 27 14:15:31 2005
|
|
||||||
Mime-Version: 1.0 (Apple Message framework v619.2)
|
|
||||||
To: xxxxx@xxxxx <matmail>
|
|
||||||
Message-Id: <416eaebec6d333ec6939eaf8a7d80724@xxxxx>
|
|
||||||
Content-Type: multipart/alternative;
|
|
||||||
boundary=Apple-Mail-5-1037861608
|
|
||||||
From: xxxxx@xxxxx <xxxxx@xxxxx>
|
|
||||||
Subject: worse when you use them.
|
|
||||||
Date: Wed, 27 Apr 2005 14:15:31 -0700
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=US-ASCII;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
|
|
||||||
XXXXX Xxxxx
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/enriched;
|
|
||||||
charset=US-ASCII
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<bold>XXXXX Xxxxx</bold>
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-5-1037861608--
|
|
||||||
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
Mime-Version: 1.0 (Apple Message framework v730)
|
|
||||||
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
|
|
||||||
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
|
|
||||||
From: foo@example.com
|
|
||||||
Subject: testing
|
|
||||||
Date: Mon, 6 Jun 2005 22:21:22 +0200
|
|
||||||
To: blah@example.com
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=ISO-8859-1;
|
|
||||||
delsp=yes;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
This is the first part.
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Type: image/jpeg
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Location: Photo25.jpg
|
|
||||||
Content-ID: <qbFGyPQAS8>
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
|
|
||||||
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
|
|
||||||
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
|
|
||||||
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151--
|
|
||||||
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
Mime-Version: 1.0 (Apple Message framework v730)
|
|
||||||
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
|
|
||||||
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
|
|
||||||
From: foo@example.com
|
|
||||||
Subject: testing
|
|
||||||
Date: Mon, 6 Jun 2005 22:21:22 +0200
|
|
||||||
To: blah@example.com
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=ISO-8859-1;
|
|
||||||
delsp=yes;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
This is the first part.
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Type: text/x-ruby-script; name="hello.rb"
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename="api.rb"
|
|
||||||
|
|
||||||
puts "Hello, world!"
|
|
||||||
gets
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151--
|
|
||||||
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
|
|
||||||
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com
|
|
||||||
Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com
|
|
||||||
Received: from localhost (localhost [127.0.0.1])
|
|
||||||
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D
|
|
||||||
for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT)
|
|
||||||
Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1])
|
|
||||||
by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
|
|
||||||
with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>;
|
|
||||||
Sun, 8 May 2005 19:09:12 +0000 (GMT)
|
|
||||||
Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150])
|
|
||||||
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960
|
|
||||||
for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT)
|
|
||||||
Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199])
|
|
||||||
by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB
|
|
||||||
for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT)
|
|
||||||
Received: by zproxy.gmail.com with SMTP id 13so1233405nzp
|
|
||||||
for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
|
|
||||||
s=beta; d=gmail.com;
|
|
||||||
h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references;
|
|
||||||
b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8=
|
|
||||||
Received: by 10.36.10.16 with SMTP id 16mr1012493nzj;
|
|
||||||
Sun, 08 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT)
|
|
||||||
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
|
|
||||||
Date: Sun, 8 May 2005 14:09:11 -0500
|
|
||||||
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
|
|
||||||
Subject: Fwd: Signed email causes file attachments
|
|
||||||
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_5028_7368284.1115579351471"
|
|
||||||
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
We should not include these files or vcards as attachments.
|
|
||||||
|
|
||||||
---------- Forwarded message ----------
|
|
||||||
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
|
|
||||||
Date: May 8, 2005 1:17 PM
|
|
||||||
Subject: Signed email causes file attachments
|
|
||||||
To: xxxxxxx@xxxxxxxxxx.com
|
|
||||||
|
|
||||||
|
|
||||||
Hi,
|
|
||||||
|
|
||||||
Just started to use my xxxxxxxx account (to set-up a GTD system,
|
|
||||||
natch) and noticed that when I send content via email the signature/
|
|
||||||
certificate from my email account gets added as a file (e.g.
|
|
||||||
"smime.p7s").
|
|
||||||
|
|
||||||
Obviously I can uncheck the signature option in the Mail compose
|
|
||||||
window but how often will I remember to do that?
|
|
||||||
|
|
||||||
Is there any way these kind of files could be ignored, e.g. via some
|
|
||||||
sort of exclusions list?
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: application/pkcs7-signature; name=smime.p7s
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment; filename="smime.p7s"
|
|
||||||
|
|
||||||
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
|
|
||||||
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
|
|
||||||
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
|
|
||||||
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
|
|
||||||
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
|
|
||||||
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
|
|
||||||
o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla
|
|
||||||
hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp
|
|
||||||
+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1
|
|
||||||
ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw
|
|
||||||
DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd
|
|
||||||
L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/
|
|
||||||
lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3
|
|
||||||
DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD
|
|
||||||
YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0
|
|
||||||
aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg
|
|
||||||
Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3
|
|
||||||
MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv
|
|
||||||
bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz
|
|
||||||
c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f
|
|
||||||
6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk
|
|
||||||
KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj
|
|
||||||
gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo
|
|
||||||
YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R
|
|
||||||
BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM
|
|
||||||
0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ
|
|
||||||
GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ
|
|
||||||
Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl
|
|
||||||
IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls
|
|
||||||
IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
|
|
||||||
MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp
|
|
||||||
fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
|
|
||||||
dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
|
|
||||||
aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG
|
|
||||||
A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv
|
|
||||||
bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW
|
|
||||||
8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8
|
|
||||||
rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA
|
|
||||||
nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7
|
|
||||||
Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0
|
|
||||||
fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA
|
|
||||||
------=_Part_5028_7368284.1115579351471--
|
|
||||||
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
From xxxx@xxxx.com Tue May 10 11:28:07 2005
|
|
||||||
Return-Path: <xxxx@xxxx.com>
|
|
||||||
X-Original-To: xxxx@xxxx.com
|
|
||||||
Delivered-To: xxxx@xxxx.com
|
|
||||||
Received: from localhost (localhost [127.0.0.1])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com ([127.0.0.1])
|
|
||||||
by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
|
|
||||||
with LMTP id 70060-03 for <xxxx@xxxx.com>;
|
|
||||||
Tue, 10 May 2005 17:26:49 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT)
|
|
||||||
Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203])
|
|
||||||
by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT)
|
|
||||||
Received: by xxx.xxxxx.com with SMTP id 68so1694448wri
|
|
||||||
for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT)
|
|
||||||
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
|
|
||||||
s=beta; d=xxxxx.com;
|
|
||||||
h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type;
|
|
||||||
b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A=
|
|
||||||
Received: by 10.54.96.19 with SMTP id t19mr621017wrb;
|
|
||||||
Tue, 10 May 2005 10:26:39 -0700 (PDT)
|
|
||||||
Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT)
|
|
||||||
Message-ID: <xxxx@xxxx.com>
|
|
||||||
Date: Tue, 10 May 2005 11:26:39 -0600
|
|
||||||
From: Test Tester <xxxx@xxxx.com>
|
|
||||||
Reply-To: Test Tester <xxxx@xxxx.com>
|
|
||||||
To: xxxx@xxxx.com, xxxx@xxxx.com
|
|
||||||
Subject: Another PDF
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_2192_32400445.1115745999735"
|
|
||||||
X-Virus-Scanned: amavisd-new at textdrive.com
|
|
||||||
|
|
||||||
------=_Part_2192_32400445.1115745999735
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
Just attaching another PDF, here, to see what the message looks like,
|
|
||||||
and to see if I can figure out what is going wrong here.
|
|
||||||
|
|
||||||
------=_Part_2192_32400445.1115745999735
|
|
||||||
Content-Type: application/pdf; name="broken.pdf"
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment; filename="broken.pdf"
|
|
||||||
|
|
||||||
JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G
|
|
||||||
bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE
|
|
||||||
ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu
|
|
||||||
rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD
|
|
||||||
QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU
|
|
||||||
x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab
|
|
||||||
gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg
|
|
||||||
1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw
|
|
||||||
wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI
|
|
||||||
8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl
|
|
||||||
fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ
|
|
||||||
fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR
|
|
||||||
xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss
|
|
||||||
nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU
|
|
||||||
OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF
|
|
||||||
yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS
|
|
||||||
HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk
|
|
||||||
ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw
|
|
||||||
------=_Part_2192_32400445.1115745999735--
|
|
||||||
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500
|
|
||||||
Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
|
|
||||||
Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
|
|
||||||
Date: Sun, 8 May 2005 12:30:08 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
To: xxx@xxxx.xxx
|
|
||||||
Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx>
|
|
||||||
Subject: Filth
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
X-Mms-Priority: 1
|
|
||||||
X-Mms-Transaction-Id: 3198421808-0
|
|
||||||
X-Mms-Message-Type: 0
|
|
||||||
X-Mms-Sender-Visibility: 1
|
|
||||||
X-Mms-Read-Reply: 1
|
|
||||||
X-Original-To: xxx@xxxx.xxx
|
|
||||||
X-Mms-Message-Class: 0
|
|
||||||
X-Mms-Delivery-Report: 0
|
|
||||||
X-Mms-Mms-Version: 16
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
X-Nokia-Ag-Version: 2.0
|
|
||||||
|
|
||||||
This is a multi-part message in MIME format.
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4cbd97_133ae40413c8217
|
|
||||||
Content-Type: text/plain; charset=utf-8
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Disposition: inline
|
|
||||||
Content-Location: text.txt
|
|
||||||
|
|
||||||
Some text
|
|
||||||
|
|
||||||
--mimepart_427e4cb4cbd97_133ae40413c8217--
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
Content-Type: text/plain; charset=us-ascii
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
|
|
||||||
|
|
||||||
--
|
|
||||||
This Orange Multi Media Message was sent wirefree from an Orange
|
|
||||||
MMS phone. If you would like to reply, please text or phone the
|
|
||||||
sender directly by using the phone number listed in the sender's
|
|
||||||
address. To learn more about Orange's Multi Media Messaging
|
|
||||||
Service, find us on the Web at xxx.xxxx.xxx.uk/mms
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef
|
|
||||||
|
|
||||||
|
|
||||||
--mimepart_427e4cb4ca329_133ae40413c81ef-
|
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
Return-Path: <xxx@xxxx.xxx>
|
|
||||||
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
|
|
||||||
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
|
|
||||||
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
Date: Tue, 10 May 2005 15:27:03 -0500
|
|
||||||
From: xxx@xxxx.xxx
|
|
||||||
Sender: xxx@xxxx.xxx
|
|
||||||
To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Message-Id: <xxx@xxxx.xxx>
|
|
||||||
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
|
|
||||||
Delivered-To: xxx@xxxx.xxx
|
|
||||||
Importance: normal
|
|
||||||
Content-Type: text/plain; charset=us-ascii
|
|
||||||
|
|
||||||
Test test. Hi. Waving. m
|
|
||||||
|
|
||||||
----------------------------------------------------------------
|
|
||||||
Sent via Bell Mobility's Text Messaging service.
|
|
||||||
Envoyé par le service de messagerie texte de Bell Mobilité.
|
|
||||||
----------------------------------------------------------------
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
Mime-Version: 1.0 (Apple Message framework v730)
|
|
||||||
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
|
|
||||||
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
|
|
||||||
From: foo@example.com
|
|
||||||
Subject: testing
|
|
||||||
Date: Mon, 6 Jun 2005 22:21:22 +0200
|
|
||||||
To: blah@example.com
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary=Apple-Mail-12-196940926
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=ISO-8859-1;
|
|
||||||
delsp=yes;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
This is the first part.
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/x-ruby-script;
|
|
||||||
x-unix-mode=0666;
|
|
||||||
name="test.rb"
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename=test.rb
|
|
||||||
|
|
||||||
puts "testing, testing"
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Type: application/pdf;
|
|
||||||
x-unix-mode=0666;
|
|
||||||
name="test.pdf"
|
|
||||||
Content-Disposition: inline;
|
|
||||||
filename=test.pdf
|
|
||||||
|
|
||||||
YmxhaCBibGFoIGJsYWg=
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Content-Type: text/plain;
|
|
||||||
charset=US-ASCII;
|
|
||||||
format=flowed
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--Apple-Mail-12-196940926--
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Type: application/pkcs7-signature;
|
|
||||||
name=smime.p7s
|
|
||||||
Content-Disposition: attachment;
|
|
||||||
filename=smime.p7s
|
|
||||||
|
|
||||||
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
|
|
||||||
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
|
|
||||||
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
|
|
||||||
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
|
|
||||||
|
|
||||||
--Apple-Mail-13-196941151--
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
|
|
||||||
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
|
|
||||||
Date: Sun, 8 May 2005 14:09:11 -0500
|
|
||||||
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
|
|
||||||
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
|
|
||||||
Subject: Fwd: Signed email causes file attachments
|
|
||||||
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
Mime-Version: 1.0
|
|
||||||
Content-Type: multipart/mixed;
|
|
||||||
boundary="----=_Part_5028_7368284.1115579351471"
|
|
||||||
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: text/plain; charset=ISO-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
Content-Disposition: inline
|
|
||||||
|
|
||||||
We should not include these files or vcards as attachments.
|
|
||||||
|
|
||||||
---------- Forwarded message ----------
|
|
||||||
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
|
|
||||||
Date: May 8, 2005 1:17 PM
|
|
||||||
Subject: Signed email causes file attachments
|
|
||||||
To: xxxxxxx@xxxxxxxxxx.com
|
|
||||||
|
|
||||||
|
|
||||||
Hi,
|
|
||||||
|
|
||||||
Test attachments oddly encoded with japanese charset.
|
|
||||||
|
|
||||||
|
|
||||||
------=_Part_5028_7368284.1115579351471
|
|
||||||
Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
Content-Disposition: attachment
|
|
||||||
|
|
||||||
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
|
|
||||||
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
|
|
||||||
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
|
|
||||||
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
|
|
||||||
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
|
|
||||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
|
|
||||||
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
|
|
||||||
------=_Part_5028_7368284.1115579351471--
|
|
||||||
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified)
|
|
||||||
by xxx.com (CommuniGate Pro SMTP 4.2.8)
|
|
||||||
with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500
|
|
||||||
Received-SPF: softfail
|
|
||||||
receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx
|
|
||||||
quite Delivered-To: xxx@xxx.xxx
|
|
||||||
Received: by xxx.xxx.xxx (Wostfix, from userid xxx)
|
|
||||||
id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600
|
|
||||||
Date: Wed, 23 Feb 2005 18:20:17 -0400
|
|
||||||
From: "xxx xxx" <xxx@xxx.xxx>
|
|
||||||
Message-ID: <4D6AA7EB.6490534@xxx.xxx>
|
|
||||||
To: xxx@xxx.com
|
|
||||||
Subject: Stop adware/spyware once and for all.
|
|
||||||
X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang)
|
|
||||||
|
|
||||||
You are infected with:
|
|
||||||
Ad Ware and Spy Ware
|
|
||||||
|
|
||||||
Get your free scan and removal download now,
|
|
||||||
before it gets any worse.
|
|
||||||
|
|
||||||
http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
no more? (you will still be infected)
|
|
||||||
http://xxx.xxx.info/discon/?xxx@xxx.com
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Hello there,
|
|
||||||
|
|
||||||
Mr. <%= @recipient %>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Ignored when searching for implicitly multipart parts.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Ignored when searching for implicitly multipart parts.
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
HTML formatted message to <strong><%= @recipient %></strong>.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
HTML formatted message to <strong><%= @recipient %></strong>.
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
Plain text to <%= @recipient %>.
|
|
||||||
Plain text to <%= @recipient %>.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
yaml to: <%= @recipient %>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Hello there,
|
|
||||||
|
|
||||||
Mr. <%= @recipient %>
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
|
|
||||||
|
|
||||||
require 'test/unit'
|
|
||||||
require 'action_mailer'
|
|
||||||
|
|
||||||
module MailerHelper
|
|
||||||
def person_name
|
|
||||||
"Mr. Joe Person"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class HelperMailer < ActionMailer::Base
|
|
||||||
helper MailerHelper
|
|
||||||
helper :test
|
|
||||||
|
|
||||||
def use_helper(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_test_helper(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
self.body = { :text => "emphasize me!" }
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_mail_helper(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using mailing helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
self.body = { :text =>
|
|
||||||
"But soft! What light through yonder window breaks? It is the east, " +
|
|
||||||
"and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
|
|
||||||
"which is sick and pale with grief that thou, her maid, art far more " +
|
|
||||||
"fair than she. Be not her maid, for she is envious! Her vestal " +
|
|
||||||
"livery is but sick and green, and none but fools do wear it. Cast " +
|
|
||||||
"it off!"
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def use_helper_method(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
self.body = { :text => "emphasize me!" }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def name_of_the_mailer_class
|
|
||||||
self.class.name
|
|
||||||
end
|
|
||||||
helper_method :name_of_the_mailer_class
|
|
||||||
end
|
|
||||||
|
|
||||||
HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures"
|
|
||||||
|
|
||||||
class MailerHelperTest < Test::Unit::TestCase
|
|
||||||
def new_mail( charset="utf-8" )
|
|
||||||
mail = TMail::Mail.new
|
|
||||||
mail.set_content_type "text", "plain", { "charset" => charset } if charset
|
|
||||||
mail
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
|
||||||
ActionMailer::Base.delivery_method = :test
|
|
||||||
ActionMailer::Base.perform_deliveries = true
|
|
||||||
ActionMailer::Base.deliveries = []
|
|
||||||
|
|
||||||
@recipient = 'test@localhost'
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_use_helper
|
|
||||||
mail = HelperMailer.create_use_helper(@recipient)
|
|
||||||
assert_match %r{Mr. Joe Person}, mail.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_use_test_helper
|
|
||||||
mail = HelperMailer.create_use_test_helper(@recipient)
|
|
||||||
assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_use_helper_method
|
|
||||||
mail = HelperMailer.create_use_helper_method(@recipient)
|
|
||||||
assert_match %r{HelperMailer}, mail.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_use_mail_helper
|
|
||||||
mail = HelperMailer.create_use_mail_helper(@recipient)
|
|
||||||
assert_match %r{ But soft!}, mail.encoded
|
|
||||||
assert_match %r{east, and\n Juliet}, mail.encoded
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
|
|
||||||
require 'test/unit'
|
|
||||||
require 'action_mailer'
|
|
||||||
|
|
||||||
class RenderMailer < ActionMailer::Base
|
|
||||||
def inline_template(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def file_template(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "using helpers"
|
|
||||||
from "tester@example.com"
|
|
||||||
body render(:file => "signed_up", :body => { :recipient => recipient })
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize_defaults(method_name)
|
|
||||||
super
|
|
||||||
mailer_name "test_mailer"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures"
|
|
||||||
|
|
||||||
class RenderHelperTest < Test::Unit::TestCase
|
|
||||||
def setup
|
|
||||||
ActionMailer::Base.delivery_method = :test
|
|
||||||
ActionMailer::Base.perform_deliveries = true
|
|
||||||
ActionMailer::Base.deliveries = []
|
|
||||||
|
|
||||||
@recipient = 'test@localhost'
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_inline_template
|
|
||||||
mail = RenderMailer.create_inline_template(@recipient)
|
|
||||||
assert_equal "Hello, Earth", mail.body.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_file_template
|
|
||||||
mail = RenderMailer.create_file_template(@recipient)
|
|
||||||
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
@ -1,818 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
|
|
||||||
require 'test/unit'
|
|
||||||
require 'action_mailer'
|
|
||||||
|
|
||||||
class MockSMTP
|
|
||||||
def self.deliveries
|
|
||||||
@@deliveries
|
|
||||||
end
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@@deliveries = []
|
|
||||||
end
|
|
||||||
|
|
||||||
def sendmail(mail, from, to)
|
|
||||||
@@deliveries << [mail, from, to]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Net::SMTP
|
|
||||||
def self.start(*args)
|
|
||||||
yield MockSMTP.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class FunkyPathMailer < ActionMailer::Base
|
|
||||||
def multipart_with_template_path_with_dots(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "Have a lovely picture"
|
|
||||||
from "Chad Fowler <chad@chadfowler.com>"
|
|
||||||
attachment :content_type => "image/jpeg",
|
|
||||||
:body => "not really a jpeg, we're only testing, after all"
|
|
||||||
end
|
|
||||||
|
|
||||||
def template_path
|
|
||||||
"#{File.dirname(__FILE__)}/fixtures/path.with.dots"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class TestMailer < ActionMailer::Base
|
|
||||||
|
|
||||||
def signed_up(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "[Signed up] Welcome #{recipient}"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@sent_on = Time.local(2004, 12, 12)
|
|
||||||
@body["recipient"] = recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
def cancelled_account(recipient)
|
|
||||||
self.recipients = recipient
|
|
||||||
self.subject = "[Cancelled] Goodbye #{recipient}"
|
|
||||||
self.from = "system@loudthinking.com"
|
|
||||||
self.sent_on = Time.local(2004, 12, 12)
|
|
||||||
self.body = "Goodbye, Mr. #{recipient}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def cc_bcc(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "testing bcc/cc"
|
|
||||||
from "system@loudthinking.com"
|
|
||||||
sent_on Time.local(2004, 12, 12)
|
|
||||||
cc "nobody@loudthinking.com"
|
|
||||||
bcc "root@loudthinking.com"
|
|
||||||
body "Nothing to see here."
|
|
||||||
end
|
|
||||||
|
|
||||||
def iso_charset(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing isø charsets"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@sent_on = Time.local 2004, 12, 12
|
|
||||||
@cc = "nobody@loudthinking.com"
|
|
||||||
@bcc = "root@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
def unencoded_subject(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing unencoded subject"
|
|
||||||
@from = "system@loudthinking.com"
|
|
||||||
@sent_on = Time.local 2004, 12, 12
|
|
||||||
@cc = "nobody@loudthinking.com"
|
|
||||||
@bcc = "root@loudthinking.com"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
end
|
|
||||||
|
|
||||||
def extended_headers(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing extended headers"
|
|
||||||
@from = "Grytøyr <stian1@example.net>"
|
|
||||||
@sent_on = Time.local 2004, 12, 12
|
|
||||||
@cc = "Grytøyr <stian2@example.net>"
|
|
||||||
@bcc = "Grytøyr <stian3@example.net>"
|
|
||||||
@body = "Nothing to see here."
|
|
||||||
@charset = "iso-8859-1"
|
|
||||||
end
|
|
||||||
|
|
||||||
def utf8_body(recipient)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "testing utf-8 body"
|
|
||||||
@from = "Foo áëô îü <extended@example.net>"
|
|
||||||
@sent_on = Time.local 2004, 12, 12
|
|
||||||
@cc = "Foo áëô îü <extended@example.net>"
|
|
||||||
@bcc = "Foo áëô îü <extended@example.net>"
|
|
||||||
@body = "åœö blah"
|
|
||||||
@charset = "utf-8"
|
|
||||||
end
|
|
||||||
|
|
||||||
def multipart_with_mime_version(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "multipart with mime_version"
|
|
||||||
from "test@example.com"
|
|
||||||
sent_on Time.local(2004, 12, 12)
|
|
||||||
mime_version "1.1"
|
|
||||||
content_type "multipart/alternative"
|
|
||||||
|
|
||||||
part "text/plain" do |p|
|
|
||||||
p.body = "blah"
|
|
||||||
end
|
|
||||||
|
|
||||||
part "text/html" do |p|
|
|
||||||
p.body = "<b>blah</b>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def multipart_with_utf8_subject(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "Foo áëô îü"
|
|
||||||
from "test@example.com"
|
|
||||||
charset "utf-8"
|
|
||||||
|
|
||||||
part "text/plain" do |p|
|
|
||||||
p.body = "blah"
|
|
||||||
end
|
|
||||||
|
|
||||||
part "text/html" do |p|
|
|
||||||
p.body = "<b>blah</b>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def explicitly_multipart_example(recipient, ct=nil)
|
|
||||||
recipients recipient
|
|
||||||
subject "multipart example"
|
|
||||||
from "test@example.com"
|
|
||||||
sent_on Time.local(2004, 12, 12)
|
|
||||||
body "plain text default"
|
|
||||||
content_type ct if ct
|
|
||||||
|
|
||||||
part "text/html" do |p|
|
|
||||||
p.charset = "iso-8859-1"
|
|
||||||
p.body = "blah"
|
|
||||||
end
|
|
||||||
|
|
||||||
attachment :content_type => "image/jpeg", :filename => "foo.jpg",
|
|
||||||
:body => "123456789"
|
|
||||||
end
|
|
||||||
|
|
||||||
def implicitly_multipart_example(recipient, cs = nil, order = nil)
|
|
||||||
@recipients = recipient
|
|
||||||
@subject = "multipart example"
|
|
||||||
@from = "test@example.com"
|
|
||||||
@sent_on = Time.local 2004, 12, 12
|
|
||||||
@body = { "recipient" => recipient }
|
|
||||||
@charset = cs if cs
|
|
||||||
@implicit_parts_order = order if order
|
|
||||||
end
|
|
||||||
|
|
||||||
def implicitly_multipart_with_utf8
|
|
||||||
recipients "no.one@nowhere.test"
|
|
||||||
subject "Foo áëô îü"
|
|
||||||
from "some.one@somewhere.test"
|
|
||||||
template "implicitly_multipart_example"
|
|
||||||
body ({ "recipient" => "no.one@nowhere.test" })
|
|
||||||
end
|
|
||||||
|
|
||||||
def html_mail(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "html mail"
|
|
||||||
from "test@example.com"
|
|
||||||
body "<em>Emphasize</em> <strong>this</strong>"
|
|
||||||
content_type "text/html"
|
|
||||||
end
|
|
||||||
|
|
||||||
def html_mail_with_underscores(recipient)
|
|
||||||
subject "html mail with underscores"
|
|
||||||
body %{<a href="http://google.com" target="_blank">_Google</a>}
|
|
||||||
end
|
|
||||||
|
|
||||||
def custom_template(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "[Signed up] Welcome #{recipient}"
|
|
||||||
from "system@loudthinking.com"
|
|
||||||
sent_on Time.local(2004, 12, 12)
|
|
||||||
template "signed_up"
|
|
||||||
|
|
||||||
body["recipient"] = recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
def various_newlines(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "various newlines"
|
|
||||||
from "test@example.com"
|
|
||||||
body "line #1\nline #2\rline #3\r\nline #4\r\r" +
|
|
||||||
"line #5\n\nline#6\r\n\r\nline #7"
|
|
||||||
end
|
|
||||||
|
|
||||||
def various_newlines_multipart(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "various newlines multipart"
|
|
||||||
from "test@example.com"
|
|
||||||
content_type "multipart/alternative"
|
|
||||||
part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r"
|
|
||||||
part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r"
|
|
||||||
end
|
|
||||||
|
|
||||||
def nested_multipart(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "nested multipart"
|
|
||||||
from "test@example.com"
|
|
||||||
content_type "multipart/mixed"
|
|
||||||
part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
|
|
||||||
p.part :content_type => "text/plain", :body => "test text\nline #2"
|
|
||||||
p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
|
|
||||||
end
|
|
||||||
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
|
|
||||||
end
|
|
||||||
|
|
||||||
def attachment_with_custom_header(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "custom header in attachment"
|
|
||||||
from "test@example.com"
|
|
||||||
content_type "multipart/related"
|
|
||||||
part :content_type => "text/html", :body => 'yo'
|
|
||||||
attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' }
|
|
||||||
end
|
|
||||||
|
|
||||||
def unnamed_attachment(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "nested multipart"
|
|
||||||
from "test@example.com"
|
|
||||||
content_type "multipart/mixed"
|
|
||||||
part :content_type => "text/plain", :body => "hullo"
|
|
||||||
attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
|
|
||||||
end
|
|
||||||
|
|
||||||
def headers_with_nonalpha_chars(recipient)
|
|
||||||
recipients recipient
|
|
||||||
subject "nonalpha chars"
|
|
||||||
from "One: Two <test@example.com>"
|
|
||||||
cc "Three: Four <test@example.com>"
|
|
||||||
bcc "Five: Six <test@example.com>"
|
|
||||||
body "testing"
|
|
||||||
end
|
|
||||||
|
|
||||||
def custom_content_type_attributes
|
|
||||||
recipients "no.one@nowhere.test"
|
|
||||||
subject "custom content types"
|
|
||||||
from "some.one@somewhere.test"
|
|
||||||
content_type "text/plain; format=flowed"
|
|
||||||
body "testing"
|
|
||||||
end
|
|
||||||
|
|
||||||
class <<self
|
|
||||||
attr_accessor :received_body
|
|
||||||
end
|
|
||||||
|
|
||||||
def receive(mail)
|
|
||||||
self.class.received_body = mail.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"
|
|
||||||
|
|
||||||
class ActionMailerTest < Test::Unit::TestCase
|
|
||||||
include ActionMailer::Quoting
|
|
||||||
|
|
||||||
def encode( text, charset="utf-8" )
|
|
||||||
quoted_printable( text, charset )
|
|
||||||
end
|
|
||||||
|
|
||||||
def new_mail( charset="utf-8" )
|
|
||||||
mail = TMail::Mail.new
|
|
||||||
if charset
|
|
||||||
mail.set_content_type "text", "plain", { "charset" => charset }
|
|
||||||
end
|
|
||||||
mail
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup
|
|
||||||
ActionMailer::Base.delivery_method = :test
|
|
||||||
ActionMailer::Base.perform_deliveries = true
|
|
||||||
ActionMailer::Base.deliveries = []
|
|
||||||
|
|
||||||
@recipient = 'test@localhost'
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_nested_parts
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
|
|
||||||
assert_equal 2,created.parts.size
|
|
||||||
assert_equal 2,created.parts.first.parts.size
|
|
||||||
|
|
||||||
assert_equal "multipart/mixed", created.content_type
|
|
||||||
assert_equal "multipart/alternative", created.parts.first.content_type
|
|
||||||
assert_equal "text/plain", created.parts.first.parts.first.content_type
|
|
||||||
assert_equal "text/html", created.parts.first.parts[1].content_type
|
|
||||||
assert_equal "application/octet-stream", created.parts[1].content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_attachment_with_custom_header
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
|
|
||||||
assert_equal "<test@test.com>", created.parts[1].header['content-id'].to_s
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_signed_up
|
|
||||||
expected = new_mail
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
|
||||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.date = Time.local(2004, 12, 12)
|
|
||||||
expected.mime_version = nil
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_template
|
|
||||||
expected = new_mail
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = "[Signed up] Welcome #{@recipient}"
|
|
||||||
expected.body = "Hello there, \n\nMr. #{@recipient}"
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.date = Time.local(2004, 12, 12)
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cancelled_account
|
|
||||||
expected = new_mail
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = "[Cancelled] Goodbye #{@recipient}"
|
|
||||||
expected.body = "Goodbye, Mr. #{@recipient}"
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.date = Time.local(2004, 12, 12)
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_cc_bcc
|
|
||||||
expected = new_mail
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = "testing bcc/cc"
|
|
||||||
expected.body = "Nothing to see here."
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.cc = "nobody@loudthinking.com"
|
|
||||||
expected.bcc = "root@loudthinking.com"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised do
|
|
||||||
created = TestMailer.create_cc_bcc @recipient
|
|
||||||
end
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
TestMailer.deliver_cc_bcc @recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_iso_charset
|
|
||||||
expected = new_mail( "iso-8859-1" )
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = encode "testing isø charsets", "iso-8859-1"
|
|
||||||
expected.body = "Nothing to see here."
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.cc = "nobody@loudthinking.com"
|
|
||||||
expected.bcc = "root@loudthinking.com"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised do
|
|
||||||
created = TestMailer.create_iso_charset @recipient
|
|
||||||
end
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
TestMailer.deliver_iso_charset @recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unencoded_subject
|
|
||||||
expected = new_mail
|
|
||||||
expected.to = @recipient
|
|
||||||
expected.subject = "testing unencoded subject"
|
|
||||||
expected.body = "Nothing to see here."
|
|
||||||
expected.from = "system@loudthinking.com"
|
|
||||||
expected.cc = "nobody@loudthinking.com"
|
|
||||||
expected.bcc = "root@loudthinking.com"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised do
|
|
||||||
created = TestMailer.create_unencoded_subject @recipient
|
|
||||||
end
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
TestMailer.deliver_unencoded_subject @recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_instances_are_nil
|
|
||||||
assert_nil ActionMailer::Base.new
|
|
||||||
assert_nil TestMailer.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_deliveries_array
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries
|
|
||||||
assert_equal 0, ActionMailer::Base.deliveries.size
|
|
||||||
TestMailer.deliver_signed_up(@recipient)
|
|
||||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_perform_deliveries_flag
|
|
||||||
ActionMailer::Base.perform_deliveries = false
|
|
||||||
TestMailer.deliver_signed_up(@recipient)
|
|
||||||
assert_equal 0, ActionMailer::Base.deliveries.size
|
|
||||||
ActionMailer::Base.perform_deliveries = true
|
|
||||||
TestMailer.deliver_signed_up(@recipient)
|
|
||||||
assert_equal 1, ActionMailer::Base.deliveries.size
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_quoted_printable_subject
|
|
||||||
msg = <<EOF
|
|
||||||
From: me@example.com
|
|
||||||
Subject: =?utf-8?Q?testing_testing_=D6=A4?=
|
|
||||||
Content-Type: text/plain; charset=iso-8859-1
|
|
||||||
|
|
||||||
The body
|
|
||||||
EOF
|
|
||||||
mail = TMail::Mail.parse(msg)
|
|
||||||
assert_equal "testing testing \326\244", mail.subject
|
|
||||||
assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_7bit_subject
|
|
||||||
msg = <<EOF
|
|
||||||
From: me@example.com
|
|
||||||
Subject: this == working?
|
|
||||||
Content-Type: text/plain; charset=iso-8859-1
|
|
||||||
|
|
||||||
The body
|
|
||||||
EOF
|
|
||||||
mail = TMail::Mail.parse(msg)
|
|
||||||
assert_equal "this == working?", mail.subject
|
|
||||||
assert_equal "this == working?", mail.quoted_subject
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_7bit_body
|
|
||||||
msg = <<EOF
|
|
||||||
From: me@example.com
|
|
||||||
Subject: subject
|
|
||||||
Content-Type: text/plain; charset=iso-8859-1
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
|
|
||||||
The=3Dbody
|
|
||||||
EOF
|
|
||||||
mail = TMail::Mail.parse(msg)
|
|
||||||
assert_equal "The=3Dbody", mail.body.strip
|
|
||||||
assert_equal "The=3Dbody", mail.quoted_body.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_quoted_printable_body
|
|
||||||
msg = <<EOF
|
|
||||||
From: me@example.com
|
|
||||||
Subject: subject
|
|
||||||
Content-Type: text/plain; charset=iso-8859-1
|
|
||||||
Content-Transfer-Encoding: quoted-printable
|
|
||||||
|
|
||||||
The=3Dbody
|
|
||||||
EOF
|
|
||||||
mail = TMail::Mail.parse(msg)
|
|
||||||
assert_equal "The=body", mail.body.strip
|
|
||||||
assert_equal "The=3Dbody", mail.quoted_body.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_unquote_base64_body
|
|
||||||
msg = <<EOF
|
|
||||||
From: me@example.com
|
|
||||||
Subject: subject
|
|
||||||
Content-Type: text/plain; charset=iso-8859-1
|
|
||||||
Content-Transfer-Encoding: base64
|
|
||||||
|
|
||||||
VGhlIGJvZHk=
|
|
||||||
EOF
|
|
||||||
mail = TMail::Mail.parse(msg)
|
|
||||||
assert_equal "The body", mail.body.strip
|
|
||||||
assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_extended_headers
|
|
||||||
@recipient = "Grytøyr <test@localhost>"
|
|
||||||
|
|
||||||
expected = new_mail "iso-8859-1"
|
|
||||||
expected.to = quote_address_if_necessary @recipient, "iso-8859-1"
|
|
||||||
expected.subject = "testing extended headers"
|
|
||||||
expected.body = "Nothing to see here."
|
|
||||||
expected.from = quote_address_if_necessary "Grytøyr <stian1@example.net>", "iso-8859-1"
|
|
||||||
expected.cc = quote_address_if_necessary "Grytøyr <stian2@example.net>", "iso-8859-1"
|
|
||||||
expected.bcc = quote_address_if_necessary "Grytøyr <stian3@example.net>", "iso-8859-1"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = nil
|
|
||||||
assert_nothing_raised do
|
|
||||||
created = TestMailer.create_extended_headers @recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_nil created
|
|
||||||
assert_equal expected.encoded, created.encoded
|
|
||||||
|
|
||||||
assert_nothing_raised do
|
|
||||||
TestMailer.deliver_extended_headers @recipient
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_not_nil ActionMailer::Base.deliveries.first
|
|
||||||
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_utf8_body_is_not_quoted
|
|
||||||
@recipient = "Foo áëô îü <extended@example.net>"
|
|
||||||
expected = new_mail "utf-8"
|
|
||||||
expected.to = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.subject = "testing utf-8 body"
|
|
||||||
expected.body = "åœö blah"
|
|
||||||
expected.from = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.cc = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.bcc = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = TestMailer.create_utf8_body @recipient
|
|
||||||
assert_match(/åœö blah/, created.encoded)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_multiple_utf8_recipients
|
|
||||||
@recipient = ["\"Foo áëô îü\" <extended@example.net>", "\"Example Recipient\" <me@example.com>"]
|
|
||||||
expected = new_mail "utf-8"
|
|
||||||
expected.to = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.subject = "testing utf-8 body"
|
|
||||||
expected.body = "åœö blah"
|
|
||||||
expected.from = quote_address_if_necessary @recipient.first, "utf-8"
|
|
||||||
expected.cc = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.bcc = quote_address_if_necessary @recipient, "utf-8"
|
|
||||||
expected.date = Time.local 2004, 12, 12
|
|
||||||
|
|
||||||
created = TestMailer.create_utf8_body @recipient
|
|
||||||
assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
|
|
||||||
assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_receive_decodes_base64_encoded_mail
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
|
|
||||||
TestMailer.receive(fixture)
|
|
||||||
assert_match(/Jamis/, TestMailer.received_body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_receive_attachments
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
attachment = mail.attachments.last
|
|
||||||
assert_equal "smime.p7s", attachment.original_filename
|
|
||||||
assert_equal "application/pkcs7-signature", attachment.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_attachment_without_charset
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
attachment = mail.attachments.last
|
|
||||||
assert_equal 1026, attachment.read.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_attachment_using_content_location
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_equal 1, mail.attachments.length
|
|
||||||
assert_equal "Photo25.jpg", mail.attachments.first.original_filename
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_attachment_with_text_type
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email13")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert mail.has_attachments?
|
|
||||||
assert_equal 1, mail.attachments.length
|
|
||||||
assert_equal "hello.rb", mail.attachments.first.original_filename
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_part_without_content_type
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_nothing_raised { mail.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_message_without_content_type
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_nothing_raised { mail.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_message_with_incorrect_charset
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_nothing_raised { mail.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_multipart_with_mime_version
|
|
||||||
mail = TestMailer.create_multipart_with_mime_version(@recipient)
|
|
||||||
assert_equal "1.1", mail.mime_version
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_multipart_with_utf8_subject
|
|
||||||
mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
|
|
||||||
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_implicitly_multipart_with_utf8
|
|
||||||
mail = TestMailer.create_implicitly_multipart_with_utf8
|
|
||||||
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_explicitly_multipart_messages
|
|
||||||
mail = TestMailer.create_explicitly_multipart_example(@recipient)
|
|
||||||
assert_equal 3, mail.parts.length
|
|
||||||
assert_nil mail.content_type
|
|
||||||
assert_equal "text/plain", mail.parts[0].content_type
|
|
||||||
|
|
||||||
assert_equal "text/html", mail.parts[1].content_type
|
|
||||||
assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
|
|
||||||
assert_equal "inline", mail.parts[1].content_disposition
|
|
||||||
|
|
||||||
assert_equal "image/jpeg", mail.parts[2].content_type
|
|
||||||
assert_equal "attachment", mail.parts[2].content_disposition
|
|
||||||
assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename")
|
|
||||||
assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name")
|
|
||||||
assert_nil mail.parts[2].sub_header("content-type", "charset")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_explicitly_multipart_with_content_type
|
|
||||||
mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
|
|
||||||
assert_equal 3, mail.parts.length
|
|
||||||
assert_equal "multipart/alternative", mail.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_explicitly_multipart_with_invalid_content_type
|
|
||||||
mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
|
|
||||||
assert_equal 3, mail.parts.length
|
|
||||||
assert_nil mail.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_implicitly_multipart_messages
|
|
||||||
mail = TestMailer.create_implicitly_multipart_example(@recipient)
|
|
||||||
assert_equal 3, mail.parts.length
|
|
||||||
assert_equal "1.0", mail.mime_version
|
|
||||||
assert_equal "multipart/alternative", mail.content_type
|
|
||||||
assert_equal "text/yaml", mail.parts[0].content_type
|
|
||||||
assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
|
|
||||||
assert_equal "text/plain", mail.parts[1].content_type
|
|
||||||
assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
|
|
||||||
assert_equal "text/html", mail.parts[2].content_type
|
|
||||||
assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_implicitly_multipart_messages_with_custom_order
|
|
||||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
|
|
||||||
assert_equal 3, mail.parts.length
|
|
||||||
assert_equal "text/html", mail.parts[0].content_type
|
|
||||||
assert_equal "text/plain", mail.parts[1].content_type
|
|
||||||
assert_equal "text/yaml", mail.parts[2].content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_implicitly_multipart_messages_with_charset
|
|
||||||
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
|
|
||||||
|
|
||||||
assert_equal "multipart/alternative", mail.header['content-type'].body
|
|
||||||
|
|
||||||
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
|
|
||||||
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
|
|
||||||
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_html_mail
|
|
||||||
mail = TestMailer.create_html_mail(@recipient)
|
|
||||||
assert_equal "text/html", mail.content_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_html_mail_with_underscores
|
|
||||||
mail = TestMailer.create_html_mail_with_underscores(@recipient)
|
|
||||||
assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_various_newlines
|
|
||||||
mail = TestMailer.create_various_newlines(@recipient)
|
|
||||||
assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
|
|
||||||
"line #5\n\nline#6\n\nline #7", mail.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_various_newlines_multipart
|
|
||||||
mail = TestMailer.create_various_newlines_multipart(@recipient)
|
|
||||||
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
|
|
||||||
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_headers_removed_on_smtp_delivery
|
|
||||||
ActionMailer::Base.delivery_method = :smtp
|
|
||||||
TestMailer.deliver_cc_bcc(@recipient)
|
|
||||||
assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
|
|
||||||
assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
|
|
||||||
assert MockSMTP.deliveries[0][2].include?(@recipient)
|
|
||||||
assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
|
|
||||||
assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
|
|
||||||
assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_recursive_multipart_processing
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_encoded_attachment_filename
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
attachment = mail.attachments.last
|
|
||||||
assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_wrong_mail_header
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
|
|
||||||
assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_message_with_unknown_charset
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_nothing_raised { mail.body }
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_decode_message_with_unquoted_atchar_in_header
|
|
||||||
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
|
|
||||||
mail = TMail::Mail.parse(fixture)
|
|
||||||
assert_not_nil mail.from
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_empty_header_values_omitted
|
|
||||||
result = TestMailer.create_unnamed_attachment(@recipient).encoded
|
|
||||||
assert_match %r{Content-Type: application/octet-stream[^;]}, result
|
|
||||||
assert_match %r{Content-Disposition: attachment[^;]}, result
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_headers_with_nonalpha_chars
|
|
||||||
mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
|
|
||||||
assert !mail.from_addrs.empty?
|
|
||||||
assert !mail.cc_addrs.empty?
|
|
||||||
assert !mail.bcc_addrs.empty?
|
|
||||||
assert_match(/:/, mail.from_addrs.to_s)
|
|
||||||
assert_match(/:/, mail.cc_addrs.to_s)
|
|
||||||
assert_match(/:/, mail.bcc_addrs.to_s)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_deliver_with_mail_object
|
|
||||||
mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
|
|
||||||
assert_nothing_raised { TestMailer.deliver(mail) }
|
|
||||||
assert_equal 1, TestMailer.deliveries.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_multipart_with_template_path_with_dots
|
|
||||||
mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
|
|
||||||
assert_equal 2, mail.parts.length
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_custom_content_type_attributes
|
|
||||||
mail = TestMailer.create_custom_content_type_attributes
|
|
||||||
assert_match %r{format=flowed}, mail['content-type'].to_s
|
|
||||||
assert_match %r{charset=utf-8}, mail['content-type'].to_s
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")
|
|
||||||
|
|
||||||
require 'test/unit'
|
|
||||||
require 'tmail'
|
|
||||||
require 'tempfile'
|
|
||||||
|
|
||||||
class QuotingTest < Test::Unit::TestCase
|
|
||||||
def test_quote_multibyte_chars
|
|
||||||
original = "\303\246 \303\270 and \303\245"
|
|
||||||
|
|
||||||
result = execute_in_sandbox(<<-CODE)
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
$KCODE = 'u'
|
|
||||||
require 'jcode'
|
|
||||||
require 'action_mailer/quoting'
|
|
||||||
include ActionMailer::Quoting
|
|
||||||
quoted_printable(#{original.inspect}, "UTF-8")
|
|
||||||
CODE
|
|
||||||
|
|
||||||
unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
|
|
||||||
assert_equal unquoted, original
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
# This whole thing *could* be much simpler, but I don't think Tempfile,
|
|
||||||
# popen and others exist on all platforms (like Windows).
|
|
||||||
def execute_in_sandbox(code)
|
|
||||||
test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
|
|
||||||
res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
|
|
||||||
|
|
||||||
File.open(test_name, "w+") do |file|
|
|
||||||
file.write(<<-CODE)
|
|
||||||
block = Proc.new do
|
|
||||||
#{code}
|
|
||||||
end
|
|
||||||
puts block.call
|
|
||||||
CODE
|
|
||||||
end
|
|
||||||
|
|
||||||
system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
|
|
||||||
File.read(res_name)
|
|
||||||
ensure
|
|
||||||
File.delete(test_name) rescue nil
|
|
||||||
File.delete(res_name) rescue nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib/")
|
|
||||||
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
|
|
||||||
|
|
||||||
require 'test/unit'
|
|
||||||
require 'action_mailer'
|
|
||||||
|
|
||||||
class TMailMailTest < Test::Unit::TestCase
|
|
||||||
def test_body
|
|
||||||
m = TMail::Mail.new
|
|
||||||
expected = 'something_with_underscores'
|
|
||||||
m.encoding = 'quoted-printable'
|
|
||||||
quoted_body = [expected].pack('*M')
|
|
||||||
m.body = quoted_body
|
|
||||||
assert_equal "something_with_underscores=\n", m.quoted_body
|
|
||||||
assert_equal expected, m.body
|
|
||||||
end
|
|
||||||
end
|
|
||||||
2507
tracks/vendor/rails/actionpack/CHANGELOG
vendored
2507
tracks/vendor/rails/actionpack/CHANGELOG
vendored
File diff suppressed because it is too large
Load diff
21
tracks/vendor/rails/actionpack/MIT-LICENSE
vendored
21
tracks/vendor/rails/actionpack/MIT-LICENSE
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
"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.
|
|
||||||
|
|
||||||
471
tracks/vendor/rails/actionpack/README
vendored
471
tracks/vendor/rails/actionpack/README
vendored
|
|
@ -1,471 +0,0 @@
|
||||||
= Action Pack -- On rails from request to response
|
|
||||||
|
|
||||||
Action Pack splits the response to a web request into a controller part
|
|
||||||
(performing the logic) and a view part (rendering a template). This two-step
|
|
||||||
approach is known as an action, which will normally create, read, update, or
|
|
||||||
delete (CRUD for short) some sort of model part (often backed by a database)
|
|
||||||
before choosing either to render a template or redirecting to another action.
|
|
||||||
|
|
||||||
Action Pack implements these actions as public methods on Action Controllers
|
|
||||||
and uses Action Views to implement the template rendering. Action Controllers
|
|
||||||
are then responsible for handling all the actions relating to a certain part
|
|
||||||
of an application. This grouping usually consists of actions for lists and for
|
|
||||||
CRUDs revolving around a single (or a few) model objects. So ContactController
|
|
||||||
would be responsible for listing contacts, creating, deleting, and updating
|
|
||||||
contacts. A WeblogController could be responsible for both posts and comments.
|
|
||||||
|
|
||||||
Action View templates are written using embedded Ruby in tags mingled in with
|
|
||||||
the HTML. To avoid cluttering the templates with code, a bunch of helper
|
|
||||||
classes provide common behavior for forms, dates, and strings. And it's easy
|
|
||||||
to add specific helpers to keep the separation as the application evolves.
|
|
||||||
|
|
||||||
Note: Some of the features, such as scaffolding and form building, are tied to
|
|
||||||
ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
|
|
||||||
mapping package), but that doesn't mean that Action Pack depends on Active
|
|
||||||
Record. Action Pack is an independent package that can be used with any sort
|
|
||||||
of backend (Instiki[http://www.instiki.org], which is based on an older version
|
|
||||||
of Action Pack, used Madeleine for example). Read more about the role Action
|
|
||||||
Pack can play when used together with Active Record on
|
|
||||||
http://www.rubyonrails.org.
|
|
||||||
|
|
||||||
A short rundown of the major features:
|
|
||||||
|
|
||||||
* Actions grouped in controller as methods instead of separate command objects
|
|
||||||
and can therefore share helper methods.
|
|
||||||
|
|
||||||
BlogController < ActionController::Base
|
|
||||||
def display
|
|
||||||
@customer = find_customer
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@customer = find_customer
|
|
||||||
@customer.attributes = params[:customer]
|
|
||||||
@customer.save ?
|
|
||||||
redirect_to(:action => "display") :
|
|
||||||
render(:action => "edit")
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def find_customer() Customer.find(params[:id]) end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Base.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Embedded Ruby for templates (no new "easy" template language)
|
|
||||||
|
|
||||||
<% for post in @posts %>
|
|
||||||
Title: <%= post.title %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
|
|
||||||
|
|
||||||
<% unless @person.is_client? %>
|
|
||||||
Not for clients to see...
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Builder-based templates (great for XML content, like RSS)
|
|
||||||
|
|
||||||
xml.rss("version" => "2.0") do
|
|
||||||
xml.channel do
|
|
||||||
xml.title(@feed_title)
|
|
||||||
xml.link(@url)
|
|
||||||
xml.description "Basecamp: Recent items"
|
|
||||||
xml.language "en-us"
|
|
||||||
xml.ttl "40"
|
|
||||||
|
|
||||||
for item in @recent_items
|
|
||||||
xml.item do
|
|
||||||
xml.title(item_title(item))
|
|
||||||
xml.description(item_description(item))
|
|
||||||
xml.pubDate(item_pubDate(item))
|
|
||||||
xml.guid(@recent_items.url(item))
|
|
||||||
xml.link(@recent_items.url(item))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView/Base.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Filters for pre and post processing of the response (as methods, procs, and classes)
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
before_filter :authenticate, :cache, :audit
|
|
||||||
after_filter { |c| c.response.body = GZip::compress(c.response.body) }
|
|
||||||
after_filter LocalizeFilter
|
|
||||||
|
|
||||||
def list
|
|
||||||
# Before this action is run, the user will be authenticated, the cache
|
|
||||||
# will be examined to see if a valid copy of the results already
|
|
||||||
# exists, and the action will be logged for auditing.
|
|
||||||
|
|
||||||
# After this action has run, the output will first be localized then
|
|
||||||
# compressed to minimize bandwidth usage
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def authenticate
|
|
||||||
# Implement the filter with full access to both request and response
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Helpers for forms, dates, action links, and text
|
|
||||||
|
|
||||||
<%= text_field "post", "title", "size" => 30 %>
|
|
||||||
<%= html_date_select(Date.today) %>
|
|
||||||
<%= link_to "New post", :controller => "post", :action => "new" %>
|
|
||||||
<%= truncate(post.title, 25) %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView/Helpers.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Layout sharing for template reuse (think simple version of Struts
|
|
||||||
Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
layout "weblog_layout"
|
|
||||||
|
|
||||||
def hello_world
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Layout file (called weblog_layout):
|
|
||||||
<html><body><%= @content_for_layout %></body></html>
|
|
||||||
|
|
||||||
Template for hello_world action:
|
|
||||||
<h1>Hello world</h1>
|
|
||||||
|
|
||||||
Result of running hello_world action:
|
|
||||||
<html><body><h1>Hello world</h1></body></html>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Routing makes pretty urls incredibly easy
|
|
||||||
|
|
||||||
map.connect 'clients/:client_name/:project_name/:controller/:action'
|
|
||||||
|
|
||||||
Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
|
|
||||||
{ "client_name" => "37signals", "project_name" => "basecamp" } in @params["params"]
|
|
||||||
|
|
||||||
From that URL, you can rewrite the redirect in a number of ways:
|
|
||||||
|
|
||||||
redirect_to(:action => "edit") =>
|
|
||||||
/clients/37signals/basecamp/project/dash
|
|
||||||
|
|
||||||
redirect_to(:client_name => "nextangle", :project_name => "rails") =>
|
|
||||||
/clients/nextangle/rails/project/dash
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Base.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Javascript and Ajax integration.
|
|
||||||
|
|
||||||
link_to_function "Greeting", "alert('Hello world!')"
|
|
||||||
link_to_remote "Delete this post", :update => "posts",
|
|
||||||
:url => { :action => "destroy", :id => post.id }
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Pagination for navigating lists of results.
|
|
||||||
|
|
||||||
# controller
|
|
||||||
def list
|
|
||||||
@pages, @people =
|
|
||||||
paginate :people, :order => 'last_name, first_name'
|
|
||||||
end
|
|
||||||
|
|
||||||
# view
|
|
||||||
<%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
|
|
||||||
<%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Pagination.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Easy testing of both controller and template result through TestRequest/Response
|
|
||||||
|
|
||||||
class LoginControllerTest < Test::Unit::TestCase
|
|
||||||
def setup
|
|
||||||
@controller = LoginController.new
|
|
||||||
@request = ActionController::TestRequest.new
|
|
||||||
@response = ActionController::TestResponse.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_failing_authenticate
|
|
||||||
process :authenticate, :user_name => "nop", :password => ""
|
|
||||||
assert flash.has_key?(:alert)
|
|
||||||
assert_redirected_to :action => "index"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/TestRequest.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Automated benchmarking and integrated logging
|
|
||||||
|
|
||||||
Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
|
|
||||||
Parameters: {"action"=>"index", "controller"=>"weblog"}
|
|
||||||
Rendering weblog/index (200 OK)
|
|
||||||
Completed in 0.029281 (34 reqs/sec)
|
|
||||||
|
|
||||||
If Active Record is used as the model, you'll have the database debugging
|
|
||||||
as well:
|
|
||||||
|
|
||||||
Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
|
|
||||||
Params: {"controller"=>"weblog", "action"=>"create",
|
|
||||||
"post"=>{"title"=>"this is good"} }
|
|
||||||
SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
|
|
||||||
Redirected to http://test/weblog/display/5
|
|
||||||
Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
|
|
||||||
|
|
||||||
You specify a logger through a class method, such as:
|
|
||||||
|
|
||||||
ActionController::Base.logger = Logger.new("Application Log")
|
|
||||||
ActionController::Base.logger = Log4r::Logger.new("Application Log")
|
|
||||||
|
|
||||||
|
|
||||||
* Caching at three levels of granularity (page, action, fragment)
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
caches_page :show
|
|
||||||
caches_action :account
|
|
||||||
|
|
||||||
def show
|
|
||||||
# the output of the method will be cached as
|
|
||||||
# ActionController::Base.page_cache_directory + "/weblog/show/n.html"
|
|
||||||
# and the web server will pick it up without even hitting Rails
|
|
||||||
end
|
|
||||||
|
|
||||||
def account
|
|
||||||
# the output of the method will be cached in the fragment store
|
|
||||||
# but Rails is hit to retrieve it, so filters are run
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
List.update(params[:list][:id], params[:list])
|
|
||||||
expire_page :action => "show", :id => params[:list][:id]
|
|
||||||
expire_action :action => "account"
|
|
||||||
redirect_to :action => "show", :id => params[:list][:id]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Caching.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Component requests from one controller to another
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
# Performs a method and then lets hello_world output its render
|
|
||||||
def delegate_action
|
|
||||||
do_other_stuff_before_hello_world
|
|
||||||
render_component :controller => "greeter", :action => "hello_world"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class GreeterController < ActionController::Base
|
|
||||||
def hello_world
|
|
||||||
render_text "Hello World!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
The same can be done in a view to do a partial rendering:
|
|
||||||
|
|
||||||
Let's see a greeting:
|
|
||||||
<%= render_component :controller => "greeter", :action => "hello_world" %>
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Components.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Powerful debugging mechanism for local requests
|
|
||||||
|
|
||||||
All exceptions raised on actions performed on the request of a local user
|
|
||||||
will be presented with a tailored debugging screen that includes exception
|
|
||||||
message, stack trace, request parameters, session contents, and the
|
|
||||||
half-finished response.
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionController/Rescue.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Scaffolding for Action Record model objects
|
|
||||||
|
|
||||||
require 'account' # must be an Active Record class
|
|
||||||
class AccountController < ActionController::Base
|
|
||||||
scaffold :account
|
|
||||||
end
|
|
||||||
|
|
||||||
The AccountController now has the full CRUD range of actions and default
|
|
||||||
templates: list, show, destroy, new, create, edit, update
|
|
||||||
|
|
||||||
{Learn more}link:classes/ActionController/Scaffolding/ClassMethods.html
|
|
||||||
|
|
||||||
|
|
||||||
* Form building for Active Record model objects
|
|
||||||
|
|
||||||
The post object has a title (varchar), content (text), and
|
|
||||||
written_on (date)
|
|
||||||
|
|
||||||
<%= form "post" %>
|
|
||||||
|
|
||||||
...will generate something like (the selects will have more options, of
|
|
||||||
course):
|
|
||||||
|
|
||||||
<form action="create" method="POST">
|
|
||||||
<p>
|
|
||||||
<b>Title:</b><br/>
|
|
||||||
<input type="text" name="post[title]" value="<%= @post.title %>" />
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>Content:</b><br/>
|
|
||||||
<textarea name="post[content]"><%= @post.title %></textarea>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<b>Written on:</b><br/>
|
|
||||||
<select name='post[written_on(3i)]'><option>18</option></select>
|
|
||||||
<select name='post[written_on(2i)]'><option value='7'>July</option></select>
|
|
||||||
<select name='post[written_on(1i)]'><option>2004</option></select>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<input type="submit" value="Create">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
This form generates a @params["post"] array that can be used directly in a save action:
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
def save
|
|
||||||
post = Post.create(params[:post])
|
|
||||||
redirect_to :action => "display", :id => post.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
{Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
|
|
||||||
|
|
||||||
|
|
||||||
* Runs on top of WEBrick, CGI, FCGI, and mod_ruby
|
|
||||||
|
|
||||||
|
|
||||||
== Simple example
|
|
||||||
|
|
||||||
This example will implement a simple weblog system using inline templates and
|
|
||||||
an Active Record model. So let's build that WeblogController with just a few
|
|
||||||
methods:
|
|
||||||
|
|
||||||
require 'action_controller'
|
|
||||||
require 'post'
|
|
||||||
|
|
||||||
class WeblogController < ActionController::Base
|
|
||||||
layout "weblog/layout"
|
|
||||||
|
|
||||||
def index
|
|
||||||
@posts = Post.find_all
|
|
||||||
end
|
|
||||||
|
|
||||||
def display
|
|
||||||
@post = Post.find(:params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
|
||||||
@post = Post.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@post = Post.create(params[:post])
|
|
||||||
redirect_to :action => "display", :id => @post.id
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
WeblogController::Base.template_root = File.dirname(__FILE__)
|
|
||||||
WeblogController.process_cgi if $0 == __FILE__
|
|
||||||
|
|
||||||
The last two lines are responsible for telling ActionController where the
|
|
||||||
template files are located and actually running the controller on a new
|
|
||||||
request from the web-server (like to be Apache).
|
|
||||||
|
|
||||||
And the templates look like this:
|
|
||||||
|
|
||||||
weblog/layout.rhtml:
|
|
||||||
<html><body>
|
|
||||||
<%= @content_for_layout %>
|
|
||||||
</body></html>
|
|
||||||
|
|
||||||
weblog/index.rhtml:
|
|
||||||
<% for post in @posts %>
|
|
||||||
<p><%= link_to(post.title, :action => "display", :id => post.id %></p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
weblog/display.rhtml:
|
|
||||||
<p>
|
|
||||||
<b><%= post.title %></b><br/>
|
|
||||||
<b><%= post.content %></b>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
weblog/new.rhtml:
|
|
||||||
<%= form "post" %>
|
|
||||||
|
|
||||||
This simple setup will list all the posts in the system on the index page,
|
|
||||||
which is called by accessing /weblog/. It uses the form builder for the Active
|
|
||||||
Record model to make the new screen, which in turn hands everything over to
|
|
||||||
the create action (that's the default target for the form builder when given a
|
|
||||||
new model). After creating the post, it'll redirect to the display page using
|
|
||||||
an URL such as /weblog/display/5 (where 5 is the id of the post).
|
|
||||||
|
|
||||||
|
|
||||||
== Examples
|
|
||||||
|
|
||||||
Action Pack ships with three examples that all demonstrate an increasingly
|
|
||||||
detailed view of the possibilities. First is blog_controller that is just a
|
|
||||||
single file for the whole MVC (but still split into separate parts). Second is
|
|
||||||
the debate_controller that uses separate template files and multiple screens.
|
|
||||||
Third is the address_book_controller that uses the layout feature to separate
|
|
||||||
template casing from content.
|
|
||||||
|
|
||||||
Please note that you might need to change the "shebang" line to
|
|
||||||
#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
|
|
||||||
|
|
||||||
|
|
||||||
== Download
|
|
||||||
|
|
||||||
The latest version of Action Pack can be found at
|
|
||||||
|
|
||||||
* http://rubyforge.org/project/showfiles.php?group_id=249
|
|
||||||
|
|
||||||
Documentation can be found at
|
|
||||||
|
|
||||||
* http://ap.rubyonrails.com
|
|
||||||
|
|
||||||
|
|
||||||
== Installation
|
|
||||||
|
|
||||||
You can install Action Pack with the following command.
|
|
||||||
|
|
||||||
% [sudo] ruby install.rb
|
|
||||||
|
|
||||||
from its distribution directory.
|
|
||||||
|
|
||||||
|
|
||||||
== License
|
|
||||||
|
|
||||||
Action Pack is released under the MIT license.
|
|
||||||
|
|
||||||
|
|
||||||
== Support
|
|
||||||
|
|
||||||
The Action Pack homepage is http://www.rubyonrails.com. You can find
|
|
||||||
the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
|
|
||||||
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.
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
== Running with Rake
|
|
||||||
|
|
||||||
The easiest way to run the unit tests is through Rake. The default task runs
|
|
||||||
the entire test suite for all classes. For more information, checkout the
|
|
||||||
full array of rake tasks with "rake -T"
|
|
||||||
|
|
||||||
Rake can be found at http://rake.rubyforge.org
|
|
||||||
|
|
||||||
== Running by hand
|
|
||||||
|
|
||||||
If you only want to run a single test suite, or don't want to bother with Rake,
|
|
||||||
you can do so with something like:
|
|
||||||
|
|
||||||
ruby controller/base_tests.rb
|
|
||||||
|
|
||||||
== Dependency on ActiveRecord and database setup
|
|
||||||
|
|
||||||
Test cases in test/controller/active_record_assertions.rb depend on having
|
|
||||||
activerecord installed and configured in a particular way. See comment in the
|
|
||||||
test file itself for details. If ActiveRecord is not in
|
|
||||||
actionpack/../activerecord directory, these tests are skipped. If activerecord
|
|
||||||
is installed, but not configured as expected, the tests will fail.
|
|
||||||
|
|
||||||
Other tests are runnable from a fresh copy of actionpack without any configuration.
|
|
||||||
|
|
||||||
251
tracks/vendor/rails/actionpack/Rakefile
vendored
251
tracks/vendor/rails/actionpack/Rakefile
vendored
|
|
@ -1,251 +0,0 @@
|
||||||
require 'rubygems'
|
|
||||||
require 'rake'
|
|
||||||
require 'rake/testtask'
|
|
||||||
require 'rake/rdoctask'
|
|
||||||
require 'rake/packagetask'
|
|
||||||
require 'rake/gempackagetask'
|
|
||||||
require 'rake/contrib/rubyforgepublisher'
|
|
||||||
require File.join(File.dirname(__FILE__), 'lib', 'action_pack', 'version')
|
|
||||||
|
|
||||||
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
|
|
||||||
PKG_NAME = 'actionpack'
|
|
||||||
PKG_VERSION = ActionPack::VERSION::STRING + PKG_BUILD
|
|
||||||
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
|
|
||||||
|
|
||||||
RELEASE_NAME = "REL #{PKG_VERSION}"
|
|
||||||
|
|
||||||
RUBY_FORGE_PROJECT = "actionpack"
|
|
||||||
RUBY_FORGE_USER = "webster132"
|
|
||||||
|
|
||||||
desc "Default Task"
|
|
||||||
task :default => [ :test ]
|
|
||||||
|
|
||||||
# Run the unit tests
|
|
||||||
|
|
||||||
Rake::TestTask.new { |t|
|
|
||||||
t.libs << "test"
|
|
||||||
# make sure we include the controller tests (c*) first as on some systems
|
|
||||||
# this will not happen automatically and the tests (as a whole) will error
|
|
||||||
t.test_files=Dir.glob( "test/c*/*_test.rb" ) + Dir.glob( "test/[ft]*/*_test.rb" )
|
|
||||||
# t.pattern = 'test/*/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
}
|
|
||||||
|
|
||||||
desc 'ActiveRecord Integration Tests'
|
|
||||||
Rake::TestTask.new(:test_active_record_integration) do |t|
|
|
||||||
t.libs << "test"
|
|
||||||
t.test_files = Dir.glob("test/activerecord/*_test.rb")
|
|
||||||
t.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
# Genereate the RDoc documentation
|
|
||||||
|
|
||||||
Rake::RDocTask.new { |rdoc|
|
|
||||||
rdoc.rdoc_dir = 'doc'
|
|
||||||
rdoc.title = "Action Pack -- On rails from request to response"
|
|
||||||
rdoc.options << '--line-numbers' << '--inline-source'
|
|
||||||
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
|
|
||||||
rdoc.rdoc_files.include('README', 'RUNNING_UNIT_TESTS', 'CHANGELOG')
|
|
||||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create compressed packages
|
|
||||||
dist_dirs = [ "lib", "test", "examples" ]
|
|
||||||
|
|
||||||
spec = Gem::Specification.new do |s|
|
|
||||||
s.platform = Gem::Platform::RUBY
|
|
||||||
s.name = PKG_NAME
|
|
||||||
s.version = PKG_VERSION
|
|
||||||
s.summary = "Web-flow and rendering framework putting the VC in MVC."
|
|
||||||
s.description = %q{Eases web-request routing, handling, and response as a half-way front, half-way page controller. Implemented with specific emphasis on enabling easy unit/integration testing that doesn't require a browser.} #'
|
|
||||||
|
|
||||||
s.author = "David Heinemeier Hansson"
|
|
||||||
s.email = "david@loudthinking.com"
|
|
||||||
s.rubyforge_project = "actionpack"
|
|
||||||
s.homepage = "http://www.rubyonrails.org"
|
|
||||||
|
|
||||||
s.has_rdoc = true
|
|
||||||
s.requirements << 'none'
|
|
||||||
|
|
||||||
s.add_dependency('activesupport', '= 1.3.0' + PKG_BUILD)
|
|
||||||
|
|
||||||
s.require_path = 'lib'
|
|
||||||
s.autorequire = 'action_controller'
|
|
||||||
|
|
||||||
s.files = [ "rakefile", "install.rb", "README", "RUNNING_UNIT_TESTS", "CHANGELOG", "MIT-LICENSE", "examples/.htaccess" ]
|
|
||||||
dist_dirs.each do |dir|
|
|
||||||
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
|
|
||||||
end
|
|
||||||
s.files.delete "examples/benchmark.rb"
|
|
||||||
s.files.delete "examples/benchmark_with_ar.fcgi"
|
|
||||||
end
|
|
||||||
|
|
||||||
Rake::GemPackageTask.new(spec) do |p|
|
|
||||||
p.gem_spec = spec
|
|
||||||
p.need_tar = true
|
|
||||||
p.need_zip = true
|
|
||||||
end
|
|
||||||
|
|
||||||
task :lines do
|
|
||||||
lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
|
|
||||||
|
|
||||||
for file_name in FileList["lib/**/*.rb"]
|
|
||||||
next if file_name =~ /vendor/
|
|
||||||
f = File.open(file_name)
|
|
||||||
|
|
||||||
while line = f.gets
|
|
||||||
lines += 1
|
|
||||||
next if line =~ /^\s*$/
|
|
||||||
next if line =~ /^\s*#/
|
|
||||||
codelines += 1
|
|
||||||
end
|
|
||||||
puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
|
|
||||||
|
|
||||||
total_lines += lines
|
|
||||||
total_codelines += codelines
|
|
||||||
|
|
||||||
lines, codelines = 0, 0
|
|
||||||
end
|
|
||||||
|
|
||||||
puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
|
|
||||||
end
|
|
||||||
|
|
||||||
# Publishing ------------------------------------------------------
|
|
||||||
|
|
||||||
task :update_scriptaculous do
|
|
||||||
for js in %w( controls dragdrop effects )
|
|
||||||
system("svn export --force http://dev.rubyonrails.org/svn/rails/spinoffs/scriptaculous/src/#{js}.js #{File.dirname(__FILE__)}/lib/action_view/helpers/javascripts/#{js}.js")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Updates actionpack to the latest version of the javascript spinoffs"
|
|
||||||
task :update_js => [ :update_scriptaculous ]
|
|
||||||
|
|
||||||
# Publishing ------------------------------------------------------
|
|
||||||
|
|
||||||
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
|
|
||||||
`ssh davidhh@wrath.rubyonrails.org './gemupdate.sh'`
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Publish the API documentation"
|
|
||||||
task :pdoc => [:rdoc] do
|
|
||||||
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/ap", "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
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<IfModule mod_ruby.c>
|
|
||||||
RubyRequire apache/ruby-run
|
|
||||||
RubySafeLevel 0
|
|
||||||
|
|
||||||
<Files *.rbx>
|
|
||||||
SetHandler ruby-object
|
|
||||||
RubyHandler Apache::RubyRun.instance
|
|
||||||
</Files>
|
|
||||||
</IfModule>
|
|
||||||
|
|
||||||
|
|
||||||
RewriteEngine On
|
|
||||||
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.fcgi?action=$2&id=$3 [QSA]
|
|
||||||
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.fcgi?action=$2 [QSA]
|
|
||||||
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/$ /$1_controller.fcgi?action=index [QSA]
|
|
||||||
|
|
||||||
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.rbx?action=$2&id=$3 [QSA]
|
|
||||||
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.rbx?action=$2 [QSA]
|
|
||||||
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/$ /$1_controller.rbx?action=index [QSA]
|
|
||||||
|
|
||||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.cgi?action=$2&id=$3 [QSA]
|
|
||||||
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.cgi?action=$2 [QSA]
|
|
||||||
RewriteRule ^([-_a-zA-Z0-9]+)/$ /$1_controller.cgi?action=index [QSA]
|
|
||||||
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
<h1>Address Book</h1>
|
|
||||||
|
|
||||||
<% if @people.empty? %>
|
|
||||||
<p>No people in the address book yet</p>
|
|
||||||
<% else %>
|
|
||||||
<table>
|
|
||||||
<tr><th>Name</th><th>Email Address</th><th>Phone Number</th></tr>
|
|
||||||
<% for person in @people %>
|
|
||||||
<tr><td><%= person.name %></td><td><%= person.email_address %></td><td><%= person.phone_number %></td></tr>
|
|
||||||
<% end %>
|
|
||||||
</table>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<form action="create_person">
|
|
||||||
<p>
|
|
||||||
Name:<br />
|
|
||||||
<input type="text" name="person[name]">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Email address:<br />
|
|
||||||
<input type="text" name="person[email_address]">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Phone number:<br />
|
|
||||||
<input type="text" name="person[phone_number]">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Create Person">
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title><%= @title || "Untitled" %></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<%= @content_for_layout %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
require "address_book_controller"
|
|
||||||
|
|
||||||
begin
|
|
||||||
AddressBookController.process_cgi(CGI.new)
|
|
||||||
rescue => e
|
|
||||||
CGI.new.out { "#{e.class}: #{e.message}" }
|
|
||||||
end
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
require "address_book_controller"
|
|
||||||
require "fcgi"
|
|
||||||
|
|
||||||
FCGI.each_cgi { |cgi| AddressBookController.process_cgi(cgi) }
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
|
||||||
|
|
||||||
require "action_controller"
|
|
||||||
require "action_controller/test_process"
|
|
||||||
|
|
||||||
Person = Struct.new("Person", :id, :name, :email_address, :phone_number)
|
|
||||||
|
|
||||||
class AddressBookService
|
|
||||||
attr_reader :people
|
|
||||||
|
|
||||||
def initialize() @people = [] end
|
|
||||||
def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
|
|
||||||
def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
|
|
||||||
def next_person_id() people.first.id + 1 end
|
|
||||||
end
|
|
||||||
|
|
||||||
class AddressBookController < ActionController::Base
|
|
||||||
layout "address_book/layout"
|
|
||||||
|
|
||||||
before_filter :initialize_session_storage
|
|
||||||
|
|
||||||
# Could also have used a proc
|
|
||||||
# before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) }
|
|
||||||
|
|
||||||
def index
|
|
||||||
@title = "Address Book"
|
|
||||||
@people = @address_book.people
|
|
||||||
end
|
|
||||||
|
|
||||||
def person
|
|
||||||
@person = @address_book.find_person(@params["id"])
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_person
|
|
||||||
@address_book.create_person(@params["person"])
|
|
||||||
redirect_to :action => "index"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def initialize_session_storage
|
|
||||||
@address_book = @session["address_book"] ||= AddressBookService.new
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionController::Base.template_root = File.dirname(__FILE__)
|
|
||||||
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
|
|
||||||
|
|
||||||
begin
|
|
||||||
AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
|
|
||||||
rescue => e
|
|
||||||
CGI.new.out { "#{e.class}: #{e.message}" }
|
|
||||||
end
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
require "address_book_controller"
|
|
||||||
AddressBookController.process_cgi(CGI.new)
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
|
||||||
|
|
||||||
require "action_controller"
|
|
||||||
require 'action_controller/test_process'
|
|
||||||
|
|
||||||
Person = Struct.new("Person", :name, :address, :age)
|
|
||||||
|
|
||||||
class BenchmarkController < ActionController::Base
|
|
||||||
def message
|
|
||||||
render_text "hello world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def list
|
|
||||||
@people = [ Person.new("David"), Person.new("Mary") ]
|
|
||||||
render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def form_helper
|
|
||||||
@person = Person.new "david", "hyacintvej", 24
|
|
||||||
render_template(
|
|
||||||
"<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
|
|
||||||
"change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
#ActionController::Base.template_root = File.dirname(__FILE__)
|
|
||||||
|
|
||||||
require "benchmark"
|
|
||||||
|
|
||||||
RUNS = ARGV[0] ? ARGV[0].to_i : 50
|
|
||||||
|
|
||||||
require "profile" if ARGV[1]
|
|
||||||
|
|
||||||
runtime = Benchmark.measure {
|
|
||||||
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
|
|
||||||
}
|
|
||||||
|
|
||||||
puts "List: #{RUNS / runtime.real}"
|
|
||||||
|
|
||||||
|
|
||||||
runtime = Benchmark.measure {
|
|
||||||
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
|
|
||||||
}
|
|
||||||
|
|
||||||
puts "Message: #{RUNS / runtime.real}"
|
|
||||||
|
|
||||||
runtime = Benchmark.measure {
|
|
||||||
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
|
|
||||||
}
|
|
||||||
|
|
||||||
puts "Form helper: #{RUNS / runtime.real}"
|
|
||||||
|
|
@ -1,89 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
begin
|
|
||||||
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../../../edge/activerecord/lib")
|
|
||||||
|
|
||||||
require 'fcgi'
|
|
||||||
require 'action_controller'
|
|
||||||
require 'action_controller/test_process'
|
|
||||||
|
|
||||||
require 'active_record'
|
|
||||||
|
|
||||||
class Post < ActiveRecord::Base; end
|
|
||||||
|
|
||||||
ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "basecamp")
|
|
||||||
|
|
||||||
SESSION_OPTIONS = { "database_manager" => CGI::Session::MemoryStore }
|
|
||||||
|
|
||||||
class TestController < ActionController::Base
|
|
||||||
def index
|
|
||||||
render_template <<-EOT
|
|
||||||
<% for post in Post.find_all(nil,nil,100) %>
|
|
||||||
<%= post.title %>
|
|
||||||
<% end %>
|
|
||||||
EOT
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_one
|
|
||||||
render_template <<-EOT
|
|
||||||
<%= Post.find_first.title %>
|
|
||||||
EOT
|
|
||||||
end
|
|
||||||
|
|
||||||
def text
|
|
||||||
render_text "hello world"
|
|
||||||
end
|
|
||||||
|
|
||||||
def erb_text
|
|
||||||
render_template "hello <%= 'world' %>"
|
|
||||||
end
|
|
||||||
|
|
||||||
def erb_loop
|
|
||||||
render_template <<-EOT
|
|
||||||
<% for post in 1..100 %>
|
|
||||||
<%= post %>
|
|
||||||
<% end %>
|
|
||||||
EOT
|
|
||||||
end
|
|
||||||
|
|
||||||
def rescue_action(e) puts e.message + e.backtrace.join("\n") end
|
|
||||||
end
|
|
||||||
|
|
||||||
if ARGV.empty? && ENV["REQUEST_URI"]
|
|
||||||
FCGI.each_cgi do |cgi|
|
|
||||||
TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi)).out
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if ARGV.empty?
|
|
||||||
cgi = CGI.new
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'benchmark'
|
|
||||||
require 'profile' if ARGV[2] == "profile"
|
|
||||||
|
|
||||||
RUNS = ARGV[1] ? ARGV[1].to_i : 50
|
|
||||||
|
|
||||||
runtime = Benchmark::measure {
|
|
||||||
RUNS.times {
|
|
||||||
if ARGV.empty?
|
|
||||||
TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi))
|
|
||||||
else
|
|
||||||
response = TestController.process_test(
|
|
||||||
ActionController::TestRequest.new({"action" => ARGV[0]})
|
|
||||||
)
|
|
||||||
puts(response.body) if ARGV[2] == "show"
|
|
||||||
end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
puts "Runs: #{RUNS}"
|
|
||||||
puts "Avg. runtime: #{runtime.real / RUNS}"
|
|
||||||
puts "Requests/second: #{RUNS / runtime.real}"
|
|
||||||
end
|
|
||||||
|
|
||||||
rescue Exception => e
|
|
||||||
# CGI.new.out { "<pre>" + e.message + e.backtrace.join("\n") + "</pre>" }
|
|
||||||
$stderr << e.message + e.backtrace.join("\n")
|
|
||||||
end
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
|
||||||
|
|
||||||
require "action_controller"
|
|
||||||
|
|
||||||
Post = Struct.new("Post", :title, :body)
|
|
||||||
|
|
||||||
class BlogController < ActionController::Base
|
|
||||||
before_filter :initialize_session_storage
|
|
||||||
|
|
||||||
def index
|
|
||||||
@posts = @session["posts"]
|
|
||||||
|
|
||||||
render_template <<-"EOF"
|
|
||||||
<html><body>
|
|
||||||
<%= @flash["alert"] %>
|
|
||||||
<h1>Posts</h1>
|
|
||||||
<% @posts.each do |post| %>
|
|
||||||
<p><b><%= post.title %></b><br /><%= post.body %></p>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<h1>Create post</h1>
|
|
||||||
<form action="create">
|
|
||||||
Title: <input type="text" name="post[title]"><br>
|
|
||||||
Body: <textarea name="post[body]"></textarea><br>
|
|
||||||
<input type="submit" value="save">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</body></html>
|
|
||||||
EOF
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@session["posts"].unshift(Post.new(@params["post"]["title"], @params["post"]["body"]))
|
|
||||||
flash["alert"] = "New post added!"
|
|
||||||
redirect_to :action => "index"
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def initialize_session_storage
|
|
||||||
@session["posts"] = [] if @session["posts"].nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionController::Base.template_root = File.dirname(__FILE__)
|
|
||||||
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
|
|
||||||
|
|
||||||
begin
|
|
||||||
BlogController.process_cgi(CGI.new) if $0 == __FILE__
|
|
||||||
rescue => e
|
|
||||||
CGI.new.out { "#{e.class}: #{e.message}" }
|
|
||||||
end
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>Topics</h1>
|
|
||||||
|
|
||||||
<%= link_to "New topic", :action => "new_topic" %>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<% for topic in @topics %>
|
|
||||||
<li><%= link_to "#{topic.title} (#{topic.replies.length} replies)", :action => "topic", :path_params => { "id" => topic.id } %></li>
|
|
||||||
<% end %>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1>New topic</h1>
|
|
||||||
|
|
||||||
<form action="<%= url_for(:action => "create_topic") %>" method="post">
|
|
||||||
<p>
|
|
||||||
Title:<br>
|
|
||||||
<input type="text" name="topic[title]">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Body:<br>
|
|
||||||
<textarea name="topic[body]" style="width: 200px; height: 200px"></textarea>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Create topic">
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<html>
|
|
||||||
<body>
|
|
||||||
<h1><%= @topic.title %></h1>
|
|
||||||
|
|
||||||
<p><%= @topic.body %></p>
|
|
||||||
|
|
||||||
<%= link_to "Back to topics", :action => "index" %>
|
|
||||||
|
|
||||||
<% unless @topic.replies.empty? %>
|
|
||||||
<h2>Replies</h2>
|
|
||||||
<ol>
|
|
||||||
<% for reply in @topic.replies %>
|
|
||||||
<li><%= reply.body %></li>
|
|
||||||
<% end %>
|
|
||||||
</ol>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<h2>Reply to this topic</h2>
|
|
||||||
|
|
||||||
<form action="<%= url_for(:action => "create_reply") %>" method="post">
|
|
||||||
<input type="hidden" name="reply[topic_id]" value="<%= @topic.id %>">
|
|
||||||
<p>
|
|
||||||
<textarea name="reply[body]" style="width: 200px; height: 200px"></textarea>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Create reply">
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
#!/usr/local/bin/ruby
|
|
||||||
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../lib")
|
|
||||||
|
|
||||||
require "action_controller"
|
|
||||||
|
|
||||||
Topic = Struct.new("Topic", :id, :title, :body, :replies)
|
|
||||||
Reply = Struct.new("Reply", :body)
|
|
||||||
|
|
||||||
class DebateService
|
|
||||||
attr_reader :topics
|
|
||||||
|
|
||||||
def initialize() @topics = [] end
|
|
||||||
def create_topic(data) topics.unshift(Topic.new(next_topic_id, data["title"], data["body"], [])) end
|
|
||||||
def create_reply(data) find_topic(data["topic_id"]).replies << Reply.new(data["body"]) end
|
|
||||||
def find_topic(topic_id) topics.select { |topic| topic.id == topic_id.to_i }.first end
|
|
||||||
def next_topic_id() topics.first.id + 1 end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DebateController < ActionController::Base
|
|
||||||
before_filter :initialize_session_storage
|
|
||||||
|
|
||||||
def index
|
|
||||||
@topics = @debate.topics
|
|
||||||
end
|
|
||||||
|
|
||||||
def topic
|
|
||||||
@topic = @debate.find_topic(@params["id"])
|
|
||||||
end
|
|
||||||
|
|
||||||
# def new_topic() end <-- This is not needed as the template doesn't require any assigns
|
|
||||||
|
|
||||||
def create_topic
|
|
||||||
@debate.create_topic(@params["topic"])
|
|
||||||
redirect_to :action => "index"
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_reply
|
|
||||||
@debate.create_reply(@params["reply"])
|
|
||||||
redirect_to :action => "topic", :path_params => { "id" => @params["reply"]["topic_id"] }
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def initialize_session_storage
|
|
||||||
@session["debate"] = DebateService.new if @session["debate"].nil?
|
|
||||||
@debate = @session["debate"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ActionController::Base.template_root = File.dirname(__FILE__)
|
|
||||||
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
|
|
||||||
|
|
||||||
begin
|
|
||||||
DebateController.process_cgi(CGI.new) if $0 == __FILE__
|
|
||||||
rescue => e
|
|
||||||
CGI.new.out { "#{e.class}: #{e.message}" }
|
|
||||||
end
|
|
||||||
30
tracks/vendor/rails/actionpack/install.rb
vendored
30
tracks/vendor/rails/actionpack/install.rb
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
require 'rbconfig'
|
|
||||||
require 'find'
|
|
||||||
require 'ftools'
|
|
||||||
|
|
||||||
include Config
|
|
||||||
|
|
||||||
# this was adapted from rdoc's install.rb by ways 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_controller", "action_controller.rb", "action_view", "action_view.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
|
|
||||||
}
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#--
|
|
||||||
# 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
|
|
||||||
# "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.
|
|
||||||
#++
|
|
||||||
|
|
||||||
$:.unshift(File.dirname(__FILE__)) unless
|
|
||||||
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
|
||||||
|
|
||||||
unless defined?(ActiveSupport)
|
|
||||||
begin
|
|
||||||
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
|
|
||||||
require 'active_support'
|
|
||||||
rescue LoadError
|
|
||||||
require 'rubygems'
|
|
||||||
require_gem 'activesupport'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
require 'action_controller/base'
|
|
||||||
require 'action_controller/deprecated_redirects'
|
|
||||||
require 'action_controller/request'
|
|
||||||
require 'action_controller/deprecated_request_methods'
|
|
||||||
require 'action_controller/rescue'
|
|
||||||
require 'action_controller/benchmarking'
|
|
||||||
require 'action_controller/flash'
|
|
||||||
require 'action_controller/filters'
|
|
||||||
require 'action_controller/layout'
|
|
||||||
require 'action_controller/dependencies'
|
|
||||||
require 'action_controller/mime_responds'
|
|
||||||
require 'action_controller/pagination'
|
|
||||||
require 'action_controller/scaffolding'
|
|
||||||
require 'action_controller/helpers'
|
|
||||||
require 'action_controller/cookies'
|
|
||||||
require 'action_controller/cgi_process'
|
|
||||||
require 'action_controller/caching'
|
|
||||||
require 'action_controller/verification'
|
|
||||||
require 'action_controller/streaming'
|
|
||||||
require 'action_controller/session_management'
|
|
||||||
require 'action_controller/components'
|
|
||||||
require 'action_controller/macros/auto_complete'
|
|
||||||
require 'action_controller/macros/in_place_editing'
|
|
||||||
|
|
||||||
require 'action_view'
|
|
||||||
ActionController::Base.template_class = ActionView::Base
|
|
||||||
|
|
||||||
ActionController::Base.class_eval do
|
|
||||||
include ActionController::Flash
|
|
||||||
include ActionController::Filters
|
|
||||||
include ActionController::Layout
|
|
||||||
include ActionController::Benchmarking
|
|
||||||
include ActionController::Rescue
|
|
||||||
include ActionController::Dependencies
|
|
||||||
include ActionController::MimeResponds
|
|
||||||
include ActionController::Pagination
|
|
||||||
include ActionController::Scaffolding
|
|
||||||
include ActionController::Helpers
|
|
||||||
include ActionController::Cookies
|
|
||||||
include ActionController::Caching
|
|
||||||
include ActionController::Verification
|
|
||||||
include ActionController::Streaming
|
|
||||||
include ActionController::SessionManagement
|
|
||||||
include ActionController::Components
|
|
||||||
include ActionController::Macros::AutoComplete
|
|
||||||
include ActionController::Macros::InPlaceEditing
|
|
||||||
end
|
|
||||||
|
|
@ -1,320 +0,0 @@
|
||||||
require 'test/unit'
|
|
||||||
require 'test/unit/assertions'
|
|
||||||
require 'rexml/document'
|
|
||||||
require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"
|
|
||||||
|
|
||||||
module Test #:nodoc:
|
|
||||||
module Unit #:nodoc:
|
|
||||||
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
|
|
||||||
# can be used against. These collections are:
|
|
||||||
#
|
|
||||||
# * assigns: Instance variables assigned in the action that are available for the view.
|
|
||||||
# * session: Objects being saved in the session.
|
|
||||||
# * flash: The flash objects currently in the session.
|
|
||||||
# * cookies: Cookies being sent to the user on this request.
|
|
||||||
#
|
|
||||||
# These collections can be used just like any other hash:
|
|
||||||
#
|
|
||||||
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
|
|
||||||
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
|
|
||||||
# assert flash.empty? # makes sure that there's nothing in the flash
|
|
||||||
#
|
|
||||||
# For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
|
|
||||||
# appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
|
|
||||||
# So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
|
|
||||||
#
|
|
||||||
# On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
|
|
||||||
#
|
|
||||||
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
|
|
||||||
# action call which can then be asserted against.
|
|
||||||
#
|
|
||||||
# == Manipulating the request collections
|
|
||||||
#
|
|
||||||
# The collections described above link to the response, so you can test if what the actions were expected to do happened. But
|
|
||||||
# sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
|
|
||||||
# and cookies, though. For sessions, you just do:
|
|
||||||
#
|
|
||||||
# @request.session[:key] = "value"
|
|
||||||
#
|
|
||||||
# For cookies, you need to manually create the cookie, like this:
|
|
||||||
#
|
|
||||||
# @request.cookies["key"] = CGI::Cookie.new("key", "value")
|
|
||||||
#
|
|
||||||
# == Testing named routes
|
|
||||||
#
|
|
||||||
# If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
|
|
||||||
# Example:
|
|
||||||
#
|
|
||||||
# assert_redirected_to page_url(:title => 'foo')
|
|
||||||
module Assertions
|
|
||||||
# Asserts that the response is one of the following types:
|
|
||||||
#
|
|
||||||
# * <tt>:success</tt>: Status code was 200
|
|
||||||
# * <tt>:redirect</tt>: Status code was in the 300-399 range
|
|
||||||
# * <tt>:missing</tt>: Status code was 404
|
|
||||||
# * <tt>:error</tt>: Status code was in the 500-599 range
|
|
||||||
#
|
|
||||||
# You can also pass an explicit status code number as the type, like assert_response(501)
|
|
||||||
def assert_response(type, message = nil)
|
|
||||||
clean_backtrace do
|
|
||||||
if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
|
|
||||||
assert_block("") { true } # to count the assertion
|
|
||||||
elsif type.is_a?(Fixnum) && @response.response_code == type
|
|
||||||
assert_block("") { true } # to count the assertion
|
|
||||||
else
|
|
||||||
assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
|
|
||||||
# such that assert_redirected_to(:controller => "weblog") will also match the redirection of
|
|
||||||
# redirect_to(:controller => "weblog", :action => "show") and so on.
|
|
||||||
def assert_redirected_to(options = {}, message=nil)
|
|
||||||
clean_backtrace do
|
|
||||||
assert_response(:redirect, message)
|
|
||||||
|
|
||||||
if options.is_a?(String)
|
|
||||||
msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
|
|
||||||
url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
|
|
||||||
eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
|
|
||||||
u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
|
|
||||||
[u, (p[0..0] == '/') ? p : '/' + p]
|
|
||||||
end.flatten
|
|
||||||
|
|
||||||
assert_equal(eurl, url, msg) if eurl && url
|
|
||||||
assert_equal(epath, path, msg) if epath && path
|
|
||||||
else
|
|
||||||
@response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
|
|
||||||
msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}",
|
|
||||||
@response.redirected_to || @response.redirect_url, @response_diff)
|
|
||||||
|
|
||||||
assert_block(msg) do
|
|
||||||
if options.is_a?(Symbol)
|
|
||||||
@response.redirected_to == options
|
|
||||||
else
|
|
||||||
options.keys.all? do |k|
|
|
||||||
if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
|
|
||||||
else options[k] == (@response.redirected_to[k].respond_to?(:to_param) ? @response.redirected_to[k].to_param : @response.redirected_to[k] unless @response.redirected_to[k].nil?)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that the request was rendered with the appropriate template file.
|
|
||||||
def assert_template(expected = nil, message=nil)
|
|
||||||
clean_backtrace do
|
|
||||||
rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
|
|
||||||
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
|
|
||||||
assert_block(msg) do
|
|
||||||
if expected.nil?
|
|
||||||
!@response.rendered_with_file?
|
|
||||||
else
|
|
||||||
expected == rendered
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that the routing of the given path was handled correctly and that the parsed options match.
|
|
||||||
def assert_recognizes(expected_options, path, extras={}, message=nil)
|
|
||||||
clean_backtrace do
|
|
||||||
path = "/#{path}" unless path[0..0] == '/'
|
|
||||||
# Load routes.rb if it hasn't been loaded.
|
|
||||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
|
||||||
|
|
||||||
# Assume given controller
|
|
||||||
request = ActionController::TestRequest.new({}, {}, nil)
|
|
||||||
request.path = path
|
|
||||||
ActionController::Routing::Routes.recognize!(request)
|
|
||||||
|
|
||||||
expected_options = expected_options.clone
|
|
||||||
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
|
||||||
|
|
||||||
expected_options.stringify_keys!
|
|
||||||
msg = build_message(message, "The recognized options <?> did not match <?>",
|
|
||||||
request.path_parameters, expected_options)
|
|
||||||
assert_block(msg) { request.path_parameters == expected_options }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that the provided options can be used to generate the provided path.
|
|
||||||
def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
|
|
||||||
clean_backtrace do
|
|
||||||
expected_path = "/#{expected_path}" unless expected_path[0] == ?/
|
|
||||||
# Load routes.rb if it hasn't been loaded.
|
|
||||||
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
|
||||||
|
|
||||||
generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
|
|
||||||
found_extras = options.reject {|k, v| ! extra_keys.include? k}
|
|
||||||
|
|
||||||
msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
|
|
||||||
assert_block(msg) { found_extras == extras }
|
|
||||||
|
|
||||||
msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
|
|
||||||
expected_path)
|
|
||||||
assert_block(msg) { expected_path == generated_path }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that path and options match both ways; in other words, the URL generated from
|
|
||||||
# options is the same as path, and also that the options recognized from path are the same as options
|
|
||||||
def assert_routing(path, options, defaults={}, extras={}, message=nil)
|
|
||||||
assert_recognizes(options, path, extras, message)
|
|
||||||
|
|
||||||
controller, default_controller = options[:controller], defaults[:controller]
|
|
||||||
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
|
|
||||||
options[:controller] = "/#{controller}"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert_generates(path, options, defaults, extras, message)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Asserts that there is a tag/node/element in the body of the response
|
|
||||||
# that meets all of the given conditions. The +conditions+ parameter must
|
|
||||||
# be a hash of any of the following keys (all are optional):
|
|
||||||
#
|
|
||||||
# * <tt>:tag</tt>: the node type must match the corresponding value
|
|
||||||
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
|
|
||||||
# corresponding values in the hash.
|
|
||||||
# * <tt>:parent</tt>: a hash. The node's parent must match the
|
|
||||||
# corresponding hash.
|
|
||||||
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
|
|
||||||
# must meet the criteria described by the hash.
|
|
||||||
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
|
|
||||||
# meet the criteria described by the hash.
|
|
||||||
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
|
|
||||||
# must meet the criteria described by the hash.
|
|
||||||
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
|
|
||||||
# meet the criteria described by the hash.
|
|
||||||
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
|
|
||||||
# the criteria described by the hash, and at least one sibling must match.
|
|
||||||
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
|
|
||||||
# the criteria described by the hash, and at least one sibling must match.
|
|
||||||
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
|
|
||||||
# the keys:
|
|
||||||
# * <tt>:count</tt>: either a number or a range which must equal (or
|
|
||||||
# include) the number of children that match.
|
|
||||||
# * <tt>:less_than</tt>: the number of matching children must be less
|
|
||||||
# than this number.
|
|
||||||
# * <tt>:greater_than</tt>: the number of matching children must be
|
|
||||||
# greater than this number.
|
|
||||||
# * <tt>:only</tt>: another hash consisting of the keys to use
|
|
||||||
# to match on the children, and only matching children will be
|
|
||||||
# counted.
|
|
||||||
# * <tt>:content</tt>: the textual content of the node must match the
|
|
||||||
# given value. This will not match HTML tags in the body of a
|
|
||||||
# tag--only text.
|
|
||||||
#
|
|
||||||
# Conditions are matched using the following algorithm:
|
|
||||||
#
|
|
||||||
# * if the condition is a string, it must be a substring of the value.
|
|
||||||
# * if the condition is a regexp, it must match the value.
|
|
||||||
# * if the condition is a number, the value must match number.to_s.
|
|
||||||
# * if the condition is +true+, the value must not be +nil+.
|
|
||||||
# * if the condition is +false+ or +nil+, the value must be +nil+.
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" tag
|
|
||||||
# assert_tag :tag => "span"
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" tag with id="x"
|
|
||||||
# assert_tag :tag => "span", :attributes => { :id => "x" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" tag using the short-hand
|
|
||||||
# assert_tag :span
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" tag with id="x" using the short-hand
|
|
||||||
# assert_tag :span, :attributes => { :id => "x" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" inside of a "div"
|
|
||||||
# assert_tag :tag => "span", :parent => { :tag => "div" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" somewhere inside a table
|
|
||||||
# assert_tag :tag => "span", :ancestor => { :tag => "table" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" with at least one "em" child
|
|
||||||
# assert_tag :tag => "span", :child => { :tag => "em" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" containing a (possibly nested)
|
|
||||||
# # "strong" tag.
|
|
||||||
# assert_tag :tag => "span", :descendant => { :tag => "strong" }
|
|
||||||
#
|
|
||||||
# # assert that there is a "span" containing between 2 and 4 "em" tags
|
|
||||||
# # as immediate children
|
|
||||||
# assert_tag :tag => "span",
|
|
||||||
# :children => { :count => 2..4, :only => { :tag => "em" } }
|
|
||||||
#
|
|
||||||
# # get funky: assert that there is a "div", with an "ul" ancestor
|
|
||||||
# # and an "li" parent (with "class" = "enum"), and containing a
|
|
||||||
# # "span" descendant that contains text matching /hello world/
|
|
||||||
# assert_tag :tag => "div",
|
|
||||||
# :ancestor => { :tag => "ul" },
|
|
||||||
# :parent => { :tag => "li",
|
|
||||||
# :attributes => { :class => "enum" } },
|
|
||||||
# :descendant => { :tag => "span",
|
|
||||||
# :child => /hello world/ }
|
|
||||||
#
|
|
||||||
# <strong>Please note</strong: #assert_tag and #assert_no_tag only work
|
|
||||||
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
|
|
||||||
# (like br and hr and such) but will not work correctly with tags
|
|
||||||
# that allow optional closing tags (p, li, td). <em>You must explicitly
|
|
||||||
# close all of your tags to use these assertions.</em>
|
|
||||||
def assert_tag(*opts)
|
|
||||||
clean_backtrace do
|
|
||||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
|
||||||
tag = find_tag(opts)
|
|
||||||
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Identical to #assert_tag, but asserts that a matching tag does _not_
|
|
||||||
# exist. (See #assert_tag for a full discussion of the syntax.)
|
|
||||||
def assert_no_tag(*opts)
|
|
||||||
clean_backtrace do
|
|
||||||
opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
|
|
||||||
tag = find_tag(opts)
|
|
||||||
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
|
|
||||||
def assert_dom_equal(expected, actual, message="")
|
|
||||||
clean_backtrace do
|
|
||||||
expected_dom = HTML::Document.new(expected).root
|
|
||||||
actual_dom = HTML::Document.new(actual).root
|
|
||||||
full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
|
||||||
assert_block(full_message) { expected_dom == actual_dom }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# negated form of +assert_dom_equivalent+
|
|
||||||
def assert_dom_not_equal(expected, actual, message="")
|
|
||||||
clean_backtrace do
|
|
||||||
expected_dom = HTML::Document.new(expected).root
|
|
||||||
actual_dom = HTML::Document.new(actual).root
|
|
||||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
|
|
||||||
assert_block(full_message) { expected_dom != actual_dom }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# ensures that the passed record is valid by active record standards. returns the error messages if not
|
|
||||||
def assert_valid(record)
|
|
||||||
clean_backtrace do
|
|
||||||
assert record.valid?, record.errors.full_messages.join("\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def clean_backtrace(&block)
|
|
||||||
yield
|
|
||||||
rescue AssertionFailedError => e
|
|
||||||
path = File.expand_path(__FILE__)
|
|
||||||
raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,92 +0,0 @@
|
||||||
require 'benchmark'
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
|
||||||
# The benchmarking module times the performance of actions and reports to the logger. If the Active Record
|
|
||||||
# package has been included, a separate timing section for database calls will be added as well.
|
|
||||||
module Benchmarking #:nodoc:
|
|
||||||
def self.included(base)
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
|
|
||||||
base.class_eval do
|
|
||||||
alias_method :perform_action_without_benchmark, :perform_action
|
|
||||||
alias_method :perform_action, :perform_action_with_benchmark
|
|
||||||
|
|
||||||
alias_method :render_without_benchmark, :render
|
|
||||||
alias_method :render, :render_with_benchmark
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
|
|
||||||
# (unless <tt>use_silence</tt> is set to false).
|
|
||||||
#
|
|
||||||
# The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
|
|
||||||
# easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
|
|
||||||
# will only be conducted if the log level is low enough.
|
|
||||||
def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
|
|
||||||
if logger && logger.level == log_level
|
|
||||||
result = nil
|
|
||||||
seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
|
|
||||||
logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
|
|
||||||
result
|
|
||||||
else
|
|
||||||
yield
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Silences the logger for the duration of the block.
|
|
||||||
def silence
|
|
||||||
old_logger_level, logger.level = logger.level, Logger::ERROR if logger
|
|
||||||
yield
|
|
||||||
ensure
|
|
||||||
logger.level = old_logger_level if logger
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_with_benchmark(options = nil, deprecated_status = nil, &block)
|
|
||||||
unless logger
|
|
||||||
render_without_benchmark(options, deprecated_status, &block)
|
|
||||||
else
|
|
||||||
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
|
||||||
|
|
||||||
render_output = nil
|
|
||||||
@rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status, &block) }.real
|
|
||||||
|
|
||||||
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
|
||||||
@db_rt_before_render = db_runtime
|
|
||||||
@db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
|
|
||||||
@rendering_runtime -= @db_rt_after_render
|
|
||||||
end
|
|
||||||
|
|
||||||
render_output
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def perform_action_with_benchmark
|
|
||||||
unless logger
|
|
||||||
perform_action_without_benchmark
|
|
||||||
else
|
|
||||||
runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
|
|
||||||
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
|
||||||
log_message << rendering_runtime(runtime) if @rendering_runtime
|
|
||||||
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
|
||||||
log_message << " | #{headers["Status"]}"
|
|
||||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
|
||||||
logger.info(log_message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def rendering_runtime(runtime)
|
|
||||||
" | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)"
|
|
||||||
end
|
|
||||||
|
|
||||||
def active_record_runtime(runtime)
|
|
||||||
db_runtime = ActiveRecord::Base.connection.reset_runtime
|
|
||||||
db_runtime += @db_rt_before_render if @db_rt_before_render
|
|
||||||
db_runtime += @db_rt_after_render if @db_rt_after_render
|
|
||||||
db_percentage = (db_runtime * 100) / runtime
|
|
||||||
" | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,555 +0,0 @@
|
||||||
require 'fileutils'
|
|
||||||
|
|
||||||
module ActionController #:nodoc:
|
|
||||||
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
|
||||||
# around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
|
|
||||||
#
|
|
||||||
# You can read more about each approach and the sweeping assistance by clicking the modules below.
|
|
||||||
#
|
|
||||||
# Note: To turn off all caching and sweeping, set Base.perform_caching = false.
|
|
||||||
module Caching
|
|
||||||
def self.included(base) #:nodoc:
|
|
||||||
base.send(:include, Pages, Actions, Fragments, Sweeping)
|
|
||||||
|
|
||||||
base.class_eval do
|
|
||||||
@@perform_caching = true
|
|
||||||
cattr_accessor :perform_caching
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
|
|
||||||
# can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
|
|
||||||
# generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
|
|
||||||
# are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
|
|
||||||
# for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
|
|
||||||
#
|
|
||||||
# Specifying which actions to cache is done through the <tt>caches</tt> class method:
|
|
||||||
#
|
|
||||||
# class WeblogController < ActionController::Base
|
|
||||||
# caches_page :show, :new
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
|
|
||||||
# generation. This is how the web server is able pick up a cache file when it exists and otherwise let the request pass on to
|
|
||||||
# the Action Pack to generate it.
|
|
||||||
#
|
|
||||||
# Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
|
|
||||||
# is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
|
|
||||||
#
|
|
||||||
# class WeblogController < ActionController::Base
|
|
||||||
# def update
|
|
||||||
# List.update(@params["list"]["id"], @params["list"])
|
|
||||||
# expire_page :action => "show", :id => @params["list"]["id"]
|
|
||||||
# redirect_to :action => "show", :id => @params["list"]["id"]
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
|
|
||||||
# expired.
|
|
||||||
#
|
|
||||||
# == Setting the cache directory
|
|
||||||
#
|
|
||||||
# The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
|
|
||||||
# For Rails, this directory has already been set to RAILS_ROOT + "/public".
|
|
||||||
#
|
|
||||||
# == Setting the cache extension
|
|
||||||
#
|
|
||||||
# By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
|
|
||||||
# something else, like .php or .shtml, just set Base.page_cache_extension.
|
|
||||||
module Pages
|
|
||||||
def self.included(base) #:nodoc:
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
base.class_eval do
|
|
||||||
@@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
|
|
||||||
cattr_accessor :page_cache_directory
|
|
||||||
|
|
||||||
@@page_cache_extension = '.html'
|
|
||||||
cattr_accessor :page_cache_extension
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Expires the page that was cached with the +path+ as a key. Example:
|
|
||||||
# expire_page "/lists/show"
|
|
||||||
def expire_page(path)
|
|
||||||
return unless perform_caching
|
|
||||||
|
|
||||||
benchmark "Expired page: #{page_cache_file(path)}" do
|
|
||||||
File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Manually cache the +content+ in the key determined by +path+. Example:
|
|
||||||
# cache_page "I'm the cached content", "/lists/show"
|
|
||||||
def cache_page(content, path)
|
|
||||||
return unless perform_caching
|
|
||||||
|
|
||||||
benchmark "Cached page: #{page_cache_file(path)}" do
|
|
||||||
FileUtils.makedirs(File.dirname(page_cache_path(path)))
|
|
||||||
File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
|
|
||||||
# matches the triggering url.
|
|
||||||
def caches_page(*actions)
|
|
||||||
return unless perform_caching
|
|
||||||
actions.each do |action|
|
|
||||||
class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def page_cache_file(path)
|
|
||||||
name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
|
|
||||||
name << page_cache_extension unless (name.split('/').last || name).include? '.'
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
|
|
||||||
def page_cache_path(path)
|
|
||||||
page_cache_directory + page_cache_file(path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Expires the page that was cached with the +options+ as a key. Example:
|
|
||||||
# expire_page :controller => "lists", :action => "show"
|
|
||||||
def expire_page(options = {})
|
|
||||||
return unless perform_caching
|
|
||||||
if options[:action].is_a?(Array)
|
|
||||||
options[:action].dup.each do |action|
|
|
||||||
self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action })))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
|
|
||||||
# If no options are provided, the current +options+ for this action is used. Example:
|
|
||||||
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
|
||||||
def cache_page(content = nil, options = {})
|
|
||||||
return unless perform_caching && caching_allowed
|
|
||||||
self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def caching_allowed
|
|
||||||
!@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
|
|
||||||
# every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
|
|
||||||
# allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
|
|
||||||
#
|
|
||||||
# class ListsController < ApplicationController
|
|
||||||
# before_filter :authenticate, :except => :public
|
|
||||||
# caches_page :public
|
|
||||||
# caches_action :show, :feed
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
|
|
||||||
# show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
|
|
||||||
#
|
|
||||||
# Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
|
|
||||||
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
|
||||||
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
|
||||||
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
|
||||||
module Actions
|
|
||||||
def self.append_features(base) #:nodoc:
|
|
||||||
super
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
base.send(:attr_accessor, :rendered_action_cache)
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods #:nodoc:
|
|
||||||
def caches_action(*actions)
|
|
||||||
return unless perform_caching
|
|
||||||
around_filter(ActionCacheFilter.new(*actions))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def expire_action(options = {})
|
|
||||||
return unless perform_caching
|
|
||||||
if options[:action].is_a?(Array)
|
|
||||||
options[:action].dup.each do |action|
|
|
||||||
expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
expire_fragment(url_for(options).split("://").last)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class ActionCacheFilter #:nodoc:
|
|
||||||
def initialize(*actions)
|
|
||||||
@actions = actions
|
|
||||||
end
|
|
||||||
|
|
||||||
def before(controller)
|
|
||||||
return unless @actions.include?(controller.action_name.intern)
|
|
||||||
if cache = controller.read_fragment(controller.url_for.split("://").last)
|
|
||||||
controller.rendered_action_cache = true
|
|
||||||
controller.send(:render_text, cache)
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def after(controller)
|
|
||||||
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
|
||||||
controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
|
|
||||||
# certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
|
|
||||||
# parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
|
|
||||||
#
|
|
||||||
# <b>Hello <%= @name %></b>
|
|
||||||
# <% cache do %>
|
|
||||||
# All the topics in the system:
|
|
||||||
# <%= render_collection_of_partials "topic", Topic.find_all %>
|
|
||||||
# <% end %>
|
|
||||||
#
|
|
||||||
# This cache will bind to the name of action that called it. So you would be able to invalidate it using
|
|
||||||
# <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful
|
|
||||||
# if you need to cache multiple fragments per action or if the action itself is cached using <tt>caches_action</tt>. So instead we should
|
|
||||||
# qualify the name of the action used with something like:
|
|
||||||
#
|
|
||||||
# <% cache(:action => "list", :action_suffix => "all_topics") do %>
|
|
||||||
#
|
|
||||||
# That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
|
|
||||||
# fragment using a different suffix. Note that the URL doesn't have to really exist or be callable. We're just using the url_for system
|
|
||||||
# to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
|
|
||||||
# <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
|
|
||||||
#
|
|
||||||
# == Fragment stores
|
|
||||||
#
|
|
||||||
# In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
|
|
||||||
# of which there are four different kinds:
|
|
||||||
#
|
|
||||||
# * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for
|
|
||||||
# all the web server processes running off the same application directory.
|
|
||||||
# * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
|
|
||||||
# own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
|
|
||||||
# up a lot of memory since each process keeps all the caches in memory.
|
|
||||||
# * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
|
|
||||||
# around for all processes, but requires that you run and manage a separate DRb process.
|
|
||||||
# * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
|
|
||||||
# Requires the ruby-memcache library: gem install ruby-memcache.
|
|
||||||
#
|
|
||||||
# Configuration examples (MemoryStore is the default):
|
|
||||||
#
|
|
||||||
# ActionController::Base.fragment_cache_store = :memory_store
|
|
||||||
# ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
|
|
||||||
# ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
|
|
||||||
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
|
||||||
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
|
||||||
module Fragments
|
|
||||||
def self.append_features(base) #:nodoc:
|
|
||||||
super
|
|
||||||
base.class_eval do
|
|
||||||
@@fragment_cache_store = MemoryStore.new
|
|
||||||
cattr_reader :fragment_cache_store
|
|
||||||
|
|
||||||
def self.fragment_cache_store=(store_option)
|
|
||||||
store, *parameters = *([ store_option ].flatten)
|
|
||||||
@@fragment_cache_store = if store.is_a?(Symbol)
|
|
||||||
store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
|
|
||||||
store_class = ActionController::Caching::Fragments.const_get(store_class_name)
|
|
||||||
store_class.new(*parameters)
|
|
||||||
else
|
|
||||||
store
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fragment_cache_key(name)
|
|
||||||
name.is_a?(Hash) ? url_for(name).split("://").last : name
|
|
||||||
end
|
|
||||||
|
|
||||||
# Called by CacheHelper#cache
|
|
||||||
def cache_erb_fragment(block, name = {}, options = nil)
|
|
||||||
unless perform_caching then block.call; return end
|
|
||||||
|
|
||||||
buffer = eval("_erbout", block.binding)
|
|
||||||
|
|
||||||
if cache = read_fragment(name, options)
|
|
||||||
buffer.concat(cache)
|
|
||||||
else
|
|
||||||
pos = buffer.length
|
|
||||||
block.call
|
|
||||||
write_fragment(name, buffer[pos..-1], options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_fragment(name, content, options = nil)
|
|
||||||
return unless perform_caching
|
|
||||||
|
|
||||||
key = fragment_cache_key(name)
|
|
||||||
self.class.benchmark "Cached fragment: #{key}" do
|
|
||||||
fragment_cache_store.write(key, content, options)
|
|
||||||
end
|
|
||||||
content
|
|
||||||
end
|
|
||||||
|
|
||||||
def read_fragment(name, options = nil)
|
|
||||||
return unless perform_caching
|
|
||||||
|
|
||||||
key = fragment_cache_key(name)
|
|
||||||
self.class.benchmark "Fragment read: #{key}" do
|
|
||||||
fragment_cache_store.read(key, options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Name can take one of three forms:
|
|
||||||
# * String: This would normally take the form of a path like "pages/45/notes"
|
|
||||||
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
|
||||||
# * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes} Ensure you do not specify start and finish in the regex (^$) because the actual filename matched looks like ./cache/filename/path.cache
|
|
||||||
def expire_fragment(name, options = nil)
|
|
||||||
return unless perform_caching
|
|
||||||
|
|
||||||
key = fragment_cache_key(name)
|
|
||||||
|
|
||||||
if key.is_a?(Regexp)
|
|
||||||
self.class.benchmark "Expired fragments matching: #{key.source}" do
|
|
||||||
fragment_cache_store.delete_matched(key, options)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
self.class.benchmark "Expired fragment: #{key}" do
|
|
||||||
fragment_cache_store.delete(key, options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Deprecated -- just call expire_fragment with a regular expression
|
|
||||||
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
|
||||||
expire_fragment(matcher, options)
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
class UnthreadedMemoryStore #:nodoc:
|
|
||||||
def initialize #:nodoc:
|
|
||||||
@data = {}
|
|
||||||
end
|
|
||||||
|
|
||||||
def read(name, options=nil) #:nodoc:
|
|
||||||
@data[name]
|
|
||||||
end
|
|
||||||
|
|
||||||
def write(name, value, options=nil) #:nodoc:
|
|
||||||
@data[name] = value
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(name, options=nil) #:nodoc:
|
|
||||||
@data.delete(name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_matched(matcher, options=nil) #:nodoc:
|
|
||||||
@data.delete_if { |k,v| k =~ matcher }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module ThreadSafety #:nodoc:
|
|
||||||
def read(name, options=nil) #:nodoc:
|
|
||||||
@mutex.synchronize { super }
|
|
||||||
end
|
|
||||||
|
|
||||||
def write(name, value, options=nil) #:nodoc:
|
|
||||||
@mutex.synchronize { super }
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(name, options=nil) #:nodoc:
|
|
||||||
@mutex.synchronize { super }
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_matched(matcher, options=nil) #:nodoc:
|
|
||||||
@mutex.synchronize { super }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MemoryStore < UnthreadedMemoryStore #:nodoc:
|
|
||||||
def initialize #:nodoc:
|
|
||||||
super
|
|
||||||
if ActionController::Base.allow_concurrency
|
|
||||||
@mutex = Mutex.new
|
|
||||||
MemoryStore.send(:include, ThreadSafety)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class DRbStore < MemoryStore #:nodoc:
|
|
||||||
attr_reader :address
|
|
||||||
|
|
||||||
def initialize(address = 'druby://localhost:9192')
|
|
||||||
super()
|
|
||||||
@address = address
|
|
||||||
@data = DRbObject.new(nil, address)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class MemCacheStore < MemoryStore #:nodoc:
|
|
||||||
attr_reader :addresses
|
|
||||||
|
|
||||||
def initialize(*addresses)
|
|
||||||
super()
|
|
||||||
addresses = addresses.flatten
|
|
||||||
addresses = ["localhost"] if addresses.empty?
|
|
||||||
@addresses = addresses
|
|
||||||
@data = MemCache.new(*addresses)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class UnthreadedFileStore #:nodoc:
|
|
||||||
attr_reader :cache_path
|
|
||||||
|
|
||||||
def initialize(cache_path)
|
|
||||||
@cache_path = cache_path
|
|
||||||
end
|
|
||||||
|
|
||||||
def write(name, value, options = nil) #:nodoc:
|
|
||||||
ensure_cache_path(File.dirname(real_file_path(name)))
|
|
||||||
File.open(real_file_path(name), "wb+") { |f| f.write(value) }
|
|
||||||
rescue => e
|
|
||||||
Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
|
|
||||||
end
|
|
||||||
|
|
||||||
def read(name, options = nil) #:nodoc:
|
|
||||||
IO.read(real_file_path(name)) rescue nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(name, options) #:nodoc:
|
|
||||||
File.delete(real_file_path(name))
|
|
||||||
rescue SystemCallError => e
|
|
||||||
# If there's no cache, then there's nothing to complain about
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_matched(matcher, options) #:nodoc:
|
|
||||||
search_dir(@cache_path) do |f|
|
|
||||||
if f =~ matcher
|
|
||||||
begin
|
|
||||||
File.delete(f)
|
|
||||||
rescue Object => e
|
|
||||||
# If there's no cache, then there's nothing to complain about
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def real_file_path(name)
|
|
||||||
'%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_cache_path(path)
|
|
||||||
FileUtils.makedirs(path) unless File.exists?(path)
|
|
||||||
end
|
|
||||||
|
|
||||||
def search_dir(dir, &callback)
|
|
||||||
Dir.foreach(dir) do |d|
|
|
||||||
next if d == "." || d == ".."
|
|
||||||
name = File.join(dir, d)
|
|
||||||
if File.directory?(name)
|
|
||||||
search_dir(name, &callback)
|
|
||||||
else
|
|
||||||
callback.call name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class FileStore < UnthreadedFileStore #:nodoc:
|
|
||||||
def initialize(cache_path)
|
|
||||||
super(cache_path)
|
|
||||||
if ActionController::Base.allow_concurrency
|
|
||||||
@mutex = Mutex.new
|
|
||||||
FileStore.send(:include, ThreadSafety)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
|
|
||||||
# They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
|
|
||||||
#
|
|
||||||
# class ListSweeper < ActionController::Caching::Sweeper
|
|
||||||
# observe List, Item
|
|
||||||
#
|
|
||||||
# def after_save(record)
|
|
||||||
# list = record.is_a?(List) ? record : record.list
|
|
||||||
# expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
|
|
||||||
# expire_action(:controller => "lists", :action => "all")
|
|
||||||
# list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
|
|
||||||
#
|
|
||||||
# class ListsController < ApplicationController
|
|
||||||
# caches_action :index, :show, :public, :feed
|
|
||||||
# cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
|
||||||
module Sweeping
|
|
||||||
def self.append_features(base) #:nodoc:
|
|
||||||
super
|
|
||||||
base.extend(ClassMethods)
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods #:nodoc:
|
|
||||||
def cache_sweeper(*sweepers)
|
|
||||||
return unless perform_caching
|
|
||||||
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
|
||||||
sweepers.each do |sweeper|
|
|
||||||
observer(sweeper)
|
|
||||||
|
|
||||||
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
|
||||||
|
|
||||||
if sweeper_instance.is_a?(Sweeper)
|
|
||||||
around_filter(sweeper_instance, :only => configuration[:only])
|
|
||||||
else
|
|
||||||
after_filter(sweeper_instance, :only => configuration[:only])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
|
|
||||||
class Sweeper < ActiveRecord::Observer #:nodoc:
|
|
||||||
attr_accessor :controller
|
|
||||||
|
|
||||||
# ActiveRecord::Observer will mark this class as reloadable even though it should not be.
|
|
||||||
# However, subclasses of ActionController::Caching::Sweeper should be Reloadable
|
|
||||||
include Reloadable::Subclasses
|
|
||||||
|
|
||||||
def before(controller)
|
|
||||||
self.controller = controller
|
|
||||||
callback(:before)
|
|
||||||
end
|
|
||||||
|
|
||||||
def after(controller)
|
|
||||||
callback(:after)
|
|
||||||
# Clean up, so that the controller can be collected after this request
|
|
||||||
self.controller = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
def callback(timing)
|
|
||||||
controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
|
|
||||||
action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
|
|
||||||
|
|
||||||
send(controller_callback_method_name) if respond_to?(controller_callback_method_name)
|
|
||||||
send(action_callback_method_name) if respond_to?(action_callback_method_name)
|
|
||||||
end
|
|
||||||
|
|
||||||
def method_missing(method, *arguments)
|
|
||||||
return if @controller.nil?
|
|
||||||
@controller.send(method, *arguments)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
require 'cgi'
|
|
||||||
require 'cgi/session'
|
|
||||||
require 'cgi/session/pstore'
|
|
||||||
require 'action_controller/cgi_ext/cgi_methods'
|
|
||||||
|
|
||||||
# Wrapper around the CGIMethods that have been secluded to allow testing without
|
|
||||||
# an instantiated CGI object
|
|
||||||
class CGI #:nodoc:
|
|
||||||
class << self
|
|
||||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
|
||||||
|
|
||||||
def escapeHTML(string)
|
|
||||||
escapeHTML_fail_on_nil(string) unless string.nil?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Returns a parameter hash including values from both the request (POST/GET)
|
|
||||||
# and the query string with the latter taking precedence.
|
|
||||||
def parameters
|
|
||||||
request_parameters.update(query_parameters)
|
|
||||||
end
|
|
||||||
|
|
||||||
def query_parameters
|
|
||||||
CGIMethods.parse_query_parameters(query_string)
|
|
||||||
end
|
|
||||||
|
|
||||||
def request_parameters
|
|
||||||
CGIMethods.parse_request_parameters(params, env_table)
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirect(where)
|
|
||||||
header({
|
|
||||||
"Status" => "302 Moved",
|
|
||||||
"location" => "#{where}"
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
def session(parameters = nil)
|
|
||||||
parameters = {} if parameters.nil?
|
|
||||||
parameters['database_manager'] = CGI::Session::PStore
|
|
||||||
CGI::Session.new(self, parameters)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue