mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 23:30: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
|
||||
# of inactivity
|
||||
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}"
|
||||
redirect_back_or_default :controller => "todo", :action => "list"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
* Homepage: http://www.rousette.org.uk/projects/
|
||||
* 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
|
||||
* Copyright: (cc) 2004-2006 rousette.org.uk
|
||||
* License: GNU GPL
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
* Trac (for bug reports): http://dev.rousette.org.uk/report/6
|
||||
* Wiki (more info on installation): http://dev.rousette.org.uk/wiki
|
||||
* 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
|
||||
* Copyright: (cc) 2004-2006 rousette.org.uk
|
||||
* 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