PageRenderTime 123ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 2ms

/text/src/test/resources/examples/ruby/rails.in.rb

https://github.com/rimolive/core
Ruby | 15466 lines | 11031 code | 1665 blank | 2770 comment | 670 complexity | cc3b4273ae9f93be89092edb70074a1b MD5 | raw file
Possible License(s): EPL-1.0, MPL-2.0-no-copyleft-exception
  1. require 'rbconfig'
  2. require 'find'
  3. require 'ftools'
  4. include Config
  5. # this was adapted from rdoc's install.rb by way of Log4r
  6. $sitedir = CONFIG["sitelibdir"]
  7. unless $sitedir
  8. version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  9. $libdir = File.join(CONFIG["libdir"], "ruby", version)
  10. $sitedir = $:.find {|x| x =~ /site_ruby/ }
  11. if !$sitedir
  12. $sitedir = File.join($libdir, "site_ruby")
  13. elsif $sitedir !~ Regexp.quote(version)
  14. $sitedir = File.join($sitedir, version)
  15. end
  16. end
  17. # the acual gruntwork
  18. Dir.chdir("lib")
  19. Find.find("action_mailer", "action_mailer.rb") { |f|
  20. if f[-3..-1] == ".rb"
  21. File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  22. else
  23. File::makedirs(File.join($sitedir, *f.split(/\//)))
  24. end
  25. }
  26. module ActionMailer
  27. module AdvAttrAccessor #:nodoc:
  28. def self.append_features(base)
  29. super
  30. base.extend(ClassMethods)
  31. end
  32. module ClassMethods #:nodoc:
  33. def adv_attr_accessor(*names)
  34. names.each do |name|
  35. ivar = "@#{name}"
  36. define_method("#{name}=") do |value|
  37. instance_variable_set(ivar, value)
  38. end
  39. define_method(name) do |*parameters|
  40. raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
  41. if parameters.empty?
  42. if instance_variables.include?(ivar)
  43. instance_variable_get(ivar)
  44. end
  45. else
  46. instance_variable_set(ivar, parameters.first)
  47. end
  48. end
  49. end
  50. end
  51. end
  52. end
  53. end
  54. require 'action_mailer/adv_attr_accessor'
  55. require 'action_mailer/part'
  56. require 'action_mailer/part_container'
  57. require 'action_mailer/utils'
  58. require 'tmail/net'
  59. module ActionMailer #:nodoc:
  60. # ActionMailer allows you to send email from your application using a mailer model and views.
  61. #
  62. # = Mailer Models
  63. # To use ActionMailer, you need to create a mailer model.
  64. #
  65. # $ script/generate mailer Notifier
  66. #
  67. # The generated model inherits from ActionMailer::Base. Emails are defined by creating methods within the model which are then
  68. # used to set variables to be used in the mail template, to change options on the mail, or
  69. # to add attachments.
  70. #
  71. # Examples:
  72. #
  73. # class Notifier < ActionMailer::Base
  74. # def signup_notification(recipient)
  75. # recipients recipient.email_address_with_name
  76. # from "system@example.com"
  77. # subject "New account information"
  78. # body "account" => recipient
  79. # end
  80. # end
  81. #
  82. # Mailer methods have the following configuration methods available.
  83. #
  84. # * <tt>recipients</tt> - Takes one or more email addresses. These addresses are where your email will be delivered to. Sets the <tt>To:</tt> header.
  85. # * <tt>subject</tt> - The subject of your email. Sets the <tt>Subject:</tt> header.
  86. # * <tt>from</tt> - Who the email you are sending is from. Sets the <tt>From:</tt> header.
  87. # * <tt>cc</tt> - Takes one or more email addresses. These addresses will receive a carbon copy of your email. Sets the <tt>Cc:</tt> header.
  88. # * <tt>bcc</tt> - Takes one or more email address. These addresses will receive a blind carbon copy of your email. Sets the <tt>Bcc</tt> header.
  89. # * <tt>sent_on</tt> - The date on which the message was sent. If not set, the header wil be set by the delivery agent.
  90. # * <tt>content_type</tt> - Specify the content type of the message. Defaults to <tt>text/plain</tt>.
  91. # * <tt>headers</tt> - Specify additional headers to be set for the message, e.g. <tt>headers 'X-Mail-Count' => 107370</tt>.
  92. #
  93. # The <tt>body</tt> method has special behavior. It takes a hash which generates an instance variable
  94. # named after each key in the hash containing the value that that key points to.
  95. #
  96. # So, for example, <tt>body "account" => recipient</tt> would result
  97. # in an instance variable <tt>@account</tt> with the value of <tt>recipient</tt> being accessible in the
  98. # view.
  99. #
  100. # = Mailer Views
  101. # Like ActionController, each mailer class has a corresponding view directory
  102. # in which each method of the class looks for a template with its name.
  103. # To define a template to be used with a mailing, create an <tt>.rhtml</tt> file with the same name as the method
  104. # in your mailer model. For example, in the mailer defined above, the template at
  105. # <tt>app/views/notifier/signup_notification.rhtml</tt> would be used to generate the email.
  106. #
  107. # Variables defined in the model are accessible as instance variables in the view.
  108. #
  109. # Emails by default are sent in plain text, so a sample view for our model example might look like this:
  110. #
  111. # Hi <%= @account.name %>,
  112. # Thanks for joining our service! Please check back often.
  113. #
  114. # = Sending Mail
  115. # Once a mailer action and template are defined, you can deliver your message or create it and save it
  116. # for delivery later:
  117. #
  118. # Notifier.deliver_signup_notification(david) # sends the email
  119. # mail = Notifier.create_signup_notification(david) # => a tmail object
  120. # Notifier.deliver(mail)
  121. #
  122. # You never instantiate your mailer class. Rather, your delivery instance
  123. # methods are automatically wrapped in class methods that start with the word
  124. # <tt>deliver_</tt> followed by the name of the mailer method that you would
  125. # like to deliver. The <tt>signup_notification</tt> method defined above is
  126. # delivered by invoking <tt>Notifier.deliver_signup_notification</tt>.
  127. #
  128. # = HTML Email
  129. # To send mail as HTML, make sure your view (the <tt>.rhtml</tt> file) generates HTML and
  130. # set the content type to html.
  131. #
  132. # class MyMailer < ActionMailer::Base
  133. # def signup_notification(recipient)
  134. # recipients recipient.email_address_with_name
  135. # subject "New account information"
  136. # body "account" => recipient
  137. # from "system@example.com"
  138. # content_type "text/html" # Here's where the magic happens
  139. # end
  140. # end
  141. #
  142. # = Multipart Email
  143. # You can explicitly specify multipart messages:
  144. #
  145. # class ApplicationMailer < ActionMailer::Base
  146. # def signup_notification(recipient)
  147. # recipients recipient.email_address_with_name
  148. # subject "New account information"
  149. # from "system@example.com"
  150. #
  151. # part :content_type => "text/html",
  152. # :body => render_message("signup-as-html", :account => recipient)
  153. #
  154. # part "text/plain" do |p|
  155. # p.body = render_message("signup-as-plain", :account => recipient)
  156. # p.transfer_encoding = "base64"
  157. # end
  158. # end
  159. # end
  160. #
  161. # Multipart messages can also be used implicitly because ActionMailer will automatically
  162. # detect and use multipart templates, where each template is named after the name of the action, followed
  163. # by the content type. Each such detected template will be added as separate part to the message.
  164. #
  165. # For example, if the following templates existed:
  166. # * signup_notification.text.plain.rhtml
  167. # * signup_notification.text.html.rhtml
  168. # * signup_notification.text.xml.rxml
  169. # * signup_notification.text.x-yaml.rhtml
  170. #
  171. # Each would be rendered and added as a separate part to the message,
  172. # with the corresponding content type. The same body hash is passed to
  173. # each template.
  174. #
  175. # = Attachments
  176. # Attachments can be added by using the +attachment+ method.
  177. #
  178. # Example:
  179. #
  180. # class ApplicationMailer < ActionMailer::Base
  181. # # attachments
  182. # def signup_notification(recipient)
  183. # recipients recipient.email_address_with_name
  184. # subject "New account information"
  185. # from "system@example.com"
  186. #
  187. # attachment :content_type => "image/jpeg",
  188. # :body => File.read("an-image.jpg")
  189. #
  190. # attachment "application/pdf" do |a|
  191. # a.body = generate_your_pdf_here()
  192. # end
  193. # end
  194. # end
  195. #
  196. # = Configuration options
  197. #
  198. # These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
  199. #
  200. # * <tt>template_root</tt> - template root determines the base from which template references will be made.
  201. #
  202. # * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
  203. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
  204. #
  205. # * <tt>server_settings</tt> - Allows detailed configuration of the server:
  206. # * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
  207. # * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
  208. # * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
  209. # * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
  210. # * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
  211. # * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
  212. # This is a symbol and one of :plain, :login, :cram_md5
  213. #
  214. # * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
  215. #
  216. # * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
  217. # Sendmail is assumed to be present at "/usr/sbin/sendmail".
  218. #
  219. # * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
  220. # but this can be turned off to help functional testing.
  221. #
  222. # * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
  223. # for unit and functional testing.
  224. #
  225. # * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
  226. # pick a different charset from inside a method with <tt>@charset</tt>.
  227. # * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
  228. # can also pick a different content type from inside a method with <tt>@content_type</tt>.
  229. # * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
  230. # can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
  231. # use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
  232. # * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
  233. # which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
  234. # ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client
  235. # and appear last in the mime encoded message. You can also pick a different order from inside a method with
  236. # <tt>@implicit_parts_order</tt>.
  237. class Base
  238. include AdvAttrAccessor, PartContainer
  239. # Action Mailer subclasses should be reloaded by the dispatcher in Rails
  240. # when Dependencies.mechanism = :load.
  241. include Reloadable::Subclasses
  242. private_class_method :new #:nodoc:
  243. class_inheritable_accessor :template_root
  244. cattr_accessor :logger
  245. @@server_settings = {
  246. :address => "localhost",
  247. :port => 25,
  248. :domain => 'localhost.localdomain',
  249. :user_name => nil,
  250. :password => nil,
  251. :authentication => nil
  252. }
  253. cattr_accessor :server_settings
  254. @@raise_delivery_errors = true
  255. cattr_accessor :raise_delivery_errors
  256. @@delivery_method = :smtp
  257. cattr_accessor :delivery_method
  258. @@perform_deliveries = true
  259. cattr_accessor :perform_deliveries
  260. @@deliveries = []
  261. cattr_accessor :deliveries
  262. @@default_charset = "utf-8"
  263. cattr_accessor :default_charset
  264. @@default_content_type = "text/plain"
  265. cattr_accessor :default_content_type
  266. @@default_mime_version = nil
  267. cattr_accessor :default_mime_version
  268. @@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
  269. cattr_accessor :default_implicit_parts_order
  270. # Specify the BCC addresses for the message
  271. adv_attr_accessor :bcc
  272. # Define the body of the message. This is either a Hash (in which case it
  273. # specifies the variables to pass to the template when it is rendered),
  274. # or a string, in which case it specifies the actual text of the message.
  275. adv_attr_accessor :body
  276. # Specify the CC addresses for the message.
  277. adv_attr_accessor :cc
  278. # Specify the charset to use for the message. This defaults to the
  279. # +default_charset+ specified for ActionMailer::Base.
  280. adv_attr_accessor :charset
  281. # Specify the content type for the message. This defaults to <tt>text/plain</tt>
  282. # in most cases, but can be automatically set in some situations.
  283. adv_attr_accessor :content_type
  284. # Specify the from address for the message.
  285. adv_attr_accessor :from
  286. # Specify additional headers to be added to the message.
  287. adv_attr_accessor :headers
  288. # Specify the order in which parts should be sorted, based on content-type.
  289. # This defaults to the value for the +default_implicit_parts_order+.
  290. adv_attr_accessor :implicit_parts_order
  291. # Override the mailer name, which defaults to an inflected version of the
  292. # mailer's class name. If you want to use a template in a non-standard
  293. # location, you can use this to specify that location.
  294. adv_attr_accessor :mailer_name
  295. # Defaults to "1.0", but may be explicitly given if needed.
  296. adv_attr_accessor :mime_version
  297. # The recipient addresses for the message, either as a string (for a single
  298. # address) or an array (for multiple addresses).
  299. adv_attr_accessor :recipients
  300. # The date on which the message was sent. If not set (the default), the
  301. # header will be set by the delivery agent.
  302. adv_attr_accessor :sent_on
  303. # Specify the subject of the message.
  304. adv_attr_accessor :subject
  305. # Specify the template name to use for current message. This is the "base"
  306. # template name, without the extension or directory, and may be used to
  307. # have multiple mailer methods share the same template.
  308. adv_attr_accessor :template
  309. # The mail object instance referenced by this mailer.
  310. attr_reader :mail
  311. class << self
  312. def method_missing(method_symbol, *parameters)#:nodoc:
  313. case method_symbol.id2name
  314. when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
  315. when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
  316. when "new" then nil
  317. else super
  318. end
  319. end
  320. # Receives a raw email, parses it into an email object, decodes it,
  321. # instantiates a new mailer, and passes the email object to the mailer
  322. # object's #receive method. If you want your mailer to be able to
  323. # process incoming messages, you'll need to implement a #receive
  324. # method that accepts the email object as a parameter:
  325. #
  326. # class MyMailer < ActionMailer::Base
  327. # def receive(mail)
  328. # ...
  329. # end
  330. # end
  331. def receive(raw_email)
  332. logger.info "Received mail:\n #{raw_email}" unless logger.nil?
  333. mail = TMail::Mail.parse(raw_email)
  334. mail.base64_decode
  335. new.receive(mail)
  336. end
  337. # Deliver the given mail object directly. This can be used to deliver
  338. # a preconstructed mail object, like:
  339. #
  340. # email = MyMailer.create_some_mail(parameters)
  341. # email.set_some_obscure_header "frobnicate"
  342. # MyMailer.deliver(email)
  343. def deliver(mail)
  344. new.deliver!(mail)
  345. end
  346. end
  347. # Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
  348. # will be initialized according to the named method. If not, the mailer will
  349. # remain uninitialized (useful when you only need to invoke the "receive"
  350. # method, for instance).
  351. def initialize(method_name=nil, *parameters) #:nodoc:
  352. create!(method_name, *parameters) if method_name
  353. end
  354. # Initialize the mailer via the given +method_name+. The body will be
  355. # rendered and a new TMail::Mail object created.
  356. def create!(method_name, *parameters) #:nodoc:
  357. initialize_defaults(method_name)
  358. send(method_name, *parameters)
  359. # If an explicit, textual body has not been set, we check assumptions.
  360. unless String === @body
  361. # First, we look to see if there are any likely templates that match,
  362. # which include the content-type in their file name (i.e.,
  363. # "the_template_file.text.html.rhtml", etc.). Only do this if parts
  364. # have not already been specified manually.
  365. if @parts.empty?
  366. templates = Dir.glob("#{template_path}/#{@template}.*")
  367. templates.each do |path|
  368. # TODO: don't hardcode rhtml|rxml
  369. basename = File.basename(path)
  370. next unless md = /^([^\.]+)\.([^\.]+\.[^\+]+)\.(rhtml|rxml)$/.match(basename)
  371. template_name = basename
  372. content_type = md.captures[1].gsub('.', '/')
  373. @parts << Part.new(:content_type => content_type,
  374. :disposition => "inline", :charset => charset,
  375. :body => render_message(template_name, @body))
  376. end
  377. unless @parts.empty?
  378. @content_type = "multipart/alternative"
  379. @parts = sort_parts(@parts, @implicit_parts_order)
  380. end
  381. end
  382. # Then, if there were such templates, we check to see if we ought to
  383. # also render a "normal" template (without the content type). If a
  384. # normal template exists (or if there were no implicit parts) we render
  385. # it.
  386. template_exists = @parts.empty?
  387. template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| File.basename(i).split(".").length == 2 }
  388. @body = render_message(@template, @body) if template_exists
  389. # Finally, if there are other message parts and a textual body exists,
  390. # we shift it onto the front of the parts and set the body to nil (so
  391. # that create_mail doesn't try to render it in addition to the parts).
  392. if !@parts.empty? && String === @body
  393. @parts.unshift Part.new(:charset => charset, :body => @body)
  394. @body = nil
  395. end
  396. end
  397. # If this is a multipart e-mail add the mime_version if it is not
  398. # already set.
  399. @mime_version ||= "1.0" if !@parts.empty?
  400. # build the mail object itself
  401. @mail = create_mail
  402. end
  403. # Delivers a TMail::Mail object. By default, it delivers the cached mail
  404. # object (from the #create! method). If no cached mail object exists, and
  405. # no alternate has been given as the parameter, this will fail.
  406. def deliver!(mail = @mail)
  407. raise "no mail object available for delivery!" unless mail
  408. logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
  409. begin
  410. send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
  411. rescue Object => e
  412. raise e if raise_delivery_errors
  413. end
  414. return mail
  415. end
  416. private
  417. # Set up the default values for the various instance variables of this
  418. # mailer. Subclasses may override this method to provide different
  419. # defaults.
  420. def initialize_defaults(method_name)
  421. @charset ||= @@default_charset.dup
  422. @content_type ||= @@default_content_type.dup
  423. @implicit_parts_order ||= @@default_implicit_parts_order.dup
  424. @template ||= method_name
  425. @mailer_name ||= Inflector.underscore(self.class.name)
  426. @parts ||= []
  427. @headers ||= {}
  428. @body ||= {}
  429. @mime_version = @@default_mime_version.dup if @@default_mime_version
  430. end
  431. def render_message(method_name, body)
  432. render :file => method_name, :body => body
  433. end
  434. def render(opts)
  435. body = opts.delete(:body)
  436. initialize_template_class(body).render(opts)
  437. end
  438. def template_path
  439. "#{template_root}/#{mailer_name}"
  440. end
  441. def initialize_template_class(assigns)
  442. ActionView::Base.new(template_path, assigns, self)
  443. end
  444. def sort_parts(parts, order = [])
  445. order = order.collect { |s| s.downcase }
  446. parts = parts.sort do |a, b|
  447. a_ct = a.content_type.downcase
  448. b_ct = b.content_type.downcase
  449. a_in = order.include? a_ct
  450. b_in = order.include? b_ct
  451. s = case
  452. when a_in && b_in
  453. order.index(a_ct) <=> order.index(b_ct)
  454. when a_in
  455. -1
  456. when b_in
  457. 1
  458. else
  459. a_ct <=> b_ct
  460. end
  461. # reverse the ordering because parts that come last are displayed
  462. # first in mail clients
  463. (s * -1)
  464. end
  465. parts
  466. end
  467. def create_mail
  468. m = TMail::Mail.new
  469. m.subject, = quote_any_if_necessary(charset, subject)
  470. m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
  471. m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
  472. m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
  473. m.mime_version = mime_version unless mime_version.nil?
  474. m.date = sent_on.to_time rescue sent_on if sent_on
  475. headers.each { |k, v| m[k] = v }
  476. real_content_type, ctype_attrs = parse_content_type
  477. if @parts.empty?
  478. m.set_content_type(real_content_type, nil, ctype_attrs)
  479. m.body = Utils.normalize_new_lines(body)
  480. else
  481. if String === body
  482. part = TMail::Mail.new
  483. part.body = Utils.normalize_new_lines(body)
  484. part.set_content_type(real_content_type, nil, ctype_attrs)
  485. part.set_content_disposition "inline"
  486. m.parts << part
  487. end
  488. @parts.each do |p|
  489. part = (TMail::Mail === p ? p : p.to_mail(self))
  490. m.parts << part
  491. end
  492. if real_content_type =~ /multipart/
  493. ctype_attrs.delete "charset"
  494. m.set_content_type(real_content_type, nil, ctype_attrs)
  495. end
  496. end
  497. @mail = m
  498. end
  499. def perform_delivery_smtp(mail)
  500. destinations = mail.destinations
  501. mail.ready_to_send
  502. Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
  503. server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
  504. smtp.sendmail(mail.encoded, mail.from, destinations)
  505. end
  506. end
  507. def perform_delivery_sendmail(mail)
  508. IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
  509. sm.print(mail.encoded.gsub(/\r/, ''))
  510. sm.flush
  511. end
  512. end
  513. def perform_delivery_test(mail)
  514. deliveries << mail
  515. end
  516. end
  517. end
  518. module ActionMailer
  519. module Helpers #:nodoc:
  520. def self.append_features(base) #:nodoc:
  521. super
  522. # Initialize the base module to aggregate its helpers.
  523. base.class_inheritable_accessor :master_helper_module
  524. base.master_helper_module = Module.new
  525. # Extend base with class methods to declare helpers.
  526. base.extend(ClassMethods)
  527. base.class_eval do
  528. # Wrap inherited to create a new master helper module for subclasses.
  529. class << self
  530. alias_method :inherited_without_helper, :inherited
  531. alias_method :inherited, :inherited_with_helper
  532. end
  533. # Wrap initialize_template_class to extend new template class
  534. # instances with the master helper module.
  535. alias_method :initialize_template_class_without_helper, :initialize_template_class
  536. alias_method :initialize_template_class, :initialize_template_class_with_helper
  537. end
  538. end
  539. module ClassMethods
  540. # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
  541. # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
  542. # available to the templates.
  543. def add_template_helper(helper_module) #:nodoc:
  544. master_helper_module.module_eval "include #{helper_module}"
  545. end
  546. # Declare a helper:
  547. # helper :foo
  548. # requires 'foo_helper' and includes FooHelper in the template class.
  549. # helper FooHelper
  550. # includes FooHelper in the template class.
  551. # helper { def foo() "#{bar} is the very best" end }
  552. # evaluates the block in the template class, adding method #foo.
  553. # helper(:three, BlindHelper) { def mice() 'mice' end }
  554. # does all three.
  555. def helper(*args, &block)
  556. args.flatten.each do |arg|
  557. case arg
  558. when Module
  559. add_template_helper(arg)
  560. when String, Symbol
  561. file_name = arg.to_s.underscore + '_helper'
  562. class_name = file_name.camelize
  563. begin
  564. require_dependency(file_name)
  565. rescue LoadError => load_error
  566. requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
  567. msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
  568. raise LoadError.new(msg).copy_blame!(load_error)
  569. end
  570. add_template_helper(class_name.constantize)
  571. else
  572. raise ArgumentError, 'helper expects String, Symbol, or Module argument'
  573. end
  574. end
  575. # Evaluate block in template class if given.
  576. master_helper_module.module_eval(&block) if block_given?
  577. end
  578. # Declare a controller method as a helper. For example,
  579. # helper_method :link_to
  580. # def link_to(name, options) ... end
  581. # makes the link_to controller method available in the view.
  582. def helper_method(*methods)
  583. methods.flatten.each do |method|
  584. master_helper_module.module_eval <<-end_eval
  585. def #{method}(*args, &block)
  586. controller.send(%(#{method}), *args, &block)
  587. end
  588. end_eval
  589. end
  590. end
  591. # Declare a controller attribute as a helper. For example,
  592. # helper_attr :name
  593. # attr_accessor :name
  594. # makes the name and name= controller methods available in the view.
  595. # The is a convenience wrapper for helper_method.
  596. def helper_attr(*attrs)
  597. attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
  598. end
  599. private
  600. def inherited_with_helper(child)
  601. inherited_without_helper(child)
  602. begin
  603. child.master_helper_module = Module.new
  604. child.master_helper_module.send :include, master_helper_module
  605. child.helper child.name.underscore
  606. rescue MissingSourceFile => e
  607. raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
  608. end
  609. end
  610. end
  611. private
  612. # Extend the template class instance with our controller's helper module.
  613. def initialize_template_class_with_helper(assigns)
  614. returning(template = initialize_template_class_without_helper(assigns)) do
  615. template.extend self.class.master_helper_module
  616. end
  617. end
  618. end
  619. endrequire 'text/format'
  620. module MailHelper
  621. # Uses Text::Format to take the text and format it, indented two spaces for
  622. # each line, and wrapped at 72 columns.
  623. def block_format(text)
  624. formatted = text.split(/\n\r\n/).collect { |paragraph|
  625. Text::Format.new(
  626. :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
  627. ).format
  628. }.join("\n")
  629. # Make list points stand on their own line
  630. formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
  631. formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
  632. formatted
  633. end
  634. end
  635. require 'action_mailer/adv_attr_accessor'
  636. require 'action_mailer/part_container'
  637. require 'action_mailer/utils'
  638. module ActionMailer
  639. # Represents a subpart of an email message. It shares many similar
  640. # attributes of ActionMailer::Base. Although you can create parts manually
  641. # and add them to the #parts list of the mailer, it is easier
  642. # to use the helper methods in ActionMailer::PartContainer.
  643. class Part
  644. include ActionMailer::AdvAttrAccessor
  645. include ActionMailer::PartContainer
  646. # Represents the body of the part, as a string. This should not be a
  647. # Hash (like ActionMailer::Base), but if you want a template to be rendered
  648. # into the body of a subpart you can do it with the mailer's #render method
  649. # and assign the result here.
  650. adv_attr_accessor :body
  651. # Specify the charset for this subpart. By default, it will be the charset
  652. # of the containing part or mailer.
  653. adv_attr_accessor :charset
  654. # The content disposition of this part, typically either "inline" or
  655. # "attachment".
  656. adv_attr_accessor :content_disposition
  657. # The content type of the part.
  658. adv_attr_accessor :content_type
  659. # The filename to use for this subpart (usually for attachments).
  660. adv_attr_accessor :filename
  661. # Accessor for specifying additional headers to include with this part.
  662. adv_attr_accessor :headers
  663. # The transfer encoding to use for this subpart, like "base64" or
  664. # "quoted-printable".
  665. adv_attr_accessor :transfer_encoding
  666. # Create a new part from the given +params+ hash. The valid params keys
  667. # correspond to the accessors.
  668. def initialize(params)
  669. @content_type = params[:content_type]
  670. @content_disposition = params[:disposition] || "inline"
  671. @charset = params[:charset]
  672. @body = params[:body]
  673. @filename = params[:filename]
  674. @transfer_encoding = params[:transfer_encoding] || "quoted-printable"
  675. @headers = params[:headers] || {}
  676. @parts = []
  677. end
  678. # Convert the part to a mail object which can be included in the parts
  679. # list of another mail object.
  680. def to_mail(defaults)
  681. part = TMail::Mail.new
  682. real_content_type, ctype_attrs = parse_content_type(defaults)
  683. if @parts.empty?
  684. part.content_transfer_encoding = transfer_encoding || "quoted-printable"
  685. case (transfer_encoding || "").downcase
  686. when "base64" then
  687. part.body = TMail::Base64.folding_encode(body)
  688. when "quoted-printable"
  689. part.body = [Utils.normalize_new_lines(body)].pack("M*")
  690. else
  691. part.body = body
  692. end
  693. # Always set the content_type after setting the body and or parts!
  694. # Also don't set filename and name when there is none (like in
  695. # non-attachment parts)
  696. if content_disposition == "attachment"
  697. ctype_attrs.delete "charset"
  698. part.set_content_type(real_content_type, nil,
  699. squish("name" => filename).merge(ctype_attrs))
  700. part.set_content_disposition(content_disposition,
  701. squish("filename" => filename).merge(ctype_attrs))
  702. else
  703. part.set_content_type(real_content_type, nil, ctype_attrs)
  704. part.set_content_disposition(content_disposition)
  705. end
  706. else
  707. if String === body
  708. part = TMail::Mail.new
  709. part.body = body
  710. part.set_content_type(real_content_type, nil, ctype_attrs)
  711. part.set_content_disposition "inline"
  712. m.parts << part
  713. end
  714. @parts.each do |p|
  715. prt = (TMail::Mail === p ? p : p.to_mail(defaults))
  716. part.parts << prt
  717. end
  718. part.set_content_type(real_content_type, nil, ctype_attrs) if real_content_type =~ /multipart/
  719. end
  720. headers.each { |k,v| part[k] = v }
  721. part
  722. end
  723. private
  724. def squish(values={})
  725. values.delete_if { |k,v| v.nil? }
  726. end
  727. end
  728. end
  729. module ActionMailer
  730. # Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
  731. # in common. Using these helpers you can easily add subparts or attachments
  732. # to your message:
  733. #
  734. # def my_mail_message(...)
  735. # ...
  736. # part "text/plain" do |p|
  737. # p.body "hello, world"
  738. # p.transfer_encoding "base64"
  739. # end
  740. #
  741. # attachment "image/jpg" do |a|
  742. # a.body = File.read("hello.jpg")
  743. # a.filename = "hello.jpg"
  744. # end
  745. # end
  746. module PartContainer
  747. # The list of subparts of this container
  748. attr_reader :parts
  749. # Add a part to a multipart message, with the given content-type. The
  750. # part itself is yielded to the block so that other properties (charset,
  751. # body, headers, etc.) can be set on it.
  752. def part(params)
  753. params = {:content_type => params} if String === params
  754. part = Part.new(params)
  755. yield part if block_given?
  756. @parts << part
  757. end
  758. # Add an attachment to a multipart message. This is simply a part with the
  759. # content-disposition set to "attachment".
  760. def attachment(params, &block)
  761. params = { :content_type => params } if String === params
  762. params = { :disposition => "attachment",
  763. :transfer_encoding => "base64" }.merge(params)
  764. part(params, &block)
  765. end
  766. private
  767. def parse_content_type(defaults=nil)
  768. return [defaults && defaults.content_type, {}] if content_type.blank?
  769. ctype, *attrs = content_type.split(/;\s*/)
  770. attrs = attrs.inject({}) { |h,s| k,v = s.split(/=/, 2); h[k] = v; h }
  771. [ctype, {"charset" => charset || defaults && defaults.charset}.merge(attrs)]
  772. end
  773. end
  774. end
  775. module ActionMailer
  776. module Quoting #:nodoc:
  777. # Convert the given text into quoted printable format, with an instruction
  778. # that the text be eventually interpreted in the given charset.
  779. def quoted_printable(text, charset)
  780. text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
  781. gsub( / /, "_" )
  782. "=?#{charset}?Q?#{text}?="
  783. end
  784. # Convert the given character to quoted printable format, taking into
  785. # account multi-byte characters (if executing with $KCODE="u", for instance)
  786. def quoted_printable_encode(character)
  787. result = ""
  788. character.each_byte { |b| result << "=%02x" % b }
  789. result
  790. end
  791. # A quick-and-dirty regexp for determining whether a string contains any
  792. # characters that need escaping.
  793. if !defined?(CHARS_NEEDING_QUOTING)
  794. CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
  795. end
  796. # Quote the given text if it contains any "illegal" characters
  797. def quote_if_necessary(text, charset)
  798. (text =~ CHARS_NEEDING_QUOTING) ?
  799. quoted_printable(text, charset) :
  800. text
  801. end
  802. # Quote any of the given strings if they contain any "illegal" characters
  803. def quote_any_if_necessary(charset, *args)
  804. args.map { |v| quote_if_necessary(v, charset) }
  805. end
  806. # Quote the given address if it needs to be. The address may be a
  807. # regular email address, or it can be a phrase followed by an address in
  808. # brackets. The phrase is the only part that will be quoted, and only if
  809. # it needs to be. This allows extended characters to be used in the
  810. # "to", "from", "cc", and "bcc" headers.
  811. def quote_address_if_necessary(address, charset)
  812. if Array === address
  813. address.map { |a| quote_address_if_necessary(a, charset) }
  814. elsif address =~ /^(\S.*)\s+(<.*>)$/
  815. address = $2
  816. phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
  817. "\"#{phrase}\" #{address}"
  818. else
  819. address
  820. end
  821. end
  822. # Quote any of the given addresses, if they need to be.
  823. def quote_any_address_if_necessary(charset, *args)
  824. args.map { |v| quote_address_if_necessary(v, charset) }
  825. end
  826. end
  827. end
  828. module ActionMailer
  829. module Utils #:nodoc:
  830. def normalize_new_lines(text)
  831. text.to_s.gsub(/\r\n?/, "\n")
  832. end
  833. module_function :normalize_new_lines
  834. end
  835. end
  836. #--
  837. # Text::Format for Ruby
  838. # Version 0.63
  839. #
  840. # Copyright (c) 2002 - 2003 Austin Ziegler
  841. #
  842. # $Id: format.rb,v 1.1.1.1 2004/10/14 11:59:57 webster132 Exp $
  843. #
  844. # ==========================================================================
  845. # Revision History ::
  846. # YYYY.MM.DD Change ID Developer
  847. # Description
  848. # --------------------------------------------------------------------------
  849. # 2002.10.18 Austin Ziegler
  850. # Fixed a minor problem with tabs not being counted. Changed
  851. # abbreviations from Hash to Array to better suit Ruby's
  852. # capabilities. Fixed problems with the way that Array arguments
  853. # are handled in calls to the major object types, excepting in
  854. # Text::Format#expand and Text::Format#unexpand (these will
  855. # probably need to be fixed).
  856. # 2002.10.30 Austin Ziegler
  857. # Fixed the ordering of the <=> for binary tests. Fixed
  858. # Text::Format#expand and Text::Format#unexpand to handle array
  859. # arguments better.
  860. # 2003.01.24 Austin Ziegler
  861. # Fixed a problem with Text::Format::RIGHT_FILL handling where a
  862. # single word is larger than #columns. Removed Comparable
  863. # capabilities (<=> doesn't make sense; == does). Added Symbol
  864. # equivalents for the Hash initialization. Hash initialization has
  865. # been modified so that values are set as follows (Symbols are
  866. # highest priority; strings are middle; defaults are lowest):
  867. # @columns = arg[:columns] || arg['columns'] || @columns
  868. # Added #hard_margins, #split_rules, #hyphenator, and #split_words.
  869. # 2003.02.07 Austin Ziegler
  870. # Fixed the installer for proper case-sensitive handling.
  871. # 2003.03.28 Austin Ziegler
  872. # Added the ability for a hyphenator to receive the formatter
  873. # object. Fixed a bug for strings matching /\A\s*\Z/ failing
  874. # entirely. Fixed a test case failing under 1.6.8.
  875. # 2003.04.04 Austin Ziegler
  876. # Handle the case of hyphenators returning nil for first/rest.
  877. # 2003.09.17 Austin Ziegler
  878. # Fixed a problem where #paragraphs(" ") was raising
  879. # NoMethodError.
  880. #
  881. # ==========================================================================
  882. #++
  883. module Text #:nodoc:
  884. # Text::Format for Ruby is copyright 2002 - 2005 by Austin Ziegler. It
  885. # is available under Ruby's licence, the Perl Artistic licence, or the
  886. # GNU GPL version 2 (or at your option, any later version). As a
  887. # special exception, for use with official Rails (provided by the
  888. # rubyonrails.org development team) and any project created with
  889. # official Rails, the following alternative MIT-style licence may be
  890. # used:
  891. #
  892. # == Text::Format Licence for Rails and Rails Applications
  893. # Permission is hereby granted, free of charge, to any person
  894. # obtaining a copy of this software and associated documentation files
  895. # (the "Software"), to deal in the Software without restriction,
  896. # including without limitation the rights to use, copy, modify, merge,
  897. # publish, distribute, sublicense, and/or sell copies of the Software,
  898. # and to permit persons to whom the Software is furnished to do so,
  899. # subject to the following conditions:
  900. #
  901. # * The names of its contributors may not be used to endorse or
  902. # promote products derived from this software without specific prior
  903. # written permission.
  904. #
  905. # The above copyright notice and this permission notice shall be
  906. # included in all copies or substantial portions of the Software.
  907. #
  908. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  909. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  910. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  911. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
  912. # BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
  913. # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  914. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  915. # SOFTWARE.
  916. class Format
  917. VERSION = '0.63'
  918. # Local abbreviations. More can be added with Text::Format.abbreviations
  919. ABBREV = [ 'Mr', 'Mrs', 'Ms', 'Jr', 'Sr' ]
  920. # Formatting values
  921. LEFT_ALIGN = 0
  922. RIGHT_ALIGN = 1
  923. RIGHT_FILL = 2
  924. JUSTIFY = 3
  925. # Word split modes (only applies when #hard_margins is true).
  926. SPLIT_FIXED = 1
  927. SPLIT_CONTINUATION = 2
  928. SPLIT_HYPHENATION = 4
  929. SPLIT_CONTINUATION_FIXED = SPLIT_CONTINUATION | SPLIT_FIXED
  930. SPLIT_HYPHENATION_FIXED = SPLIT_HYPHENATION | SPLIT_FIXED
  931. SPLIT_HYPHENATION_CONTINUATION = SPLIT_HYPHENATION | SPLIT_CONTINUATION
  932. SPLIT_ALL = SPLIT_HYPHENATION | SPLIT_CONTINUATION | SPLIT_FIXED
  933. # Words forcibly split by Text::Format will be stored as split words.
  934. # This class represents a word forcibly split.
  935. class SplitWord
  936. # The word that was split.
  937. attr_reader :word
  938. # The first part of the word that was split.
  939. attr_reader :first
  940. # The remainder of the word that was split.
  941. attr_reader :rest
  942. def initialize(word, first, rest) #:nodoc:
  943. @word = word
  944. @first = first
  945. @rest = rest
  946. end
  947. end
  948. private
  949. LEQ_RE = /[.?!]['"]?$/
  950. def brk_re(i) #:nodoc:
  951. %r/((?:\S+\s+){#{i}})(.+)/
  952. end
  953. def posint(p) #:nodoc:
  954. p.to_i.abs
  955. end
  956. public
  957. # Compares two Text::Format objects. All settings of the objects are
  958. # compared *except* #hyphenator. Generated results (e.g., #split_words)
  959. # are not compared, either.
  960. def ==(o)
  961. (@text == o.text) &&
  962. (@columns == o.columns) &&
  963. (@left_margin == o.left_margin) &&
  964. (@right_margin == o.right_margin) &&
  965. (@hard_margins == o.hard_margins) &&
  966. (@split_rules == o.split_rules) &&
  967. (@first_indent == o.first_indent) &&
  968. (@body_indent == o.body_indent) &&
  969. (@tag_text == o.tag_text) &&
  970. (@tabstop == o.tabstop) &&
  971. (@format_style == o.format_style) &&
  972. (@extra_space == o.extra_space) &&
  973. (@tag_paragraph == o.tag_paragraph) &&
  974. (@nobreak == o.nobreak) &&
  975. (@abbreviations == o.abbreviations) &&
  976. (@nobreak_regex == o.nobreak_regex)
  977. end
  978. # The text to be manipulated. Note that value is optional, but if the
  979. # formatting functions are called without values, this text is what will
  980. # be formatted.
  981. #
  982. # *Default*:: <tt>[]</tt>
  983. # <b>Used in</b>:: All methods
  984. attr_accessor :text
  985. # The total width of the format area. The margins, indentation, and text
  986. # are formatted into this space.
  987. #
  988. # COLUMNS
  989. # <-------------------------------------------------------------->
  990. # <-----------><------><---------------------------><------------>
  991. # left margin indent text is formatted into here right margin
  992. #
  993. # *Default*:: <tt>72</tt>
  994. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  995. # <tt>#center</tt>
  996. attr_reader :columns
  997. # The total width of the format area. The margins, indentation, and text
  998. # are formatted into this space. The value provided is silently
  999. # converted to a positive integer.
  1000. #
  1001. # COLUMNS
  1002. # <-------------------------------------------------------------->
  1003. # <-----------><------><---------------------------><------------>
  1004. # left margin indent text is formatted into here right margin
  1005. #
  1006. # *Default*:: <tt>72</tt>
  1007. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1008. # <tt>#center</tt>
  1009. def columns=(c)
  1010. @columns = posint(c)
  1011. end
  1012. # The number of spaces used for the left margin.
  1013. #
  1014. # columns
  1015. # <-------------------------------------------------------------->
  1016. # <-----------><------><---------------------------><------------>
  1017. # LEFT MARGIN indent text is formatted into here right margin
  1018. #
  1019. # *Default*:: <tt>0</tt>
  1020. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1021. # <tt>#center</tt>
  1022. attr_reader :left_margin
  1023. # The number of spaces used for the left margin. The value provided is
  1024. # silently converted to a positive integer value.
  1025. #
  1026. # columns
  1027. # <-------------------------------------------------------------->
  1028. # <-----------><------><---------------------------><------------>
  1029. # LEFT MARGIN indent text is formatted into here right margin
  1030. #
  1031. # *Default*:: <tt>0</tt>
  1032. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1033. # <tt>#center</tt>
  1034. def left_margin=(left)
  1035. @left_margin = posint(left)
  1036. end
  1037. # The number of spaces used for the right margin.
  1038. #
  1039. # columns
  1040. # <-------------------------------------------------------------->
  1041. # <-----------><------><---------------------------><------------>
  1042. # left margin indent text is formatted into here RIGHT MARGIN
  1043. #
  1044. # *Default*:: <tt>0</tt>
  1045. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1046. # <tt>#center</tt>
  1047. attr_reader :right_margin
  1048. # The number of spaces used for the right margin. The value provided is
  1049. # silently converted to a positive integer value.
  1050. #
  1051. # columns
  1052. # <-------------------------------------------------------------->
  1053. # <-----------><------><---------------------------><------------>
  1054. # left margin indent text is formatted into here RIGHT MARGIN
  1055. #
  1056. # *Default*:: <tt>0</tt>
  1057. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
  1058. # <tt>#center</tt>
  1059. def right_margin=(r)
  1060. @right_margin = posint(r)
  1061. end
  1062. # The number of spaces to indent the first line of a paragraph.
  1063. #
  1064. # columns
  1065. # <-------------------------------------------------------------->
  1066. # <-----------><------><---------------------------><------------>
  1067. # left margin INDENT text is formatted into here right margin
  1068. #
  1069. # *Default*:: <tt>4</tt>
  1070. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1071. attr_reader :first_indent
  1072. # The number of spaces to indent the first line of a paragraph. The
  1073. # value provided is silently converted to a positive integer value.
  1074. #
  1075. # columns
  1076. # <-------------------------------------------------------------->
  1077. # <-----------><------><---------------------------><------------>
  1078. # left margin INDENT text is formatted into here right margin
  1079. #
  1080. # *Default*:: <tt>4</tt>
  1081. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1082. def first_indent=(f)
  1083. @first_indent = posint(f)
  1084. end
  1085. # The number of spaces to indent all lines after the first line of a
  1086. # paragraph.
  1087. #
  1088. # columns
  1089. # <-------------------------------------------------------------->
  1090. # <-----------><------><---------------------------><------------>
  1091. # left margin INDENT text is formatted into here right margin
  1092. #
  1093. # *Default*:: <tt>0</tt>
  1094. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1095. attr_reader :body_indent
  1096. # The number of spaces to indent all lines after the first line of
  1097. # a paragraph. The value provided is silently converted to a
  1098. # positive integer value.
  1099. #
  1100. # columns
  1101. # <-------------------------------------------------------------->
  1102. # <-----------><------><---------------------------><------------>
  1103. # left margin INDENT text is formatted into here right margin
  1104. #
  1105. # *Default*:: <tt>0</tt>
  1106. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1107. def body_indent=(b)
  1108. @body_indent = posint(b)
  1109. end
  1110. # Normally, words larger than the format area will be placed on a line
  1111. # by themselves. Setting this to +true+ will force words larger than the
  1112. # format area to be split into one or more "words" each at most the size
  1113. # of the format area. The first line and the original word will be
  1114. # placed into <tt>#split_words</tt>. Note that this will cause the
  1115. # output to look *similar* to a #format_style of JUSTIFY. (Lines will be
  1116. # filled as much as possible.)
  1117. #
  1118. # *Default*:: +false+
  1119. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1120. attr_accessor :hard_margins
  1121. # An array of words split during formatting if #hard_margins is set to
  1122. # +true+.
  1123. # #split_words << Text::Format::SplitWord.new(word, first, rest)
  1124. attr_reader :split_words
  1125. # The object responsible for hyphenating. It must respond to
  1126. # #hyphenate_to(word, size) or #hyphenate_to(word, size, formatter) and
  1127. # return an array of the word split into two parts; if there is a
  1128. # hyphenation mark to be applied, responsibility belongs to the
  1129. # hyphenator object. The size is the MAXIMUM size permitted, including
  1130. # any hyphenation marks. If the #hyphenate_to method has an arity of 3,
  1131. # the formatter will be provided to the method. This allows the
  1132. # hyphenator to make decisions about the hyphenation based on the
  1133. # formatting rules.
  1134. #
  1135. # *Default*:: +nil+
  1136. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1137. attr_reader :hyphenator
  1138. # The object responsible for hyphenating. It must respond to
  1139. # #hyphenate_to(word, size) and return an array of the word hyphenated
  1140. # into two parts. The size is the MAXIMUM size permitted, including any
  1141. # hyphenation marks.
  1142. #
  1143. # *Default*:: +nil+
  1144. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1145. def hyphenator=(h)
  1146. raise ArgumentError, "#{h.inspect} is not a valid hyphenator." unless h.respond_to?(:hyphenate_to)
  1147. arity = h.method(:hyphenate_to).arity
  1148. raise ArgumentError, "#{h.inspect} must have exactly two or three arguments." unless [2, 3].include?(arity)
  1149. @hyphenator = h
  1150. @hyphenator_arity = arity
  1151. end
  1152. # Specifies the split mode; used only when #hard_margins is set to
  1153. # +true+. Allowable values are:
  1154. # [+SPLIT_FIXED+] The word will be split at the number of
  1155. # characters needed, with no marking at all.
  1156. # repre
  1157. # senta
  1158. # ion
  1159. # [+SPLIT_CONTINUATION+] The word will be split at the number of
  1160. # characters needed, with a C-style continuation
  1161. # character. If a word is the only item on a
  1162. # line and it cannot be split into an
  1163. # appropriate size, SPLIT_FIXED will be used.
  1164. # repr\
  1165. # esen\
  1166. # tati\
  1167. # on
  1168. # [+SPLIT_HYPHENATION+] The word will be split according to the
  1169. # hyphenator specified in #hyphenator. If there
  1170. # is no #hyphenator specified, works like
  1171. # SPLIT_CONTINUATION. The example is using
  1172. # TeX::Hyphen. If a word is the only item on a
  1173. # line and it cannot be split into an
  1174. # appropriate size, SPLIT_CONTINUATION mode will
  1175. # be used.
  1176. # rep-
  1177. # re-
  1178. # sen-
  1179. # ta-
  1180. # tion
  1181. #
  1182. # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
  1183. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1184. attr_reader :split_rules
  1185. # Specifies the split mode; used only when #hard_margins is set to
  1186. # +true+. Allowable values are:
  1187. # [+SPLIT_FIXED+] The word will be split at the number of
  1188. # characters needed, with no marking at all.
  1189. # repre
  1190. # senta
  1191. # ion
  1192. # [+SPLIT_CONTINUATION+] The word will be split at the number of
  1193. # characters needed, with a C-style continuation
  1194. # character.
  1195. # repr\
  1196. # esen\
  1197. # tati\
  1198. # on
  1199. # [+SPLIT_HYPHENATION+] The word will be split according to the
  1200. # hyphenator specified in #hyphenator. If there
  1201. # is no #hyphenator specified, works like
  1202. # SPLIT_CONTINUATION. The example is using
  1203. # TeX::Hyphen as the #hyphenator.
  1204. # rep-
  1205. # re-
  1206. # sen-
  1207. # ta-
  1208. # tion
  1209. #
  1210. # These values can be bitwise ORed together (e.g., <tt>SPLIT_FIXED |
  1211. # SPLIT_CONTINUATION</tt>) to provide fallback split methods. In the
  1212. # example given, an attempt will be made to split the word using the
  1213. # rules of SPLIT_CONTINUATION; if there is not enough room, the word
  1214. # will be split with the rules of SPLIT_FIXED. These combinations are
  1215. # also available as the following values:
  1216. # * +SPLIT_CONTINUATION_FIXED+
  1217. # * +SPLIT_HYPHENATION_FIXED+
  1218. # * +SPLIT_HYPHENATION_CONTINUATION+
  1219. # * +SPLIT_ALL+
  1220. #
  1221. # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
  1222. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1223. def split_rules=(s)
  1224. raise ArgumentError, "Invalid value provided for split_rules." if ((s < SPLIT_FIXED) || (s > SPLIT_ALL))
  1225. @split_rules = s
  1226. end
  1227. # Indicates whether sentence terminators should be followed by a single
  1228. # space (+false+), or two spaces (+true+).
  1229. #
  1230. # *Default*:: +false+
  1231. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1232. attr_accessor :extra_space
  1233. # Defines the current abbreviations as an array. This is only used if
  1234. # extra_space is turned on.
  1235. #
  1236. # If one is abbreviating "President" as "Pres." (abbreviations =
  1237. # ["Pres"]), then the results of formatting will be as illustrated in
  1238. # the table below:
  1239. #
  1240. # extra_space | include? | !include?
  1241. # true | Pres. Lincoln | Pres. Lincoln
  1242. # false | Pres. Lincoln | Pres. Lincoln
  1243. #
  1244. # *Default*:: <tt>{}</tt>
  1245. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1246. attr_accessor :abbreviations
  1247. # Indicates whether the formatting of paragraphs should be done with
  1248. # tagged paragraphs. Useful only with <tt>#tag_text</tt>.
  1249. #
  1250. # *Default*:: +false+
  1251. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1252. attr_accessor :tag_paragraph
  1253. # The array of text to be placed before each paragraph when
  1254. # <tt>#tag_paragraph</tt> is +true+. When <tt>#format()</tt> is called,
  1255. # only the first element of the array is used. When <tt>#paragraphs</tt>
  1256. # is called, then each entry in the array will be used once, with
  1257. # corresponding paragraphs. If the tag elements are exhausted before the
  1258. # text is exhausted, then the remaining paragraphs will not be tagged.
  1259. # Regardless of indentation settings, a blank line will be inserted
  1260. # between all paragraphs when <tt>#tag_paragraph</tt> is +true+.
  1261. #
  1262. # *Default*:: <tt>[]</tt>
  1263. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1264. attr_accessor :tag_text
  1265. # Indicates whether or not the non-breaking space feature should be
  1266. # used.
  1267. #
  1268. # *Default*:: +false+
  1269. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1270. attr_accessor :nobreak
  1271. # A hash which holds the regular expressions on which spaces should not
  1272. # be broken. The hash is set up such that the key is the first word and
  1273. # the value is the second word.
  1274. #
  1275. # For example, if +nobreak_regex+ contains the following hash:
  1276. #
  1277. # { '^Mrs?\.$' => '\S+$', '^\S+$' => '^(?:S|J)r\.$'}
  1278. #
  1279. # Then "Mr. Jones", "Mrs. Jones", and "Jones Jr." would not be broken.
  1280. # If this simple matching algorithm indicates that there should not be a
  1281. # break at the current end of line, then a backtrack is done until there
  1282. # are two words on which line breaking is permitted. If two such words
  1283. # are not found, then the end of the line will be broken *regardless*.
  1284. # If there is a single word on the current line, then no backtrack is
  1285. # done and the word is stuck on the end.
  1286. #
  1287. # *Default*:: <tt>{}</tt>
  1288. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1289. attr_accessor :nobreak_regex
  1290. # Indicates the number of spaces that a single tab represents.
  1291. #
  1292. # *Default*:: <tt>8</tt>
  1293. # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
  1294. # <tt>#paragraphs</tt>
  1295. attr_reader :tabstop
  1296. # Indicates the number of spaces that a single tab represents.
  1297. #
  1298. # *Default*:: <tt>8</tt>
  1299. # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
  1300. # <tt>#paragraphs</tt>
  1301. def tabstop=(t)
  1302. @tabstop = posint(t)
  1303. end
  1304. # Specifies the format style. Allowable values are:
  1305. # [+LEFT_ALIGN+] Left justified, ragged right.
  1306. # |A paragraph that is|
  1307. # |left aligned.|
  1308. # [+RIGHT_ALIGN+] Right justified, ragged left.
  1309. # |A paragraph that is|
  1310. # | right aligned.|
  1311. # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
  1312. # spaces. (Essentially the same as +LEFT_ALIGN+ except
  1313. # that lines are padded on the right.)
  1314. # |A paragraph that is|
  1315. # |left aligned. |
  1316. # [+JUSTIFY+] Fully justified, words filled to width by spaces,
  1317. # except the last line.
  1318. # |A paragraph that|
  1319. # |is justified.|
  1320. #
  1321. # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
  1322. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1323. attr_reader :format_style
  1324. # Specifies the format style. Allowable values are:
  1325. # [+LEFT_ALIGN+] Left justified, ragged right.
  1326. # |A paragraph that is|
  1327. # |left aligned.|
  1328. # [+RIGHT_ALIGN+] Right justified, ragged left.
  1329. # |A paragraph that is|
  1330. # | right aligned.|
  1331. # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
  1332. # spaces. (Essentially the same as +LEFT_ALIGN+ except
  1333. # that lines are padded on the right.)
  1334. # |A paragraph that is|
  1335. # |left aligned. |
  1336. # [+JUSTIFY+] Fully justified, words filled to width by spaces.
  1337. # |A paragraph that|
  1338. # |is justified.|
  1339. #
  1340. # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
  1341. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1342. def format_style=(fs)
  1343. raise ArgumentError, "Invalid value provided for format_style." if ((fs < LEFT_ALIGN) || (fs > JUSTIFY))
  1344. @format_style = fs
  1345. end
  1346. # Indicates that the format style is left alignment.
  1347. #
  1348. # *Default*:: +true+
  1349. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1350. def left_align?
  1351. return @format_style == LEFT_ALIGN
  1352. end
  1353. # Indicates that the format style is right alignment.
  1354. #
  1355. # *Default*:: +false+
  1356. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1357. def right_align?
  1358. return @format_style == RIGHT_ALIGN
  1359. end
  1360. # Indicates that the format style is right fill.
  1361. #
  1362. # *Default*:: +false+
  1363. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1364. def right_fill?
  1365. return @format_style == RIGHT_FILL
  1366. end
  1367. # Indicates that the format style is full justification.
  1368. #
  1369. # *Default*:: +false+
  1370. # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
  1371. def justify?
  1372. return @format_style == JUSTIFY
  1373. end
  1374. # The default implementation of #hyphenate_to implements
  1375. # SPLIT_CONTINUATION.
  1376. def hyphenate_to(word, size)
  1377. [word[0 .. (size - 2)] + "\\", word[(size - 1) .. -1]]
  1378. end
  1379. private
  1380. def __do_split_word(word, size) #:nodoc:
  1381. [word[0 .. (size - 1)], word[size .. -1]]
  1382. end
  1383. def __format(to_wrap) #:nodoc:
  1384. words = to_wrap.split(/\s+/).compact
  1385. words.shift if words[0].nil? or words[0].empty?
  1386. to_wrap = []
  1387. abbrev = false
  1388. width = @columns - @first_indent - @left_margin - @right_margin
  1389. indent_str = ' ' * @first_indent
  1390. first_line = true
  1391. line = words.shift
  1392. abbrev = __is_abbrev(line) unless line.nil? || line.empty?
  1393. while w = words.shift
  1394. if (w.size + line.size < (width - 1)) ||
  1395. ((line !~ LEQ_RE || abbrev) && (w.size + line.size < width))
  1396. line << " " if (line =~ LEQ_RE) && (not abbrev)
  1397. line << " #{w}"
  1398. else
  1399. line, w = __do_break(line, w) if @nobreak
  1400. line, w = __do_hyphenate(line, w, width) if @hard_margins
  1401. if w.index(/\s+/)
  1402. w, *w2 = w.split(/\s+/)
  1403. words.unshift(w2)
  1404. words.flatten!
  1405. end
  1406. to_wrap << __make_line(line, indent_str, width, w.nil?) unless line.nil?
  1407. if first_line
  1408. first_line = false
  1409. width = @columns - @body_indent - @left_margin - @right_margin
  1410. indent_str = ' ' * @body_indent
  1411. end
  1412. line = w
  1413. end
  1414. abbrev = __is_abbrev(w) unless w.nil?
  1415. end
  1416. loop do
  1417. break if line.nil? or line.empty?
  1418. line, w = __do_hyphenate(line, w, width) if @hard_margins
  1419. to_wrap << __make_line(line, indent_str, width, w.nil?)
  1420. line = w
  1421. end
  1422. if (@tag_paragraph && (to_wrap.size > 0)) then
  1423. clr = %r{`(\w+)'}.match([caller(1)].flatten[0])[1]
  1424. clr = "" if clr.nil?
  1425. if ((not @tag_text[0].nil?) && (@tag_cur.size < 1) &&
  1426. (clr != "__paragraphs")) then
  1427. @tag_cur = @tag_text[0]
  1428. end
  1429. fchar = /(\S)/.match(to_wrap[0])[1]
  1430. white = to_wrap[0].index(fchar)
  1431. if ((white - @left_margin - 1) > @tag_cur.size) then
  1432. white = @tag_cur.size + @left_margin
  1433. to_wrap[0].gsub!(/^ {#{white}}/, "#{' ' * @left_margin}#{@tag_cur}")
  1434. else
  1435. to_wrap.unshift("#{' ' * @left_margin}#{@tag_cur}\n")
  1436. end
  1437. end
  1438. to_wrap.join('')
  1439. end
  1440. # format lines in text into paragraphs with each element of @wrap a
  1441. # paragraph; uses Text::Format.format for the formatting
  1442. def __paragraphs(to_wrap) #:nodoc:
  1443. if ((@first_indent == @body_indent) || @tag_paragraph) then
  1444. p_end = "\n"
  1445. else
  1446. p_end = ''
  1447. end
  1448. cnt = 0
  1449. ret = []
  1450. to_wrap.each do |tw|
  1451. @tag_cur = @tag_text[cnt] if @tag_paragraph
  1452. @tag_cur = '' if @tag_cur.nil?
  1453. line = __format(tw)
  1454. ret << "#{line}#{p_end}" if (not line.nil?) && (line.size > 0)
  1455. cnt += 1
  1456. end
  1457. ret[-1].chomp! unless ret.empty?
  1458. ret.join('')
  1459. end
  1460. # center text using spaces on left side to pad it out empty lines
  1461. # are preserved
  1462. def __center(to_center) #:nodoc:
  1463. tabs = 0
  1464. width = @columns - @left_margin - @right_margin
  1465. centered = []
  1466. to_center.each do |tc|
  1467. s = tc.strip
  1468. tabs = s.count("\t")
  1469. tabs = 0 if tabs.nil?
  1470. ct = ((width - s.size - (tabs * @tabstop) + tabs) / 2)
  1471. ct = (width - @left_margin - @right_margin) - ct
  1472. centered << "#{s.rjust(ct)}\n"
  1473. end
  1474. centered.join('')
  1475. end
  1476. # expand tabs to spaces should be similar to Text::Tabs::expand
  1477. def __expand(to_expand) #:nodoc:
  1478. expanded = []
  1479. to_expand.split("\n").each { |te| expanded << te.gsub(/\t/, ' ' * @tabstop) }
  1480. expanded.join('')
  1481. end
  1482. def __unexpand(to_unexpand) #:nodoc:
  1483. unexpanded = []
  1484. to_unexpand.split("\n").each { |tu| unexpanded << tu.gsub(/ {#{@tabstop}}/, "\t") }
  1485. unexpanded.join('')
  1486. end
  1487. def __is_abbrev(word) #:nodoc:
  1488. # remove period if there is one.
  1489. w = word.gsub(/\.$/, '') unless word.nil?
  1490. return true if (!@extra_space || ABBREV.include?(w) || @abbreviations.include?(w))
  1491. false
  1492. end
  1493. def __make_line(line, indent, width, last = false) #:nodoc:
  1494. lmargin = " " * @left_margin
  1495. fill = " " * (width - line.size) if right_fill? && (line.size <= width)
  1496. if (justify? && ((not line.nil?) && (not line.empty?)) && line =~ /\S+\s+\S+/ && !last)
  1497. spaces = width - line.size
  1498. words = line.split(/(\s+)/)
  1499. ws = spaces / (words.size / 2)
  1500. spaces = spaces % (words.size / 2) if ws > 0
  1501. words.reverse.each do |rw|
  1502. next if (rw =~ /^\S/)
  1503. rw.sub!(/^/, " " * ws)
  1504. next unless (spaces > 0)
  1505. rw.sub!(/^/, " ")
  1506. spaces -= 1
  1507. end
  1508. line = words.join('')
  1509. end
  1510. line = "#{lmargin}#{indent}#{line}#{fill}\n" unless line.nil?
  1511. if right_align? && (not line.nil?)
  1512. line.sub(/^/, " " * (@columns - @right_margin - (line.size - 1)))
  1513. else
  1514. line
  1515. end
  1516. end
  1517. def __do_hyphenate(line, next_line, width) #:nodoc:
  1518. rline = line.dup rescue line
  1519. rnext = next_line.dup rescue next_line
  1520. loop do
  1521. if rline.size == width
  1522. break
  1523. elsif rline.size > width
  1524. words = rline.strip.split(/\s+/)
  1525. word = words[-1].dup
  1526. size = width - rline.size + word.size
  1527. if (size <= 0)
  1528. words[-1] = nil
  1529. rline = words.join(' ').strip
  1530. rnext = "#{word} #{rnext}".strip
  1531. next
  1532. end
  1533. first = rest = nil
  1534. if ((@split_rules & SPLIT_HYPHENATION) != 0)
  1535. if @hyphenator_arity == 2
  1536. first, rest = @hyphenator.hyphenate_to(word, size)
  1537. else
  1538. first, rest = @hyphenator.hyphenate_to(word, size, self)
  1539. end
  1540. end
  1541. if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
  1542. first, rest = self.hyphenate_to(word, size)
  1543. end
  1544. if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
  1545. first.nil? or @split_rules == SPLIT_FIXED
  1546. first, rest = __do_split_word(word, size)
  1547. end
  1548. if first.nil?
  1549. words[-1] = nil
  1550. rest = word
  1551. else
  1552. words[-1] = first
  1553. @split_words << SplitWord.new(word, first, rest)
  1554. end
  1555. rline = words.join(' ').strip
  1556. rnext = "#{rest} #{rnext}".strip
  1557. break
  1558. else
  1559. break if rnext.nil? or rnext.empty? or rline.nil? or rline.empty?
  1560. words = rnext.split(/\s+/)
  1561. word = words.shift
  1562. size = width - rline.size - 1
  1563. if (size <= 0)
  1564. rnext = "#{word} #{words.join(' ')}".strip
  1565. break
  1566. end
  1567. first = rest = nil
  1568. if ((@split_rules & SPLIT_HYPHENATION) != 0)
  1569. if @hyphenator_arity == 2
  1570. first, rest = @hyphenator.hyphenate_to(word, size)
  1571. else
  1572. first, rest = @hyphenator.hyphenate_to(word, size, self)
  1573. end
  1574. end
  1575. first, rest = self.hyphenate_to(word, size) if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
  1576. first, rest = __do_split_word(word, size) if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
  1577. if (rline.size + (first ? first.size : 0)) < width
  1578. @split_words << SplitWord.new(word, first, rest)
  1579. rline = "#{rline} #{first}".strip
  1580. rnext = "#{rest} #{words.join(' ')}".strip
  1581. end
  1582. break
  1583. end
  1584. end
  1585. [rline, rnext]
  1586. end
  1587. def __do_break(line, next_line) #:nodoc:
  1588. no_brk = false
  1589. words = []
  1590. words = line.split(/\s+/) unless line.nil?
  1591. last_word = words[-1]
  1592. @nobreak_regex.each { |k, v| no_brk = ((last_word =~ /#{k}/) and (next_line =~ /#{v}/)) }
  1593. if no_brk && words.size > 1
  1594. i = words.size
  1595. while i > 0
  1596. no_brk = false
  1597. @nobreak_regex.each { |k, v| no_brk = ((words[i + 1] =~ /#{k}/) && (words[i] =~ /#{v}/)) }
  1598. i -= 1
  1599. break if not no_brk
  1600. end
  1601. if i > 0
  1602. l = brk_re(i).match(line)
  1603. line.sub!(brk_re(i), l[1])
  1604. next_line = "#{l[2]} #{next_line}"
  1605. line.sub!(/\s+$/, '')
  1606. end
  1607. end
  1608. [line, next_line]
  1609. end
  1610. def __create(arg = nil, &block) #:nodoc:
  1611. # Format::Text.new(text-to-wrap)
  1612. @text = arg unless arg.nil?
  1613. # Defaults
  1614. @columns = 72
  1615. @tabstop = 8
  1616. @first_indent = 4
  1617. @body_indent = 0
  1618. @format_style = LEFT_ALIGN
  1619. @left_margin = 0
  1620. @right_margin = 0
  1621. @extra_space = false
  1622. @text = Array.new if @text.nil?
  1623. @tag_paragraph = false
  1624. @tag_text = Array.new
  1625. @tag_cur = ""
  1626. @abbreviations = Array.new
  1627. @nobreak = false
  1628. @nobreak_regex = Hash.new
  1629. @split_words = Array.new
  1630. @hard_margins = false
  1631. @split_rules = SPLIT_FIXED
  1632. @hyphenator = self
  1633. @hyphenator_arity = self.method(:hyphenate_to).arity
  1634. instance_eval(&block) unless block.nil?
  1635. end
  1636. public
  1637. # Formats text into a nice paragraph format. The text is separated
  1638. # into words and then reassembled a word at a time using the settings
  1639. # of this Format object. If a word is larger than the number of
  1640. # columns available for formatting, then that word will appear on the
  1641. # line by itself.
  1642. #
  1643. # If +to_wrap+ is +nil+, then the value of <tt>#text</tt> will be
  1644. # worked on.
  1645. def format(to_wrap = nil)
  1646. to_wrap = @text if to_wrap.nil?
  1647. if to_wrap.class == Array
  1648. __format(to_wrap[0])
  1649. else
  1650. __format(to_wrap)
  1651. end
  1652. end
  1653. # Considers each element of text (provided or internal) as a paragraph.
  1654. # If <tt>#first_indent</tt> is the same as <tt>#body_indent</tt>, then
  1655. # paragraphs will be separated by a single empty line in the result;
  1656. # otherwise, the paragraphs will follow immediately after each other.
  1657. # Uses <tt>#format</tt> to do the heavy lifting.
  1658. def paragraphs(to_wrap = nil)
  1659. to_wrap = @text if to_wrap.nil?
  1660. __paragraphs([to_wrap].flatten)
  1661. end
  1662. # Centers the text, preserving empty lines and tabs.
  1663. def center(to_center = nil)
  1664. to_center = @text if to_center.nil?
  1665. __center([to_center].flatten)
  1666. end
  1667. # Replaces all tab characters in the text with <tt>#tabstop</tt> spaces.
  1668. def expand(to_expand = nil)
  1669. to_expand = @text if to_expand.nil?
  1670. if to_expand.class == Array
  1671. to_expand.collect { |te| __expand(te) }
  1672. else
  1673. __expand(to_expand)
  1674. end
  1675. end
  1676. # Replaces all occurrences of <tt>#tabstop</tt> consecutive spaces
  1677. # with a tab character.
  1678. def unexpand(to_unexpand = nil)
  1679. to_unexpand = @text if to_unexpand.nil?
  1680. if to_unexpand.class == Array
  1681. to_unexpand.collect { |te| v << __unexpand(te) }
  1682. else
  1683. __unexpand(to_unexpand)
  1684. end
  1685. end
  1686. # This constructor takes advantage of a technique for Ruby object
  1687. # construction introduced by Andy Hunt and Dave Thomas (see reference),
  1688. # where optional values are set using commands in a block.
  1689. #
  1690. # Text::Format.new {
  1691. # columns = 72
  1692. # left_margin = 0
  1693. # right_margin = 0
  1694. # first_indent = 4
  1695. # body_indent = 0
  1696. # format_style = Text::Format::LEFT_ALIGN
  1697. # extra_space = false
  1698. # abbreviations = {}
  1699. # tag_paragraph = false
  1700. # tag_text = []
  1701. # nobreak = false
  1702. # nobreak_regex = {}
  1703. # tabstop = 8
  1704. # text = nil
  1705. # }
  1706. #
  1707. # As shown above, +arg+ is optional. If +arg+ is specified and is a
  1708. # +String+, then arg is used as the default value of <tt>#text</tt>.
  1709. # Alternately, an existing Text::Format object can be used or a Hash can
  1710. # be used. With all forms, a block can be specified.
  1711. #
  1712. # *Reference*:: "Object Construction and Blocks"
  1713. # <http://www.pragmaticprogrammer.com/ruby/articles/insteval.html>
  1714. #
  1715. def initialize(arg = nil, &block)
  1716. case arg
  1717. when Text::Format
  1718. __create(arg.text) do
  1719. @columns = arg.columns
  1720. @tabstop = arg.tabstop
  1721. @first_indent = arg.first_indent
  1722. @body_indent = arg.body_indent
  1723. @format_style = arg.format_style
  1724. @left_margin = arg.left_margin
  1725. @right_margin = arg.right_margin
  1726. @extra_space = arg.extra_space
  1727. @tag_paragraph = arg.tag_paragraph
  1728. @tag_text = arg.tag_text
  1729. @abbreviations = arg.abbreviations
  1730. @nobreak = arg.nobreak
  1731. @nobreak_regex = arg.nobreak_regex
  1732. @text = arg.text
  1733. @hard_margins = arg.hard_margins
  1734. @split_words = arg.split_words
  1735. @split_rules = arg.split_rules
  1736. @hyphenator = arg.hyphenator
  1737. end
  1738. instance_eval(&block) unless block.nil?
  1739. when Hash
  1740. __create do
  1741. @columns = arg[:columns] || arg['columns'] || @columns
  1742. @tabstop = arg[:tabstop] || arg['tabstop'] || @tabstop
  1743. @first_indent = arg[:first_indent] || arg['first_indent'] || @first_indent
  1744. @body_indent = arg[:body_indent] || arg['body_indent'] || @body_indent
  1745. @format_style = arg[:format_style] || arg['format_style'] || @format_style
  1746. @left_margin = arg[:left_margin] || arg['left_margin'] || @left_margin
  1747. @right_margin = arg[:right_margin] || arg['right_margin'] || @right_margin
  1748. @extra_space = arg[:extra_space] || arg['extra_space'] || @extra_space
  1749. @text = arg[:text] || arg['text'] || @text
  1750. @tag_paragraph = arg[:tag_paragraph] || arg['tag_paragraph'] || @tag_paragraph
  1751. @tag_text = arg[:tag_text] || arg['tag_text'] || @tag_text
  1752. @abbreviations = arg[:abbreviations] || arg['abbreviations'] || @abbreviations
  1753. @nobreak = arg[:nobreak] || arg['nobreak'] || @nobreak
  1754. @nobreak_regex = arg[:nobreak_regex] || arg['nobreak_regex'] || @nobreak_regex
  1755. @hard_margins = arg[:hard_margins] || arg['hard_margins'] || @hard_margins
  1756. @split_rules = arg[:split_rules] || arg['split_rules'] || @split_rules
  1757. @hyphenator = arg[:hyphenator] || arg['hyphenator'] || @hyphenator
  1758. end
  1759. instance_eval(&block) unless block.nil?
  1760. when String
  1761. __create(arg, &block)
  1762. when NilClass
  1763. __create(&block)
  1764. else
  1765. raise TypeError
  1766. end
  1767. end
  1768. end
  1769. end
  1770. if __FILE__ == $0
  1771. require 'test/unit'
  1772. class TestText__Format < Test::Unit::TestCase #:nodoc:
  1773. attr_accessor :format_o
  1774. GETTYSBURG = <<-'EOS'
  1775. Four score and seven years ago our fathers brought forth on this
  1776. continent a new nation, conceived in liberty and dedicated to the
  1777. proposition that all men are created equal. Now we are engaged in
  1778. a great civil war, testing whether that nation or any nation so
  1779. conceived and so dedicated can long endure. We are met on a great
  1780. battlefield of that war. We have come to dedicate a portion of
  1781. that field as a final resting-place for those who here gave their
  1782. lives that that nation might live. It is altogether fitting and
  1783. proper that we should do this. But in a larger sense, we cannot
  1784. dedicate, we cannot consecrate, we cannot hallow this ground.
  1785. The brave men, living and dead who struggled here have consecrated
  1786. it far above our poor power to add or detract. The world will
  1787. little note nor long remember what we say here, but it can never
  1788. forget what they did here. It is for us the living rather to be
  1789. dedicated here to the unfinished work which they who fought here
  1790. have thus far so nobly advanced. It is rather for us to be here
  1791. dedicated to the great task remaining before us--that from these
  1792. honored dead we take increased devotion to that cause for which
  1793. they gave the last full measure of devotion--that we here highly
  1794. resolve that these dead shall not have died in vain, that this
  1795. nation under God shall have a new birth of freedom, and that
  1796. government of the people, by the people, for the people shall
  1797. not perish from the earth.
  1798. -- Pres. Abraham Lincoln, 19 November 1863
  1799. EOS
  1800. FIVE_COL = "Four \nscore\nand s\neven \nyears\nago o\nur fa\nthers\nbroug\nht fo\nrth o\nn thi\ns con\ntinen\nt a n\new na\ntion,\nconce\nived \nin li\nberty\nand d\nedica\nted t\no the\npropo\nsitio\nn tha\nt all\nmen a\nre cr\neated\nequal\n. Now\nwe ar\ne eng\naged \nin a \ngreat\ncivil\nwar, \ntesti\nng wh\nether\nthat \nnatio\nn or \nany n\nation\nso co\nnceiv\ned an\nd so \ndedic\nated \ncan l\nong e\nndure\n. We \nare m\net on\na gre\nat ba\nttlef\nield \nof th\nat wa\nr. We\nhave \ncome \nto de\ndicat\ne a p\nortio\nn of \nthat \nfield\nas a \nfinal\nresti\nng-pl\nace f\nor th\nose w\nho he\nre ga\nve th\neir l\nives \nthat \nthat \nnatio\nn mig\nht li\nve. I\nt is \naltog\nether\nfitti\nng an\nd pro\nper t\nhat w\ne sho\nuld d\no thi\ns. Bu\nt in \na lar\nger s\nense,\nwe ca\nnnot \ndedic\nate, \nwe ca\nnnot \nconse\ncrate\n, we \ncanno\nt hal\nlow t\nhis g\nround\n. The\nbrave\nmen, \nlivin\ng and\ndead \nwho s\ntrugg\nled h\nere h\nave c\nonsec\nrated\nit fa\nr abo\nve ou\nr poo\nr pow\ner to\nadd o\nr det\nract.\nThe w\norld \nwill \nlittl\ne not\ne nor\nlong \nremem\nber w\nhat w\ne say\nhere,\nbut i\nt can\nnever\nforge\nt wha\nt the\ny did\nhere.\nIt is\nfor u\ns the\nlivin\ng rat\nher t\no be \ndedic\nated \nhere \nto th\ne unf\ninish\ned wo\nrk wh\nich t\nhey w\nho fo\nught \nhere \nhave \nthus \nfar s\no nob\nly ad\nvance\nd. It\nis ra\nther \nfor u\ns to \nbe he\nre de\ndicat\ned to\nthe g\nreat \ntask \nremai\nning \nbefor\ne us-\n-that\nfrom \nthese\nhonor\ned de\nad we\ntake \nincre\nased \ndevot\nion t\no tha\nt cau\nse fo\nr whi\nch th\ney ga\nve th\ne las\nt ful\nl mea\nsure \nof de\nvotio\nn--th\nat we\nhere \nhighl\ny res\nolve \nthat \nthese\ndead \nshall\nnot h\nave d\nied i\nn vai\nn, th\nat th\nis na\ntion \nunder\nGod s\nhall \nhave \na new\nbirth\nof fr\needom\n, and\nthat \ngover\nnment\nof th\ne peo\nple, \nby th\ne peo\nple, \nfor t\nhe pe\nople \nshall\nnot p\nerish\nfrom \nthe e\narth.\n-- Pr\nes. A\nbraha\nm Lin\ncoln,\n19 No\nvembe\nr 186\n3 \n"
  1801. FIVE_CNT = "Four \nscore\nand \nseven\nyears\nago \nour \nfath\\\ners \nbrou\\\nght \nforth\non t\\\nhis \ncont\\\ninent\na new\nnati\\\non, \nconc\\\neived\nin l\\\niber\\\nty a\\\nnd d\\\nedic\\\nated \nto t\\\nhe p\\\nropo\\\nsiti\\\non t\\\nhat \nall \nmen \nare \ncrea\\\nted \nequa\\\nl. N\\\now we\nare \nenga\\\nged \nin a \ngreat\ncivil\nwar, \ntest\\\ning \nwhet\\\nher \nthat \nnati\\\non or\nany \nnati\\\non so\nconc\\\neived\nand \nso d\\\nedic\\\nated \ncan \nlong \nendu\\\nre. \nWe a\\\nre m\\\net on\na gr\\\neat \nbatt\\\nlefi\\\neld \nof t\\\nhat \nwar. \nWe h\\\nave \ncome \nto d\\\nedic\\\nate a\nport\\\nion \nof t\\\nhat \nfield\nas a \nfinal\nrest\\\ning-\\\nplace\nfor \nthose\nwho \nhere \ngave \ntheir\nlives\nthat \nthat \nnati\\\non m\\\night \nlive.\nIt is\nalto\\\ngeth\\\ner f\\\nitti\\\nng a\\\nnd p\\\nroper\nthat \nwe s\\\nhould\ndo t\\\nhis. \nBut \nin a \nlarg\\\ner s\\\nense,\nwe c\\\nannot\ndedi\\\ncate,\nwe c\\\nannot\ncons\\\necra\\\nte, \nwe c\\\nannot\nhall\\\now t\\\nhis \ngrou\\\nnd. \nThe \nbrave\nmen, \nlivi\\\nng a\\\nnd d\\\nead \nwho \nstru\\\nggled\nhere \nhave \ncons\\\necra\\\nted \nit f\\\nar a\\\nbove \nour \npoor \npower\nto a\\\ndd or\ndetr\\\nact. \nThe \nworld\nwill \nlitt\\\nle n\\\note \nnor \nlong \nreme\\\nmber \nwhat \nwe s\\\nay h\\\nere, \nbut \nit c\\\nan n\\\never \nforg\\\net w\\\nhat \nthey \ndid \nhere.\nIt is\nfor \nus t\\\nhe l\\\niving\nrath\\\ner to\nbe d\\\nedic\\\nated \nhere \nto t\\\nhe u\\\nnfin\\\nished\nwork \nwhich\nthey \nwho \nfoug\\\nht h\\\nere \nhave \nthus \nfar \nso n\\\nobly \nadva\\\nnced.\nIt is\nrath\\\ner f\\\nor us\nto be\nhere \ndedi\\\ncated\nto t\\\nhe g\\\nreat \ntask \nrema\\\nining\nbefo\\\nre u\\\ns--t\\\nhat \nfrom \nthese\nhono\\\nred \ndead \nwe t\\\nake \nincr\\\neased\ndevo\\\ntion \nto t\\\nhat \ncause\nfor \nwhich\nthey \ngave \nthe \nlast \nfull \nmeas\\\nure \nof d\\\nevot\\\nion-\\\n-that\nwe h\\\nere \nhigh\\\nly r\\\nesol\\\nve t\\\nhat \nthese\ndead \nshall\nnot \nhave \ndied \nin v\\\nain, \nthat \nthis \nnati\\\non u\\\nnder \nGod \nshall\nhave \na new\nbirth\nof f\\\nreed\\\nom, \nand \nthat \ngove\\\nrnme\\\nnt of\nthe \npeop\\\nle, \nby t\\\nhe p\\\neopl\\\ne, f\\\nor t\\\nhe p\\\neople\nshall\nnot \nperi\\\nsh f\\\nrom \nthe \neart\\\nh. --\nPres.\nAbra\\\nham \nLinc\\\noln, \n19 N\\\novem\\\nber \n1863 \n"
  1802. # Tests both abbreviations and abbreviations=
  1803. def test_abbreviations
  1804. abbr = [" Pres. Abraham Lincoln\n", " Pres. Abraham Lincoln\n"]
  1805. assert_nothing_raised { @format_o = Text::Format.new }
  1806. assert_equal([], @format_o.abbreviations)
  1807. assert_nothing_raised { @format_o.abbreviations = [ 'foo', 'bar' ] }
  1808. assert_equal([ 'foo', 'bar' ], @format_o.abbreviations)
  1809. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1810. assert_nothing_raised { @format_o.extra_space = true }
  1811. assert_equal(abbr[1], @format_o.format(abbr[0]))
  1812. assert_nothing_raised { @format_o.abbreviations = [ "Pres" ] }
  1813. assert_equal([ "Pres" ], @format_o.abbreviations)
  1814. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1815. assert_nothing_raised { @format_o.extra_space = false }
  1816. assert_equal(abbr[0], @format_o.format(abbr[0]))
  1817. end
  1818. # Tests both body_indent and body_indent=
  1819. def test_body_indent
  1820. assert_nothing_raised { @format_o = Text::Format.new }
  1821. assert_equal(0, @format_o.body_indent)
  1822. assert_nothing_raised { @format_o.body_indent = 7 }
  1823. assert_equal(7, @format_o.body_indent)
  1824. assert_nothing_raised { @format_o.body_indent = -3 }
  1825. assert_equal(3, @format_o.body_indent)
  1826. assert_nothing_raised { @format_o.body_indent = "9" }
  1827. assert_equal(9, @format_o.body_indent)
  1828. assert_nothing_raised { @format_o.body_indent = "-2" }
  1829. assert_equal(2, @format_o.body_indent)
  1830. assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[1])
  1831. end
  1832. # Tests both columns and columns=
  1833. def test_columns
  1834. assert_nothing_raised { @format_o = Text::Format.new }
  1835. assert_equal(72, @format_o.columns)
  1836. assert_nothing_raised { @format_o.columns = 7 }
  1837. assert_equal(7, @format_o.columns)
  1838. assert_nothing_raised { @format_o.columns = -3 }
  1839. assert_equal(3, @format_o.columns)
  1840. assert_nothing_raised { @format_o.columns = "9" }
  1841. assert_equal(9, @format_o.columns)
  1842. assert_nothing_raised { @format_o.columns = "-2" }
  1843. assert_equal(2, @format_o.columns)
  1844. assert_nothing_raised { @format_o.columns = 40 }
  1845. assert_equal(40, @format_o.columns)
  1846. assert_match(/this continent$/,
  1847. @format_o.format(GETTYSBURG).split("\n")[1])
  1848. end
  1849. # Tests both extra_space and extra_space=
  1850. def test_extra_space
  1851. assert_nothing_raised { @format_o = Text::Format.new }
  1852. assert(!@format_o.extra_space)
  1853. assert_nothing_raised { @format_o.extra_space = true }
  1854. assert(@format_o.extra_space)
  1855. # The behaviour of extra_space is tested in test_abbreviations. There
  1856. # is no need to reproduce it here.
  1857. end
  1858. # Tests both first_indent and first_indent=
  1859. def test_first_indent
  1860. assert_nothing_raised { @format_o = Text::Format.new }
  1861. assert_equal(4, @format_o.first_indent)
  1862. assert_nothing_raised { @format_o.first_indent = 7 }
  1863. assert_equal(7, @format_o.first_indent)
  1864. assert_nothing_raised { @format_o.first_indent = -3 }
  1865. assert_equal(3, @format_o.first_indent)
  1866. assert_nothing_raised { @format_o.first_indent = "9" }
  1867. assert_equal(9, @format_o.first_indent)
  1868. assert_nothing_raised { @format_o.first_indent = "-2" }
  1869. assert_equal(2, @format_o.first_indent)
  1870. assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[0])
  1871. end
  1872. def test_format_style
  1873. assert_nothing_raised { @format_o = Text::Format.new }
  1874. assert_equal(Text::Format::LEFT_ALIGN, @format_o.format_style)
  1875. assert_match(/^November 1863$/,
  1876. @format_o.format(GETTYSBURG).split("\n")[-1])
  1877. assert_nothing_raised {
  1878. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1879. }
  1880. assert_equal(Text::Format::RIGHT_ALIGN, @format_o.format_style)
  1881. assert_match(/^ +November 1863$/,
  1882. @format_o.format(GETTYSBURG).split("\n")[-1])
  1883. assert_nothing_raised {
  1884. @format_o.format_style = Text::Format::RIGHT_FILL
  1885. }
  1886. assert_equal(Text::Format::RIGHT_FILL, @format_o.format_style)
  1887. assert_match(/^November 1863 +$/,
  1888. @format_o.format(GETTYSBURG).split("\n")[-1])
  1889. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  1890. assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
  1891. assert_match(/^of freedom, and that government of the people, by the people, for the$/,
  1892. @format_o.format(GETTYSBURG).split("\n")[-3])
  1893. assert_raises(ArgumentError) { @format_o.format_style = 33 }
  1894. end
  1895. def test_tag_paragraph
  1896. assert_nothing_raised { @format_o = Text::Format.new }
  1897. assert(!@format_o.tag_paragraph)
  1898. assert_nothing_raised { @format_o.tag_paragraph = true }
  1899. assert(@format_o.tag_paragraph)
  1900. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
  1901. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
  1902. end
  1903. def test_tag_text
  1904. assert_nothing_raised { @format_o = Text::Format.new }
  1905. assert_equal([], @format_o.tag_text)
  1906. assert_equal(@format_o.format(GETTYSBURG),
  1907. Text::Format.new.format(GETTYSBURG))
  1908. assert_nothing_raised {
  1909. @format_o.tag_paragraph = true
  1910. @format_o.tag_text = ["Gettysburg Address", "---"]
  1911. }
  1912. assert_not_equal(@format_o.format(GETTYSBURG),
  1913. Text::Format.new.format(GETTYSBURG))
  1914. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
  1915. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
  1916. assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG,
  1917. GETTYSBURG]),
  1918. Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG,
  1919. GETTYSBURG]))
  1920. end
  1921. def test_justify?
  1922. assert_nothing_raised { @format_o = Text::Format.new }
  1923. assert(!@format_o.justify?)
  1924. assert_nothing_raised {
  1925. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1926. }
  1927. assert(!@format_o.justify?)
  1928. assert_nothing_raised {
  1929. @format_o.format_style = Text::Format::RIGHT_FILL
  1930. }
  1931. assert(!@format_o.justify?)
  1932. assert_nothing_raised {
  1933. @format_o.format_style = Text::Format::JUSTIFY
  1934. }
  1935. assert(@format_o.justify?)
  1936. # The format testing is done in test_format_style
  1937. end
  1938. def test_left_align?
  1939. assert_nothing_raised { @format_o = Text::Format.new }
  1940. assert(@format_o.left_align?)
  1941. assert_nothing_raised {
  1942. @format_o.format_style = Text::Format::RIGHT_ALIGN
  1943. }
  1944. assert(!@format_o.left_align?)
  1945. assert_nothing_raised {
  1946. @format_o.format_style = Text::Format::RIGHT_FILL
  1947. }
  1948. assert(!@format_o.left_align?)
  1949. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  1950. assert(!@format_o.left_align?)
  1951. # The format testing is done in test_format_style
  1952. end
  1953. def test_left_margin
  1954. assert_nothing_raised { @format_o = Text::Format.new }
  1955. assert_equal(0, @format_o.left_margin)
  1956. assert_nothing_raised { @format_o.left_margin = -3 }
  1957. assert_equal(3, @format_o.left_margin)
  1958. assert_nothing_raised { @format_o.left_margin = "9" }
  1959. assert_equal(9, @format_o.left_margin)
  1960. assert_nothing_raised { @format_o.left_margin = "-2" }
  1961. assert_equal(2, @format_o.left_margin)
  1962. assert_nothing_raised { @format_o.left_margin = 7 }
  1963. assert_equal(7, @format_o.left_margin)
  1964. assert_nothing_raised {
  1965. ft = @format_o.format(GETTYSBURG).split("\n")
  1966. assert_match(/^ {11}Four score/, ft[0])
  1967. assert_match(/^ {7}November/, ft[-1])
  1968. }
  1969. end
  1970. def test_hard_margins
  1971. assert_nothing_raised { @format_o = Text::Format.new }
  1972. assert(!@format_o.hard_margins)
  1973. assert_nothing_raised {
  1974. @format_o.hard_margins = true
  1975. @format_o.columns = 5
  1976. @format_o.first_indent = 0
  1977. @format_o.format_style = Text::Format::RIGHT_FILL
  1978. }
  1979. assert(@format_o.hard_margins)
  1980. assert_equal(FIVE_COL, @format_o.format(GETTYSBURG))
  1981. assert_nothing_raised {
  1982. @format_o.split_rules |= Text::Format::SPLIT_CONTINUATION
  1983. assert_equal(Text::Format::SPLIT_CONTINUATION_FIXED,
  1984. @format_o.split_rules)
  1985. }
  1986. assert_equal(FIVE_CNT, @format_o.format(GETTYSBURG))
  1987. end
  1988. # Tests both nobreak and nobreak_regex, since one is only useful
  1989. # with the other.
  1990. def test_nobreak
  1991. assert_nothing_raised { @format_o = Text::Format.new }
  1992. assert(!@format_o.nobreak)
  1993. assert(@format_o.nobreak_regex.empty?)
  1994. assert_nothing_raised {
  1995. @format_o.nobreak = true
  1996. @format_o.nobreak_regex = { '^this$' => '^continent$' }
  1997. @format_o.columns = 77
  1998. }
  1999. assert(@format_o.nobreak)
  2000. assert_equal({ '^this$' => '^continent$' }, @format_o.nobreak_regex)
  2001. assert_match(/^this continent/,
  2002. @format_o.format(GETTYSBURG).split("\n")[1])
  2003. end
  2004. def test_right_align?
  2005. assert_nothing_raised { @format_o = Text::Format.new }
  2006. assert(!@format_o.right_align?)
  2007. assert_nothing_raised {
  2008. @format_o.format_style = Text::Format::RIGHT_ALIGN
  2009. }
  2010. assert(@format_o.right_align?)
  2011. assert_nothing_raised {
  2012. @format_o.format_style = Text::Format::RIGHT_FILL
  2013. }
  2014. assert(!@format_o.right_align?)
  2015. assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
  2016. assert(!@format_o.right_align?)
  2017. # The format testing is done in test_format_style
  2018. end
  2019. def test_right_fill?
  2020. assert_nothing_raised { @format_o = Text::Format.new }
  2021. assert(!@format_o.right_fill?)
  2022. assert_nothing_raised {
  2023. @format_o.format_style = Text::Format::RIGHT_ALIGN
  2024. }
  2025. assert(!@format_o.right_fill?)
  2026. assert_nothing_raised {
  2027. @format_o.format_style = Text::Format::RIGHT_FILL
  2028. }
  2029. assert(@format_o.right_fill?)
  2030. assert_nothing_raised {
  2031. @format_o.format_style = Text::Format::JUSTIFY
  2032. }
  2033. assert(!@format_o.right_fill?)
  2034. # The format testing is done in test_format_style
  2035. end
  2036. def test_right_margin
  2037. assert_nothing_raised { @format_o = Text::Format.new }
  2038. assert_equal(0, @format_o.right_margin)
  2039. assert_nothing_raised { @format_o.right_margin = -3 }
  2040. assert_equal(3, @format_o.right_margin)
  2041. assert_nothing_raised { @format_o.right_margin = "9" }
  2042. assert_equal(9, @format_o.right_margin)
  2043. assert_nothing_raised { @format_o.right_margin = "-2" }
  2044. assert_equal(2, @format_o.right_margin)
  2045. assert_nothing_raised { @format_o.right_margin = 7 }
  2046. assert_equal(7, @format_o.right_margin)
  2047. assert_nothing_raised {
  2048. ft = @format_o.format(GETTYSBURG).split("\n")
  2049. assert_match(/^ {4}Four score.*forth on$/, ft[0])
  2050. assert_match(/^November/, ft[-1])
  2051. }
  2052. end
  2053. def test_tabstop
  2054. assert_nothing_raised { @format_o = Text::Format.new }
  2055. assert_equal(8, @format_o.tabstop)
  2056. assert_nothing_raised { @format_o.tabstop = 7 }
  2057. assert_equal(7, @format_o.tabstop)
  2058. assert_nothing_raised { @format_o.tabstop = -3 }
  2059. assert_equal(3, @format_o.tabstop)
  2060. assert_nothing_raised { @format_o.tabstop = "9" }
  2061. assert_equal(9, @format_o.tabstop)
  2062. assert_nothing_raised { @format_o.tabstop = "-2" }
  2063. assert_equal(2, @format_o.tabstop)
  2064. end
  2065. def test_text
  2066. assert_nothing_raised { @format_o = Text::Format.new }
  2067. assert_equal([], @format_o.text)
  2068. assert_nothing_raised { @format_o.text = "Test Text" }
  2069. assert_equal("Test Text", @format_o.text)
  2070. assert_nothing_raised { @format_o.text = ["Line 1", "Line 2"] }
  2071. assert_equal(["Line 1", "Line 2"], @format_o.text)
  2072. end
  2073. def test_s_new
  2074. # new(NilClass) { block }
  2075. assert_nothing_raised do
  2076. @format_o = Text::Format.new {
  2077. self.text = "Test 1, 2, 3"
  2078. }
  2079. end
  2080. assert_equal("Test 1, 2, 3", @format_o.text)
  2081. # new(Hash Symbols)
  2082. assert_nothing_raised { @format_o = Text::Format.new(:columns => 72) }
  2083. assert_equal(72, @format_o.columns)
  2084. # new(Hash String)
  2085. assert_nothing_raised { @format_o = Text::Format.new('columns' => 72) }
  2086. assert_equal(72, @format_o.columns)
  2087. # new(Hash) { block }
  2088. assert_nothing_raised do
  2089. @format_o = Text::Format.new('columns' => 80) {
  2090. self.text = "Test 4, 5, 6"
  2091. }
  2092. end
  2093. assert_equal("Test 4, 5, 6", @format_o.text)
  2094. assert_equal(80, @format_o.columns)
  2095. # new(Text::Format)
  2096. assert_nothing_raised do
  2097. fo = Text::Format.new(@format_o)
  2098. assert(fo == @format_o)
  2099. end
  2100. # new(Text::Format) { block }
  2101. assert_nothing_raised do
  2102. fo = Text::Format.new(@format_o) { self.columns = 79 }
  2103. assert(fo != @format_o)
  2104. end
  2105. # new(String)
  2106. assert_nothing_raised { @format_o = Text::Format.new("Test A, B, C") }
  2107. assert_equal("Test A, B, C", @format_o.text)
  2108. # new(String) { block }
  2109. assert_nothing_raised do
  2110. @format_o = Text::Format.new("Test X, Y, Z") { self.columns = -5 }
  2111. end
  2112. assert_equal("Test X, Y, Z", @format_o.text)
  2113. assert_equal(5, @format_o.columns)
  2114. end
  2115. def test_center
  2116. assert_nothing_raised { @format_o = Text::Format.new }
  2117. assert_nothing_raised do
  2118. ct = @format_o.center(GETTYSBURG.split("\n")).split("\n")
  2119. assert_match(/^ Four score and seven years ago our fathers brought forth on this/, ct[0])
  2120. assert_match(/^ not perish from the earth./, ct[-3])
  2121. end
  2122. end
  2123. def test_expand
  2124. assert_nothing_raised { @format_o = Text::Format.new }
  2125. assert_equal(" ", @format_o.expand("\t "))
  2126. assert_nothing_raised { @format_o.tabstop = 4 }
  2127. assert_equal(" ", @format_o.expand("\t "))
  2128. end
  2129. def test_unexpand
  2130. assert_nothing_raised { @format_o = Text::Format.new }
  2131. assert_equal("\t ", @format_o.unexpand(" "))
  2132. assert_nothing_raised { @format_o.tabstop = 4 }
  2133. assert_equal("\t ", @format_o.unexpand(" "))
  2134. end
  2135. def test_space_only
  2136. assert_equal("", Text::Format.new.format(" "))
  2137. assert_equal("", Text::Format.new.format("\n"))
  2138. assert_equal("", Text::Format.new.format(" "))
  2139. assert_equal("", Text::Format.new.format(" \n"))
  2140. assert_equal("", Text::Format.new.paragraphs("\n"))
  2141. assert_equal("", Text::Format.new.paragraphs(" "))
  2142. assert_equal("", Text::Format.new.paragraphs(" "))
  2143. assert_equal("", Text::Format.new.paragraphs(" \n"))
  2144. assert_equal("", Text::Format.new.paragraphs(["\n"]))
  2145. assert_equal("", Text::Format.new.paragraphs([" "]))
  2146. assert_equal("", Text::Format.new.paragraphs([" "]))
  2147. assert_equal("", Text::Format.new.paragraphs([" \n"]))
  2148. end
  2149. def test_splendiferous
  2150. h = nil
  2151. test = "This is a splendiferous test"
  2152. assert_nothing_raised { @format_o = Text::Format.new(:columns => 6, :left_margin => 0, :indent => 0, :first_indent => 0) }
  2153. assert_match(/^splendiferous$/, @format_o.format(test))
  2154. assert_nothing_raised { @format_o.hard_margins = true }
  2155. assert_match(/^lendif$/, @format_o.format(test))
  2156. assert_nothing_raised { h = Object.new }
  2157. assert_nothing_raised do
  2158. @format_o.split_rules = Text::Format::SPLIT_HYPHENATION
  2159. class << h #:nodoc:
  2160. def hyphenate_to(word, size)
  2161. return ["", word] if size < 2
  2162. [word[0 ... size], word[size .. -1]]
  2163. end
  2164. end
  2165. @format_o.hyphenator = h
  2166. end
  2167. assert_match(/^iferou$/, @format_o.format(test))
  2168. assert_nothing_raised { h = Object.new }
  2169. assert_nothing_raised do
  2170. class << h #:nodoc:
  2171. def hyphenate_to(word, size, formatter)
  2172. return ["", word] if word.size < formatter.columns
  2173. [word[0 ... size], word[size .. -1]]
  2174. end
  2175. end
  2176. @format_o.hyphenator = h
  2177. end
  2178. assert_match(/^ferous$/, @format_o.format(test))
  2179. end
  2180. end
  2181. end
  2182. #
  2183. # address.rb
  2184. #
  2185. #--
  2186. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2187. #
  2188. # Permission is hereby granted, free of charge, to any person obtaining
  2189. # a copy of this software and associated documentation files (the
  2190. # "Software"), to deal in the Software without restriction, including
  2191. # without limitation the rights to use, copy, modify, merge, publish,
  2192. # distribute, sublicense, and/or sell copies of the Software, and to
  2193. # permit persons to whom the Software is furnished to do so, subject to
  2194. # the following conditions:
  2195. #
  2196. # The above copyright notice and this permission notice shall be
  2197. # included in all copies or substantial portions of the Software.
  2198. #
  2199. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2200. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2201. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2202. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2203. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2204. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2205. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2206. #
  2207. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2208. # with permission of Minero Aoki.
  2209. #++
  2210. require 'tmail/encode'
  2211. require 'tmail/parser'
  2212. module TMail
  2213. class Address
  2214. include TextUtils
  2215. def Address.parse( str )
  2216. Parser.parse :ADDRESS, str
  2217. end
  2218. def address_group?
  2219. false
  2220. end
  2221. def initialize( local, domain )
  2222. if domain
  2223. domain.each do |s|
  2224. raise SyntaxError, 'empty word in domain' if s.empty?
  2225. end
  2226. end
  2227. @local = local
  2228. @domain = domain
  2229. @name = nil
  2230. @routes = []
  2231. end
  2232. attr_reader :name
  2233. def name=( str )
  2234. @name = str
  2235. @name = nil if str and str.empty?
  2236. end
  2237. alias phrase name
  2238. alias phrase= name=
  2239. attr_reader :routes
  2240. def inspect
  2241. "#<#{self.class} #{address()}>"
  2242. end
  2243. def local
  2244. return nil unless @local
  2245. return '""' if @local.size == 1 and @local[0].empty?
  2246. @local.map {|i| quote_atom(i) }.join('.')
  2247. end
  2248. def domain
  2249. return nil unless @domain
  2250. join_domain(@domain)
  2251. end
  2252. def spec
  2253. s = self.local
  2254. d = self.domain
  2255. if s and d
  2256. s + '@' + d
  2257. else
  2258. s
  2259. end
  2260. end
  2261. alias address spec
  2262. def ==( other )
  2263. other.respond_to? :spec and self.spec == other.spec
  2264. end
  2265. alias eql? ==
  2266. def hash
  2267. @local.hash ^ @domain.hash
  2268. end
  2269. def dup
  2270. obj = self.class.new(@local.dup, @domain.dup)
  2271. obj.name = @name.dup if @name
  2272. obj.routes.replace @routes
  2273. obj
  2274. end
  2275. include StrategyInterface
  2276. def accept( strategy, dummy1 = nil, dummy2 = nil )
  2277. unless @local
  2278. strategy.meta '<>' # empty return-path
  2279. return
  2280. end
  2281. spec_p = (not @name and @routes.empty?)
  2282. if @name
  2283. strategy.phrase @name
  2284. strategy.space
  2285. end
  2286. tmp = spec_p ? '' : '<'
  2287. unless @routes.empty?
  2288. tmp << @routes.map {|i| '@' + i }.join(',') << ':'
  2289. end
  2290. tmp << self.spec
  2291. tmp << '>' unless spec_p
  2292. strategy.meta tmp
  2293. strategy.lwsp ''
  2294. end
  2295. end
  2296. class AddressGroup
  2297. include Enumerable
  2298. def address_group?
  2299. true
  2300. end
  2301. def initialize( name, addrs )
  2302. @name = name
  2303. @addresses = addrs
  2304. end
  2305. attr_reader :name
  2306. def ==( other )
  2307. other.respond_to? :to_a and @addresses == other.to_a
  2308. end
  2309. alias eql? ==
  2310. def hash
  2311. map {|i| i.hash }.hash
  2312. end
  2313. def []( idx )
  2314. @addresses[idx]
  2315. end
  2316. def size
  2317. @addresses.size
  2318. end
  2319. def empty?
  2320. @addresses.empty?
  2321. end
  2322. def each( &block )
  2323. @addresses.each(&block)
  2324. end
  2325. def to_a
  2326. @addresses.dup
  2327. end
  2328. alias to_ary to_a
  2329. def include?( a )
  2330. @addresses.include? a
  2331. end
  2332. def flatten
  2333. set = []
  2334. @addresses.each do |a|
  2335. if a.respond_to? :flatten
  2336. set.concat a.flatten
  2337. else
  2338. set.push a
  2339. end
  2340. end
  2341. set
  2342. end
  2343. def each_address( &block )
  2344. flatten.each(&block)
  2345. end
  2346. def add( a )
  2347. @addresses.push a
  2348. end
  2349. alias push add
  2350. def delete( a )
  2351. @addresses.delete a
  2352. end
  2353. include StrategyInterface
  2354. def accept( strategy, dummy1 = nil, dummy2 = nil )
  2355. strategy.phrase @name
  2356. strategy.meta ':'
  2357. strategy.space
  2358. first = true
  2359. each do |mbox|
  2360. if first
  2361. first = false
  2362. else
  2363. strategy.meta ','
  2364. end
  2365. strategy.space
  2366. mbox.accept strategy
  2367. end
  2368. strategy.meta ';'
  2369. strategy.lwsp ''
  2370. end
  2371. end
  2372. end # module TMail
  2373. require 'stringio'
  2374. module TMail
  2375. class Attachment < StringIO
  2376. attr_accessor :original_filename, :content_type
  2377. end
  2378. class Mail
  2379. def has_attachments?
  2380. multipart? && parts.any? { |part| attachment?(part) }
  2381. end
  2382. def attachment?(part)
  2383. (part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
  2384. part.header['content-type'].main_type != "text"
  2385. end
  2386. def attachments
  2387. if multipart?
  2388. parts.collect { |part|
  2389. if attachment?(part)
  2390. content = part.body # unquoted automatically by TMail#body
  2391. file_name = (part['content-location'] &&
  2392. part['content-location'].body) ||
  2393. part.sub_header("content-type", "name") ||
  2394. part.sub_header("content-disposition", "filename")
  2395. next if file_name.blank? || content.blank?
  2396. attachment = Attachment.new(content)
  2397. attachment.original_filename = file_name.strip
  2398. attachment.content_type = part.content_type
  2399. attachment
  2400. end
  2401. }.compact
  2402. end
  2403. end
  2404. end
  2405. end
  2406. #
  2407. # base64.rb
  2408. #
  2409. #--
  2410. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2411. #
  2412. # Permission is hereby granted, free of charge, to any person obtaining
  2413. # a copy of this software and associated documentation files (the
  2414. # "Software"), to deal in the Software without restriction, including
  2415. # without limitation the rights to use, copy, modify, merge, publish,
  2416. # distribute, sublicense, and/or sell copies of the Software, and to
  2417. # permit persons to whom the Software is furnished to do so, subject to
  2418. # the following conditions:
  2419. #
  2420. # The above copyright notice and this permission notice shall be
  2421. # included in all copies or substantial portions of the Software.
  2422. #
  2423. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2424. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2425. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2426. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2427. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2428. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2429. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2430. #
  2431. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2432. # with permission of Minero Aoki.
  2433. #++
  2434. module TMail
  2435. module Base64
  2436. module_function
  2437. def rb_folding_encode( str, eol = "\n", limit = 60 )
  2438. [str].pack('m')
  2439. end
  2440. def rb_encode( str )
  2441. [str].pack('m').tr( "\r\n", '' )
  2442. end
  2443. def rb_decode( str, strict = false )
  2444. str.unpack('m')
  2445. end
  2446. begin
  2447. require 'tmail/base64.so'
  2448. alias folding_encode c_folding_encode
  2449. alias encode c_encode
  2450. alias decode c_decode
  2451. class << self
  2452. alias folding_encode c_folding_encode
  2453. alias encode c_encode
  2454. alias decode c_decode
  2455. end
  2456. rescue LoadError
  2457. alias folding_encode rb_folding_encode
  2458. alias encode rb_encode
  2459. alias decode rb_decode
  2460. class << self
  2461. alias folding_encode rb_folding_encode
  2462. alias encode rb_encode
  2463. alias decode rb_decode
  2464. end
  2465. end
  2466. end
  2467. end
  2468. #
  2469. # config.rb
  2470. #
  2471. #--
  2472. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2473. #
  2474. # Permission is hereby granted, free of charge, to any person obtaining
  2475. # a copy of this software and associated documentation files (the
  2476. # "Software"), to deal in the Software without restriction, including
  2477. # without limitation the rights to use, copy, modify, merge, publish,
  2478. # distribute, sublicense, and/or sell copies of the Software, and to
  2479. # permit persons to whom the Software is furnished to do so, subject to
  2480. # the following conditions:
  2481. #
  2482. # The above copyright notice and this permission notice shall be
  2483. # included in all copies or substantial portions of the Software.
  2484. #
  2485. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2486. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2487. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2488. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2489. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2490. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2491. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2492. #
  2493. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2494. # with permission of Minero Aoki.
  2495. #++
  2496. module TMail
  2497. class Config
  2498. def initialize( strict )
  2499. @strict_parse = strict
  2500. @strict_base64decode = strict
  2501. end
  2502. def strict_parse?
  2503. @strict_parse
  2504. end
  2505. attr_writer :strict_parse
  2506. def strict_base64decode?
  2507. @strict_base64decode
  2508. end
  2509. attr_writer :strict_base64decode
  2510. def new_body_port( mail )
  2511. StringPort.new
  2512. end
  2513. alias new_preamble_port new_body_port
  2514. alias new_part_port new_body_port
  2515. end
  2516. DEFAULT_CONFIG = Config.new(false)
  2517. DEFAULT_STRICT_CONFIG = Config.new(true)
  2518. def Config.to_config( arg )
  2519. return DEFAULT_STRICT_CONFIG if arg == true
  2520. return DEFAULT_CONFIG if arg == false
  2521. arg or DEFAULT_CONFIG
  2522. end
  2523. end
  2524. #
  2525. # encode.rb
  2526. #
  2527. #--
  2528. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2529. #
  2530. # Permission is hereby granted, free of charge, to any person obtaining
  2531. # a copy of this software and associated documentation files (the
  2532. # "Software"), to deal in the Software without restriction, including
  2533. # without limitation the rights to use, copy, modify, merge, publish,
  2534. # distribute, sublicense, and/or sell copies of the Software, and to
  2535. # permit persons to whom the Software is furnished to do so, subject to
  2536. # the following conditions:
  2537. #
  2538. # The above copyright notice and this permission notice shall be
  2539. # included in all copies or substantial portions of the Software.
  2540. #
  2541. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2542. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2543. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2544. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2545. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2546. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2547. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2548. #
  2549. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2550. # with permission of Minero Aoki.
  2551. #++
  2552. require 'nkf'
  2553. require 'tmail/base64.rb'
  2554. require 'tmail/stringio'
  2555. require 'tmail/utils'
  2556. module TMail
  2557. module StrategyInterface
  2558. def create_dest( obj )
  2559. case obj
  2560. when nil
  2561. StringOutput.new
  2562. when String
  2563. StringOutput.new(obj)
  2564. when IO, StringOutput
  2565. obj
  2566. else
  2567. raise TypeError, 'cannot handle this type of object for dest'
  2568. end
  2569. end
  2570. module_function :create_dest
  2571. def encoded( eol = "\r\n", charset = 'j', dest = nil )
  2572. accept_strategy Encoder, eol, charset, dest
  2573. end
  2574. def decoded( eol = "\n", charset = 'e', dest = nil )
  2575. accept_strategy Decoder, eol, charset, dest
  2576. end
  2577. alias to_s decoded
  2578. def accept_strategy( klass, eol, charset, dest = nil )
  2579. dest ||= ''
  2580. accept klass.new(create_dest(dest), charset, eol)
  2581. dest
  2582. end
  2583. end
  2584. ###
  2585. ### MIME B encoding decoder
  2586. ###
  2587. class Decoder
  2588. include TextUtils
  2589. encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
  2590. ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
  2591. OUTPUT_ENCODING = {
  2592. 'EUC' => 'e',
  2593. 'SJIS' => 's',
  2594. }
  2595. def self.decode( str, encoding = nil )
  2596. encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
  2597. opt = '-m' + encoding
  2598. str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
  2599. end
  2600. def initialize( dest, encoding = nil, eol = "\n" )
  2601. @f = StrategyInterface.create_dest(dest)
  2602. @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
  2603. @eol = eol
  2604. end
  2605. def decode( str )
  2606. self.class.decode(str, @encoding)
  2607. end
  2608. private :decode
  2609. def terminate
  2610. end
  2611. def header_line( str )
  2612. @f << decode(str)
  2613. end
  2614. def header_name( nm )
  2615. @f << nm << ': '
  2616. end
  2617. def header_body( str )
  2618. @f << decode(str)
  2619. end
  2620. def space
  2621. @f << ' '
  2622. end
  2623. alias spc space
  2624. def lwsp( str )
  2625. @f << str
  2626. end
  2627. def meta( str )
  2628. @f << str
  2629. end
  2630. def text( str )
  2631. @f << decode(str)
  2632. end
  2633. def phrase( str )
  2634. @f << quote_phrase(decode(str))
  2635. end
  2636. def kv_pair( k, v )
  2637. @f << k << '=' << v
  2638. end
  2639. def puts( str = nil )
  2640. @f << str if str
  2641. @f << @eol
  2642. end
  2643. def write( str )
  2644. @f << str
  2645. end
  2646. end
  2647. ###
  2648. ### MIME B-encoding encoder
  2649. ###
  2650. #
  2651. # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
  2652. #
  2653. class Encoder
  2654. include TextUtils
  2655. BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
  2656. def Encoder.encode( str )
  2657. e = new()
  2658. e.header_body str
  2659. e.terminate
  2660. e.dest.string
  2661. end
  2662. SPACER = "\t"
  2663. MAX_LINE_LEN = 70
  2664. OPTIONS = {
  2665. 'EUC' => '-Ej -m0',
  2666. 'SJIS' => '-Sj -m0',
  2667. 'UTF8' => nil, # FIXME
  2668. 'NONE' => nil
  2669. }
  2670. def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
  2671. @f = StrategyInterface.create_dest(dest)
  2672. @opt = OPTIONS[$KCODE]
  2673. @eol = eol
  2674. reset
  2675. end
  2676. def normalize_encoding( str )
  2677. if @opt
  2678. then NKF.nkf(@opt, str)
  2679. else str
  2680. end
  2681. end
  2682. def reset
  2683. @text = ''
  2684. @lwsp = ''
  2685. @curlen = 0
  2686. end
  2687. def terminate
  2688. add_lwsp ''
  2689. reset
  2690. end
  2691. def dest
  2692. @f
  2693. end
  2694. def puts( str = nil )
  2695. @f << str if str
  2696. @f << @eol
  2697. end
  2698. def write( str )
  2699. @f << str
  2700. end
  2701. #
  2702. # add
  2703. #
  2704. def header_line( line )
  2705. scanadd line
  2706. end
  2707. def header_name( name )
  2708. add_text name.split(/-/).map {|i| i.capitalize }.join('-')
  2709. add_text ':'
  2710. add_lwsp ' '
  2711. end
  2712. def header_body( str )
  2713. scanadd normalize_encoding(str)
  2714. end
  2715. def space
  2716. add_lwsp ' '
  2717. end
  2718. alias spc space
  2719. def lwsp( str )
  2720. add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
  2721. end
  2722. def meta( str )
  2723. add_text str
  2724. end
  2725. def text( str )
  2726. scanadd normalize_encoding(str)
  2727. end
  2728. def phrase( str )
  2729. str = normalize_encoding(str)
  2730. if CONTROL_CHAR === str
  2731. scanadd str
  2732. else
  2733. add_text quote_phrase(str)
  2734. end
  2735. end
  2736. # FIXME: implement line folding
  2737. #
  2738. def kv_pair( k, v )
  2739. return if v.nil?
  2740. v = normalize_encoding(v)
  2741. if token_safe?(v)
  2742. add_text k + '=' + v
  2743. elsif not CONTROL_CHAR === v
  2744. add_text k + '=' + quote_token(v)
  2745. else
  2746. # apply RFC2231 encoding
  2747. kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
  2748. add_text kv
  2749. end
  2750. end
  2751. def encode_value( str )
  2752. str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
  2753. end
  2754. private
  2755. def scanadd( str, force = false )
  2756. types = ''
  2757. strs = []
  2758. until str.empty?
  2759. if m = /\A[^\e\t\r\n ]+/.match(str)
  2760. types << (force ? 'j' : 'a')
  2761. strs.push m[0]
  2762. elsif m = /\A[\t\r\n ]+/.match(str)
  2763. types << 's'
  2764. strs.push m[0]
  2765. elsif m = /\A\e../.match(str)
  2766. esc = m[0]
  2767. str = m.post_match
  2768. if esc != "\e(B" and m = /\A[^\e]+/.match(str)
  2769. types << 'j'
  2770. strs.push m[0]
  2771. end
  2772. else
  2773. raise 'TMail FATAL: encoder scan fail'
  2774. end
  2775. (str = m.post_match) unless m.nil?
  2776. end
  2777. do_encode types, strs
  2778. end
  2779. def do_encode( types, strs )
  2780. #
  2781. # result : (A|E)(S(A|E))*
  2782. # E : W(SW)*
  2783. # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
  2784. # A : <<A character string not to be encoded>>
  2785. # J : <<A character string to be encoded>>
  2786. # S : <<LWSP>>
  2787. #
  2788. # An encoding unit is `E'.
  2789. # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
  2790. #
  2791. if BENCODE_DEBUG
  2792. puts
  2793. puts '-- do_encode ------------'
  2794. puts types.split(//).join(' ')
  2795. p strs
  2796. end
  2797. e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
  2798. while m = e.match(types)
  2799. pre = m.pre_match
  2800. concat_A_S pre, strs[0, pre.size] unless pre.empty?
  2801. concat_E m[0], strs[m.begin(0) ... m.end(0)]
  2802. types = m.post_match
  2803. strs.slice! 0, m.end(0)
  2804. end
  2805. concat_A_S types, strs
  2806. end
  2807. def concat_A_S( types, strs )
  2808. i = 0
  2809. types.each_byte do |t|
  2810. case t
  2811. when ?a then add_text strs[i]
  2812. when ?s then add_lwsp strs[i]
  2813. else
  2814. raise "TMail FATAL: unknown flag: #{t.chr}"
  2815. end
  2816. i += 1
  2817. end
  2818. end
  2819. METHOD_ID = {
  2820. ?j => :extract_J,
  2821. ?e => :extract_E,
  2822. ?a => :extract_A,
  2823. ?s => :extract_S
  2824. }
  2825. def concat_E( types, strs )
  2826. if BENCODE_DEBUG
  2827. puts '---- concat_E'
  2828. puts "types=#{types.split(//).join(' ')}"
  2829. puts "strs =#{strs.inspect}"
  2830. end
  2831. flush() unless @text.empty?
  2832. chunk = ''
  2833. strs.each_with_index do |s,i|
  2834. mid = METHOD_ID[types[i]]
  2835. until s.empty?
  2836. unless c = __send__(mid, chunk.size, s)
  2837. add_with_encode chunk unless chunk.empty?
  2838. flush
  2839. chunk = ''
  2840. fold
  2841. c = __send__(mid, 0, s)
  2842. raise 'TMail FATAL: extract fail' unless c
  2843. end
  2844. chunk << c
  2845. end
  2846. end
  2847. add_with_encode chunk unless chunk.empty?
  2848. end
  2849. def extract_J( chunksize, str )
  2850. size = max_bytes(chunksize, str.size) - 6
  2851. size = (size % 2 == 0) ? (size) : (size - 1)
  2852. return nil if size <= 0
  2853. "\e$B#{str.slice!(0, size)}\e(B"
  2854. end
  2855. def extract_A( chunksize, str )
  2856. size = max_bytes(chunksize, str.size)
  2857. return nil if size <= 0
  2858. str.slice!(0, size)
  2859. end
  2860. alias extract_S extract_A
  2861. def max_bytes( chunksize, ssize )
  2862. (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
  2863. end
  2864. #
  2865. # free length buffer
  2866. #
  2867. def add_text( str )
  2868. @text << str
  2869. # puts '---- text -------------------------------------'
  2870. # puts "+ #{str.inspect}"
  2871. # puts "txt >>>#{@text.inspect}<<<"
  2872. end
  2873. def add_with_encode( str )
  2874. @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
  2875. end
  2876. def add_lwsp( lwsp )
  2877. # puts '---- lwsp -------------------------------------'
  2878. # puts "+ #{lwsp.inspect}"
  2879. fold if restsize() <= 0
  2880. flush
  2881. @lwsp = lwsp
  2882. end
  2883. def flush
  2884. # puts '---- flush ----'
  2885. # puts "spc >>>#{@lwsp.inspect}<<<"
  2886. # puts "txt >>>#{@text.inspect}<<<"
  2887. @f << @lwsp << @text
  2888. @curlen += (@lwsp.size + @text.size)
  2889. @text = ''
  2890. @lwsp = ''
  2891. end
  2892. def fold
  2893. # puts '---- fold ----'
  2894. @f << @eol
  2895. @curlen = 0
  2896. @lwsp = SPACER
  2897. end
  2898. def restsize
  2899. MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
  2900. end
  2901. end
  2902. end # module TMail
  2903. #
  2904. # facade.rb
  2905. #
  2906. #--
  2907. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  2908. #
  2909. # Permission is hereby granted, free of charge, to any person obtaining
  2910. # a copy of this software and associated documentation files (the
  2911. # "Software"), to deal in the Software without restriction, including
  2912. # without limitation the rights to use, copy, modify, merge, publish,
  2913. # distribute, sublicense, and/or sell copies of the Software, and to
  2914. # permit persons to whom the Software is furnished to do so, subject to
  2915. # the following conditions:
  2916. #
  2917. # The above copyright notice and this permission notice shall be
  2918. # included in all copies or substantial portions of the Software.
  2919. #
  2920. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  2921. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  2922. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  2923. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  2924. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  2925. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  2926. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  2927. #
  2928. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  2929. # with permission of Minero Aoki.
  2930. #++
  2931. require 'tmail/utils'
  2932. module TMail
  2933. class Mail
  2934. def header_string( name, default = nil )
  2935. h = @header[name.downcase] or return default
  2936. h.to_s
  2937. end
  2938. ###
  2939. ### attributes
  2940. ###
  2941. include TextUtils
  2942. def set_string_array_attr( key, strs )
  2943. strs.flatten!
  2944. if strs.empty?
  2945. @header.delete key.downcase
  2946. else
  2947. store key, strs.join(', ')
  2948. end
  2949. strs
  2950. end
  2951. private :set_string_array_attr
  2952. def set_string_attr( key, str )
  2953. if str
  2954. store key, str
  2955. else
  2956. @header.delete key.downcase
  2957. end
  2958. str
  2959. end
  2960. private :set_string_attr
  2961. def set_addrfield( name, arg )
  2962. if arg
  2963. h = HeaderField.internal_new(name, @config)
  2964. h.addrs.replace [arg].flatten
  2965. @header[name] = h
  2966. else
  2967. @header.delete name
  2968. end
  2969. arg
  2970. end
  2971. private :set_addrfield
  2972. def addrs2specs( addrs )
  2973. return nil unless addrs
  2974. list = addrs.map {|addr|
  2975. if addr.address_group?
  2976. then addr.map {|a| a.spec }
  2977. else addr.spec
  2978. end
  2979. }.flatten
  2980. return nil if list.empty?
  2981. list
  2982. end
  2983. private :addrs2specs
  2984. #
  2985. # date time
  2986. #
  2987. def date( default = nil )
  2988. if h = @header['date']
  2989. h.date
  2990. else
  2991. default
  2992. end
  2993. end
  2994. def date=( time )
  2995. if time
  2996. store 'Date', time2str(time)
  2997. else
  2998. @header.delete 'date'
  2999. end
  3000. time
  3001. end
  3002. def strftime( fmt, default = nil )
  3003. if t = date
  3004. t.strftime(fmt)
  3005. else
  3006. default
  3007. end
  3008. end
  3009. #
  3010. # destination
  3011. #
  3012. def to_addrs( default = nil )
  3013. if h = @header['to']
  3014. h.addrs
  3015. else
  3016. default
  3017. end
  3018. end
  3019. def cc_addrs( default = nil )
  3020. if h = @header['cc']
  3021. h.addrs
  3022. else
  3023. default
  3024. end
  3025. end
  3026. def bcc_addrs( default = nil )
  3027. if h = @header['bcc']
  3028. h.addrs
  3029. else
  3030. default
  3031. end
  3032. end
  3033. def to_addrs=( arg )
  3034. set_addrfield 'to', arg
  3035. end
  3036. def cc_addrs=( arg )
  3037. set_addrfield 'cc', arg
  3038. end
  3039. def bcc_addrs=( arg )
  3040. set_addrfield 'bcc', arg
  3041. end
  3042. def to( default = nil )
  3043. addrs2specs(to_addrs(nil)) || default
  3044. end
  3045. def cc( default = nil )
  3046. addrs2specs(cc_addrs(nil)) || default
  3047. end
  3048. def bcc( default = nil )
  3049. addrs2specs(bcc_addrs(nil)) || default
  3050. end
  3051. def to=( *strs )
  3052. set_string_array_attr 'To', strs
  3053. end
  3054. def cc=( *strs )
  3055. set_string_array_attr 'Cc', strs
  3056. end
  3057. def bcc=( *strs )
  3058. set_string_array_attr 'Bcc', strs
  3059. end
  3060. #
  3061. # originator
  3062. #
  3063. def from_addrs( default = nil )
  3064. if h = @header['from']
  3065. h.addrs
  3066. else
  3067. default
  3068. end
  3069. end
  3070. def from_addrs=( arg )
  3071. set_addrfield 'from', arg
  3072. end
  3073. def from( default = nil )
  3074. addrs2specs(from_addrs(nil)) || default
  3075. end
  3076. def from=( *strs )
  3077. set_string_array_attr 'From', strs
  3078. end
  3079. def friendly_from( default = nil )
  3080. h = @header['from']
  3081. a, = h.addrs
  3082. return default unless a
  3083. return a.phrase if a.phrase
  3084. return h.comments.join(' ') unless h.comments.empty?
  3085. a.spec
  3086. end
  3087. def reply_to_addrs( default = nil )
  3088. if h = @header['reply-to']
  3089. h.addrs
  3090. else
  3091. default
  3092. end
  3093. end
  3094. def reply_to_addrs=( arg )
  3095. set_addrfield 'reply-to', arg
  3096. end
  3097. def reply_to( default = nil )
  3098. addrs2specs(reply_to_addrs(nil)) || default
  3099. end
  3100. def reply_to=( *strs )
  3101. set_string_array_attr 'Reply-To', strs
  3102. end
  3103. def sender_addr( default = nil )
  3104. f = @header['sender'] or return default
  3105. f.addr or return default
  3106. end
  3107. def sender_addr=( addr )
  3108. if addr
  3109. h = HeaderField.internal_new('sender', @config)
  3110. h.addr = addr
  3111. @header['sender'] = h
  3112. else
  3113. @header.delete 'sender'
  3114. end
  3115. addr
  3116. end
  3117. def sender( default )
  3118. f = @header['sender'] or return default
  3119. a = f.addr or return default
  3120. a.spec
  3121. end
  3122. def sender=( str )
  3123. set_string_attr 'Sender', str
  3124. end
  3125. #
  3126. # subject
  3127. #
  3128. def subject( default = nil )
  3129. if h = @header['subject']
  3130. h.body
  3131. else
  3132. default
  3133. end
  3134. end
  3135. alias quoted_subject subject
  3136. def subject=( str )
  3137. set_string_attr 'Subject', str
  3138. end
  3139. #
  3140. # identity & threading
  3141. #
  3142. def message_id( default = nil )
  3143. if h = @header['message-id']
  3144. h.id || default
  3145. else
  3146. default
  3147. end
  3148. end
  3149. def message_id=( str )
  3150. set_string_attr 'Message-Id', str
  3151. end
  3152. def in_reply_to( default = nil )
  3153. if h = @header['in-reply-to']
  3154. h.ids
  3155. else
  3156. default
  3157. end
  3158. end
  3159. def in_reply_to=( *idstrs )
  3160. set_string_array_attr 'In-Reply-To', idstrs
  3161. end
  3162. def references( default = nil )
  3163. if h = @header['references']
  3164. h.refs
  3165. else
  3166. default
  3167. end
  3168. end
  3169. def references=( *strs )
  3170. set_string_array_attr 'References', strs
  3171. end
  3172. #
  3173. # MIME headers
  3174. #
  3175. def mime_version( default = nil )
  3176. if h = @header['mime-version']
  3177. h.version || default
  3178. else
  3179. default
  3180. end
  3181. end
  3182. def mime_version=( m, opt = nil )
  3183. if opt
  3184. if h = @header['mime-version']
  3185. h.major = m
  3186. h.minor = opt
  3187. else
  3188. store 'Mime-Version', "#{m}.#{opt}"
  3189. end
  3190. else
  3191. store 'Mime-Version', m
  3192. end
  3193. m
  3194. end
  3195. def content_type( default = nil )
  3196. if h = @header['content-type']
  3197. h.content_type || default
  3198. else
  3199. default
  3200. end
  3201. end
  3202. def main_type( default = nil )
  3203. if h = @header['content-type']
  3204. h.main_type || default
  3205. else
  3206. default
  3207. end
  3208. end
  3209. def sub_type( default = nil )
  3210. if h = @header['content-type']
  3211. h.sub_type || default
  3212. else
  3213. default
  3214. end
  3215. end
  3216. def set_content_type( str, sub = nil, param = nil )
  3217. if sub
  3218. main, sub = str, sub
  3219. else
  3220. main, sub = str.split(%r</>, 2)
  3221. raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
  3222. end
  3223. if h = @header['content-type']
  3224. h.main_type = main
  3225. h.sub_type = sub
  3226. h.params.clear
  3227. else
  3228. store 'Content-Type', "#{main}/#{sub}"
  3229. end
  3230. @header['content-type'].params.replace param if param
  3231. str
  3232. end
  3233. alias content_type= set_content_type
  3234. def type_param( name, default = nil )
  3235. if h = @header['content-type']
  3236. h[name] || default
  3237. else
  3238. default
  3239. end
  3240. end
  3241. def charset( default = nil )
  3242. if h = @header['content-type']
  3243. h['charset'] or default
  3244. else
  3245. default
  3246. end
  3247. end
  3248. def charset=( str )
  3249. if str
  3250. if h = @header[ 'content-type' ]
  3251. h['charset'] = str
  3252. else
  3253. store 'Content-Type', "text/plain; charset=#{str}"
  3254. end
  3255. end
  3256. str
  3257. end
  3258. def transfer_encoding( default = nil )
  3259. if h = @header['content-transfer-encoding']
  3260. h.encoding || default
  3261. else
  3262. default
  3263. end
  3264. end
  3265. def transfer_encoding=( str )
  3266. set_string_attr 'Content-Transfer-Encoding', str
  3267. end
  3268. alias encoding transfer_encoding
  3269. alias encoding= transfer_encoding=
  3270. alias content_transfer_encoding transfer_encoding
  3271. alias content_transfer_encoding= transfer_encoding=
  3272. def disposition( default = nil )
  3273. if h = @header['content-disposition']
  3274. h.disposition || default
  3275. else
  3276. default
  3277. end
  3278. end
  3279. alias content_disposition disposition
  3280. def set_disposition( str, params = nil )
  3281. if h = @header['content-disposition']
  3282. h.disposition = str
  3283. h.params.clear
  3284. else
  3285. store('Content-Disposition', str)
  3286. h = @header['content-disposition']
  3287. end
  3288. h.params.replace params if params
  3289. end
  3290. alias disposition= set_disposition
  3291. alias set_content_disposition set_disposition
  3292. alias content_disposition= set_disposition
  3293. def disposition_param( name, default = nil )
  3294. if h = @header['content-disposition']
  3295. h[name] || default
  3296. else
  3297. default
  3298. end
  3299. end
  3300. ###
  3301. ### utils
  3302. ###
  3303. def create_reply
  3304. mail = TMail::Mail.parse('')
  3305. mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
  3306. mail.to_addrs = reply_addresses([])
  3307. mail.in_reply_to = [message_id(nil)].compact
  3308. mail.references = references([]) + [message_id(nil)].compact
  3309. mail.mime_version = '1.0'
  3310. mail
  3311. end
  3312. def base64_encode
  3313. store 'Content-Transfer-Encoding', 'Base64'
  3314. self.body = Base64.folding_encode(self.body)
  3315. end
  3316. def base64_decode
  3317. if /base64/i === self.transfer_encoding('')
  3318. store 'Content-Transfer-Encoding', '8bit'
  3319. self.body = Base64.decode(self.body, @config.strict_base64decode?)
  3320. end
  3321. end
  3322. def destinations( default = nil )
  3323. ret = []
  3324. %w( to cc bcc ).each do |nm|
  3325. if h = @header[nm]
  3326. h.addrs.each {|i| ret.push i.address }
  3327. end
  3328. end
  3329. ret.empty? ? default : ret
  3330. end
  3331. def each_destination( &block )
  3332. destinations([]).each do |i|
  3333. if Address === i
  3334. yield i
  3335. else
  3336. i.each(&block)
  3337. end
  3338. end
  3339. end
  3340. alias each_dest each_destination
  3341. def reply_addresses( default = nil )
  3342. reply_to_addrs(nil) or from_addrs(nil) or default
  3343. end
  3344. def error_reply_addresses( default = nil )
  3345. if s = sender(nil)
  3346. [s]
  3347. else
  3348. from_addrs(default)
  3349. end
  3350. end
  3351. def multipart?
  3352. main_type('').downcase == 'multipart'
  3353. end
  3354. end # class Mail
  3355. end # module TMail
  3356. #
  3357. # header.rb
  3358. #
  3359. #--
  3360. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  3361. #
  3362. # Permission is hereby granted, free of charge, to any person obtaining
  3363. # a copy of this software and associated documentation files (the
  3364. # "Software"), to deal in the Software without restriction, including
  3365. # without limitation the rights to use, copy, modify, merge, publish,
  3366. # distribute, sublicense, and/or sell copies of the Software, and to
  3367. # permit persons to whom the Software is furnished to do so, subject to
  3368. # the following conditions:
  3369. #
  3370. # The above copyright notice and this permission notice shall be
  3371. # included in all copies or substantial portions of the Software.
  3372. #
  3373. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  3374. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  3375. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  3376. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  3377. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  3378. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  3379. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  3380. #
  3381. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  3382. # with permission of Minero Aoki.
  3383. #++
  3384. require 'tmail/encode'
  3385. require 'tmail/address'
  3386. require 'tmail/parser'
  3387. require 'tmail/config'
  3388. require 'tmail/utils'
  3389. module TMail
  3390. class HeaderField
  3391. include TextUtils
  3392. class << self
  3393. alias newobj new
  3394. def new( name, body, conf = DEFAULT_CONFIG )
  3395. klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
  3396. klass.newobj body, conf
  3397. end
  3398. def new_from_port( port, name, conf = DEFAULT_CONFIG )
  3399. re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
  3400. str = nil
  3401. port.ropen {|f|
  3402. f.each do |line|
  3403. if m = re.match(line) then str = m.post_match.strip
  3404. elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
  3405. elsif /\A-*\s*\z/ === line then break
  3406. elsif str then break
  3407. end
  3408. end
  3409. }
  3410. new(name, str, Config.to_config(conf))
  3411. end
  3412. def internal_new( name, conf )
  3413. FNAME_TO_CLASS[name].newobj('', conf, true)
  3414. end
  3415. end # class << self
  3416. def initialize( body, conf, intern = false )
  3417. @body = body
  3418. @config = conf
  3419. @illegal = false
  3420. @parsed = false
  3421. if intern
  3422. @parsed = true
  3423. parse_init
  3424. end
  3425. end
  3426. def inspect
  3427. "#<#{self.class} #{@body.inspect}>"
  3428. end
  3429. def illegal?
  3430. @illegal
  3431. end
  3432. def empty?
  3433. ensure_parsed
  3434. return true if @illegal
  3435. isempty?
  3436. end
  3437. private
  3438. def ensure_parsed
  3439. return if @parsed
  3440. @parsed = true
  3441. parse
  3442. end
  3443. # defabstract parse
  3444. # end
  3445. def clear_parse_status
  3446. @parsed = false
  3447. @illegal = false
  3448. end
  3449. public
  3450. def body
  3451. ensure_parsed
  3452. v = Decoder.new(s = '')
  3453. do_accept v
  3454. v.terminate
  3455. s
  3456. end
  3457. def body=( str )
  3458. @body = str
  3459. clear_parse_status
  3460. end
  3461. include StrategyInterface
  3462. def accept( strategy, dummy1 = nil, dummy2 = nil )
  3463. ensure_parsed
  3464. do_accept strategy
  3465. strategy.terminate
  3466. end
  3467. # abstract do_accept
  3468. end
  3469. class UnstructuredHeader < HeaderField
  3470. def body
  3471. ensure_parsed
  3472. @body
  3473. end
  3474. def body=( arg )
  3475. ensure_parsed
  3476. @body = arg
  3477. end
  3478. private
  3479. def parse_init
  3480. end
  3481. def parse
  3482. @body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
  3483. end
  3484. def isempty?
  3485. not @body
  3486. end
  3487. def do_accept( strategy )
  3488. strategy.text @body
  3489. end
  3490. end
  3491. class StructuredHeader < HeaderField
  3492. def comments
  3493. ensure_parsed
  3494. @comments
  3495. end
  3496. private
  3497. def parse
  3498. save = nil
  3499. begin
  3500. parse_init
  3501. do_parse
  3502. rescue SyntaxError
  3503. if not save and mime_encoded? @body
  3504. save = @body
  3505. @body = Decoder.decode(save)
  3506. retry
  3507. elsif save
  3508. @body = save
  3509. end
  3510. @illegal = true
  3511. raise if @config.strict_parse?
  3512. end
  3513. end
  3514. def parse_init
  3515. @comments = []
  3516. init
  3517. end
  3518. def do_parse
  3519. obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
  3520. set obj if obj
  3521. end
  3522. end
  3523. class DateTimeHeader < StructuredHeader
  3524. PARSE_TYPE = :DATETIME
  3525. def date
  3526. ensure_parsed
  3527. @date
  3528. end
  3529. def date=( arg )
  3530. ensure_parsed
  3531. @date = arg
  3532. end
  3533. private
  3534. def init
  3535. @date = nil
  3536. end
  3537. def set( t )
  3538. @date = t
  3539. end
  3540. def isempty?
  3541. not @date
  3542. end
  3543. def do_accept( strategy )
  3544. strategy.meta time2str(@date)
  3545. end
  3546. end
  3547. class AddressHeader < StructuredHeader
  3548. PARSE_TYPE = :MADDRESS
  3549. def addrs
  3550. ensure_parsed
  3551. @addrs
  3552. end
  3553. private
  3554. def init
  3555. @addrs = []
  3556. end
  3557. def set( a )
  3558. @addrs = a
  3559. end
  3560. def isempty?
  3561. @addrs.empty?
  3562. end
  3563. def do_accept( strategy )
  3564. first = true
  3565. @addrs.each do |a|
  3566. if first
  3567. first = false
  3568. else
  3569. strategy.meta ','
  3570. strategy.space
  3571. end
  3572. a.accept strategy
  3573. end
  3574. @comments.each do |c|
  3575. strategy.space
  3576. strategy.meta '('
  3577. strategy.text c
  3578. strategy.meta ')'
  3579. end
  3580. end
  3581. end
  3582. class ReturnPathHeader < AddressHeader
  3583. PARSE_TYPE = :RETPATH
  3584. def addr
  3585. addrs()[0]
  3586. end
  3587. def spec
  3588. a = addr() or return nil
  3589. a.spec
  3590. end
  3591. def routes
  3592. a = addr() or return nil
  3593. a.routes
  3594. end
  3595. private
  3596. def do_accept( strategy )
  3597. a = addr()
  3598. strategy.meta '<'
  3599. unless a.routes.empty?
  3600. strategy.meta a.routes.map {|i| '@' + i }.join(',')
  3601. strategy.meta ':'
  3602. end
  3603. spec = a.spec
  3604. strategy.meta spec if spec
  3605. strategy.meta '>'
  3606. end
  3607. end
  3608. class SingleAddressHeader < AddressHeader
  3609. def addr
  3610. addrs()[0]
  3611. end
  3612. private
  3613. def do_accept( strategy )
  3614. a = addr()
  3615. a.accept strategy
  3616. @comments.each do |c|
  3617. strategy.space
  3618. strategy.meta '('
  3619. strategy.text c
  3620. strategy.meta ')'
  3621. end
  3622. end
  3623. end
  3624. class MessageIdHeader < StructuredHeader
  3625. def id
  3626. ensure_parsed
  3627. @id
  3628. end
  3629. def id=( arg )
  3630. ensure_parsed
  3631. @id = arg
  3632. end
  3633. private
  3634. def init
  3635. @id = nil
  3636. end
  3637. def isempty?
  3638. not @id
  3639. end
  3640. def do_parse
  3641. @id = @body.slice(MESSAGE_ID) or
  3642. raise SyntaxError, "wrong Message-ID format: #{@body}"
  3643. end
  3644. def do_accept( strategy )
  3645. strategy.meta @id
  3646. end
  3647. end
  3648. class ReferencesHeader < StructuredHeader
  3649. def refs
  3650. ensure_parsed
  3651. @refs
  3652. end
  3653. def each_id
  3654. self.refs.each do |i|
  3655. yield i if MESSAGE_ID === i
  3656. end
  3657. end
  3658. def ids
  3659. ensure_parsed
  3660. @ids
  3661. end
  3662. def each_phrase
  3663. self.refs.each do |i|
  3664. yield i unless MESSAGE_ID === i
  3665. end
  3666. end
  3667. def phrases
  3668. ret = []
  3669. each_phrase {|i| ret.push i }
  3670. ret
  3671. end
  3672. private
  3673. def init
  3674. @refs = []
  3675. @ids = []
  3676. end
  3677. def isempty?
  3678. @ids.empty?
  3679. end
  3680. def do_parse
  3681. str = @body
  3682. while m = MESSAGE_ID.match(str)
  3683. pre = m.pre_match.strip
  3684. @refs.push pre unless pre.empty?
  3685. @refs.push s = m[0]
  3686. @ids.push s
  3687. str = m.post_match
  3688. end
  3689. str = str.strip
  3690. @refs.push str unless str.empty?
  3691. end
  3692. def do_accept( strategy )
  3693. first = true
  3694. @ids.each do |i|
  3695. if first
  3696. first = false
  3697. else
  3698. strategy.space
  3699. end
  3700. strategy.meta i
  3701. end
  3702. end
  3703. end
  3704. class ReceivedHeader < StructuredHeader
  3705. PARSE_TYPE = :RECEIVED
  3706. def from
  3707. ensure_parsed
  3708. @from
  3709. end
  3710. def from=( arg )
  3711. ensure_parsed
  3712. @from = arg
  3713. end
  3714. def by
  3715. ensure_parsed
  3716. @by
  3717. end
  3718. def by=( arg )
  3719. ensure_parsed
  3720. @by = arg
  3721. end
  3722. def via
  3723. ensure_parsed
  3724. @via
  3725. end
  3726. def via=( arg )
  3727. ensure_parsed
  3728. @via = arg
  3729. end
  3730. def with
  3731. ensure_parsed
  3732. @with
  3733. end
  3734. def id
  3735. ensure_parsed
  3736. @id
  3737. end
  3738. def id=( arg )
  3739. ensure_parsed
  3740. @id = arg
  3741. end
  3742. def _for
  3743. ensure_parsed
  3744. @_for
  3745. end
  3746. def _for=( arg )
  3747. ensure_parsed
  3748. @_for = arg
  3749. end
  3750. def date
  3751. ensure_parsed
  3752. @date
  3753. end
  3754. def date=( arg )
  3755. ensure_parsed
  3756. @date = arg
  3757. end
  3758. private
  3759. def init
  3760. @from = @by = @via = @with = @id = @_for = nil
  3761. @with = []
  3762. @date = nil
  3763. end
  3764. def set( args )
  3765. @from, @by, @via, @with, @id, @_for, @date = *args
  3766. end
  3767. def isempty?
  3768. @with.empty? and not (@from or @by or @via or @id or @_for or @date)
  3769. end
  3770. def do_accept( strategy )
  3771. list = []
  3772. list.push 'from ' + @from if @from
  3773. list.push 'by ' + @by if @by
  3774. list.push 'via ' + @via if @via
  3775. @with.each do |i|
  3776. list.push 'with ' + i
  3777. end
  3778. list.push 'id ' + @id if @id
  3779. list.push 'for <' + @_for + '>' if @_for
  3780. first = true
  3781. list.each do |i|
  3782. strategy.space unless first
  3783. strategy.meta i
  3784. first = false
  3785. end
  3786. if @date
  3787. strategy.meta ';'
  3788. strategy.space
  3789. strategy.meta time2str(@date)
  3790. end
  3791. end
  3792. end
  3793. class KeywordsHeader < StructuredHeader
  3794. PARSE_TYPE = :KEYWORDS
  3795. def keys
  3796. ensure_parsed
  3797. @keys
  3798. end
  3799. private
  3800. def init
  3801. @keys = []
  3802. end
  3803. def set( a )
  3804. @keys = a
  3805. end
  3806. def isempty?
  3807. @keys.empty?
  3808. end
  3809. def do_accept( strategy )
  3810. first = true
  3811. @keys.each do |i|
  3812. if first
  3813. first = false
  3814. else
  3815. strategy.meta ','
  3816. end
  3817. strategy.meta i
  3818. end
  3819. end
  3820. end
  3821. class EncryptedHeader < StructuredHeader
  3822. PARSE_TYPE = :ENCRYPTED
  3823. def encrypter
  3824. ensure_parsed
  3825. @encrypter
  3826. end
  3827. def encrypter=( arg )
  3828. ensure_parsed
  3829. @encrypter = arg
  3830. end
  3831. def keyword
  3832. ensure_parsed
  3833. @keyword
  3834. end
  3835. def keyword=( arg )
  3836. ensure_parsed
  3837. @keyword = arg
  3838. end
  3839. private
  3840. def init
  3841. @encrypter = nil
  3842. @keyword = nil
  3843. end
  3844. def set( args )
  3845. @encrypter, @keyword = args
  3846. end
  3847. def isempty?
  3848. not (@encrypter or @keyword)
  3849. end
  3850. def do_accept( strategy )
  3851. if @key
  3852. strategy.meta @encrypter + ','
  3853. strategy.space
  3854. strategy.meta @keyword
  3855. else
  3856. strategy.meta @encrypter
  3857. end
  3858. end
  3859. end
  3860. class MimeVersionHeader < StructuredHeader
  3861. PARSE_TYPE = :MIMEVERSION
  3862. def major
  3863. ensure_parsed
  3864. @major
  3865. end
  3866. def major=( arg )
  3867. ensure_parsed
  3868. @major = arg
  3869. end
  3870. def minor
  3871. ensure_parsed
  3872. @minor
  3873. end
  3874. def minor=( arg )
  3875. ensure_parsed
  3876. @minor = arg
  3877. end
  3878. def version
  3879. sprintf('%d.%d', major, minor)
  3880. end
  3881. private
  3882. def init
  3883. @major = nil
  3884. @minor = nil
  3885. end
  3886. def set( args )
  3887. @major, @minor = *args
  3888. end
  3889. def isempty?
  3890. not (@major or @minor)
  3891. end
  3892. def do_accept( strategy )
  3893. strategy.meta sprintf('%d.%d', @major, @minor)
  3894. end
  3895. end
  3896. class ContentTypeHeader < StructuredHeader
  3897. PARSE_TYPE = :CTYPE
  3898. def main_type
  3899. ensure_parsed
  3900. @main
  3901. end
  3902. def main_type=( arg )
  3903. ensure_parsed
  3904. @main = arg.downcase
  3905. end
  3906. def sub_type
  3907. ensure_parsed
  3908. @sub
  3909. end
  3910. def sub_type=( arg )
  3911. ensure_parsed
  3912. @sub = arg.downcase
  3913. end
  3914. def content_type
  3915. ensure_parsed
  3916. @sub ? sprintf('%s/%s', @main, @sub) : @main
  3917. end
  3918. def params
  3919. ensure_parsed
  3920. @params
  3921. end
  3922. def []( key )
  3923. ensure_parsed
  3924. @params and @params[key]
  3925. end
  3926. def []=( key, val )
  3927. ensure_parsed
  3928. (@params ||= {})[key] = val
  3929. end
  3930. private
  3931. def init
  3932. @main = @sub = @params = nil
  3933. end
  3934. def set( args )
  3935. @main, @sub, @params = *args
  3936. end
  3937. def isempty?
  3938. not (@main or @sub)
  3939. end
  3940. def do_accept( strategy )
  3941. if @sub
  3942. strategy.meta sprintf('%s/%s', @main, @sub)
  3943. else
  3944. strategy.meta @main
  3945. end
  3946. @params.each do |k,v|
  3947. if v
  3948. strategy.meta ';'
  3949. strategy.space
  3950. strategy.kv_pair k, v
  3951. end
  3952. end
  3953. end
  3954. end
  3955. class ContentTransferEncodingHeader < StructuredHeader
  3956. PARSE_TYPE = :CENCODING
  3957. def encoding
  3958. ensure_parsed
  3959. @encoding
  3960. end
  3961. def encoding=( arg )
  3962. ensure_parsed
  3963. @encoding = arg
  3964. end
  3965. private
  3966. def init
  3967. @encoding = nil
  3968. end
  3969. def set( s )
  3970. @encoding = s
  3971. end
  3972. def isempty?
  3973. not @encoding
  3974. end
  3975. def do_accept( strategy )
  3976. strategy.meta @encoding.capitalize
  3977. end
  3978. end
  3979. class ContentDispositionHeader < StructuredHeader
  3980. PARSE_TYPE = :CDISPOSITION
  3981. def disposition
  3982. ensure_parsed
  3983. @disposition
  3984. end
  3985. def disposition=( str )
  3986. ensure_parsed
  3987. @disposition = str.downcase
  3988. end
  3989. def params
  3990. ensure_parsed
  3991. @params
  3992. end
  3993. def []( key )
  3994. ensure_parsed
  3995. @params and @params[key]
  3996. end
  3997. def []=( key, val )
  3998. ensure_parsed
  3999. (@params ||= {})[key] = val
  4000. end
  4001. private
  4002. def init
  4003. @disposition = @params = nil
  4004. end
  4005. def set( args )
  4006. @disposition, @params = *args
  4007. end
  4008. def isempty?
  4009. not @disposition and (not @params or @params.empty?)
  4010. end
  4011. def do_accept( strategy )
  4012. strategy.meta @disposition
  4013. @params.each do |k,v|
  4014. strategy.meta ';'
  4015. strategy.space
  4016. strategy.kv_pair k, v
  4017. end
  4018. end
  4019. end
  4020. class HeaderField # redefine
  4021. FNAME_TO_CLASS = {
  4022. 'date' => DateTimeHeader,
  4023. 'resent-date' => DateTimeHeader,
  4024. 'to' => AddressHeader,
  4025. 'cc' => AddressHeader,
  4026. 'bcc' => AddressHeader,
  4027. 'from' => AddressHeader,
  4028. 'reply-to' => AddressHeader,
  4029. 'resent-to' => AddressHeader,
  4030. 'resent-cc' => AddressHeader,
  4031. 'resent-bcc' => AddressHeader,
  4032. 'resent-from' => AddressHeader,
  4033. 'resent-reply-to' => AddressHeader,
  4034. 'sender' => SingleAddressHeader,
  4035. 'resent-sender' => SingleAddressHeader,
  4036. 'return-path' => ReturnPathHeader,
  4037. 'message-id' => MessageIdHeader,
  4038. 'resent-message-id' => MessageIdHeader,
  4039. 'in-reply-to' => ReferencesHeader,
  4040. 'received' => ReceivedHeader,
  4041. 'references' => ReferencesHeader,
  4042. 'keywords' => KeywordsHeader,
  4043. 'encrypted' => EncryptedHeader,
  4044. 'mime-version' => MimeVersionHeader,
  4045. 'content-type' => ContentTypeHeader,
  4046. 'content-transfer-encoding' => ContentTransferEncodingHeader,
  4047. 'content-disposition' => ContentDispositionHeader,
  4048. 'content-id' => MessageIdHeader,
  4049. 'subject' => UnstructuredHeader,
  4050. 'comments' => UnstructuredHeader,
  4051. 'content-description' => UnstructuredHeader
  4052. }
  4053. end
  4054. end # module TMail
  4055. #
  4056. # info.rb
  4057. #
  4058. #--
  4059. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4060. #
  4061. # Permission is hereby granted, free of charge, to any person obtaining
  4062. # a copy of this software and associated documentation files (the
  4063. # "Software"), to deal in the Software without restriction, including
  4064. # without limitation the rights to use, copy, modify, merge, publish,
  4065. # distribute, sublicense, and/or sell copies of the Software, and to
  4066. # permit persons to whom the Software is furnished to do so, subject to
  4067. # the following conditions:
  4068. #
  4069. # The above copyright notice and this permission notice shall be
  4070. # included in all copies or substantial portions of the Software.
  4071. #
  4072. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4073. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4074. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4075. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4076. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4077. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4078. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4079. #
  4080. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4081. # with permission of Minero Aoki.
  4082. #++
  4083. module TMail
  4084. Version = '0.10.7'
  4085. Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
  4086. end
  4087. require 'tmail/mailbox'
  4088. #
  4089. # mail.rb
  4090. #
  4091. #--
  4092. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4093. #
  4094. # Permission is hereby granted, free of charge, to any person obtaining
  4095. # a copy of this software and associated documentation files (the
  4096. # "Software"), to deal in the Software without restriction, including
  4097. # without limitation the rights to use, copy, modify, merge, publish,
  4098. # distribute, sublicense, and/or sell copies of the Software, and to
  4099. # permit persons to whom the Software is furnished to do so, subject to
  4100. # the following conditions:
  4101. #
  4102. # The above copyright notice and this permission notice shall be
  4103. # included in all copies or substantial portions of the Software.
  4104. #
  4105. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4106. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4107. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4108. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4109. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4110. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4111. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4112. #
  4113. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4114. # with permission of Minero Aoki.
  4115. #++
  4116. require 'tmail/facade'
  4117. require 'tmail/encode'
  4118. require 'tmail/header'
  4119. require 'tmail/port'
  4120. require 'tmail/config'
  4121. require 'tmail/utils'
  4122. require 'tmail/attachments'
  4123. require 'tmail/quoting'
  4124. require 'socket'
  4125. module TMail
  4126. class Mail
  4127. class << self
  4128. def load( fname )
  4129. new(FilePort.new(fname))
  4130. end
  4131. alias load_from load
  4132. alias loadfrom load
  4133. def parse( str )
  4134. new(StringPort.new(str))
  4135. end
  4136. end
  4137. def initialize( port = nil, conf = DEFAULT_CONFIG )
  4138. @port = port || StringPort.new
  4139. @config = Config.to_config(conf)
  4140. @header = {}
  4141. @body_port = nil
  4142. @body_parsed = false
  4143. @epilogue = ''
  4144. @parts = []
  4145. @port.ropen {|f|
  4146. parse_header f
  4147. parse_body f unless @port.reproducible?
  4148. }
  4149. end
  4150. attr_reader :port
  4151. def inspect
  4152. "\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
  4153. end
  4154. #
  4155. # to_s interfaces
  4156. #
  4157. public
  4158. include StrategyInterface
  4159. def write_back( eol = "\n", charset = 'e' )
  4160. parse_body
  4161. @port.wopen {|stream| encoded eol, charset, stream }
  4162. end
  4163. def accept( strategy )
  4164. with_multipart_encoding(strategy) {
  4165. ordered_each do |name, field|
  4166. next if field.empty?
  4167. strategy.header_name canonical(name)
  4168. field.accept strategy
  4169. strategy.puts
  4170. end
  4171. strategy.puts
  4172. body_port().ropen {|r|
  4173. strategy.write r.read
  4174. }
  4175. }
  4176. end
  4177. private
  4178. def canonical( name )
  4179. name.split(/-/).map {|s| s.capitalize }.join('-')
  4180. end
  4181. def with_multipart_encoding( strategy )
  4182. if parts().empty? # DO NOT USE @parts
  4183. yield
  4184. else
  4185. bound = ::TMail.new_boundary
  4186. if @header.key? 'content-type'
  4187. @header['content-type'].params['boundary'] = bound
  4188. else
  4189. store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
  4190. end
  4191. yield
  4192. parts().each do |tm|
  4193. strategy.puts
  4194. strategy.puts '--' + bound
  4195. tm.accept strategy
  4196. end
  4197. strategy.puts
  4198. strategy.puts '--' + bound + '--'
  4199. strategy.write epilogue()
  4200. end
  4201. end
  4202. ###
  4203. ### header
  4204. ###
  4205. public
  4206. ALLOW_MULTIPLE = {
  4207. 'received' => true,
  4208. 'resent-date' => true,
  4209. 'resent-from' => true,
  4210. 'resent-sender' => true,
  4211. 'resent-to' => true,
  4212. 'resent-cc' => true,
  4213. 'resent-bcc' => true,
  4214. 'resent-message-id' => true,
  4215. 'comments' => true,
  4216. 'keywords' => true
  4217. }
  4218. USE_ARRAY = ALLOW_MULTIPLE
  4219. def header
  4220. @header.dup
  4221. end
  4222. def []( key )
  4223. @header[key.downcase]
  4224. end
  4225. def sub_header(key, param)
  4226. (hdr = self[key]) ? hdr[param] : nil
  4227. end
  4228. alias fetch []
  4229. def []=( key, val )
  4230. dkey = key.downcase
  4231. if val.nil?
  4232. @header.delete dkey
  4233. return nil
  4234. end
  4235. case val
  4236. when String
  4237. header = new_hf(key, val)
  4238. when HeaderField
  4239. ;
  4240. when Array
  4241. ALLOW_MULTIPLE.include? dkey or
  4242. raise ArgumentError, "#{key}: Header must not be multiple"
  4243. @header[dkey] = val
  4244. return val
  4245. else
  4246. header = new_hf(key, val.to_s)
  4247. end
  4248. if ALLOW_MULTIPLE.include? dkey
  4249. (@header[dkey] ||= []).push header
  4250. else
  4251. @header[dkey] = header
  4252. end
  4253. val
  4254. end
  4255. alias store []=
  4256. def each_header
  4257. @header.each do |key, val|
  4258. [val].flatten.each {|v| yield key, v }
  4259. end
  4260. end
  4261. alias each_pair each_header
  4262. def each_header_name( &block )
  4263. @header.each_key(&block)
  4264. end
  4265. alias each_key each_header_name
  4266. def each_field( &block )
  4267. @header.values.flatten.each(&block)
  4268. end
  4269. alias each_value each_field
  4270. FIELD_ORDER = %w(
  4271. return-path received
  4272. resent-date resent-from resent-sender resent-to
  4273. resent-cc resent-bcc resent-message-id
  4274. date from sender reply-to to cc bcc
  4275. message-id in-reply-to references
  4276. subject comments keywords
  4277. mime-version content-type content-transfer-encoding
  4278. content-disposition content-description
  4279. )
  4280. def ordered_each
  4281. list = @header.keys
  4282. FIELD_ORDER.each do |name|
  4283. if list.delete(name)
  4284. [@header[name]].flatten.each {|v| yield name, v }
  4285. end
  4286. end
  4287. list.each do |name|
  4288. [@header[name]].flatten.each {|v| yield name, v }
  4289. end
  4290. end
  4291. def clear
  4292. @header.clear
  4293. end
  4294. def delete( key )
  4295. @header.delete key.downcase
  4296. end
  4297. def delete_if
  4298. @header.delete_if do |key,val|
  4299. if Array === val
  4300. val.delete_if {|v| yield key, v }
  4301. val.empty?
  4302. else
  4303. yield key, val
  4304. end
  4305. end
  4306. end
  4307. def keys
  4308. @header.keys
  4309. end
  4310. def key?( key )
  4311. @header.key? key.downcase
  4312. end
  4313. def values_at( *args )
  4314. args.map {|k| @header[k.downcase] }.flatten
  4315. end
  4316. alias indexes values_at
  4317. alias indices values_at
  4318. private
  4319. def parse_header( f )
  4320. name = field = nil
  4321. unixfrom = nil
  4322. while line = f.gets
  4323. case line
  4324. when /\A[ \t]/ # continue from prev line
  4325. raise SyntaxError, 'mail is began by space' unless field
  4326. field << ' ' << line.strip
  4327. when /\A([^\: \t]+):\s*/ # new header line
  4328. add_hf name, field if field
  4329. name = $1
  4330. field = $' #.strip
  4331. when /\A\-*\s*\z/ # end of header
  4332. add_hf name, field if field
  4333. name = field = nil
  4334. break
  4335. when /\AFrom (\S+)/
  4336. unixfrom = $1
  4337. when /^charset=.*/
  4338. else
  4339. raise SyntaxError, "wrong mail header: '#{line.inspect}'"
  4340. end
  4341. end
  4342. add_hf name, field if name
  4343. if unixfrom
  4344. add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
  4345. end
  4346. end
  4347. def add_hf( name, field )
  4348. key = name.downcase
  4349. field = new_hf(name, field)
  4350. if ALLOW_MULTIPLE.include? key
  4351. (@header[key] ||= []).push field
  4352. else
  4353. @header[key] = field
  4354. end
  4355. end
  4356. def new_hf( name, field )
  4357. HeaderField.new(name, field, @config)
  4358. end
  4359. ###
  4360. ### body
  4361. ###
  4362. public
  4363. def body_port
  4364. parse_body
  4365. @body_port
  4366. end
  4367. def each( &block )
  4368. body_port().ropen {|f| f.each(&block) }
  4369. end
  4370. def quoted_body
  4371. parse_body
  4372. @body_port.ropen {|f|
  4373. return f.read
  4374. }
  4375. end
  4376. def body=( str )
  4377. parse_body
  4378. @body_port.wopen {|f| f.write str }
  4379. str
  4380. end
  4381. alias preamble body
  4382. alias preamble= body=
  4383. def epilogue
  4384. parse_body
  4385. @epilogue.dup
  4386. end
  4387. def epilogue=( str )
  4388. parse_body
  4389. @epilogue = str
  4390. str
  4391. end
  4392. def parts
  4393. parse_body
  4394. @parts
  4395. end
  4396. def each_part( &block )
  4397. parts().each(&block)
  4398. end
  4399. private
  4400. def parse_body( f = nil )
  4401. return if @body_parsed
  4402. if f
  4403. parse_body_0 f
  4404. else
  4405. @port.ropen {|f|
  4406. skip_header f
  4407. parse_body_0 f
  4408. }
  4409. end
  4410. @body_parsed = true
  4411. end
  4412. def skip_header( f )
  4413. while line = f.gets
  4414. return if /\A[\r\n]*\z/ === line
  4415. end
  4416. end
  4417. def parse_body_0( f )
  4418. if multipart?
  4419. read_multipart f
  4420. else
  4421. @body_port = @config.new_body_port(self)
  4422. @body_port.wopen {|w|
  4423. w.write f.read
  4424. }
  4425. end
  4426. end
  4427. def read_multipart( src )
  4428. bound = @header['content-type'].params['boundary']
  4429. is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
  4430. lastbound = "--#{bound}--"
  4431. ports = [ @config.new_preamble_port(self) ]
  4432. begin
  4433. f = ports.last.wopen
  4434. while line = src.gets
  4435. if is_sep === line
  4436. f.close
  4437. break if line.strip == lastbound
  4438. ports.push @config.new_part_port(self)
  4439. f = ports.last.wopen
  4440. else
  4441. f << line
  4442. end
  4443. end
  4444. @epilogue = (src.read || '')
  4445. ensure
  4446. f.close if f and not f.closed?
  4447. end
  4448. @body_port = ports.shift
  4449. @parts = ports.map {|p| self.class.new(p, @config) }
  4450. end
  4451. end # class Mail
  4452. end # module TMail
  4453. #
  4454. # mailbox.rb
  4455. #
  4456. #--
  4457. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4458. #
  4459. # Permission is hereby granted, free of charge, to any person obtaining
  4460. # a copy of this software and associated documentation files (the
  4461. # "Software"), to deal in the Software without restriction, including
  4462. # without limitation the rights to use, copy, modify, merge, publish,
  4463. # distribute, sublicense, and/or sell copies of the Software, and to
  4464. # permit persons to whom the Software is furnished to do so, subject to
  4465. # the following conditions:
  4466. #
  4467. # The above copyright notice and this permission notice shall be
  4468. # included in all copies or substantial portions of the Software.
  4469. #
  4470. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4471. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4472. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4473. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4474. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4475. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4476. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4477. #
  4478. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4479. # with permission of Minero Aoki.
  4480. #++
  4481. require 'tmail/port'
  4482. require 'socket'
  4483. require 'mutex_m'
  4484. unless [].respond_to?(:sort_by)
  4485. module Enumerable#:nodoc:
  4486. def sort_by
  4487. map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
  4488. end
  4489. end
  4490. end
  4491. module TMail
  4492. class MhMailbox
  4493. PORT_CLASS = MhPort
  4494. def initialize( dir )
  4495. edir = File.expand_path(dir)
  4496. raise ArgumentError, "not directory: #{dir}"\
  4497. unless FileTest.directory? edir
  4498. @dirname = edir
  4499. @last_file = nil
  4500. @last_atime = nil
  4501. end
  4502. def directory
  4503. @dirname
  4504. end
  4505. alias dirname directory
  4506. attr_accessor :last_atime
  4507. def inspect
  4508. "#<#{self.class} #{@dirname}>"
  4509. end
  4510. def close
  4511. end
  4512. def new_port
  4513. PORT_CLASS.new(next_file_name())
  4514. end
  4515. def each_port
  4516. mail_files().each do |path|
  4517. yield PORT_CLASS.new(path)
  4518. end
  4519. @last_atime = Time.now
  4520. end
  4521. alias each each_port
  4522. def reverse_each_port
  4523. mail_files().reverse_each do |path|
  4524. yield PORT_CLASS.new(path)
  4525. end
  4526. @last_atime = Time.now
  4527. end
  4528. alias reverse_each reverse_each_port
  4529. # old #each_mail returns Port
  4530. #def each_mail
  4531. # each_port do |port|
  4532. # yield Mail.new(port)
  4533. # end
  4534. #end
  4535. def each_new_port( mtime = nil, &block )
  4536. mtime ||= @last_atime
  4537. return each_port(&block) unless mtime
  4538. return unless File.mtime(@dirname) >= mtime
  4539. mail_files().each do |path|
  4540. yield PORT_CLASS.new(path) if File.mtime(path) > mtime
  4541. end
  4542. @last_atime = Time.now
  4543. end
  4544. private
  4545. def mail_files
  4546. Dir.entries(@dirname)\
  4547. .select {|s| /\A\d+\z/ === s }\
  4548. .map {|s| s.to_i }\
  4549. .sort\
  4550. .map {|i| "#{@dirname}/#{i}" }\
  4551. .select {|path| FileTest.file? path }
  4552. end
  4553. def next_file_name
  4554. unless n = @last_file
  4555. n = 0
  4556. Dir.entries(@dirname)\
  4557. .select {|s| /\A\d+\z/ === s }\
  4558. .map {|s| s.to_i }.sort\
  4559. .each do |i|
  4560. next unless FileTest.file? "#{@dirname}/#{i}"
  4561. n = i
  4562. end
  4563. end
  4564. begin
  4565. n += 1
  4566. end while FileTest.exist? "#{@dirname}/#{n}"
  4567. @last_file = n
  4568. "#{@dirname}/#{n}"
  4569. end
  4570. end # MhMailbox
  4571. MhLoader = MhMailbox
  4572. class UNIXMbox
  4573. def UNIXMbox.lock( fname )
  4574. begin
  4575. f = File.open(fname)
  4576. f.flock File::LOCK_EX
  4577. yield f
  4578. ensure
  4579. f.flock File::LOCK_UN
  4580. f.close if f and not f.closed?
  4581. end
  4582. end
  4583. class << self
  4584. alias newobj new
  4585. end
  4586. def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
  4587. tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
  4588. newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
  4589. end
  4590. def UNIXMbox.static_new( fname, dir, readonly = false )
  4591. newobj(fname, dir, readonly, true)
  4592. end
  4593. def initialize( fname, mhdir, readonly, static )
  4594. @filename = fname
  4595. @readonly = readonly
  4596. @closed = false
  4597. Dir.mkdir mhdir
  4598. @real = MhMailbox.new(mhdir)
  4599. @finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
  4600. ObjectSpace.define_finalizer self, @finalizer
  4601. end
  4602. def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
  4603. lambda {
  4604. if writeback_p
  4605. lock(mboxfile) {|f|
  4606. mh.each_port do |port|
  4607. f.puts create_from_line(port)
  4608. port.ropen {|r|
  4609. f.puts r.read
  4610. }
  4611. end
  4612. }
  4613. end
  4614. if cleanup_p
  4615. Dir.foreach(mh.dirname) do |fname|
  4616. next if /\A\.\.?\z/ === fname
  4617. File.unlink "#{mh.dirname}/#{fname}"
  4618. end
  4619. Dir.rmdir mh.dirname
  4620. end
  4621. }
  4622. end
  4623. # make _From line
  4624. def UNIXMbox.create_from_line( port )
  4625. sprintf 'From %s %s',
  4626. fromaddr(), TextUtils.time2str(File.mtime(port.filename))
  4627. end
  4628. def UNIXMbox.fromaddr
  4629. h = HeaderField.new_from_port(port, 'Return-Path') ||
  4630. HeaderField.new_from_port(port, 'From') or return 'nobody'
  4631. a = h.addrs[0] or return 'nobody'
  4632. a.spec
  4633. end
  4634. private_class_method :fromaddr
  4635. def close
  4636. return if @closed
  4637. ObjectSpace.undefine_finalizer self
  4638. @finalizer.call
  4639. @finalizer = nil
  4640. @real = nil
  4641. @closed = true
  4642. @updated = nil
  4643. end
  4644. def each_port( &block )
  4645. close_check
  4646. update
  4647. @real.each_port(&block)
  4648. end
  4649. alias each each_port
  4650. def reverse_each_port( &block )
  4651. close_check
  4652. update
  4653. @real.reverse_each_port(&block)
  4654. end
  4655. alias reverse_each reverse_each_port
  4656. # old #each_mail returns Port
  4657. #def each_mail( &block )
  4658. # each_port do |port|
  4659. # yield Mail.new(port)
  4660. # end
  4661. #end
  4662. def each_new_port( mtime = nil )
  4663. close_check
  4664. update
  4665. @real.each_new_port(mtime) {|p| yield p }
  4666. end
  4667. def new_port
  4668. close_check
  4669. @real.new_port
  4670. end
  4671. private
  4672. def close_check
  4673. @closed and raise ArgumentError, 'accessing already closed mbox'
  4674. end
  4675. def update
  4676. return if FileTest.zero?(@filename)
  4677. return if @updated and File.mtime(@filename) < @updated
  4678. w = nil
  4679. port = nil
  4680. time = nil
  4681. UNIXMbox.lock(@filename) {|f|
  4682. begin
  4683. f.each do |line|
  4684. if /\AFrom / === line
  4685. w.close if w
  4686. File.utime time, time, port.filename if time
  4687. port = @real.new_port
  4688. w = port.wopen
  4689. time = fromline2time(line)
  4690. else
  4691. w.print line if w
  4692. end
  4693. end
  4694. ensure
  4695. if w and not w.closed?
  4696. w.close
  4697. File.utime time, time, port.filename if time
  4698. end
  4699. end
  4700. f.truncate(0) unless @readonly
  4701. @updated = Time.now
  4702. }
  4703. end
  4704. def fromline2time( line )
  4705. m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
  4706. or return nil
  4707. Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
  4708. end
  4709. end # UNIXMbox
  4710. MboxLoader = UNIXMbox
  4711. class Maildir
  4712. extend Mutex_m
  4713. PORT_CLASS = MaildirPort
  4714. @seq = 0
  4715. def Maildir.unique_number
  4716. synchronize {
  4717. @seq += 1
  4718. return @seq
  4719. }
  4720. end
  4721. def initialize( dir = nil )
  4722. @dirname = dir || ENV['MAILDIR']
  4723. raise ArgumentError, "not directory: #{@dirname}"\
  4724. unless FileTest.directory? @dirname
  4725. @new = "#{@dirname}/new"
  4726. @tmp = "#{@dirname}/tmp"
  4727. @cur = "#{@dirname}/cur"
  4728. end
  4729. def directory
  4730. @dirname
  4731. end
  4732. def inspect
  4733. "#<#{self.class} #{@dirname}>"
  4734. end
  4735. def close
  4736. end
  4737. def each_port
  4738. mail_files(@cur).each do |path|
  4739. yield PORT_CLASS.new(path)
  4740. end
  4741. end
  4742. alias each each_port
  4743. def reverse_each_port
  4744. mail_files(@cur).reverse_each do |path|
  4745. yield PORT_CLASS.new(path)
  4746. end
  4747. end
  4748. alias reverse_each reverse_each_port
  4749. def new_port
  4750. fname = nil
  4751. tmpfname = nil
  4752. newfname = nil
  4753. begin
  4754. fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
  4755. tmpfname = "#{@tmp}/#{fname}"
  4756. newfname = "#{@new}/#{fname}"
  4757. end while FileTest.exist? tmpfname
  4758. if block_given?
  4759. File.open(tmpfname, 'w') {|f| yield f }
  4760. File.rename tmpfname, newfname
  4761. PORT_CLASS.new(newfname)
  4762. else
  4763. File.open(tmpfname, 'w') {|f| f.write "\n\n" }
  4764. PORT_CLASS.new(tmpfname)
  4765. end
  4766. end
  4767. def each_new_port
  4768. mail_files(@new).each do |path|
  4769. dest = @cur + '/' + File.basename(path)
  4770. File.rename path, dest
  4771. yield PORT_CLASS.new(dest)
  4772. end
  4773. check_tmp
  4774. end
  4775. TOO_OLD = 60 * 60 * 36 # 36 hour
  4776. def check_tmp
  4777. old = Time.now.to_i - TOO_OLD
  4778. each_filename(@tmp) do |full, fname|
  4779. if FileTest.file? full and
  4780. File.stat(full).mtime.to_i < old
  4781. File.unlink full
  4782. end
  4783. end
  4784. end
  4785. private
  4786. def mail_files( dir )
  4787. Dir.entries(dir)\
  4788. .select {|s| s[0] != ?. }\
  4789. .sort_by {|s| s.slice(/\A\d+/).to_i }\
  4790. .map {|s| "#{dir}/#{s}" }\
  4791. .select {|path| FileTest.file? path }
  4792. end
  4793. def each_filename( dir )
  4794. Dir.foreach(dir) do |fname|
  4795. path = "#{dir}/#{fname}"
  4796. if fname[0] != ?. and FileTest.file? path
  4797. yield path, fname
  4798. end
  4799. end
  4800. end
  4801. end # Maildir
  4802. MaildirLoader = Maildir
  4803. end # module TMail
  4804. require 'tmail/mailbox'
  4805. #
  4806. # net.rb
  4807. #
  4808. #--
  4809. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  4810. #
  4811. # Permission is hereby granted, free of charge, to any person obtaining
  4812. # a copy of this software and associated documentation files (the
  4813. # "Software"), to deal in the Software without restriction, including
  4814. # without limitation the rights to use, copy, modify, merge, publish,
  4815. # distribute, sublicense, and/or sell copies of the Software, and to
  4816. # permit persons to whom the Software is furnished to do so, subject to
  4817. # the following conditions:
  4818. #
  4819. # The above copyright notice and this permission notice shall be
  4820. # included in all copies or substantial portions of the Software.
  4821. #
  4822. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  4823. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  4824. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  4825. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  4826. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  4827. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  4828. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  4829. #
  4830. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  4831. # with permission of Minero Aoki.
  4832. #++
  4833. require 'nkf'
  4834. module TMail
  4835. class Mail
  4836. def send_to( smtp )
  4837. do_send_to(smtp) do
  4838. ready_to_send
  4839. end
  4840. end
  4841. def send_text_to( smtp )
  4842. do_send_to(smtp) do
  4843. ready_to_send
  4844. mime_encode
  4845. end
  4846. end
  4847. def do_send_to( smtp )
  4848. from = from_address or raise ArgumentError, 'no from address'
  4849. (dests = destinations).empty? and raise ArgumentError, 'no receipient'
  4850. yield
  4851. send_to_0 smtp, from, dests
  4852. end
  4853. private :do_send_to
  4854. def send_to_0( smtp, from, to )
  4855. smtp.ready(from, to) do |f|
  4856. encoded "\r\n", 'j', f, ''
  4857. end
  4858. end
  4859. def ready_to_send
  4860. delete_no_send_fields
  4861. add_message_id
  4862. add_date
  4863. end
  4864. NOSEND_FIELDS = %w(
  4865. received
  4866. bcc
  4867. )
  4868. def delete_no_send_fields
  4869. NOSEND_FIELDS.each do |nm|
  4870. delete nm
  4871. end
  4872. delete_if {|n,v| v.empty? }
  4873. end
  4874. def add_message_id( fqdn = nil )
  4875. self.message_id = ::TMail::new_message_id(fqdn)
  4876. end
  4877. def add_date
  4878. self.date = Time.now
  4879. end
  4880. def mime_encode
  4881. if parts.empty?
  4882. mime_encode_singlepart
  4883. else
  4884. mime_encode_multipart true
  4885. end
  4886. end
  4887. def mime_encode_singlepart
  4888. self.mime_version = '1.0'
  4889. b = body
  4890. if NKF.guess(b) != NKF::BINARY
  4891. mime_encode_text b
  4892. else
  4893. mime_encode_binary b
  4894. end
  4895. end
  4896. def mime_encode_text( body )
  4897. self.body = NKF.nkf('-j -m0', body)
  4898. self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
  4899. self.encoding = '7bit'
  4900. end
  4901. def mime_encode_binary( body )
  4902. self.body = [body].pack('m')
  4903. self.set_content_type 'application', 'octet-stream'
  4904. self.encoding = 'Base64'
  4905. end
  4906. def mime_encode_multipart( top = true )
  4907. self.mime_version = '1.0' if top
  4908. self.set_content_type 'multipart', 'mixed'
  4909. e = encoding(nil)
  4910. if e and not /\A(?:7bit|8bit|binary)\z/i === e
  4911. raise ArgumentError,
  4912. 'using C.T.Encoding with multipart mail is not permitted'
  4913. end
  4914. end
  4915. def create_empty_mail
  4916. self.class.new(StringPort.new(''), @config)
  4917. end
  4918. def create_reply
  4919. setup_reply create_empty_mail()
  4920. end
  4921. def setup_reply( m )
  4922. if tmp = reply_addresses(nil)
  4923. m.to_addrs = tmp
  4924. end
  4925. mid = message_id(nil)
  4926. tmp = references(nil) || []
  4927. tmp.push mid if mid
  4928. m.in_reply_to = [mid] if mid
  4929. m.references = tmp unless tmp.empty?
  4930. m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
  4931. m
  4932. end
  4933. def create_forward
  4934. setup_forward create_empty_mail()
  4935. end
  4936. def setup_forward( mail )
  4937. m = Mail.new(StringPort.new(''))
  4938. m.body = decoded
  4939. m.set_content_type 'message', 'rfc822'
  4940. m.encoding = encoding('7bit')
  4941. mail.parts.push m
  4942. end
  4943. end
  4944. class DeleteFields
  4945. NOSEND_FIELDS = %w(
  4946. received
  4947. bcc
  4948. )
  4949. def initialize( nosend = nil, delempty = true )
  4950. @no_send_fields = nosend || NOSEND_FIELDS.dup
  4951. @delete_empty_fields = delempty
  4952. end
  4953. attr :no_send_fields
  4954. attr :delete_empty_fields, true
  4955. def exec( mail )
  4956. @no_send_fields.each do |nm|
  4957. delete nm
  4958. end
  4959. delete_if {|n,v| v.empty? } if @delete_empty_fields
  4960. end
  4961. end
  4962. class AddMessageId
  4963. def initialize( fqdn = nil )
  4964. @fqdn = fqdn
  4965. end
  4966. attr :fqdn, true
  4967. def exec( mail )
  4968. mail.message_id = ::TMail::new_msgid(@fqdn)
  4969. end
  4970. end
  4971. class AddDate
  4972. def exec( mail )
  4973. mail.date = Time.now
  4974. end
  4975. end
  4976. class MimeEncodeAuto
  4977. def initialize( s = nil, m = nil )
  4978. @singlepart_composer = s || MimeEncodeSingle.new
  4979. @multipart_composer = m || MimeEncodeMulti.new
  4980. end
  4981. attr :singlepart_composer
  4982. attr :multipart_composer
  4983. def exec( mail )
  4984. if mail._builtin_multipart?
  4985. then @multipart_composer
  4986. else @singlepart_composer end.exec mail
  4987. end
  4988. end
  4989. class MimeEncodeSingle
  4990. def exec( mail )
  4991. mail.mime_version = '1.0'
  4992. b = mail.body
  4993. if NKF.guess(b) != NKF::BINARY
  4994. on_text b
  4995. else
  4996. on_binary b
  4997. end
  4998. end
  4999. def on_text( body )
  5000. mail.body = NKF.nkf('-j -m0', body)
  5001. mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
  5002. mail.encoding = '7bit'
  5003. end
  5004. def on_binary( body )
  5005. mail.body = [body].pack('m')
  5006. mail.set_content_type 'application', 'octet-stream'
  5007. mail.encoding = 'Base64'
  5008. end
  5009. end
  5010. class MimeEncodeMulti
  5011. def exec( mail, top = true )
  5012. mail.mime_version = '1.0' if top
  5013. mail.set_content_type 'multipart', 'mixed'
  5014. e = encoding(nil)
  5015. if e and not /\A(?:7bit|8bit|binary)\z/i === e
  5016. raise ArgumentError,
  5017. 'using C.T.Encoding with multipart mail is not permitted'
  5018. end
  5019. mail.parts.each do |m|
  5020. exec m, false if m._builtin_multipart?
  5021. end
  5022. end
  5023. end
  5024. end # module TMail
  5025. #
  5026. # obsolete.rb
  5027. #
  5028. #--
  5029. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  5030. #
  5031. # Permission is hereby granted, free of charge, to any person obtaining
  5032. # a copy of this software and associated documentation files (the
  5033. # "Software"), to deal in the Software without restriction, including
  5034. # without limitation the rights to use, copy, modify, merge, publish,
  5035. # distribute, sublicense, and/or sell copies of the Software, and to
  5036. # permit persons to whom the Software is furnished to do so, subject to
  5037. # the following conditions:
  5038. #
  5039. # The above copyright notice and this permission notice shall be
  5040. # included in all copies or substantial portions of the Software.
  5041. #
  5042. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  5043. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  5044. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  5045. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  5046. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  5047. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  5048. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  5049. #
  5050. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  5051. # with permission of Minero Aoki.
  5052. #++
  5053. module TMail
  5054. # mail.rb
  5055. class Mail
  5056. alias include? key?
  5057. alias has_key? key?
  5058. def values
  5059. ret = []
  5060. each_field {|v| ret.push v }
  5061. ret
  5062. end
  5063. def value?( val )
  5064. HeaderField === val or return false
  5065. [ @header[val.name.downcase] ].flatten.include? val
  5066. end
  5067. alias has_value? value?
  5068. end
  5069. # facade.rb
  5070. class Mail
  5071. def from_addr( default = nil )
  5072. addr, = from_addrs(nil)
  5073. addr || default
  5074. end
  5075. def from_address( default = nil )
  5076. if a = from_addr(nil)
  5077. a.spec
  5078. else
  5079. default
  5080. end
  5081. end
  5082. alias from_address= from_addrs=
  5083. def from_phrase( default = nil )
  5084. if a = from_addr(nil)
  5085. a.phrase
  5086. else
  5087. default
  5088. end
  5089. end
  5090. alias msgid message_id
  5091. alias msgid= message_id=
  5092. alias each_dest each_destination
  5093. end
  5094. # address.rb
  5095. class Address
  5096. alias route routes
  5097. alias addr spec
  5098. def spec=( str )
  5099. @local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
  5100. end
  5101. alias addr= spec=
  5102. alias address= spec=
  5103. end
  5104. # mbox.rb
  5105. class MhMailbox
  5106. alias new_mail new_port
  5107. alias each_mail each_port
  5108. alias each_newmail each_new_port
  5109. end
  5110. class UNIXMbox
  5111. alias new_mail new_port
  5112. alias each_mail each_port
  5113. alias each_newmail each_new_port
  5114. end
  5115. class Maildir
  5116. alias new_mail new_port
  5117. alias each_mail each_port
  5118. alias each_newmail each_new_port
  5119. end
  5120. # utils.rb
  5121. extend TextUtils
  5122. class << self
  5123. alias msgid? message_id?
  5124. alias boundary new_boundary
  5125. alias msgid new_message_id
  5126. alias new_msgid new_message_id
  5127. end
  5128. def Mail.boundary
  5129. ::TMail.new_boundary
  5130. end
  5131. def Mail.msgid
  5132. ::TMail.new_message_id
  5133. end
  5134. end # module TMail
  5135. #
  5136. # DO NOT MODIFY!!!!
  5137. # This file is automatically generated by racc 1.4.3
  5138. # from racc grammer file "parser.y".
  5139. #
  5140. #
  5141. # parser.rb: generated by racc (runtime embedded)
  5142. #
  5143. ###### racc/parser.rb
  5144. unless $".index 'racc/parser.rb'
  5145. $".push 'racc/parser.rb'
  5146. self.class.module_eval <<'..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d', '/home/aamine/lib/ruby/racc/parser.rb', 1
  5147. #
  5148. # parser.rb
  5149. #
  5150. # Copyright (c) 1999-2003 Minero Aoki <aamine@loveruby.net>
  5151. #
  5152. # This program is free software.
  5153. # You can distribute/modify this program under the same terms of ruby.
  5154. #
  5155. # As a special exception, when this code is copied by Racc
  5156. # into a Racc output file, you may use that output file
  5157. # without restriction.
  5158. #
  5159. # $Id: parser.rb,v 1.1.1.1 2004/10/14 11:59:58 webster132 Exp $
  5160. #
  5161. unless defined? NotImplementedError
  5162. NotImplementedError = NotImplementError
  5163. end
  5164. module Racc
  5165. class ParseError < StandardError; end
  5166. end
  5167. unless defined?(::ParseError)
  5168. ParseError = Racc::ParseError
  5169. end
  5170. module Racc
  5171. unless defined? Racc_No_Extentions
  5172. Racc_No_Extentions = false
  5173. end
  5174. class Parser
  5175. Racc_Runtime_Version = '1.4.3'
  5176. Racc_Runtime_Revision = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]
  5177. Racc_Runtime_Core_Version_R = '1.4.3'
  5178. Racc_Runtime_Core_Revision_R = '$Revision: 1.1.1.1 $'.split(/\s+/)[1]
  5179. begin
  5180. require 'racc/cparse'
  5181. # Racc_Runtime_Core_Version_C = (defined in extention)
  5182. Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split(/\s+/)[2]
  5183. unless new.respond_to?(:_racc_do_parse_c, true)
  5184. raise LoadError, 'old cparse.so'
  5185. end
  5186. if Racc_No_Extentions
  5187. raise LoadError, 'selecting ruby version of racc runtime core'
  5188. end
  5189. Racc_Main_Parsing_Routine = :_racc_do_parse_c
  5190. Racc_YY_Parse_Method = :_racc_yyparse_c
  5191. Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C
  5192. Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_C
  5193. Racc_Runtime_Type = 'c'
  5194. rescue LoadError
  5195. Racc_Main_Parsing_Routine = :_racc_do_parse_rb
  5196. Racc_YY_Parse_Method = :_racc_yyparse_rb
  5197. Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
  5198. Racc_Runtime_Core_Revision = Racc_Runtime_Core_Revision_R
  5199. Racc_Runtime_Type = 'ruby'
  5200. end
  5201. def self.racc_runtime_type
  5202. Racc_Runtime_Type
  5203. end
  5204. private
  5205. def _racc_setup
  5206. @yydebug = false unless self.class::Racc_debug_parser
  5207. @yydebug = false unless defined? @yydebug
  5208. if @yydebug
  5209. @racc_debug_out = $stderr unless defined? @racc_debug_out
  5210. @racc_debug_out ||= $stderr
  5211. end
  5212. arg = self.class::Racc_arg
  5213. arg[13] = true if arg.size < 14
  5214. arg
  5215. end
  5216. def _racc_init_sysvars
  5217. @racc_state = [0]
  5218. @racc_tstack = []
  5219. @racc_vstack = []
  5220. @racc_t = nil
  5221. @racc_val = nil
  5222. @racc_read_next = true
  5223. @racc_user_yyerror = false
  5224. @racc_error_status = 0
  5225. end
  5226. ###
  5227. ### do_parse
  5228. ###
  5229. def do_parse
  5230. __send__ Racc_Main_Parsing_Routine, _racc_setup(), false
  5231. end
  5232. def next_token
  5233. raise NotImplementedError, "#{self.class}\#next_token is not defined"
  5234. end
  5235. def _racc_do_parse_rb( arg, in_debug )
  5236. action_table, action_check, action_default, action_pointer,
  5237. goto_table, goto_check, goto_default, goto_pointer,
  5238. nt_base, reduce_table, token_table, shift_n,
  5239. reduce_n, use_result, * = arg
  5240. _racc_init_sysvars
  5241. tok = act = i = nil
  5242. nerr = 0
  5243. catch(:racc_end_parse) {
  5244. while true
  5245. if i = action_pointer[@racc_state[-1]]
  5246. if @racc_read_next
  5247. if @racc_t != 0 # not EOF
  5248. tok, @racc_val = next_token()
  5249. unless tok # EOF
  5250. @racc_t = 0
  5251. else
  5252. @racc_t = (token_table[tok] or 1) # error token
  5253. end
  5254. racc_read_token(@racc_t, tok, @racc_val) if @yydebug
  5255. @racc_read_next = false
  5256. end
  5257. end
  5258. i += @racc_t
  5259. if i >= 0 and
  5260. act = action_table[i] and
  5261. action_check[i] == @racc_state[-1]
  5262. ;
  5263. else
  5264. act = action_default[@racc_state[-1]]
  5265. end
  5266. else
  5267. act = action_default[@racc_state[-1]]
  5268. end
  5269. while act = _racc_evalact(act, arg)
  5270. end
  5271. end
  5272. }
  5273. end
  5274. ###
  5275. ### yyparse
  5276. ###
  5277. def yyparse( recv, mid )
  5278. __send__ Racc_YY_Parse_Method, recv, mid, _racc_setup(), true
  5279. end
  5280. def _racc_yyparse_rb( recv, mid, arg, c_debug )
  5281. action_table, action_check, action_default, action_pointer,
  5282. goto_table, goto_check, goto_default, goto_pointer,
  5283. nt_base, reduce_table, token_table, shift_n,
  5284. reduce_n, use_result, * = arg
  5285. _racc_init_sysvars
  5286. tok = nil
  5287. act = nil
  5288. i = nil
  5289. nerr = 0
  5290. catch(:racc_end_parse) {
  5291. until i = action_pointer[@racc_state[-1]]
  5292. while act = _racc_evalact(action_default[@racc_state[-1]], arg)
  5293. end
  5294. end
  5295. recv.__send__(mid) do |tok, val|
  5296. # $stderr.puts "rd: tok=#{tok}, val=#{val}"
  5297. unless tok
  5298. @racc_t = 0
  5299. else
  5300. @racc_t = (token_table[tok] or 1) # error token
  5301. end
  5302. @racc_val = val
  5303. @racc_read_next = false
  5304. i += @racc_t
  5305. if i >= 0 and
  5306. act = action_table[i] and
  5307. action_check[i] == @racc_state[-1]
  5308. ;
  5309. # $stderr.puts "01: act=#{act}"
  5310. else
  5311. act = action_default[@racc_state[-1]]
  5312. # $stderr.puts "02: act=#{act}"
  5313. # $stderr.puts "curstate=#{@racc_state[-1]}"
  5314. end
  5315. while act = _racc_evalact(act, arg)
  5316. end
  5317. while not (i = action_pointer[@racc_state[-1]]) or
  5318. not @racc_read_next or
  5319. @racc_t == 0 # $
  5320. if i and i += @racc_t and
  5321. i >= 0 and
  5322. act = action_table[i] and
  5323. action_check[i] == @racc_state[-1]
  5324. ;
  5325. # $stderr.puts "03: act=#{act}"
  5326. else
  5327. # $stderr.puts "04: act=#{act}"
  5328. act = action_default[@racc_state[-1]]
  5329. end
  5330. while act = _racc_evalact(act, arg)
  5331. end
  5332. end
  5333. end
  5334. }
  5335. end
  5336. ###
  5337. ### common
  5338. ###
  5339. def _racc_evalact( act, arg )
  5340. # $stderr.puts "ea: act=#{act}"
  5341. action_table, action_check, action_default, action_pointer,
  5342. goto_table, goto_check, goto_default, goto_pointer,
  5343. nt_base, reduce_table, token_table, shift_n,
  5344. reduce_n, use_result, * = arg
  5345. nerr = 0 # tmp
  5346. if act > 0 and act < shift_n
  5347. #
  5348. # shift
  5349. #
  5350. if @racc_error_status > 0
  5351. @racc_error_status -= 1 unless @racc_t == 1 # error token
  5352. end
  5353. @racc_vstack.push @racc_val
  5354. @racc_state.push act
  5355. @racc_read_next = true
  5356. if @yydebug
  5357. @racc_tstack.push @racc_t
  5358. racc_shift @racc_t, @racc_tstack, @racc_vstack
  5359. end
  5360. elsif act < 0 and act > -reduce_n
  5361. #
  5362. # reduce
  5363. #
  5364. code = catch(:racc_jump) {
  5365. @racc_state.push _racc_do_reduce(arg, act)
  5366. false
  5367. }
  5368. if code
  5369. case code
  5370. when 1 # yyerror
  5371. @racc_user_yyerror = true # user_yyerror
  5372. return -reduce_n
  5373. when 2 # yyaccept
  5374. return shift_n
  5375. else
  5376. raise RuntimeError, '[Racc Bug] unknown jump code'
  5377. end
  5378. end
  5379. elsif act == shift_n
  5380. #
  5381. # accept
  5382. #
  5383. racc_accept if @yydebug
  5384. throw :racc_end_parse, @racc_vstack[0]
  5385. elsif act == -reduce_n
  5386. #
  5387. # error
  5388. #
  5389. case @racc_error_status
  5390. when 0
  5391. unless arg[21] # user_yyerror
  5392. nerr += 1
  5393. on_error @racc_t, @racc_val, @racc_vstack
  5394. end
  5395. when 3
  5396. if @racc_t == 0 # is $
  5397. throw :racc_end_parse, nil
  5398. end
  5399. @racc_read_next = true
  5400. end
  5401. @racc_user_yyerror = false
  5402. @racc_error_status = 3
  5403. while true
  5404. if i = action_pointer[@racc_state[-1]]
  5405. i += 1 # error token
  5406. if i >= 0 and
  5407. (act = action_table[i]) and
  5408. action_check[i] == @racc_state[-1]
  5409. break
  5410. end
  5411. end
  5412. throw :racc_end_parse, nil if @racc_state.size < 2
  5413. @racc_state.pop
  5414. @racc_vstack.pop
  5415. if @yydebug
  5416. @racc_tstack.pop
  5417. racc_e_pop @racc_state, @racc_tstack, @racc_vstack
  5418. end
  5419. end
  5420. return act
  5421. else
  5422. raise RuntimeError, "[Racc Bug] unknown action #{act.inspect}"
  5423. end
  5424. racc_next_state(@racc_state[-1], @racc_state) if @yydebug
  5425. nil
  5426. end
  5427. def _racc_do_reduce( arg, act )
  5428. action_table, action_check, action_default, action_pointer,
  5429. goto_table, goto_check, goto_default, goto_pointer,
  5430. nt_base, reduce_table, token_table, shift_n,
  5431. reduce_n, use_result, * = arg
  5432. state = @racc_state
  5433. vstack = @racc_vstack
  5434. tstack = @racc_tstack
  5435. i = act * -3
  5436. len = reduce_table[i]
  5437. reduce_to = reduce_table[i+1]
  5438. method_id = reduce_table[i+2]
  5439. void_array = []
  5440. tmp_t = tstack[-len, len] if @yydebug
  5441. tmp_v = vstack[-len, len]
  5442. tstack[-len, len] = void_array if @yydebug
  5443. vstack[-len, len] = void_array
  5444. state[-len, len] = void_array
  5445. # tstack must be updated AFTER method call
  5446. if use_result
  5447. vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
  5448. else
  5449. vstack.push __send__(method_id, tmp_v, vstack)
  5450. end
  5451. tstack.push reduce_to
  5452. racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
  5453. k1 = reduce_to - nt_base
  5454. if i = goto_pointer[k1]
  5455. i += state[-1]
  5456. if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
  5457. return curstate
  5458. end
  5459. end
  5460. goto_default[k1]
  5461. end
  5462. def on_error( t, val, vstack )
  5463. raise ParseError, sprintf("\nparse error on value %s (%s)",
  5464. val.inspect, token_to_str(t) || '?')
  5465. end
  5466. def yyerror
  5467. throw :racc_jump, 1
  5468. end
  5469. def yyaccept
  5470. throw :racc_jump, 2
  5471. end
  5472. def yyerrok
  5473. @racc_error_status = 0
  5474. end
  5475. # for debugging output
  5476. def racc_read_token( t, tok, val )
  5477. @racc_debug_out.print 'read '
  5478. @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') '
  5479. @racc_debug_out.puts val.inspect
  5480. @racc_debug_out.puts
  5481. end
  5482. def racc_shift( tok, tstack, vstack )
  5483. @racc_debug_out.puts "shift #{racc_token2str tok}"
  5484. racc_print_stacks tstack, vstack
  5485. @racc_debug_out.puts
  5486. end
  5487. def racc_reduce( toks, sim, tstack, vstack )
  5488. out = @racc_debug_out
  5489. out.print 'reduce '
  5490. if toks.empty?
  5491. out.print ' <none>'
  5492. else
  5493. toks.each {|t| out.print ' ', racc_token2str(t) }
  5494. end
  5495. out.puts " --> #{racc_token2str(sim)}"
  5496. racc_print_stacks tstack, vstack
  5497. @racc_debug_out.puts
  5498. end
  5499. def racc_accept
  5500. @racc_debug_out.puts 'accept'
  5501. @racc_debug_out.puts
  5502. end
  5503. def racc_e_pop( state, tstack, vstack )
  5504. @racc_debug_out.puts 'error recovering mode: pop token'
  5505. racc_print_states state
  5506. racc_print_stacks tstack, vstack
  5507. @racc_debug_out.puts
  5508. end
  5509. def racc_next_state( curstate, state )
  5510. @racc_debug_out.puts "goto #{curstate}"
  5511. racc_print_states state
  5512. @racc_debug_out.puts
  5513. end
  5514. def racc_print_stacks( t, v )
  5515. out = @racc_debug_out
  5516. out.print ' ['
  5517. t.each_index do |i|
  5518. out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')'
  5519. end
  5520. out.puts ' ]'
  5521. end
  5522. def racc_print_states( s )
  5523. out = @racc_debug_out
  5524. out.print ' ['
  5525. s.each {|st| out.print ' ', st }
  5526. out.puts ' ]'
  5527. end
  5528. def racc_token2str( tok )
  5529. self.class::Racc_token_to_s_table[tok] or
  5530. raise RuntimeError, "[Racc Bug] can't convert token #{tok} to string"
  5531. end
  5532. def token_to_str( t )
  5533. self.class::Racc_token_to_s_table[t]
  5534. end
  5535. end
  5536. end
  5537. ..end /home/aamine/lib/ruby/racc/parser.rb modeval..idb76f2e220d
  5538. end # end of racc/parser.rb
  5539. #
  5540. # parser.rb
  5541. #
  5542. #--
  5543. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  5544. #
  5545. # Permission is hereby granted, free of charge, to any person obtaining
  5546. # a copy of this software and associated documentation files (the
  5547. # "Software"), to deal in the Software without restriction, including
  5548. # without limitation the rights to use, copy, modify, merge, publish,
  5549. # distribute, sublicense, and/or sell copies of the Software, and to
  5550. # permit persons to whom the Software is furnished to do so, subject to
  5551. # the following conditions:
  5552. #
  5553. # The above copyright notice and this permission notice shall be
  5554. # included in all copies or substantial portions of the Software.
  5555. #
  5556. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  5557. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  5558. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  5559. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  5560. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  5561. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  5562. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  5563. #
  5564. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  5565. # with permission of Minero Aoki.
  5566. #++
  5567. require 'tmail/scanner'
  5568. require 'tmail/utils'
  5569. module TMail
  5570. class Parser < Racc::Parser
  5571. module_eval <<'..end parser.y modeval..id43721faf1c', 'parser.y', 331
  5572. include TextUtils
  5573. def self.parse( ident, str, cmt = nil )
  5574. new.parse(ident, str, cmt)
  5575. end
  5576. MAILP_DEBUG = false
  5577. def initialize
  5578. self.debug = MAILP_DEBUG
  5579. end
  5580. def debug=( flag )
  5581. @yydebug = flag && Racc_debug_parser
  5582. @scanner_debug = flag
  5583. end
  5584. def debug
  5585. @yydebug
  5586. end
  5587. def parse( ident, str, comments = nil )
  5588. @scanner = Scanner.new(str, ident, comments)
  5589. @scanner.debug = @scanner_debug
  5590. @first = [ident, ident]
  5591. result = yyparse(self, :parse_in)
  5592. comments.map! {|c| to_kcode(c) } if comments
  5593. result
  5594. end
  5595. private
  5596. def parse_in( &block )
  5597. yield @first
  5598. @scanner.scan(&block)
  5599. end
  5600. def on_error( t, val, vstack )
  5601. raise SyntaxError, "parse error on token #{racc_token2str t}"
  5602. end
  5603. ..end parser.y modeval..id43721faf1c
  5604. ##### racc 1.4.3 generates ###
  5605. racc_reduce_table = [
  5606. 0, 0, :racc_error,
  5607. 2, 35, :_reduce_1,
  5608. 2, 35, :_reduce_2,
  5609. 2, 35, :_reduce_3,
  5610. 2, 35, :_reduce_4,
  5611. 2, 35, :_reduce_5,
  5612. 2, 35, :_reduce_6,
  5613. 2, 35, :_reduce_7,
  5614. 2, 35, :_reduce_8,
  5615. 2, 35, :_reduce_9,
  5616. 2, 35, :_reduce_10,
  5617. 2, 35, :_reduce_11,
  5618. 2, 35, :_reduce_12,
  5619. 6, 36, :_reduce_13,
  5620. 0, 48, :_reduce_none,
  5621. 2, 48, :_reduce_none,
  5622. 3, 49, :_reduce_16,
  5623. 5, 49, :_reduce_17,
  5624. 1, 50, :_reduce_18,
  5625. 7, 37, :_reduce_19,
  5626. 0, 51, :_reduce_none,
  5627. 2, 51, :_reduce_21,
  5628. 0, 52, :_reduce_none,
  5629. 2, 52, :_reduce_23,
  5630. 1, 58, :_reduce_24,
  5631. 3, 58, :_reduce_25,
  5632. 2, 58, :_reduce_26,
  5633. 0, 53, :_reduce_none,
  5634. 2, 53, :_reduce_28,
  5635. 0, 54, :_reduce_29,
  5636. 3, 54, :_reduce_30,
  5637. 0, 55, :_reduce_none,
  5638. 2, 55, :_reduce_32,
  5639. 2, 55, :_reduce_33,
  5640. 0, 56, :_reduce_none,
  5641. 2, 56, :_reduce_35,
  5642. 1, 61, :_reduce_36,
  5643. 1, 61, :_reduce_37,
  5644. 0, 57, :_reduce_none,
  5645. 2, 57, :_reduce_39,
  5646. 1, 38, :_reduce_none,
  5647. 1, 38, :_reduce_none,
  5648. 3, 38, :_reduce_none,
  5649. 1, 46, :_reduce_none,
  5650. 1, 46, :_reduce_none,
  5651. 1, 46, :_reduce_none,
  5652. 1, 39, :_reduce_none,
  5653. 2, 39, :_reduce_47,
  5654. 1, 64, :_reduce_48,
  5655. 3, 64, :_reduce_49,
  5656. 1, 68, :_reduce_none,
  5657. 1, 68, :_reduce_none,
  5658. 1, 69, :_reduce_52,
  5659. 3, 69, :_reduce_53,
  5660. 1, 47, :_reduce_none,
  5661. 1, 47, :_reduce_none,
  5662. 2, 47, :_reduce_56,
  5663. 2, 67, :_reduce_none,
  5664. 3, 65, :_reduce_58,
  5665. 2, 65, :_reduce_59,
  5666. 1, 70, :_reduce_60,
  5667. 2, 70, :_reduce_61,
  5668. 4, 62, :_reduce_62,
  5669. 3, 62, :_reduce_63,
  5670. 2, 72, :_reduce_none,
  5671. 2, 73, :_reduce_65,
  5672. 4, 73, :_reduce_66,
  5673. 3, 63, :_reduce_67,
  5674. 1, 63, :_reduce_68,
  5675. 1, 74, :_reduce_none,
  5676. 2, 74, :_reduce_70,
  5677. 1, 71, :_reduce_71,
  5678. 3, 71, :_reduce_72,
  5679. 1, 59, :_reduce_73,
  5680. 3, 59, :_reduce_74,
  5681. 1, 76, :_reduce_75,
  5682. 2, 76, :_reduce_76,
  5683. 1, 75, :_reduce_none,
  5684. 1, 75, :_reduce_none,
  5685. 1, 75, :_reduce_none,
  5686. 1, 77, :_reduce_none,
  5687. 1, 77, :_reduce_none,
  5688. 1, 77, :_reduce_none,
  5689. 1, 66, :_reduce_none,
  5690. 2, 66, :_reduce_none,
  5691. 3, 60, :_reduce_85,
  5692. 1, 40, :_reduce_86,
  5693. 3, 40, :_reduce_87,
  5694. 1, 79, :_reduce_none,
  5695. 2, 79, :_reduce_89,
  5696. 1, 41, :_reduce_90,
  5697. 2, 41, :_reduce_91,
  5698. 3, 42, :_reduce_92,
  5699. 5, 43, :_reduce_93,
  5700. 3, 43, :_reduce_94,
  5701. 0, 80, :_reduce_95,
  5702. 5, 80, :_reduce_96,
  5703. 1, 82, :_reduce_none,
  5704. 1, 82, :_reduce_none,
  5705. 1, 44, :_reduce_99,
  5706. 3, 45, :_reduce_100,
  5707. 0, 81, :_reduce_none,
  5708. 1, 81, :_reduce_none,
  5709. 1, 78, :_reduce_none,
  5710. 1, 78, :_reduce_none,
  5711. 1, 78, :_reduce_none,
  5712. 1, 78, :_reduce_none,
  5713. 1, 78, :_reduce_none,
  5714. 1, 78, :_reduce_none,
  5715. 1, 78, :_reduce_none ]
  5716. racc_reduce_n = 110
  5717. racc_shift_n = 168
  5718. racc_action_table = [
  5719. -70, -69, 23, 25, 146, 147, 29, 31, 105, 106,
  5720. 16, 17, 20, 22, 136, 27, -70, -69, 32, 101,
  5721. -70, -69, 154, 100, 113, 115, -70, -69, -70, 109,
  5722. 75, 23, 25, 101, 155, 29, 31, 142, 143, 16,
  5723. 17, 20, 22, 107, 27, 23, 25, 32, 98, 29,
  5724. 31, 96, 94, 16, 17, 20, 22, 78, 27, 23,
  5725. 25, 32, 112, 29, 31, 74, 91, 16, 17, 20,
  5726. 22, 88, 117, 92, 81, 32, 23, 25, 80, 123,
  5727. 29, 31, 100, 125, 16, 17, 20, 22, 126, 23,
  5728. 25, 109, 32, 29, 31, 91, 128, 16, 17, 20,
  5729. 22, 129, 27, 23, 25, 32, 101, 29, 31, 101,
  5730. 130, 16, 17, 20, 22, 79, 52, 23, 25, 32,
  5731. 78, 29, 31, 133, 78, 16, 17, 20, 22, 77,
  5732. 23, 25, 75, 32, 29, 31, 65, 62, 16, 17,
  5733. 20, 22, 139, 23, 25, 101, 32, 29, 31, 60,
  5734. 100, 16, 17, 20, 22, 44, 27, 101, 148, 32,
  5735. 23, 25, 120, 149, 29, 31, 152, 153, 16, 17,
  5736. 20, 22, 42, 27, 157, 159, 32, 23, 25, 120,
  5737. 40, 29, 31, 15, 164, 16, 17, 20, 22, 40,
  5738. 27, 23, 25, 32, 68, 29, 31, 166, 167, 16,
  5739. 17, 20, 22, nil, 27, 23, 25, 32, nil, 29,
  5740. 31, 74, nil, 16, 17, 20, 22, nil, 23, 25,
  5741. nil, 32, 29, 31, nil, nil, 16, 17, 20, 22,
  5742. nil, 23, 25, nil, 32, 29, 31, nil, nil, 16,
  5743. 17, 20, 22, nil, 23, 25, nil, 32, 29, 31,
  5744. nil, nil, 16, 17, 20, 22, nil, 23, 25, nil,
  5745. 32, 29, 31, nil, nil, 16, 17, 20, 22, nil,
  5746. 27, 23, 25, 32, nil, 29, 31, nil, nil, 16,
  5747. 17, 20, 22, nil, 23, 25, nil, 32, 29, 31,
  5748. nil, nil, 16, 17, 20, 22, nil, 23, 25, nil,
  5749. 32, 29, 31, nil, nil, 16, 17, 20, 22, nil,
  5750. 84, 25, nil, 32, 29, 31, nil, 87, 16, 17,
  5751. 20, 22, 4, 6, 7, 8, 9, 10, 11, 12,
  5752. 13, 1, 2, 3, 84, 25, nil, nil, 29, 31,
  5753. nil, 87, 16, 17, 20, 22, 84, 25, nil, nil,
  5754. 29, 31, nil, 87, 16, 17, 20, 22, 84, 25,
  5755. nil, nil, 29, 31, nil, 87, 16, 17, 20, 22,
  5756. 84, 25, nil, nil, 29, 31, nil, 87, 16, 17,
  5757. 20, 22, 84, 25, nil, nil, 29, 31, nil, 87,
  5758. 16, 17, 20, 22, 84, 25, nil, nil, 29, 31,
  5759. nil, 87, 16, 17, 20, 22 ]
  5760. racc_action_check = [
  5761. 75, 28, 68, 68, 136, 136, 68, 68, 72, 72,
  5762. 68, 68, 68, 68, 126, 68, 75, 28, 68, 67,
  5763. 75, 28, 143, 66, 86, 86, 75, 28, 75, 75,
  5764. 28, 3, 3, 86, 143, 3, 3, 134, 134, 3,
  5765. 3, 3, 3, 73, 3, 152, 152, 3, 62, 152,
  5766. 152, 60, 56, 152, 152, 152, 152, 51, 152, 52,
  5767. 52, 152, 80, 52, 52, 52, 50, 52, 52, 52,
  5768. 52, 45, 89, 52, 42, 52, 71, 71, 41, 96,
  5769. 71, 71, 97, 98, 71, 71, 71, 71, 100, 7,
  5770. 7, 101, 71, 7, 7, 102, 104, 7, 7, 7,
  5771. 7, 105, 7, 8, 8, 7, 108, 8, 8, 111,
  5772. 112, 8, 8, 8, 8, 40, 8, 9, 9, 8,
  5773. 36, 9, 9, 117, 121, 9, 9, 9, 9, 33,
  5774. 10, 10, 70, 9, 10, 10, 13, 12, 10, 10,
  5775. 10, 10, 130, 2, 2, 131, 10, 2, 2, 11,
  5776. 135, 2, 2, 2, 2, 6, 2, 138, 139, 2,
  5777. 90, 90, 90, 140, 90, 90, 141, 142, 90, 90,
  5778. 90, 90, 5, 90, 148, 151, 90, 127, 127, 127,
  5779. 4, 127, 127, 1, 157, 127, 127, 127, 127, 159,
  5780. 127, 26, 26, 127, 26, 26, 26, 163, 164, 26,
  5781. 26, 26, 26, nil, 26, 27, 27, 26, nil, 27,
  5782. 27, 27, nil, 27, 27, 27, 27, nil, 155, 155,
  5783. nil, 27, 155, 155, nil, nil, 155, 155, 155, 155,
  5784. nil, 122, 122, nil, 155, 122, 122, nil, nil, 122,
  5785. 122, 122, 122, nil, 76, 76, nil, 122, 76, 76,
  5786. nil, nil, 76, 76, 76, 76, nil, 38, 38, nil,
  5787. 76, 38, 38, nil, nil, 38, 38, 38, 38, nil,
  5788. 38, 55, 55, 38, nil, 55, 55, nil, nil, 55,
  5789. 55, 55, 55, nil, 94, 94, nil, 55, 94, 94,
  5790. nil, nil, 94, 94, 94, 94, nil, 59, 59, nil,
  5791. 94, 59, 59, nil, nil, 59, 59, 59, 59, nil,
  5792. 114, 114, nil, 59, 114, 114, nil, 114, 114, 114,
  5793. 114, 114, 0, 0, 0, 0, 0, 0, 0, 0,
  5794. 0, 0, 0, 0, 77, 77, nil, nil, 77, 77,
  5795. nil, 77, 77, 77, 77, 77, 44, 44, nil, nil,
  5796. 44, 44, nil, 44, 44, 44, 44, 44, 113, 113,
  5797. nil, nil, 113, 113, nil, 113, 113, 113, 113, 113,
  5798. 88, 88, nil, nil, 88, 88, nil, 88, 88, 88,
  5799. 88, 88, 74, 74, nil, nil, 74, 74, nil, 74,
  5800. 74, 74, 74, 74, 129, 129, nil, nil, 129, 129,
  5801. nil, 129, 129, 129, 129, 129 ]
  5802. racc_action_pointer = [
  5803. 320, 152, 129, 17, 165, 172, 137, 75, 89, 103,
  5804. 116, 135, 106, 105, nil, nil, nil, nil, nil, nil,
  5805. nil, nil, nil, nil, nil, nil, 177, 191, 1, nil,
  5806. nil, nil, nil, 109, nil, nil, 94, nil, 243, nil,
  5807. 99, 64, 74, nil, 332, 52, nil, nil, nil, nil,
  5808. 50, 31, 45, nil, nil, 257, 36, nil, nil, 283,
  5809. 22, nil, 16, nil, nil, nil, -3, -10, -12, nil,
  5810. 103, 62, -8, 15, 368, 0, 230, 320, nil, nil,
  5811. 47, nil, nil, nil, nil, nil, 4, nil, 356, 50,
  5812. 146, nil, nil, nil, 270, nil, 65, 56, 52, nil,
  5813. 57, 62, 79, nil, 68, 81, nil, nil, 77, nil,
  5814. nil, 80, 96, 344, 296, nil, nil, 108, nil, nil,
  5815. nil, 98, 217, nil, nil, nil, -19, 163, nil, 380,
  5816. 128, 116, nil, nil, 14, 124, -26, nil, 128, 141,
  5817. 148, 141, 152, 7, nil, nil, nil, nil, 160, nil,
  5818. nil, 149, 31, nil, nil, 204, nil, 167, nil, 174,
  5819. nil, nil, nil, 169, 184, nil, nil, nil ]
  5820. racc_action_default = [
  5821. -110, -110, -110, -110, -14, -110, -20, -110, -110, -110,
  5822. -110, -110, -110, -110, -10, -95, -106, -107, -77, -44,
  5823. -108, -11, -109, -79, -43, -103, -110, -110, -60, -104,
  5824. -55, -105, -78, -68, -54, -71, -45, -12, -110, -1,
  5825. -110, -110, -110, -2, -110, -22, -51, -48, -50, -3,
  5826. -40, -41, -110, -46, -4, -86, -5, -88, -6, -90,
  5827. -110, -7, -95, -8, -9, -99, -101, -61, -59, -56,
  5828. -69, -110, -110, -110, -110, -75, -110, -110, -57, -15,
  5829. -110, 168, -73, -80, -82, -21, -24, -81, -110, -27,
  5830. -110, -83, -47, -89, -110, -91, -110, -101, -110, -100,
  5831. -102, -75, -58, -52, -110, -110, -64, -63, -65, -76,
  5832. -72, -67, -110, -110, -110, -26, -23, -110, -29, -49,
  5833. -84, -42, -87, -92, -94, -95, -110, -110, -62, -110,
  5834. -110, -25, -74, -28, -31, -101, -110, -53, -66, -110,
  5835. -110, -34, -110, -110, -93, -96, -98, -97, -110, -18,
  5836. -13, -38, -110, -30, -33, -110, -32, -16, -19, -14,
  5837. -35, -36, -37, -110, -110, -39, -85, -17 ]
  5838. racc_goto_table = [
  5839. 39, 67, 70, 73, 24, 37, 69, 66, 36, 38,
  5840. 57, 59, 55, 67, 108, 83, 90, 111, 69, 99,
  5841. 85, 49, 53, 76, 158, 134, 141, 70, 73, 151,
  5842. 118, 89, 45, 156, 160, 150, 140, 21, 14, 19,
  5843. 119, 102, 64, 63, 61, 83, 70, 104, 83, 58,
  5844. 124, 132, 56, 131, 97, 54, 93, 43, 5, 83,
  5845. 95, 145, 76, nil, 116, 76, nil, nil, 127, 138,
  5846. 103, nil, nil, nil, 38, nil, nil, 110, nil, nil,
  5847. nil, nil, nil, nil, 83, 83, nil, nil, 144, nil,
  5848. nil, nil, nil, nil, nil, 57, 121, 122, nil, nil,
  5849. 83, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5850. nil, nil, nil, nil, nil, nil, nil, 135, nil, nil,
  5851. nil, nil, nil, 93, nil, nil, nil, 70, 162, 137,
  5852. 70, 163, 161, 38, nil, nil, nil, nil, nil, nil,
  5853. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5854. nil, nil, nil, nil, nil, 165 ]
  5855. racc_goto_check = [
  5856. 2, 37, 37, 29, 13, 13, 28, 46, 31, 36,
  5857. 41, 41, 45, 37, 25, 44, 32, 25, 28, 47,
  5858. 24, 4, 4, 42, 23, 20, 21, 37, 29, 22,
  5859. 19, 18, 17, 26, 27, 16, 15, 12, 11, 33,
  5860. 34, 35, 10, 9, 8, 44, 37, 29, 44, 7,
  5861. 47, 43, 6, 25, 46, 5, 41, 3, 1, 44,
  5862. 41, 48, 42, nil, 24, 42, nil, nil, 32, 25,
  5863. 13, nil, nil, nil, 36, nil, nil, 41, nil, nil,
  5864. nil, nil, nil, nil, 44, 44, nil, nil, 47, nil,
  5865. nil, nil, nil, nil, nil, 41, 31, 45, nil, nil,
  5866. 44, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5867. nil, nil, nil, nil, nil, nil, nil, 46, nil, nil,
  5868. nil, nil, nil, 41, nil, nil, nil, 37, 29, 13,
  5869. 37, 29, 28, 36, nil, nil, nil, nil, nil, nil,
  5870. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5871. nil, nil, nil, nil, nil, 2 ]
  5872. racc_goto_pointer = [
  5873. nil, 58, -4, 51, 14, 47, 43, 39, 33, 31,
  5874. 29, 37, 35, 2, nil, -94, -105, 26, -14, -59,
  5875. -93, -108, -112, -127, -24, -60, -110, -118, -20, -24,
  5876. nil, 6, -34, 37, -50, -27, 6, -25, nil, nil,
  5877. nil, 1, -5, -63, -29, 3, -8, -47, -75 ]
  5878. racc_goto_default = [
  5879. nil, nil, nil, nil, nil, nil, nil, nil, nil, nil,
  5880. nil, nil, nil, 48, 41, nil, nil, nil, nil, nil,
  5881. nil, nil, nil, nil, nil, 86, nil, nil, 30, 34,
  5882. 50, 51, nil, 46, 47, nil, 26, 28, 71, 72,
  5883. 33, 35, 114, 82, 18, nil, nil, nil, nil ]
  5884. racc_token_table = {
  5885. false => 0,
  5886. Object.new => 1,
  5887. :DATETIME => 2,
  5888. :RECEIVED => 3,
  5889. :MADDRESS => 4,
  5890. :RETPATH => 5,
  5891. :KEYWORDS => 6,
  5892. :ENCRYPTED => 7,
  5893. :MIMEVERSION => 8,
  5894. :CTYPE => 9,
  5895. :CENCODING => 10,
  5896. :CDISPOSITION => 11,
  5897. :ADDRESS => 12,
  5898. :MAILBOX => 13,
  5899. :DIGIT => 14,
  5900. :ATOM => 15,
  5901. "," => 16,
  5902. ":" => 17,
  5903. :FROM => 18,
  5904. :BY => 19,
  5905. "@" => 20,
  5906. :DOMLIT => 21,
  5907. :VIA => 22,
  5908. :WITH => 23,
  5909. :ID => 24,
  5910. :FOR => 25,
  5911. ";" => 26,
  5912. "<" => 27,
  5913. ">" => 28,
  5914. "." => 29,
  5915. :QUOTED => 30,
  5916. :TOKEN => 31,
  5917. "/" => 32,
  5918. "=" => 33 }
  5919. racc_use_result_var = false
  5920. racc_nt_base = 34
  5921. Racc_arg = [
  5922. racc_action_table,
  5923. racc_action_check,
  5924. racc_action_default,
  5925. racc_action_pointer,
  5926. racc_goto_table,
  5927. racc_goto_check,
  5928. racc_goto_default,
  5929. racc_goto_pointer,
  5930. racc_nt_base,
  5931. racc_reduce_table,
  5932. racc_token_table,
  5933. racc_shift_n,
  5934. racc_reduce_n,
  5935. racc_use_result_var ]
  5936. Racc_token_to_s_table = [
  5937. '$end',
  5938. 'error',
  5939. 'DATETIME',
  5940. 'RECEIVED',
  5941. 'MADDRESS',
  5942. 'RETPATH',
  5943. 'KEYWORDS',
  5944. 'ENCRYPTED',
  5945. 'MIMEVERSION',
  5946. 'CTYPE',
  5947. 'CENCODING',
  5948. 'CDISPOSITION',
  5949. 'ADDRESS',
  5950. 'MAILBOX',
  5951. 'DIGIT',
  5952. 'ATOM',
  5953. '","',
  5954. '":"',
  5955. 'FROM',
  5956. 'BY',
  5957. '"@"',
  5958. 'DOMLIT',
  5959. 'VIA',
  5960. 'WITH',
  5961. 'ID',
  5962. 'FOR',
  5963. '";"',
  5964. '"<"',
  5965. '">"',
  5966. '"."',
  5967. 'QUOTED',
  5968. 'TOKEN',
  5969. '"/"',
  5970. '"="',
  5971. '$start',
  5972. 'content',
  5973. 'datetime',
  5974. 'received',
  5975. 'addrs_TOP',
  5976. 'retpath',
  5977. 'keys',
  5978. 'enc',
  5979. 'version',
  5980. 'ctype',
  5981. 'cencode',
  5982. 'cdisp',
  5983. 'addr_TOP',
  5984. 'mbox',
  5985. 'day',
  5986. 'hour',
  5987. 'zone',
  5988. 'from',
  5989. 'by',
  5990. 'via',
  5991. 'with',
  5992. 'id',
  5993. 'for',
  5994. 'received_datetime',
  5995. 'received_domain',
  5996. 'domain',
  5997. 'msgid',
  5998. 'received_addrspec',
  5999. 'routeaddr',
  6000. 'spec',
  6001. 'addrs',
  6002. 'group_bare',
  6003. 'commas',
  6004. 'group',
  6005. 'addr',
  6006. 'mboxes',
  6007. 'addr_phrase',
  6008. 'local_head',
  6009. 'routes',
  6010. 'at_domains',
  6011. 'local',
  6012. 'word',
  6013. 'dots',
  6014. 'domword',
  6015. 'atom',
  6016. 'phrase',
  6017. 'params',
  6018. 'opt_semicolon',
  6019. 'value']
  6020. Racc_debug_parser = false
  6021. ##### racc system variables end #####
  6022. # reduce 0 omitted
  6023. module_eval <<'.,.,', 'parser.y', 16
  6024. def _reduce_1( val, _values)
  6025. val[1]
  6026. end
  6027. .,.,
  6028. module_eval <<'.,.,', 'parser.y', 17
  6029. def _reduce_2( val, _values)
  6030. val[1]
  6031. end
  6032. .,.,
  6033. module_eval <<'.,.,', 'parser.y', 18
  6034. def _reduce_3( val, _values)
  6035. val[1]
  6036. end
  6037. .,.,
  6038. module_eval <<'.,.,', 'parser.y', 19
  6039. def _reduce_4( val, _values)
  6040. val[1]
  6041. end
  6042. .,.,
  6043. module_eval <<'.,.,', 'parser.y', 20
  6044. def _reduce_5( val, _values)
  6045. val[1]
  6046. end
  6047. .,.,
  6048. module_eval <<'.,.,', 'parser.y', 21
  6049. def _reduce_6( val, _values)
  6050. val[1]
  6051. end
  6052. .,.,
  6053. module_eval <<'.,.,', 'parser.y', 22
  6054. def _reduce_7( val, _values)
  6055. val[1]
  6056. end
  6057. .,.,
  6058. module_eval <<'.,.,', 'parser.y', 23
  6059. def _reduce_8( val, _values)
  6060. val[1]
  6061. end
  6062. .,.,
  6063. module_eval <<'.,.,', 'parser.y', 24
  6064. def _reduce_9( val, _values)
  6065. val[1]
  6066. end
  6067. .,.,
  6068. module_eval <<'.,.,', 'parser.y', 25
  6069. def _reduce_10( val, _values)
  6070. val[1]
  6071. end
  6072. .,.,
  6073. module_eval <<'.,.,', 'parser.y', 26
  6074. def _reduce_11( val, _values)
  6075. val[1]
  6076. end
  6077. .,.,
  6078. module_eval <<'.,.,', 'parser.y', 27
  6079. def _reduce_12( val, _values)
  6080. val[1]
  6081. end
  6082. .,.,
  6083. module_eval <<'.,.,', 'parser.y', 33
  6084. def _reduce_13( val, _values)
  6085. t = Time.gm(val[3].to_i, val[2], val[1].to_i, 0, 0, 0)
  6086. (t + val[4] - val[5]).localtime
  6087. end
  6088. .,.,
  6089. # reduce 14 omitted
  6090. # reduce 15 omitted
  6091. module_eval <<'.,.,', 'parser.y', 42
  6092. def _reduce_16( val, _values)
  6093. (val[0].to_i * 60 * 60) +
  6094. (val[2].to_i * 60)
  6095. end
  6096. .,.,
  6097. module_eval <<'.,.,', 'parser.y', 47
  6098. def _reduce_17( val, _values)
  6099. (val[0].to_i * 60 * 60) +
  6100. (val[2].to_i * 60) +
  6101. (val[4].to_i)
  6102. end
  6103. .,.,
  6104. module_eval <<'.,.,', 'parser.y', 54
  6105. def _reduce_18( val, _values)
  6106. timezone_string_to_unixtime(val[0])
  6107. end
  6108. .,.,
  6109. module_eval <<'.,.,', 'parser.y', 59
  6110. def _reduce_19( val, _values)
  6111. val
  6112. end
  6113. .,.,
  6114. # reduce 20 omitted
  6115. module_eval <<'.,.,', 'parser.y', 65
  6116. def _reduce_21( val, _values)
  6117. val[1]
  6118. end
  6119. .,.,
  6120. # reduce 22 omitted
  6121. module_eval <<'.,.,', 'parser.y', 71
  6122. def _reduce_23( val, _values)
  6123. val[1]
  6124. end
  6125. .,.,
  6126. module_eval <<'.,.,', 'parser.y', 77
  6127. def _reduce_24( val, _values)
  6128. join_domain(val[0])
  6129. end
  6130. .,.,
  6131. module_eval <<'.,.,', 'parser.y', 81
  6132. def _reduce_25( val, _values)
  6133. join_domain(val[2])
  6134. end
  6135. .,.,
  6136. module_eval <<'.,.,', 'parser.y', 85
  6137. def _reduce_26( val, _values)
  6138. join_domain(val[0])
  6139. end
  6140. .,.,
  6141. # reduce 27 omitted
  6142. module_eval <<'.,.,', 'parser.y', 91
  6143. def _reduce_28( val, _values)
  6144. val[1]
  6145. end
  6146. .,.,
  6147. module_eval <<'.,.,', 'parser.y', 96
  6148. def _reduce_29( val, _values)
  6149. []
  6150. end
  6151. .,.,
  6152. module_eval <<'.,.,', 'parser.y', 100
  6153. def _reduce_30( val, _values)
  6154. val[0].push val[2]
  6155. val[0]
  6156. end
  6157. .,.,
  6158. # reduce 31 omitted
  6159. module_eval <<'.,.,', 'parser.y', 107
  6160. def _reduce_32( val, _values)
  6161. val[1]
  6162. end
  6163. .,.,
  6164. module_eval <<'.,.,', 'parser.y', 111
  6165. def _reduce_33( val, _values)
  6166. val[1]
  6167. end
  6168. .,.,
  6169. # reduce 34 omitted
  6170. module_eval <<'.,.,', 'parser.y', 117
  6171. def _reduce_35( val, _values)
  6172. val[1]
  6173. end
  6174. .,.,
  6175. module_eval <<'.,.,', 'parser.y', 123
  6176. def _reduce_36( val, _values)
  6177. val[0].spec
  6178. end
  6179. .,.,
  6180. module_eval <<'.,.,', 'parser.y', 127
  6181. def _reduce_37( val, _values)
  6182. val[0].spec
  6183. end
  6184. .,.,
  6185. # reduce 38 omitted
  6186. module_eval <<'.,.,', 'parser.y', 134
  6187. def _reduce_39( val, _values)
  6188. val[1]
  6189. end
  6190. .,.,
  6191. # reduce 40 omitted
  6192. # reduce 41 omitted
  6193. # reduce 42 omitted
  6194. # reduce 43 omitted
  6195. # reduce 44 omitted
  6196. # reduce 45 omitted
  6197. # reduce 46 omitted
  6198. module_eval <<'.,.,', 'parser.y', 146
  6199. def _reduce_47( val, _values)
  6200. [ Address.new(nil, nil) ]
  6201. end
  6202. .,.,
  6203. module_eval <<'.,.,', 'parser.y', 148
  6204. def _reduce_48( val, _values)
  6205. val
  6206. end
  6207. .,.,
  6208. module_eval <<'.,.,', 'parser.y', 149
  6209. def _reduce_49( val, _values)
  6210. val[0].push val[2]; val[0]
  6211. end
  6212. .,.,
  6213. # reduce 50 omitted
  6214. # reduce 51 omitted
  6215. module_eval <<'.,.,', 'parser.y', 156
  6216. def _reduce_52( val, _values)
  6217. val
  6218. end
  6219. .,.,
  6220. module_eval <<'.,.,', 'parser.y', 160
  6221. def _reduce_53( val, _values)
  6222. val[0].push val[2]
  6223. val[0]
  6224. end
  6225. .,.,
  6226. # reduce 54 omitted
  6227. # reduce 55 omitted
  6228. module_eval <<'.,.,', 'parser.y', 168
  6229. def _reduce_56( val, _values)
  6230. val[1].phrase = Decoder.decode(val[0])
  6231. val[1]
  6232. end
  6233. .,.,
  6234. # reduce 57 omitted
  6235. module_eval <<'.,.,', 'parser.y', 176
  6236. def _reduce_58( val, _values)
  6237. AddressGroup.new(val[0], val[2])
  6238. end
  6239. .,.,
  6240. module_eval <<'.,.,', 'parser.y', 178
  6241. def _reduce_59( val, _values)
  6242. AddressGroup.new(val[0], [])
  6243. end
  6244. .,.,
  6245. module_eval <<'.,.,', 'parser.y', 181
  6246. def _reduce_60( val, _values)
  6247. val[0].join('.')
  6248. end
  6249. .,.,
  6250. module_eval <<'.,.,', 'parser.y', 182
  6251. def _reduce_61( val, _values)
  6252. val[0] << ' ' << val[1].join('.')
  6253. end
  6254. .,.,
  6255. module_eval <<'.,.,', 'parser.y', 186
  6256. def _reduce_62( val, _values)
  6257. val[2].routes.replace val[1]
  6258. val[2]
  6259. end
  6260. .,.,
  6261. module_eval <<'.,.,', 'parser.y', 191
  6262. def _reduce_63( val, _values)
  6263. val[1]
  6264. end
  6265. .,.,
  6266. # reduce 64 omitted
  6267. module_eval <<'.,.,', 'parser.y', 196
  6268. def _reduce_65( val, _values)
  6269. [ val[1].join('.') ]
  6270. end
  6271. .,.,
  6272. module_eval <<'.,.,', 'parser.y', 197
  6273. def _reduce_66( val, _values)
  6274. val[0].push val[3].join('.'); val[0]
  6275. end
  6276. .,.,
  6277. module_eval <<'.,.,', 'parser.y', 199
  6278. def _reduce_67( val, _values)
  6279. Address.new( val[0], val[2] )
  6280. end
  6281. .,.,
  6282. module_eval <<'.,.,', 'parser.y', 200
  6283. def _reduce_68( val, _values)
  6284. Address.new( val[0], nil )
  6285. end
  6286. .,.,
  6287. # reduce 69 omitted
  6288. module_eval <<'.,.,', 'parser.y', 203
  6289. def _reduce_70( val, _values)
  6290. val[0].push ''; val[0]
  6291. end
  6292. .,.,
  6293. module_eval <<'.,.,', 'parser.y', 206
  6294. def _reduce_71( val, _values)
  6295. val
  6296. end
  6297. .,.,
  6298. module_eval <<'.,.,', 'parser.y', 209
  6299. def _reduce_72( val, _values)
  6300. val[1].times do
  6301. val[0].push ''
  6302. end
  6303. val[0].push val[2]
  6304. val[0]
  6305. end
  6306. .,.,
  6307. module_eval <<'.,.,', 'parser.y', 217
  6308. def _reduce_73( val, _values)
  6309. val
  6310. end
  6311. .,.,
  6312. module_eval <<'.,.,', 'parser.y', 220
  6313. def _reduce_74( val, _values)
  6314. val[1].times do
  6315. val[0].push ''
  6316. end
  6317. val[0].push val[2]
  6318. val[0]
  6319. end
  6320. .,.,
  6321. module_eval <<'.,.,', 'parser.y', 227
  6322. def _reduce_75( val, _values)
  6323. 0
  6324. end
  6325. .,.,
  6326. module_eval <<'.,.,', 'parser.y', 228
  6327. def _reduce_76( val, _values)
  6328. 1
  6329. end
  6330. .,.,
  6331. # reduce 77 omitted
  6332. # reduce 78 omitted
  6333. # reduce 79 omitted
  6334. # reduce 80 omitted
  6335. # reduce 81 omitted
  6336. # reduce 82 omitted
  6337. # reduce 83 omitted
  6338. # reduce 84 omitted
  6339. module_eval <<'.,.,', 'parser.y', 243
  6340. def _reduce_85( val, _values)
  6341. val[1] = val[1].spec
  6342. val.join('')
  6343. end
  6344. .,.,
  6345. module_eval <<'.,.,', 'parser.y', 247
  6346. def _reduce_86( val, _values)
  6347. val
  6348. end
  6349. .,.,
  6350. module_eval <<'.,.,', 'parser.y', 248
  6351. def _reduce_87( val, _values)
  6352. val[0].push val[2]; val[0]
  6353. end
  6354. .,.,
  6355. # reduce 88 omitted
  6356. module_eval <<'.,.,', 'parser.y', 251
  6357. def _reduce_89( val, _values)
  6358. val[0] << ' ' << val[1]
  6359. end
  6360. .,.,
  6361. module_eval <<'.,.,', 'parser.y', 255
  6362. def _reduce_90( val, _values)
  6363. val.push nil
  6364. val
  6365. end
  6366. .,.,
  6367. module_eval <<'.,.,', 'parser.y', 260
  6368. def _reduce_91( val, _values)
  6369. val
  6370. end
  6371. .,.,
  6372. module_eval <<'.,.,', 'parser.y', 265
  6373. def _reduce_92( val, _values)
  6374. [ val[0].to_i, val[2].to_i ]
  6375. end
  6376. .,.,
  6377. module_eval <<'.,.,', 'parser.y', 270
  6378. def _reduce_93( val, _values)
  6379. [ val[0].downcase, val[2].downcase, decode_params(val[3]) ]
  6380. end
  6381. .,.,
  6382. module_eval <<'.,.,', 'parser.y', 274
  6383. def _reduce_94( val, _values)
  6384. [ val[0].downcase, nil, decode_params(val[1]) ]
  6385. end
  6386. .,.,
  6387. module_eval <<'.,.,', 'parser.y', 279
  6388. def _reduce_95( val, _values)
  6389. {}
  6390. end
  6391. .,.,
  6392. module_eval <<'.,.,', 'parser.y', 283
  6393. def _reduce_96( val, _values)
  6394. val[0][ val[2].downcase ] = val[4]
  6395. val[0]
  6396. end
  6397. .,.,
  6398. # reduce 97 omitted
  6399. # reduce 98 omitted
  6400. module_eval <<'.,.,', 'parser.y', 292
  6401. def _reduce_99( val, _values)
  6402. val[0].downcase
  6403. end
  6404. .,.,
  6405. module_eval <<'.,.,', 'parser.y', 297
  6406. def _reduce_100( val, _values)
  6407. [ val[0].downcase, decode_params(val[1]) ]
  6408. end
  6409. .,.,
  6410. # reduce 101 omitted
  6411. # reduce 102 omitted
  6412. # reduce 103 omitted
  6413. # reduce 104 omitted
  6414. # reduce 105 omitted
  6415. # reduce 106 omitted
  6416. # reduce 107 omitted
  6417. # reduce 108 omitted
  6418. # reduce 109 omitted
  6419. def _reduce_none( val, _values)
  6420. val[0]
  6421. end
  6422. end # class Parser
  6423. end # module TMail
  6424. #
  6425. # port.rb
  6426. #
  6427. #--
  6428. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6429. #
  6430. # Permission is hereby granted, free of charge, to any person obtaining
  6431. # a copy of this software and associated documentation files (the
  6432. # "Software"), to deal in the Software without restriction, including
  6433. # without limitation the rights to use, copy, modify, merge, publish,
  6434. # distribute, sublicense, and/or sell copies of the Software, and to
  6435. # permit persons to whom the Software is furnished to do so, subject to
  6436. # the following conditions:
  6437. #
  6438. # The above copyright notice and this permission notice shall be
  6439. # included in all copies or substantial portions of the Software.
  6440. #
  6441. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6442. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6443. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6444. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6445. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6446. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6447. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6448. #
  6449. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6450. # with permission of Minero Aoki.
  6451. #++
  6452. require 'tmail/stringio'
  6453. module TMail
  6454. class Port
  6455. def reproducible?
  6456. false
  6457. end
  6458. end
  6459. ###
  6460. ### FilePort
  6461. ###
  6462. class FilePort < Port
  6463. def initialize( fname )
  6464. @filename = File.expand_path(fname)
  6465. super()
  6466. end
  6467. attr_reader :filename
  6468. alias ident filename
  6469. def ==( other )
  6470. other.respond_to?(:filename) and @filename == other.filename
  6471. end
  6472. alias eql? ==
  6473. def hash
  6474. @filename.hash
  6475. end
  6476. def inspect
  6477. "#<#{self.class}:#{@filename}>"
  6478. end
  6479. def reproducible?
  6480. true
  6481. end
  6482. def size
  6483. File.size @filename
  6484. end
  6485. def ropen( &block )
  6486. File.open(@filename, &block)
  6487. end
  6488. def wopen( &block )
  6489. File.open(@filename, 'w', &block)
  6490. end
  6491. def aopen( &block )
  6492. File.open(@filename, 'a', &block)
  6493. end
  6494. def read_all
  6495. ropen {|f|
  6496. return f.read
  6497. }
  6498. end
  6499. def remove
  6500. File.unlink @filename
  6501. end
  6502. def move_to( port )
  6503. begin
  6504. File.link @filename, port.filename
  6505. rescue Errno::EXDEV
  6506. copy_to port
  6507. end
  6508. File.unlink @filename
  6509. end
  6510. alias mv move_to
  6511. def copy_to( port )
  6512. if FilePort === port
  6513. copy_file @filename, port.filename
  6514. else
  6515. File.open(@filename) {|r|
  6516. port.wopen {|w|
  6517. while s = r.sysread(4096)
  6518. w.write << s
  6519. end
  6520. } }
  6521. end
  6522. end
  6523. alias cp copy_to
  6524. private
  6525. # from fileutils.rb
  6526. def copy_file( src, dest )
  6527. st = r = w = nil
  6528. File.open(src, 'rb') {|r|
  6529. File.open(dest, 'wb') {|w|
  6530. st = r.stat
  6531. begin
  6532. while true
  6533. w.write r.sysread(st.blksize)
  6534. end
  6535. rescue EOFError
  6536. end
  6537. } }
  6538. end
  6539. end
  6540. module MailFlags
  6541. def seen=( b )
  6542. set_status 'S', b
  6543. end
  6544. def seen?
  6545. get_status 'S'
  6546. end
  6547. def replied=( b )
  6548. set_status 'R', b
  6549. end
  6550. def replied?
  6551. get_status 'R'
  6552. end
  6553. def flagged=( b )
  6554. set_status 'F', b
  6555. end
  6556. def flagged?
  6557. get_status 'F'
  6558. end
  6559. private
  6560. def procinfostr( str, tag, true_p )
  6561. a = str.upcase.split(//)
  6562. a.push true_p ? tag : nil
  6563. a.delete tag unless true_p
  6564. a.compact.sort.join('').squeeze
  6565. end
  6566. end
  6567. class MhPort < FilePort
  6568. include MailFlags
  6569. private
  6570. def set_status( tag, flag )
  6571. begin
  6572. tmpfile = @filename + '.tmailtmp.' + $$.to_s
  6573. File.open(tmpfile, 'w') {|f|
  6574. write_status f, tag, flag
  6575. }
  6576. File.unlink @filename
  6577. File.link tmpfile, @filename
  6578. ensure
  6579. File.unlink tmpfile
  6580. end
  6581. end
  6582. def write_status( f, tag, flag )
  6583. stat = ''
  6584. File.open(@filename) {|r|
  6585. while line = r.gets
  6586. if line.strip.empty?
  6587. break
  6588. elsif m = /\AX-TMail-Status:/i.match(line)
  6589. stat = m.post_match.strip
  6590. else
  6591. f.print line
  6592. end
  6593. end
  6594. s = procinfostr(stat, tag, flag)
  6595. f.puts 'X-TMail-Status: ' + s unless s.empty?
  6596. f.puts
  6597. while s = r.read(2048)
  6598. f.write s
  6599. end
  6600. }
  6601. end
  6602. def get_status( tag )
  6603. File.foreach(@filename) {|line|
  6604. return false if line.strip.empty?
  6605. if m = /\AX-TMail-Status:/i.match(line)
  6606. return m.post_match.strip.include?(tag[0])
  6607. end
  6608. }
  6609. false
  6610. end
  6611. end
  6612. class MaildirPort < FilePort
  6613. def move_to_new
  6614. new = replace_dir(@filename, 'new')
  6615. File.rename @filename, new
  6616. @filename = new
  6617. end
  6618. def move_to_cur
  6619. new = replace_dir(@filename, 'cur')
  6620. File.rename @filename, new
  6621. @filename = new
  6622. end
  6623. def replace_dir( path, dir )
  6624. "#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
  6625. end
  6626. private :replace_dir
  6627. include MailFlags
  6628. private
  6629. MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
  6630. def set_status( tag, flag )
  6631. if m = MAIL_FILE.match(File.basename(@filename))
  6632. s, uniq, type, info, = m.to_a
  6633. return if type and type != '2' # do not change anything
  6634. newname = File.dirname(@filename) + '/' +
  6635. uniq + ':2,' + procinfostr(info.to_s, tag, flag)
  6636. else
  6637. newname = @filename + ':2,' + tag
  6638. end
  6639. File.link @filename, newname
  6640. File.unlink @filename
  6641. @filename = newname
  6642. end
  6643. def get_status( tag )
  6644. m = MAIL_FILE.match(File.basename(@filename)) or return false
  6645. m[2] == '2' and m[3].to_s.include?(tag[0])
  6646. end
  6647. end
  6648. ###
  6649. ### StringPort
  6650. ###
  6651. class StringPort < Port
  6652. def initialize( str = '' )
  6653. @buffer = str
  6654. super()
  6655. end
  6656. def string
  6657. @buffer
  6658. end
  6659. def to_s
  6660. @buffer.dup
  6661. end
  6662. alias read_all to_s
  6663. def size
  6664. @buffer.size
  6665. end
  6666. def ==( other )
  6667. StringPort === other and @buffer.equal? other.string
  6668. end
  6669. alias eql? ==
  6670. def hash
  6671. @buffer.object_id.hash
  6672. end
  6673. def inspect
  6674. "#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
  6675. end
  6676. def reproducible?
  6677. true
  6678. end
  6679. def ropen( &block )
  6680. @buffer or raise Errno::ENOENT, "#{inspect} is already removed"
  6681. StringInput.open(@buffer, &block)
  6682. end
  6683. def wopen( &block )
  6684. @buffer = ''
  6685. StringOutput.new(@buffer, &block)
  6686. end
  6687. def aopen( &block )
  6688. @buffer ||= ''
  6689. StringOutput.new(@buffer, &block)
  6690. end
  6691. def remove
  6692. @buffer = nil
  6693. end
  6694. alias rm remove
  6695. def copy_to( port )
  6696. port.wopen {|f|
  6697. f.write @buffer
  6698. }
  6699. end
  6700. alias cp copy_to
  6701. def move_to( port )
  6702. if StringPort === port
  6703. str = @buffer
  6704. port.instance_eval { @buffer = str }
  6705. else
  6706. copy_to port
  6707. end
  6708. remove
  6709. end
  6710. end
  6711. end # module TMail
  6712. module TMail
  6713. class Mail
  6714. def subject(to_charset = 'utf-8')
  6715. Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
  6716. end
  6717. def unquoted_body(to_charset = 'utf-8')
  6718. from_charset = sub_header("content-type", "charset")
  6719. case (content_transfer_encoding || "7bit").downcase
  6720. when "quoted-printable"
  6721. Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
  6722. to_charset, from_charset, true)
  6723. when "base64"
  6724. Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
  6725. from_charset)
  6726. when "7bit", "8bit"
  6727. Unquoter.convert_to(quoted_body, to_charset, from_charset)
  6728. when "binary"
  6729. quoted_body
  6730. else
  6731. quoted_body
  6732. end
  6733. end
  6734. def body(to_charset = 'utf-8', &block)
  6735. attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
  6736. if multipart?
  6737. parts.collect { |part|
  6738. header = part["content-type"]
  6739. if part.multipart?
  6740. part.body(to_charset, &attachment_presenter)
  6741. elsif header.nil?
  6742. ""
  6743. elsif !attachment?(part)
  6744. part.unquoted_body(to_charset)
  6745. else
  6746. attachment_presenter.call(header["name"] || "(unnamed)")
  6747. end
  6748. }.join
  6749. else
  6750. unquoted_body(to_charset)
  6751. end
  6752. end
  6753. end
  6754. class Unquoter
  6755. class << self
  6756. def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
  6757. return "" if text.nil?
  6758. if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
  6759. from_charset = $1
  6760. quoting_method = $2
  6761. text = $3
  6762. case quoting_method.upcase
  6763. when "Q" then
  6764. unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
  6765. when "B" then
  6766. unquote_base64_and_convert_to(text, to_charset, from_charset)
  6767. else
  6768. raise "unknown quoting method #{quoting_method.inspect}"
  6769. end
  6770. else
  6771. convert_to(text, to_charset, from_charset)
  6772. end
  6773. end
  6774. def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
  6775. text = text.gsub(/_/, " ") unless preserve_underscores
  6776. convert_to(text.unpack("M*").first, to, from)
  6777. end
  6778. def unquote_base64_and_convert_to(text, to, from)
  6779. convert_to(Base64.decode(text).first, to, from)
  6780. end
  6781. begin
  6782. require 'iconv'
  6783. def convert_to(text, to, from)
  6784. return text unless to && from
  6785. text ? Iconv.iconv(to, from, text).first : ""
  6786. rescue Iconv::IllegalSequence, Errno::EINVAL
  6787. # the 'from' parameter specifies a charset other than what the text
  6788. # actually is...not much we can do in this case but just return the
  6789. # unconverted text.
  6790. #
  6791. # Ditto if either parameter represents an unknown charset, like
  6792. # X-UNKNOWN.
  6793. text
  6794. end
  6795. rescue LoadError
  6796. # Not providing quoting support
  6797. def convert_to(text, to, from)
  6798. warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
  6799. text
  6800. end
  6801. end
  6802. end
  6803. end
  6804. end
  6805. if __FILE__ == $0
  6806. require 'test/unit'
  6807. class TC_Unquoter < Test::Unit::TestCase
  6808. def test_unquote_quoted_printable
  6809. a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
  6810. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6811. assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
  6812. end
  6813. def test_unquote_base64
  6814. a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
  6815. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6816. assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
  6817. end
  6818. def test_unquote_without_charset
  6819. a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
  6820. b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
  6821. assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
  6822. end
  6823. end
  6824. end
  6825. #
  6826. # scanner.rb
  6827. #
  6828. #--
  6829. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6830. #
  6831. # Permission is hereby granted, free of charge, to any person obtaining
  6832. # a copy of this software and associated documentation files (the
  6833. # "Software"), to deal in the Software without restriction, including
  6834. # without limitation the rights to use, copy, modify, merge, publish,
  6835. # distribute, sublicense, and/or sell copies of the Software, and to
  6836. # permit persons to whom the Software is furnished to do so, subject to
  6837. # the following conditions:
  6838. #
  6839. # The above copyright notice and this permission notice shall be
  6840. # included in all copies or substantial portions of the Software.
  6841. #
  6842. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6843. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6844. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6845. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6846. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6847. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6848. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6849. #
  6850. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6851. # with permission of Minero Aoki.
  6852. #++
  6853. require 'tmail/utils'
  6854. module TMail
  6855. require 'tmail/scanner_r.rb'
  6856. begin
  6857. raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
  6858. require 'tmail/scanner_c.so'
  6859. Scanner = Scanner_C
  6860. rescue LoadError
  6861. Scanner = Scanner_R
  6862. end
  6863. end
  6864. #
  6865. # scanner_r.rb
  6866. #
  6867. #--
  6868. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  6869. #
  6870. # Permission is hereby granted, free of charge, to any person obtaining
  6871. # a copy of this software and associated documentation files (the
  6872. # "Software"), to deal in the Software without restriction, including
  6873. # without limitation the rights to use, copy, modify, merge, publish,
  6874. # distribute, sublicense, and/or sell copies of the Software, and to
  6875. # permit persons to whom the Software is furnished to do so, subject to
  6876. # the following conditions:
  6877. #
  6878. # The above copyright notice and this permission notice shall be
  6879. # included in all copies or substantial portions of the Software.
  6880. #
  6881. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  6882. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  6883. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  6884. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  6885. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  6886. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  6887. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  6888. #
  6889. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  6890. # with permission of Minero Aoki.
  6891. #++
  6892. require 'tmail/config'
  6893. module TMail
  6894. class Scanner_R
  6895. Version = '0.10.7'
  6896. Version.freeze
  6897. MIME_HEADERS = {
  6898. :CTYPE => true,
  6899. :CENCODING => true,
  6900. :CDISPOSITION => true
  6901. }
  6902. alnum = 'a-zA-Z0-9'
  6903. atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
  6904. tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
  6905. atomchars = alnum + Regexp.quote(atomsyms)
  6906. tokenchars = alnum + Regexp.quote(tokensyms)
  6907. iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
  6908. eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
  6909. sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
  6910. utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
  6911. quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
  6912. domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
  6913. comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
  6914. quoted_without_iso2022 = /\A[^\\"]+/n
  6915. domlit_without_iso2022 = /\A[^\\\]]+/n
  6916. comment_without_iso2022 = /\A[^\\()]+/n
  6917. PATTERN_TABLE = {}
  6918. PATTERN_TABLE['EUC'] =
  6919. [
  6920. /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
  6921. /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
  6922. quoted_with_iso2022,
  6923. domlit_with_iso2022,
  6924. comment_with_iso2022
  6925. ]
  6926. PATTERN_TABLE['SJIS'] =
  6927. [
  6928. /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
  6929. /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
  6930. quoted_with_iso2022,
  6931. domlit_with_iso2022,
  6932. comment_with_iso2022
  6933. ]
  6934. PATTERN_TABLE['UTF8'] =
  6935. [
  6936. /\A(?:[#{atomchars}]+|#{utf8str})+/n,
  6937. /\A(?:[#{tokenchars}]+|#{utf8str})+/n,
  6938. quoted_without_iso2022,
  6939. domlit_without_iso2022,
  6940. comment_without_iso2022
  6941. ]
  6942. PATTERN_TABLE['NONE'] =
  6943. [
  6944. /\A[#{atomchars}]+/n,
  6945. /\A[#{tokenchars}]+/n,
  6946. quoted_without_iso2022,
  6947. domlit_without_iso2022,
  6948. comment_without_iso2022
  6949. ]
  6950. def initialize( str, scantype, comments )
  6951. init_scanner str
  6952. @comments = comments || []
  6953. @debug = false
  6954. # fix scanner mode
  6955. @received = (scantype == :RECEIVED)
  6956. @is_mime_header = MIME_HEADERS[scantype]
  6957. atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
  6958. @word_re = (MIME_HEADERS[scantype] ? token : atom)
  6959. end
  6960. attr_accessor :debug
  6961. def scan( &block )
  6962. if @debug
  6963. scan_main do |arr|
  6964. s, v = arr
  6965. printf "%7d %-10s %s\n",
  6966. rest_size(),
  6967. s.respond_to?(:id2name) ? s.id2name : s.inspect,
  6968. v.inspect
  6969. yield arr
  6970. end
  6971. else
  6972. scan_main(&block)
  6973. end
  6974. end
  6975. private
  6976. RECV_TOKEN = {
  6977. 'from' => :FROM,
  6978. 'by' => :BY,
  6979. 'via' => :VIA,
  6980. 'with' => :WITH,
  6981. 'id' => :ID,
  6982. 'for' => :FOR
  6983. }
  6984. def scan_main
  6985. until eof?
  6986. if skip(/\A[\n\r\t ]+/n) # LWSP
  6987. break if eof?
  6988. end
  6989. if s = readstr(@word_re)
  6990. if @is_mime_header
  6991. yield :TOKEN, s
  6992. else
  6993. # atom
  6994. if /\A\d+\z/ === s
  6995. yield :DIGIT, s
  6996. elsif @received
  6997. yield RECV_TOKEN[s.downcase] || :ATOM, s
  6998. else
  6999. yield :ATOM, s
  7000. end
  7001. end
  7002. elsif skip(/\A"/)
  7003. yield :QUOTED, scan_quoted_word()
  7004. elsif skip(/\A\[/)
  7005. yield :DOMLIT, scan_domain_literal()
  7006. elsif skip(/\A\(/)
  7007. @comments.push scan_comment()
  7008. else
  7009. c = readchar()
  7010. yield c, c
  7011. end
  7012. end
  7013. yield false, '$'
  7014. end
  7015. def scan_quoted_word
  7016. scan_qstr(@quoted_re, /\A"/, 'quoted-word')
  7017. end
  7018. def scan_domain_literal
  7019. '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
  7020. end
  7021. def scan_qstr( pattern, terminal, type )
  7022. result = ''
  7023. until eof?
  7024. if s = readstr(pattern) then result << s
  7025. elsif skip(terminal) then return result
  7026. elsif skip(/\A\\/) then result << readchar()
  7027. else
  7028. raise "TMail FATAL: not match in #{type}"
  7029. end
  7030. end
  7031. scan_error! "found unterminated #{type}"
  7032. end
  7033. def scan_comment
  7034. result = ''
  7035. nest = 1
  7036. content = @comment_re
  7037. until eof?
  7038. if s = readstr(content) then result << s
  7039. elsif skip(/\A\)/) then nest -= 1
  7040. return result if nest == 0
  7041. result << ')'
  7042. elsif skip(/\A\(/) then nest += 1
  7043. result << '('
  7044. elsif skip(/\A\\/) then result << readchar()
  7045. else
  7046. raise 'TMail FATAL: not match in comment'
  7047. end
  7048. end
  7049. scan_error! 'found unterminated comment'
  7050. end
  7051. # string scanner
  7052. def init_scanner( str )
  7053. @src = str
  7054. end
  7055. def eof?
  7056. @src.empty?
  7057. end
  7058. def rest_size
  7059. @src.size
  7060. end
  7061. def readstr( re )
  7062. if m = re.match(@src)
  7063. @src = m.post_match
  7064. m[0]
  7065. else
  7066. nil
  7067. end
  7068. end
  7069. def readchar
  7070. readstr(/\A./)
  7071. end
  7072. def skip( re )
  7073. if m = re.match(@src)
  7074. @src = m.post_match
  7075. true
  7076. else
  7077. false
  7078. end
  7079. end
  7080. def scan_error!( msg )
  7081. raise SyntaxError, msg
  7082. end
  7083. end
  7084. end # module TMail
  7085. #
  7086. # stringio.rb
  7087. #
  7088. #--
  7089. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  7090. #
  7091. # Permission is hereby granted, free of charge, to any person obtaining
  7092. # a copy of this software and associated documentation files (the
  7093. # "Software"), to deal in the Software without restriction, including
  7094. # without limitation the rights to use, copy, modify, merge, publish,
  7095. # distribute, sublicense, and/or sell copies of the Software, and to
  7096. # permit persons to whom the Software is furnished to do so, subject to
  7097. # the following conditions:
  7098. #
  7099. # The above copyright notice and this permission notice shall be
  7100. # included in all copies or substantial portions of the Software.
  7101. #
  7102. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7103. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7104. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7105. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7106. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7107. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7108. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7109. #
  7110. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  7111. # with permission of Minero Aoki.
  7112. #++
  7113. class StringInput#:nodoc:
  7114. include Enumerable
  7115. class << self
  7116. def new( str )
  7117. if block_given?
  7118. begin
  7119. f = super
  7120. yield f
  7121. ensure
  7122. f.close if f
  7123. end
  7124. else
  7125. super
  7126. end
  7127. end
  7128. alias open new
  7129. end
  7130. def initialize( str )
  7131. @src = str
  7132. @pos = 0
  7133. @closed = false
  7134. @lineno = 0
  7135. end
  7136. attr_reader :lineno
  7137. def string
  7138. @src
  7139. end
  7140. def inspect
  7141. "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
  7142. end
  7143. def close
  7144. stream_check!
  7145. @pos = nil
  7146. @closed = true
  7147. end
  7148. def closed?
  7149. @closed
  7150. end
  7151. def pos
  7152. stream_check!
  7153. [@pos, @src.size].min
  7154. end
  7155. alias tell pos
  7156. def seek( offset, whence = IO::SEEK_SET )
  7157. stream_check!
  7158. case whence
  7159. when IO::SEEK_SET
  7160. @pos = offset
  7161. when IO::SEEK_CUR
  7162. @pos += offset
  7163. when IO::SEEK_END
  7164. @pos = @src.size - offset
  7165. else
  7166. raise ArgumentError, "unknown seek flag: #{whence}"
  7167. end
  7168. @pos = 0 if @pos < 0
  7169. @pos = [@pos, @src.size + 1].min
  7170. offset
  7171. end
  7172. def rewind
  7173. stream_check!
  7174. @pos = 0
  7175. end
  7176. def eof?
  7177. stream_check!
  7178. @pos > @src.size
  7179. end
  7180. def each( &block )
  7181. stream_check!
  7182. begin
  7183. @src.each(&block)
  7184. ensure
  7185. @pos = 0
  7186. end
  7187. end
  7188. def gets
  7189. stream_check!
  7190. if idx = @src.index(?\n, @pos)
  7191. idx += 1 # "\n".size
  7192. line = @src[ @pos ... idx ]
  7193. @pos = idx
  7194. @pos += 1 if @pos == @src.size
  7195. else
  7196. line = @src[ @pos .. -1 ]
  7197. @pos = @src.size + 1
  7198. end
  7199. @lineno += 1
  7200. line
  7201. end
  7202. def getc
  7203. stream_check!
  7204. ch = @src[@pos]
  7205. @pos += 1
  7206. @pos += 1 if @pos == @src.size
  7207. ch
  7208. end
  7209. def read( len = nil )
  7210. stream_check!
  7211. return read_all unless len
  7212. str = @src[@pos, len]
  7213. @pos += len
  7214. @pos += 1 if @pos == @src.size
  7215. str
  7216. end
  7217. alias sysread read
  7218. def read_all
  7219. stream_check!
  7220. return nil if eof?
  7221. rest = @src[@pos ... @src.size]
  7222. @pos = @src.size + 1
  7223. rest
  7224. end
  7225. def stream_check!
  7226. @closed and raise IOError, 'closed stream'
  7227. end
  7228. end
  7229. class StringOutput#:nodoc:
  7230. class << self
  7231. def new( str = '' )
  7232. if block_given?
  7233. begin
  7234. f = super
  7235. yield f
  7236. ensure
  7237. f.close if f
  7238. end
  7239. else
  7240. super
  7241. end
  7242. end
  7243. alias open new
  7244. end
  7245. def initialize( str = '' )
  7246. @dest = str
  7247. @closed = false
  7248. end
  7249. def close
  7250. @closed = true
  7251. end
  7252. def closed?
  7253. @closed
  7254. end
  7255. def string
  7256. @dest
  7257. end
  7258. alias value string
  7259. alias to_str string
  7260. def size
  7261. @dest.size
  7262. end
  7263. alias pos size
  7264. def inspect
  7265. "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
  7266. end
  7267. def print( *args )
  7268. stream_check!
  7269. raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
  7270. args.each do |s|
  7271. raise ArgumentError, 'nil not allowed' if s.nil?
  7272. @dest << s.to_s
  7273. end
  7274. nil
  7275. end
  7276. def puts( *args )
  7277. stream_check!
  7278. args.each do |str|
  7279. @dest << (s = str.to_s)
  7280. @dest << "\n" unless s[-1] == ?\n
  7281. end
  7282. @dest << "\n" if args.empty?
  7283. nil
  7284. end
  7285. def putc( ch )
  7286. stream_check!
  7287. @dest << ch.chr
  7288. nil
  7289. end
  7290. def printf( *args )
  7291. stream_check!
  7292. @dest << sprintf(*args)
  7293. nil
  7294. end
  7295. def write( str )
  7296. stream_check!
  7297. s = str.to_s
  7298. @dest << s
  7299. s.size
  7300. end
  7301. alias syswrite write
  7302. def <<( str )
  7303. stream_check!
  7304. @dest << str.to_s
  7305. self
  7306. end
  7307. private
  7308. def stream_check!
  7309. @closed and raise IOError, 'closed stream'
  7310. end
  7311. end
  7312. require 'tmail'
  7313. #
  7314. # utils.rb
  7315. #
  7316. #--
  7317. # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
  7318. #
  7319. # Permission is hereby granted, free of charge, to any person obtaining
  7320. # a copy of this software and associated documentation files (the
  7321. # "Software"), to deal in the Software without restriction, including
  7322. # without limitation the rights to use, copy, modify, merge, publish,
  7323. # distribute, sublicense, and/or sell copies of the Software, and to
  7324. # permit persons to whom the Software is furnished to do so, subject to
  7325. # the following conditions:
  7326. #
  7327. # The above copyright notice and this permission notice shall be
  7328. # included in all copies or substantial portions of the Software.
  7329. #
  7330. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7331. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7332. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7333. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7334. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7335. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7336. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7337. #
  7338. # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
  7339. # with permission of Minero Aoki.
  7340. #++
  7341. module TMail
  7342. class SyntaxError < StandardError; end
  7343. def TMail.new_boundary
  7344. 'mimepart_' + random_tag
  7345. end
  7346. def TMail.new_message_id( fqdn = nil )
  7347. fqdn ||= ::Socket.gethostname
  7348. "<#{random_tag()}@#{fqdn}.tmail>"
  7349. end
  7350. def TMail.random_tag
  7351. @uniq += 1
  7352. t = Time.now
  7353. sprintf('%x%x_%x%x%d%x',
  7354. t.to_i, t.tv_usec,
  7355. $$, Thread.current.object_id, @uniq, rand(255))
  7356. end
  7357. private_class_method :random_tag
  7358. @uniq = 0
  7359. module TextUtils
  7360. aspecial = '()<>[]:;.\\,"'
  7361. tspecial = '()<>[];:\\,"/?='
  7362. lwsp = " \t\r\n"
  7363. control = '\x00-\x1f\x7f-\xff'
  7364. ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
  7365. PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
  7366. TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
  7367. CONTROL_CHAR = /[#{control}]/n
  7368. def atom_safe?( str )
  7369. not ATOM_UNSAFE === str
  7370. end
  7371. def quote_atom( str )
  7372. (ATOM_UNSAFE === str) ? dquote(str) : str
  7373. end
  7374. def quote_phrase( str )
  7375. (PHRASE_UNSAFE === str) ? dquote(str) : str
  7376. end
  7377. def token_safe?( str )
  7378. not TOKEN_UNSAFE === str
  7379. end
  7380. def quote_token( str )
  7381. (TOKEN_UNSAFE === str) ? dquote(str) : str
  7382. end
  7383. def dquote( str )
  7384. '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
  7385. end
  7386. private :dquote
  7387. def join_domain( arr )
  7388. arr.map {|i|
  7389. if /\A\[.*\]\z/ === i
  7390. i
  7391. else
  7392. quote_atom(i)
  7393. end
  7394. }.join('.')
  7395. end
  7396. ZONESTR_TABLE = {
  7397. 'jst' => 9 * 60,
  7398. 'eet' => 2 * 60,
  7399. 'bst' => 1 * 60,
  7400. 'met' => 1 * 60,
  7401. 'gmt' => 0,
  7402. 'utc' => 0,
  7403. 'ut' => 0,
  7404. 'nst' => -(3 * 60 + 30),
  7405. 'ast' => -4 * 60,
  7406. 'edt' => -4 * 60,
  7407. 'est' => -5 * 60,
  7408. 'cdt' => -5 * 60,
  7409. 'cst' => -6 * 60,
  7410. 'mdt' => -6 * 60,
  7411. 'mst' => -7 * 60,
  7412. 'pdt' => -7 * 60,
  7413. 'pst' => -8 * 60,
  7414. 'a' => -1 * 60,
  7415. 'b' => -2 * 60,
  7416. 'c' => -3 * 60,
  7417. 'd' => -4 * 60,
  7418. 'e' => -5 * 60,
  7419. 'f' => -6 * 60,
  7420. 'g' => -7 * 60,
  7421. 'h' => -8 * 60,
  7422. 'i' => -9 * 60,
  7423. # j not use
  7424. 'k' => -10 * 60,
  7425. 'l' => -11 * 60,
  7426. 'm' => -12 * 60,
  7427. 'n' => 1 * 60,
  7428. 'o' => 2 * 60,
  7429. 'p' => 3 * 60,
  7430. 'q' => 4 * 60,
  7431. 'r' => 5 * 60,
  7432. 's' => 6 * 60,
  7433. 't' => 7 * 60,
  7434. 'u' => 8 * 60,
  7435. 'v' => 9 * 60,
  7436. 'w' => 10 * 60,
  7437. 'x' => 11 * 60,
  7438. 'y' => 12 * 60,
  7439. 'z' => 0 * 60
  7440. }
  7441. def timezone_string_to_unixtime( str )
  7442. if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
  7443. sec = (m[2].to_i * 60 + m[3].to_i) * 60
  7444. m[1] == '-' ? -sec : sec
  7445. else
  7446. min = ZONESTR_TABLE[str.downcase] or
  7447. raise SyntaxError, "wrong timezone format '#{str}'"
  7448. min * 60
  7449. end
  7450. end
  7451. WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
  7452. MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
  7453. Jul Aug Sep Oct Nov Dec TMailBUG )
  7454. def time2str( tm )
  7455. # [ruby-list:7928]
  7456. gmt = Time.at(tm.to_i)
  7457. gmt.gmtime
  7458. offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
  7459. # DO NOT USE strftime: setlocale() breaks it
  7460. sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
  7461. WDAY[tm.wday], tm.mday, MONTH[tm.month],
  7462. tm.year, tm.hour, tm.min, tm.sec,
  7463. *(offset / 60).divmod(60)
  7464. end
  7465. MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
  7466. def message_id?( str )
  7467. MESSAGE_ID === str
  7468. end
  7469. MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
  7470. def mime_encoded?( str )
  7471. MIME_ENCODED === str
  7472. end
  7473. def decode_params( hash )
  7474. new = Hash.new
  7475. encoded = nil
  7476. hash.each do |key, value|
  7477. if m = /\*(?:(\d+)\*)?\z/.match(key)
  7478. ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
  7479. else
  7480. new[key] = to_kcode(value)
  7481. end
  7482. end
  7483. if encoded
  7484. encoded.each do |key, strings|
  7485. new[key] = decode_RFC2231(strings.join(''))
  7486. end
  7487. end
  7488. new
  7489. end
  7490. NKF_FLAGS = {
  7491. 'EUC' => '-e -m',
  7492. 'SJIS' => '-s -m'
  7493. }
  7494. def to_kcode( str )
  7495. flag = NKF_FLAGS[$KCODE] or return str
  7496. NKF.nkf(flag, str)
  7497. end
  7498. RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
  7499. def decode_RFC2231( str )
  7500. m = RFC2231_ENCODED.match(str) or return str
  7501. begin
  7502. NKF.nkf(NKF_FLAGS[$KCODE],
  7503. m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
  7504. rescue
  7505. m.post_match.gsub(/%[\da-f]{2}/in, "")
  7506. end
  7507. end
  7508. end
  7509. end
  7510. require 'tmail/info'
  7511. require 'tmail/mail'
  7512. require 'tmail/mailbox'
  7513. module ActionMailer
  7514. module VERSION #:nodoc:
  7515. MAJOR = 1
  7516. MINOR = 2
  7517. TINY = 5
  7518. STRING = [MAJOR, MINOR, TINY].join('.')
  7519. end
  7520. end
  7521. #--
  7522. # Copyright (c) 2004 David Heinemeier Hansson
  7523. #
  7524. # Permission is hereby granted, free of charge, to any person obtaining
  7525. # a copy of this software and associated documentation files (the
  7526. # "Software"), to deal in the Software without restriction, including
  7527. # without limitation the rights to use, copy, modify, merge, publish,
  7528. # distribute, sublicense, and/or sell copies of the Software, and to
  7529. # permit persons to whom the Software is furnished to do so, subject to
  7530. # the following conditions:
  7531. #
  7532. # The above copyright notice and this permission notice shall be
  7533. # included in all copies or substantial portions of the Software.
  7534. #
  7535. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  7536. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  7537. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  7538. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  7539. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  7540. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  7541. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  7542. #++
  7543. begin
  7544. require 'action_controller'
  7545. rescue LoadError
  7546. begin
  7547. require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
  7548. rescue LoadError
  7549. require 'rubygems'
  7550. require_gem 'actionpack', '>= 1.9.1'
  7551. end
  7552. end
  7553. $:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
  7554. require 'action_mailer/base'
  7555. require 'action_mailer/helpers'
  7556. require 'action_mailer/mail_helper'
  7557. require 'action_mailer/quoting'
  7558. require 'tmail'
  7559. require 'net/smtp'
  7560. ActionMailer::Base.class_eval do
  7561. include ActionMailer::Quoting
  7562. include ActionMailer::Helpers
  7563. helper MailHelper
  7564. end
  7565. silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }module TestHelper
  7566. def test_format(text)
  7567. "<em><strong><small>#{text}</small></strong></em>"
  7568. end
  7569. end
  7570. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7571. $:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
  7572. require 'test/unit'
  7573. require 'action_mailer'
  7574. module MailerHelper
  7575. def person_name
  7576. "Mr. Joe Person"
  7577. end
  7578. end
  7579. class HelperMailer < ActionMailer::Base
  7580. helper MailerHelper
  7581. helper :test
  7582. def use_helper(recipient)
  7583. recipients recipient
  7584. subject "using helpers"
  7585. from "tester@example.com"
  7586. end
  7587. def use_test_helper(recipient)
  7588. recipients recipient
  7589. subject "using helpers"
  7590. from "tester@example.com"
  7591. self.body = { :text => "emphasize me!" }
  7592. end
  7593. def use_mail_helper(recipient)
  7594. recipients recipient
  7595. subject "using mailing helpers"
  7596. from "tester@example.com"
  7597. self.body = { :text =>
  7598. "But soft! What light through yonder window breaks? It is the east, " +
  7599. "and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
  7600. "which is sick and pale with grief that thou, her maid, art far more " +
  7601. "fair than she. Be not her maid, for she is envious! Her vestal " +
  7602. "livery is but sick and green, and none but fools do wear it. Cast " +
  7603. "it off!"
  7604. }
  7605. end
  7606. def use_helper_method(recipient)
  7607. recipients recipient
  7608. subject "using helpers"
  7609. from "tester@example.com"
  7610. self.body = { :text => "emphasize me!" }
  7611. end
  7612. private
  7613. def name_of_the_mailer_class
  7614. self.class.name
  7615. end
  7616. helper_method :name_of_the_mailer_class
  7617. end
  7618. HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7619. class MailerHelperTest < Test::Unit::TestCase
  7620. def new_mail( charset="utf-8" )
  7621. mail = TMail::Mail.new
  7622. mail.set_content_type "text", "plain", { "charset" => charset } if charset
  7623. mail
  7624. end
  7625. def setup
  7626. ActionMailer::Base.delivery_method = :test
  7627. ActionMailer::Base.perform_deliveries = true
  7628. ActionMailer::Base.deliveries = []
  7629. @recipient = 'test@localhost'
  7630. end
  7631. def test_use_helper
  7632. mail = HelperMailer.create_use_helper(@recipient)
  7633. assert_match %r{Mr. Joe Person}, mail.encoded
  7634. end
  7635. def test_use_test_helper
  7636. mail = HelperMailer.create_use_test_helper(@recipient)
  7637. assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
  7638. end
  7639. def test_use_helper_method
  7640. mail = HelperMailer.create_use_helper_method(@recipient)
  7641. assert_match %r{HelperMailer}, mail.encoded
  7642. end
  7643. def test_use_mail_helper
  7644. mail = HelperMailer.create_use_mail_helper(@recipient)
  7645. assert_match %r{ But soft!}, mail.encoded
  7646. assert_match %r{east, and\n Juliet}, mail.encoded
  7647. end
  7648. end
  7649. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7650. require 'test/unit'
  7651. require 'action_mailer'
  7652. class RenderMailer < ActionMailer::Base
  7653. def inline_template(recipient)
  7654. recipients recipient
  7655. subject "using helpers"
  7656. from "tester@example.com"
  7657. body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
  7658. end
  7659. def file_template(recipient)
  7660. recipients recipient
  7661. subject "using helpers"
  7662. from "tester@example.com"
  7663. body render(:file => "signed_up", :body => { :recipient => recipient })
  7664. end
  7665. def initialize_defaults(method_name)
  7666. super
  7667. mailer_name "test_mailer"
  7668. end
  7669. end
  7670. RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7671. class RenderHelperTest < Test::Unit::TestCase
  7672. def setup
  7673. ActionMailer::Base.delivery_method = :test
  7674. ActionMailer::Base.perform_deliveries = true
  7675. ActionMailer::Base.deliveries = []
  7676. @recipient = 'test@localhost'
  7677. end
  7678. def test_inline_template
  7679. mail = RenderMailer.create_inline_template(@recipient)
  7680. assert_equal "Hello, Earth", mail.body.strip
  7681. end
  7682. def test_file_template
  7683. mail = RenderMailer.create_file_template(@recipient)
  7684. assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
  7685. end
  7686. end
  7687. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  7688. require 'test/unit'
  7689. require 'action_mailer'
  7690. class MockSMTP
  7691. def self.deliveries
  7692. @@deliveries
  7693. end
  7694. def initialize
  7695. @@deliveries = []
  7696. end
  7697. def sendmail(mail, from, to)
  7698. @@deliveries << [mail, from, to]
  7699. end
  7700. end
  7701. class Net::SMTP
  7702. def self.start(*args)
  7703. yield MockSMTP.new
  7704. end
  7705. end
  7706. class FunkyPathMailer < ActionMailer::Base
  7707. self.template_root = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  7708. def multipart_with_template_path_with_dots(recipient)
  7709. recipients recipient
  7710. subject "Have a lovely picture"
  7711. from "Chad Fowler <chad@chadfowler.com>"
  7712. attachment :content_type => "image/jpeg",
  7713. :body => "not really a jpeg, we're only testing, after all"
  7714. end
  7715. def template_path
  7716. "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  7717. end
  7718. end
  7719. class TestMailer < ActionMailer::Base
  7720. def signed_up(recipient)
  7721. @recipients = recipient
  7722. @subject = "[Signed up] Welcome #{recipient}"
  7723. @from = "system@loudthinking.com"
  7724. @sent_on = Time.local(2004, 12, 12)
  7725. @body["recipient"] = recipient
  7726. end
  7727. def cancelled_account(recipient)
  7728. self.recipients = recipient
  7729. self.subject = "[Cancelled] Goodbye #{recipient}"
  7730. self.from = "system@loudthinking.com"
  7731. self.sent_on = Time.local(2004, 12, 12)
  7732. self.body = "Goodbye, Mr. #{recipient}"
  7733. end
  7734. def cc_bcc(recipient)
  7735. recipients recipient
  7736. subject "testing bcc/cc"
  7737. from "system@loudthinking.com"
  7738. sent_on Time.local(2004, 12, 12)
  7739. cc "nobody@loudthinking.com"
  7740. bcc "root@loudthinking.com"
  7741. body "Nothing to see here."
  7742. end
  7743. def iso_charset(recipient)
  7744. @recipients = recipient
  7745. @subject = "testing isø charsets"
  7746. @from = "system@loudthinking.com"
  7747. @sent_on = Time.local 2004, 12, 12
  7748. @cc = "nobody@loudthinking.com"
  7749. @bcc = "root@loudthinking.com"
  7750. @body = "Nothing to see here."
  7751. @charset = "iso-8859-1"
  7752. end
  7753. def unencoded_subject(recipient)
  7754. @recipients = recipient
  7755. @subject = "testing unencoded subject"
  7756. @from = "system@loudthinking.com"
  7757. @sent_on = Time.local 2004, 12, 12
  7758. @cc = "nobody@loudthinking.com"
  7759. @bcc = "root@loudthinking.com"
  7760. @body = "Nothing to see here."
  7761. end
  7762. def extended_headers(recipient)
  7763. @recipients = recipient
  7764. @subject = "testing extended headers"
  7765. @from = "Grytøyr <stian1@example.net>"
  7766. @sent_on = Time.local 2004, 12, 12
  7767. @cc = "Grytøyr <stian2@example.net>"
  7768. @bcc = "Grytøyr <stian3@example.net>"
  7769. @body = "Nothing to see here."
  7770. @charset = "iso-8859-1"
  7771. end
  7772. def utf8_body(recipient)
  7773. @recipients = recipient
  7774. @subject = "testing utf-8 body"
  7775. @from = "Foo áëô îü <extended@example.net>"
  7776. @sent_on = Time.local 2004, 12, 12
  7777. @cc = "Foo áëô îü <extended@example.net>"
  7778. @bcc = "Foo áëô îü <extended@example.net>"
  7779. @body = "åœö blah"
  7780. @charset = "utf-8"
  7781. end
  7782. def multipart_with_mime_version(recipient)
  7783. recipients recipient
  7784. subject "multipart with mime_version"
  7785. from "test@example.com"
  7786. sent_on Time.local(2004, 12, 12)
  7787. mime_version "1.1"
  7788. content_type "multipart/alternative"
  7789. part "text/plain" do |p|
  7790. p.body = "blah"
  7791. end
  7792. part "text/html" do |p|
  7793. p.body = "<b>blah</b>"
  7794. end
  7795. end
  7796. def multipart_with_utf8_subject(recipient)
  7797. recipients recipient
  7798. subject "Foo áëô îü"
  7799. from "test@example.com"
  7800. charset "utf-8"
  7801. part "text/plain" do |p|
  7802. p.body = "blah"
  7803. end
  7804. part "text/html" do |p|
  7805. p.body = "<b>blah</b>"
  7806. end
  7807. end
  7808. def explicitly_multipart_example(recipient, ct=nil)
  7809. recipients recipient
  7810. subject "multipart example"
  7811. from "test@example.com"
  7812. sent_on Time.local(2004, 12, 12)
  7813. body "plain text default"
  7814. content_type ct if ct
  7815. part "text/html" do |p|
  7816. p.charset = "iso-8859-1"
  7817. p.body = "blah"
  7818. end
  7819. attachment :content_type => "image/jpeg", :filename => "foo.jpg",
  7820. :body => "123456789"
  7821. end
  7822. def implicitly_multipart_example(recipient, cs = nil, order = nil)
  7823. @recipients = recipient
  7824. @subject = "multipart example"
  7825. @from = "test@example.com"
  7826. @sent_on = Time.local 2004, 12, 12
  7827. @body = { "recipient" => recipient }
  7828. @charset = cs if cs
  7829. @implicit_parts_order = order if order
  7830. end
  7831. def implicitly_multipart_with_utf8
  7832. recipients "no.one@nowhere.test"
  7833. subject "Foo áëô îü"
  7834. from "some.one@somewhere.test"
  7835. template "implicitly_multipart_example"
  7836. body ({ "recipient" => "no.one@nowhere.test" })
  7837. end
  7838. def html_mail(recipient)
  7839. recipients recipient
  7840. subject "html mail"
  7841. from "test@example.com"
  7842. body "<em>Emphasize</em> <strong>this</strong>"
  7843. content_type "text/html"
  7844. end
  7845. def html_mail_with_underscores(recipient)
  7846. subject "html mail with underscores"
  7847. body %{<a href="http://google.com" target="_blank">_Google</a>}
  7848. end
  7849. def custom_template(recipient)
  7850. recipients recipient
  7851. subject "[Signed up] Welcome #{recipient}"
  7852. from "system@loudthinking.com"
  7853. sent_on Time.local(2004, 12, 12)
  7854. template "signed_up"
  7855. body["recipient"] = recipient
  7856. end
  7857. def various_newlines(recipient)
  7858. recipients recipient
  7859. subject "various newlines"
  7860. from "test@example.com"
  7861. body "line #1\nline #2\rline #3\r\nline #4\r\r" +
  7862. "line #5\n\nline#6\r\n\r\nline #7"
  7863. end
  7864. def various_newlines_multipart(recipient)
  7865. recipients recipient
  7866. subject "various newlines multipart"
  7867. from "test@example.com"
  7868. content_type "multipart/alternative"
  7869. part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r"
  7870. 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"
  7871. end
  7872. def nested_multipart(recipient)
  7873. recipients recipient
  7874. subject "nested multipart"
  7875. from "test@example.com"
  7876. content_type "multipart/mixed"
  7877. part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
  7878. p.part :content_type => "text/plain", :body => "test text\nline #2"
  7879. p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
  7880. end
  7881. attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
  7882. end
  7883. def attachment_with_custom_header(recipient)
  7884. recipients recipient
  7885. subject "custom header in attachment"
  7886. from "test@example.com"
  7887. content_type "multipart/related"
  7888. part :content_type => "text/html", :body => 'yo'
  7889. attachment :content_type => "image/jpeg",:filename => "test.jpeg", :body => "i am not a real picture", :headers => { 'Content-ID' => '<test@test.com>' }
  7890. end
  7891. def unnamed_attachment(recipient)
  7892. recipients recipient
  7893. subject "nested multipart"
  7894. from "test@example.com"
  7895. content_type "multipart/mixed"
  7896. part :content_type => "text/plain", :body => "hullo"
  7897. attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
  7898. end
  7899. def headers_with_nonalpha_chars(recipient)
  7900. recipients recipient
  7901. subject "nonalpha chars"
  7902. from "One: Two <test@example.com>"
  7903. cc "Three: Four <test@example.com>"
  7904. bcc "Five: Six <test@example.com>"
  7905. body "testing"
  7906. end
  7907. def custom_content_type_attributes
  7908. recipients "no.one@nowhere.test"
  7909. subject "custom content types"
  7910. from "some.one@somewhere.test"
  7911. content_type "text/plain; format=flowed"
  7912. body "testing"
  7913. end
  7914. class <<self
  7915. attr_accessor :received_body
  7916. end
  7917. def receive(mail)
  7918. self.class.received_body = mail.body
  7919. end
  7920. end
  7921. TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"
  7922. class ActionMailerTest < Test::Unit::TestCase
  7923. include ActionMailer::Quoting
  7924. def encode( text, charset="utf-8" )
  7925. quoted_printable( text, charset )
  7926. end
  7927. def new_mail( charset="utf-8" )
  7928. mail = TMail::Mail.new
  7929. if charset
  7930. mail.set_content_type "text", "plain", { "charset" => charset }
  7931. end
  7932. mail
  7933. end
  7934. def setup
  7935. ActionMailer::Base.delivery_method = :test
  7936. ActionMailer::Base.perform_deliveries = true
  7937. ActionMailer::Base.deliveries = []
  7938. @recipient = 'test@localhost'
  7939. end
  7940. def test_nested_parts
  7941. created = nil
  7942. assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
  7943. assert_equal 2,created.parts.size
  7944. assert_equal 2,created.parts.first.parts.size
  7945. assert_equal "multipart/mixed", created.content_type
  7946. assert_equal "multipart/alternative", created.parts.first.content_type
  7947. assert_equal "text/plain", created.parts.first.parts.first.content_type
  7948. assert_equal "text/html", created.parts.first.parts[1].content_type
  7949. assert_equal "application/octet-stream", created.parts[1].content_type
  7950. end
  7951. def test_attachment_with_custom_header
  7952. created = nil
  7953. assert_nothing_raised { created = TestMailer.create_attachment_with_custom_header(@recipient)}
  7954. assert_equal "<test@test.com>", created.parts[1].header['content-id'].to_s
  7955. end
  7956. def test_signed_up
  7957. expected = new_mail
  7958. expected.to = @recipient
  7959. expected.subject = "[Signed up] Welcome #{@recipient}"
  7960. expected.body = "Hello there, \n\nMr. #{@recipient}"
  7961. expected.from = "system@loudthinking.com"
  7962. expected.date = Time.local(2004, 12, 12)
  7963. expected.mime_version = nil
  7964. created = nil
  7965. assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
  7966. assert_not_nil created
  7967. assert_equal expected.encoded, created.encoded
  7968. assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
  7969. assert_not_nil ActionMailer::Base.deliveries.first
  7970. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  7971. end
  7972. def test_custom_template
  7973. expected = new_mail
  7974. expected.to = @recipient
  7975. expected.subject = "[Signed up] Welcome #{@recipient}"
  7976. expected.body = "Hello there, \n\nMr. #{@recipient}"
  7977. expected.from = "system@loudthinking.com"
  7978. expected.date = Time.local(2004, 12, 12)
  7979. created = nil
  7980. assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
  7981. assert_not_nil created
  7982. assert_equal expected.encoded, created.encoded
  7983. end
  7984. def test_cancelled_account
  7985. expected = new_mail
  7986. expected.to = @recipient
  7987. expected.subject = "[Cancelled] Goodbye #{@recipient}"
  7988. expected.body = "Goodbye, Mr. #{@recipient}"
  7989. expected.from = "system@loudthinking.com"
  7990. expected.date = Time.local(2004, 12, 12)
  7991. created = nil
  7992. assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
  7993. assert_not_nil created
  7994. assert_equal expected.encoded, created.encoded
  7995. assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
  7996. assert_not_nil ActionMailer::Base.deliveries.first
  7997. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  7998. end
  7999. def test_cc_bcc
  8000. expected = new_mail
  8001. expected.to = @recipient
  8002. expected.subject = "testing bcc/cc"
  8003. expected.body = "Nothing to see here."
  8004. expected.from = "system@loudthinking.com"
  8005. expected.cc = "nobody@loudthinking.com"
  8006. expected.bcc = "root@loudthinking.com"
  8007. expected.date = Time.local 2004, 12, 12
  8008. created = nil
  8009. assert_nothing_raised do
  8010. created = TestMailer.create_cc_bcc @recipient
  8011. end
  8012. assert_not_nil created
  8013. assert_equal expected.encoded, created.encoded
  8014. assert_nothing_raised do
  8015. TestMailer.deliver_cc_bcc @recipient
  8016. end
  8017. assert_not_nil ActionMailer::Base.deliveries.first
  8018. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8019. end
  8020. def test_iso_charset
  8021. expected = new_mail( "iso-8859-1" )
  8022. expected.to = @recipient
  8023. expected.subject = encode "testing isø charsets", "iso-8859-1"
  8024. expected.body = "Nothing to see here."
  8025. expected.from = "system@loudthinking.com"
  8026. expected.cc = "nobody@loudthinking.com"
  8027. expected.bcc = "root@loudthinking.com"
  8028. expected.date = Time.local 2004, 12, 12
  8029. created = nil
  8030. assert_nothing_raised do
  8031. created = TestMailer.create_iso_charset @recipient
  8032. end
  8033. assert_not_nil created
  8034. assert_equal expected.encoded, created.encoded
  8035. assert_nothing_raised do
  8036. TestMailer.deliver_iso_charset @recipient
  8037. end
  8038. assert_not_nil ActionMailer::Base.deliveries.first
  8039. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8040. end
  8041. def test_unencoded_subject
  8042. expected = new_mail
  8043. expected.to = @recipient
  8044. expected.subject = "testing unencoded subject"
  8045. expected.body = "Nothing to see here."
  8046. expected.from = "system@loudthinking.com"
  8047. expected.cc = "nobody@loudthinking.com"
  8048. expected.bcc = "root@loudthinking.com"
  8049. expected.date = Time.local 2004, 12, 12
  8050. created = nil
  8051. assert_nothing_raised do
  8052. created = TestMailer.create_unencoded_subject @recipient
  8053. end
  8054. assert_not_nil created
  8055. assert_equal expected.encoded, created.encoded
  8056. assert_nothing_raised do
  8057. TestMailer.deliver_unencoded_subject @recipient
  8058. end
  8059. assert_not_nil ActionMailer::Base.deliveries.first
  8060. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8061. end
  8062. def test_instances_are_nil
  8063. assert_nil ActionMailer::Base.new
  8064. assert_nil TestMailer.new
  8065. end
  8066. def test_deliveries_array
  8067. assert_not_nil ActionMailer::Base.deliveries
  8068. assert_equal 0, ActionMailer::Base.deliveries.size
  8069. TestMailer.deliver_signed_up(@recipient)
  8070. assert_equal 1, ActionMailer::Base.deliveries.size
  8071. assert_not_nil ActionMailer::Base.deliveries.first
  8072. end
  8073. def test_perform_deliveries_flag
  8074. ActionMailer::Base.perform_deliveries = false
  8075. TestMailer.deliver_signed_up(@recipient)
  8076. assert_equal 0, ActionMailer::Base.deliveries.size
  8077. ActionMailer::Base.perform_deliveries = true
  8078. TestMailer.deliver_signed_up(@recipient)
  8079. assert_equal 1, ActionMailer::Base.deliveries.size
  8080. end
  8081. def test_unquote_quoted_printable_subject
  8082. msg = <<EOF
  8083. From: me@example.com
  8084. Subject: =?utf-8?Q?testing_testing_=D6=A4?=
  8085. Content-Type: text/plain; charset=iso-8859-1
  8086. The body
  8087. EOF
  8088. mail = TMail::Mail.parse(msg)
  8089. assert_equal "testing testing \326\244", mail.subject
  8090. assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
  8091. end
  8092. def test_unquote_7bit_subject
  8093. msg = <<EOF
  8094. From: me@example.com
  8095. Subject: this == working?
  8096. Content-Type: text/plain; charset=iso-8859-1
  8097. The body
  8098. EOF
  8099. mail = TMail::Mail.parse(msg)
  8100. assert_equal "this == working?", mail.subject
  8101. assert_equal "this == working?", mail.quoted_subject
  8102. end
  8103. def test_unquote_7bit_body
  8104. msg = <<EOF
  8105. From: me@example.com
  8106. Subject: subject
  8107. Content-Type: text/plain; charset=iso-8859-1
  8108. Content-Transfer-Encoding: 7bit
  8109. The=3Dbody
  8110. EOF
  8111. mail = TMail::Mail.parse(msg)
  8112. assert_equal "The=3Dbody", mail.body.strip
  8113. assert_equal "The=3Dbody", mail.quoted_body.strip
  8114. end
  8115. def test_unquote_quoted_printable_body
  8116. msg = <<EOF
  8117. From: me@example.com
  8118. Subject: subject
  8119. Content-Type: text/plain; charset=iso-8859-1
  8120. Content-Transfer-Encoding: quoted-printable
  8121. The=3Dbody
  8122. EOF
  8123. mail = TMail::Mail.parse(msg)
  8124. assert_equal "The=body", mail.body.strip
  8125. assert_equal "The=3Dbody", mail.quoted_body.strip
  8126. end
  8127. def test_unquote_base64_body
  8128. msg = <<EOF
  8129. From: me@example.com
  8130. Subject: subject
  8131. Content-Type: text/plain; charset=iso-8859-1
  8132. Content-Transfer-Encoding: base64
  8133. VGhlIGJvZHk=
  8134. EOF
  8135. mail = TMail::Mail.parse(msg)
  8136. assert_equal "The body", mail.body.strip
  8137. assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
  8138. end
  8139. def test_extended_headers
  8140. @recipient = "Grytøyr <test@localhost>"
  8141. expected = new_mail "iso-8859-1"
  8142. expected.to = quote_address_if_necessary @recipient, "iso-8859-1"
  8143. expected.subject = "testing extended headers"
  8144. expected.body = "Nothing to see here."
  8145. expected.from = quote_address_if_necessary "Grytøyr <stian1@example.net>", "iso-8859-1"
  8146. expected.cc = quote_address_if_necessary "Grytøyr <stian2@example.net>", "iso-8859-1"
  8147. expected.bcc = quote_address_if_necessary "Grytøyr <stian3@example.net>", "iso-8859-1"
  8148. expected.date = Time.local 2004, 12, 12
  8149. created = nil
  8150. assert_nothing_raised do
  8151. created = TestMailer.create_extended_headers @recipient
  8152. end
  8153. assert_not_nil created
  8154. assert_equal expected.encoded, created.encoded
  8155. assert_nothing_raised do
  8156. TestMailer.deliver_extended_headers @recipient
  8157. end
  8158. assert_not_nil ActionMailer::Base.deliveries.first
  8159. assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
  8160. end
  8161. def test_utf8_body_is_not_quoted
  8162. @recipient = "Foo áëô îü <extended@example.net>"
  8163. expected = new_mail "utf-8"
  8164. expected.to = quote_address_if_necessary @recipient, "utf-8"
  8165. expected.subject = "testing utf-8 body"
  8166. expected.body = "åœö blah"
  8167. expected.from = quote_address_if_necessary @recipient, "utf-8"
  8168. expected.cc = quote_address_if_necessary @recipient, "utf-8"
  8169. expected.bcc = quote_address_if_necessary @recipient, "utf-8"
  8170. expected.date = Time.local 2004, 12, 12
  8171. created = TestMailer.create_utf8_body @recipient
  8172. assert_match(/åœö blah/, created.encoded)
  8173. end
  8174. def test_multiple_utf8_recipients
  8175. @recipient = ["\"Foo áëô îü\" <extended@example.net>", "\"Example Recipient\" <me@example.com>"]
  8176. expected = new_mail "utf-8"
  8177. expected.to = quote_address_if_necessary @recipient, "utf-8"
  8178. expected.subject = "testing utf-8 body"
  8179. expected.body = "åœö blah"
  8180. expected.from = quote_address_if_necessary @recipient.first, "utf-8"
  8181. expected.cc = quote_address_if_necessary @recipient, "utf-8"
  8182. expected.bcc = quote_address_if_necessary @recipient, "utf-8"
  8183. expected.date = Time.local 2004, 12, 12
  8184. created = TestMailer.create_utf8_body @recipient
  8185. assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
  8186. assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
  8187. end
  8188. def test_receive_decodes_base64_encoded_mail
  8189. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
  8190. TestMailer.receive(fixture)
  8191. assert_match(/Jamis/, TestMailer.received_body)
  8192. end
  8193. def test_receive_attachments
  8194. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
  8195. mail = TMail::Mail.parse(fixture)
  8196. attachment = mail.attachments.last
  8197. assert_equal "smime.p7s", attachment.original_filename
  8198. assert_equal "application/pkcs7-signature", attachment.content_type
  8199. end
  8200. def test_decode_attachment_without_charset
  8201. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
  8202. mail = TMail::Mail.parse(fixture)
  8203. attachment = mail.attachments.last
  8204. assert_equal 1026, attachment.read.length
  8205. end
  8206. def test_attachment_using_content_location
  8207. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
  8208. mail = TMail::Mail.parse(fixture)
  8209. assert_equal 1, mail.attachments.length
  8210. assert_equal "Photo25.jpg", mail.attachments.first.original_filename
  8211. end
  8212. def test_attachment_with_text_type
  8213. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email13")
  8214. mail = TMail::Mail.parse(fixture)
  8215. assert mail.has_attachments?
  8216. assert_equal 1, mail.attachments.length
  8217. assert_equal "hello.rb", mail.attachments.first.original_filename
  8218. end
  8219. def test_decode_part_without_content_type
  8220. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
  8221. mail = TMail::Mail.parse(fixture)
  8222. assert_nothing_raised { mail.body }
  8223. end
  8224. def test_decode_message_without_content_type
  8225. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
  8226. mail = TMail::Mail.parse(fixture)
  8227. assert_nothing_raised { mail.body }
  8228. end
  8229. def test_decode_message_with_incorrect_charset
  8230. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
  8231. mail = TMail::Mail.parse(fixture)
  8232. assert_nothing_raised { mail.body }
  8233. end
  8234. def test_multipart_with_mime_version
  8235. mail = TestMailer.create_multipart_with_mime_version(@recipient)
  8236. assert_equal "1.1", mail.mime_version
  8237. end
  8238. def test_multipart_with_utf8_subject
  8239. mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
  8240. assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  8241. end
  8242. def test_implicitly_multipart_with_utf8
  8243. mail = TestMailer.create_implicitly_multipart_with_utf8
  8244. assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
  8245. end
  8246. def test_explicitly_multipart_messages
  8247. mail = TestMailer.create_explicitly_multipart_example(@recipient)
  8248. assert_equal 3, mail.parts.length
  8249. assert_nil mail.content_type
  8250. assert_equal "text/plain", mail.parts[0].content_type
  8251. assert_equal "text/html", mail.parts[1].content_type
  8252. assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
  8253. assert_equal "inline", mail.parts[1].content_disposition
  8254. assert_equal "image/jpeg", mail.parts[2].content_type
  8255. assert_equal "attachment", mail.parts[2].content_disposition
  8256. assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename")
  8257. assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name")
  8258. assert_nil mail.parts[2].sub_header("content-type", "charset")
  8259. end
  8260. def test_explicitly_multipart_with_content_type
  8261. mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
  8262. assert_equal 3, mail.parts.length
  8263. assert_equal "multipart/alternative", mail.content_type
  8264. end
  8265. def test_explicitly_multipart_with_invalid_content_type
  8266. mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
  8267. assert_equal 3, mail.parts.length
  8268. assert_nil mail.content_type
  8269. end
  8270. def test_implicitly_multipart_messages
  8271. mail = TestMailer.create_implicitly_multipart_example(@recipient)
  8272. assert_equal 3, mail.parts.length
  8273. assert_equal "1.0", mail.mime_version
  8274. assert_equal "multipart/alternative", mail.content_type
  8275. assert_equal "text/yaml", mail.parts[0].content_type
  8276. assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
  8277. assert_equal "text/plain", mail.parts[1].content_type
  8278. assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
  8279. assert_equal "text/html", mail.parts[2].content_type
  8280. assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset")
  8281. end
  8282. def test_implicitly_multipart_messages_with_custom_order
  8283. mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
  8284. assert_equal 3, mail.parts.length
  8285. assert_equal "text/html", mail.parts[0].content_type
  8286. assert_equal "text/plain", mail.parts[1].content_type
  8287. assert_equal "text/yaml", mail.parts[2].content_type
  8288. end
  8289. def test_implicitly_multipart_messages_with_charset
  8290. mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
  8291. assert_equal "multipart/alternative", mail.header['content-type'].body
  8292. assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
  8293. assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
  8294. assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
  8295. end
  8296. def test_html_mail
  8297. mail = TestMailer.create_html_mail(@recipient)
  8298. assert_equal "text/html", mail.content_type
  8299. end
  8300. def test_html_mail_with_underscores
  8301. mail = TestMailer.create_html_mail_with_underscores(@recipient)
  8302. assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
  8303. end
  8304. def test_various_newlines
  8305. mail = TestMailer.create_various_newlines(@recipient)
  8306. assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
  8307. "line #5\n\nline#6\n\nline #7", mail.body)
  8308. end
  8309. def test_various_newlines_multipart
  8310. mail = TestMailer.create_various_newlines_multipart(@recipient)
  8311. assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
  8312. 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
  8313. end
  8314. def test_headers_removed_on_smtp_delivery
  8315. ActionMailer::Base.delivery_method = :smtp
  8316. TestMailer.deliver_cc_bcc(@recipient)
  8317. assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
  8318. assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
  8319. assert MockSMTP.deliveries[0][2].include?(@recipient)
  8320. assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
  8321. assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
  8322. assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
  8323. end
  8324. def test_recursive_multipart_processing
  8325. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
  8326. mail = TMail::Mail.parse(fixture)
  8327. assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
  8328. end
  8329. def test_decode_encoded_attachment_filename
  8330. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
  8331. mail = TMail::Mail.parse(fixture)
  8332. attachment = mail.attachments.last
  8333. assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
  8334. end
  8335. def test_wrong_mail_header
  8336. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
  8337. assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
  8338. end
  8339. def test_decode_message_with_unknown_charset
  8340. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
  8341. mail = TMail::Mail.parse(fixture)
  8342. assert_nothing_raised { mail.body }
  8343. end
  8344. def test_decode_message_with_unquoted_atchar_in_header
  8345. fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
  8346. mail = TMail::Mail.parse(fixture)
  8347. assert_not_nil mail.from
  8348. end
  8349. def test_empty_header_values_omitted
  8350. result = TestMailer.create_unnamed_attachment(@recipient).encoded
  8351. assert_match %r{Content-Type: application/octet-stream[^;]}, result
  8352. assert_match %r{Content-Disposition: attachment[^;]}, result
  8353. end
  8354. def test_headers_with_nonalpha_chars
  8355. mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
  8356. assert !mail.from_addrs.empty?
  8357. assert !mail.cc_addrs.empty?
  8358. assert !mail.bcc_addrs.empty?
  8359. assert_match(/:/, mail.from_addrs.to_s)
  8360. assert_match(/:/, mail.cc_addrs.to_s)
  8361. assert_match(/:/, mail.bcc_addrs.to_s)
  8362. end
  8363. def test_deliver_with_mail_object
  8364. mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
  8365. assert_nothing_raised { TestMailer.deliver(mail) }
  8366. assert_equal 1, TestMailer.deliveries.length
  8367. end
  8368. def test_multipart_with_template_path_with_dots
  8369. mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
  8370. assert_equal 2, mail.parts.length
  8371. end
  8372. def test_custom_content_type_attributes
  8373. mail = TestMailer.create_custom_content_type_attributes
  8374. assert_match %r{format=flowed}, mail['content-type'].to_s
  8375. assert_match %r{charset=utf-8}, mail['content-type'].to_s
  8376. end
  8377. end
  8378. class InheritableTemplateRootTest < Test::Unit::TestCase
  8379. def test_attr
  8380. expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
  8381. assert_equal expected, FunkyPathMailer.template_root
  8382. sub = Class.new(FunkyPathMailer)
  8383. sub.template_root = 'test/path'
  8384. assert_equal 'test/path', sub.template_root
  8385. assert_equal expected, FunkyPathMailer.template_root
  8386. end
  8387. end
  8388. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8389. $:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")
  8390. require 'test/unit'
  8391. require 'tmail'
  8392. require 'tempfile'
  8393. class QuotingTest < Test::Unit::TestCase
  8394. def test_quote_multibyte_chars
  8395. original = "\303\246 \303\270 and \303\245"
  8396. result = execute_in_sandbox(<<-CODE)
  8397. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8398. $KCODE = 'u'
  8399. require 'jcode'
  8400. require 'action_mailer/quoting'
  8401. include ActionMailer::Quoting
  8402. quoted_printable(#{original.inspect}, "UTF-8")
  8403. CODE
  8404. unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
  8405. assert_equal unquoted, original
  8406. end
  8407. private
  8408. # This whole thing *could* be much simpler, but I don't think Tempfile,
  8409. # popen and others exist on all platforms (like Windows).
  8410. def execute_in_sandbox(code)
  8411. test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
  8412. res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
  8413. File.open(test_name, "w+") do |file|
  8414. file.write(<<-CODE)
  8415. block = Proc.new do
  8416. #{code}
  8417. end
  8418. puts block.call
  8419. CODE
  8420. end
  8421. system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
  8422. File.read(res_name)
  8423. ensure
  8424. File.delete(test_name) rescue nil
  8425. File.delete(res_name) rescue nil
  8426. end
  8427. end
  8428. $:.unshift(File.dirname(__FILE__) + "/../lib/")
  8429. $:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
  8430. require 'test/unit'
  8431. require 'action_mailer'
  8432. class TMailMailTest < Test::Unit::TestCase
  8433. def test_body
  8434. m = TMail::Mail.new
  8435. expected = 'something_with_underscores'
  8436. m.encoding = 'quoted-printable'
  8437. quoted_body = [expected].pack('*M')
  8438. m.body = quoted_body
  8439. assert_equal "something_with_underscores=\n", m.quoted_body
  8440. assert_equal expected, m.body
  8441. end
  8442. end
  8443. $:.unshift(File.dirname(__FILE__) + "/../lib")
  8444. require "action_controller"
  8445. require "action_controller/test_process"
  8446. Person = Struct.new("Person", :id, :name, :email_address, :phone_number)
  8447. class AddressBookService
  8448. attr_reader :people
  8449. def initialize() @people = [] end
  8450. def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
  8451. def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
  8452. def next_person_id() people.first.id + 1 end
  8453. end
  8454. class AddressBookController < ActionController::Base
  8455. layout "address_book/layout"
  8456. before_filter :initialize_session_storage
  8457. # Could also have used a proc
  8458. # before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) }
  8459. def index
  8460. @title = "Address Book"
  8461. @people = @address_book.people
  8462. end
  8463. def person
  8464. @person = @address_book.find_person(@params["id"])
  8465. end
  8466. def create_person
  8467. @address_book.create_person(@params["person"])
  8468. redirect_to :action => "index"
  8469. end
  8470. private
  8471. def initialize_session_storage
  8472. @address_book = @session["address_book"] ||= AddressBookService.new
  8473. end
  8474. end
  8475. ActionController::Base.template_root = File.dirname(__FILE__)
  8476. # ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
  8477. begin
  8478. AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
  8479. rescue => e
  8480. CGI.new.out { "#{e.class}: #{e.message}" }
  8481. end$:.unshift(File.dirname(__FILE__) + "/../lib")
  8482. require "action_controller"
  8483. require 'action_controller/test_process'
  8484. Person = Struct.new("Person", :name, :address, :age)
  8485. class BenchmarkController < ActionController::Base
  8486. def message
  8487. render_text "hello world"
  8488. end
  8489. def list
  8490. @people = [ Person.new("David"), Person.new("Mary") ]
  8491. render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
  8492. end
  8493. def form_helper
  8494. @person = Person.new "david", "hyacintvej", 24
  8495. render_template(
  8496. "<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
  8497. "change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
  8498. )
  8499. end
  8500. end
  8501. #ActionController::Base.template_root = File.dirname(__FILE__)
  8502. require "benchmark"
  8503. RUNS = ARGV[0] ? ARGV[0].to_i : 50
  8504. require "profile" if ARGV[1]
  8505. runtime = Benchmark.measure {
  8506. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
  8507. }
  8508. puts "List: #{RUNS / runtime.real}"
  8509. runtime = Benchmark.measure {
  8510. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
  8511. }
  8512. puts "Message: #{RUNS / runtime.real}"
  8513. runtime = Benchmark.measure {
  8514. RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
  8515. }
  8516. puts "Form helper: #{RUNS / runtime.real}"
  8517. require 'rbconfig'
  8518. require 'find'
  8519. require 'ftools'
  8520. include Config
  8521. # this was adapted from rdoc's install.rb by ways of Log4r
  8522. $sitedir = CONFIG["sitelibdir"]
  8523. unless $sitedir
  8524. version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
  8525. $libdir = File.join(CONFIG["libdir"], "ruby", version)
  8526. $sitedir = $:.find {|x| x =~ /site_ruby/ }
  8527. if !$sitedir
  8528. $sitedir = File.join($libdir, "site_ruby")
  8529. elsif $sitedir !~ Regexp.quote(version)
  8530. $sitedir = File.join($sitedir, version)
  8531. end
  8532. end
  8533. # the acual gruntwork
  8534. Dir.chdir("lib")
  8535. Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
  8536. if f[-3..-1] == ".rb"
  8537. File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
  8538. else
  8539. File::makedirs(File.join($sitedir, *f.split(/\//)))
  8540. end
  8541. }require 'test/unit'
  8542. require 'test/unit/assertions'
  8543. require 'rexml/document'
  8544. require File.dirname(__FILE__) + "/vendor/html-scanner/html/document"
  8545. module Test #:nodoc:
  8546. module Unit #:nodoc:
  8547. # In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
  8548. # can be used against. These collections are:
  8549. #
  8550. # * assigns: Instance variables assigned in the action that are available for the view.
  8551. # * session: Objects being saved in the session.
  8552. # * flash: The flash objects currently in the session.
  8553. # * cookies: Cookies being sent to the user on this request.
  8554. #
  8555. # These collections can be used just like any other hash:
  8556. #
  8557. # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
  8558. # assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
  8559. # assert flash.empty? # makes sure that there's nothing in the flash
  8560. #
  8561. # For historic reasons, the assigns hash uses string-based keys. So assigns[:person] won't work, but assigns["person"] will. To
  8562. # appease our yearning for symbols, though, an alternative accessor has been deviced using a method call instead of index referencing.
  8563. # So assigns(:person) will work just like assigns["person"], but again, assigns[:person] will not work.
  8564. #
  8565. # On top of the collections, you have the complete url that a given action redirected to available in redirect_to_url.
  8566. #
  8567. # For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
  8568. # action call which can then be asserted against.
  8569. #
  8570. # == Manipulating the request collections
  8571. #
  8572. # The collections described above link to the response, so you can test if what the actions were expected to do happened. But
  8573. # sometimes you also want to manipulate these collections in the incoming request. This is really only relevant for sessions
  8574. # and cookies, though. For sessions, you just do:
  8575. #
  8576. # @request.session[:key] = "value"
  8577. #
  8578. # For cookies, you need to manually create the cookie, like this:
  8579. #
  8580. # @request.cookies["key"] = CGI::Cookie.new("key", "value")
  8581. #
  8582. # == Testing named routes
  8583. #
  8584. # If you're using named routes, they can be easily tested using the original named routes methods straight in the test case.
  8585. # Example:
  8586. #
  8587. # assert_redirected_to page_url(:title => 'foo')
  8588. module Assertions
  8589. # Asserts that the response is one of the following types:
  8590. #
  8591. # * <tt>:success</tt>: Status code was 200
  8592. # * <tt>:redirect</tt>: Status code was in the 300-399 range
  8593. # * <tt>:missing</tt>: Status code was 404
  8594. # * <tt>:error</tt>: Status code was in the 500-599 range
  8595. #
  8596. # You can also pass an explicit status code number as the type, like assert_response(501)
  8597. def assert_response(type, message = nil)
  8598. clean_backtrace do
  8599. if [ :success, :missing, :redirect, :error ].include?(type) && @response.send("#{type}?")
  8600. assert_block("") { true } # to count the assertion
  8601. elsif type.is_a?(Fixnum) && @response.response_code == type
  8602. assert_block("") { true } # to count the assertion
  8603. else
  8604. assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false }
  8605. end
  8606. end
  8607. end
  8608. # Assert that the redirection options passed in match those of the redirect called in the latest action. This match can be partial,
  8609. # such that assert_redirected_to(:controller => "weblog") will also match the redirection of
  8610. # redirect_to(:controller => "weblog", :action => "show") and so on.
  8611. def assert_redirected_to(options = {}, message=nil)
  8612. clean_backtrace do
  8613. assert_response(:redirect, message)
  8614. if options.is_a?(String)
  8615. msg = build_message(message, "expected a redirect to <?>, found one to <?>", options, @response.redirect_url)
  8616. url_regexp = %r{^(\w+://.*?(/|$|\?))(.*)$}
  8617. eurl, epath, url, path = [options, @response.redirect_url].collect do |url|
  8618. u, p = (url_regexp =~ url) ? [$1, $3] : [nil, url]
  8619. [u, (p[0..0] == '/') ? p : '/' + p]
  8620. end.flatten
  8621. assert_equal(eurl, url, msg) if eurl && url
  8622. assert_equal(epath, path, msg) if epath && path
  8623. else
  8624. @response_diff = options.diff(@response.redirected_to) if options.is_a?(Hash) && @response.redirected_to.is_a?(Hash)
  8625. msg = build_message(message, "response is not a redirection to all of the options supplied (redirection is <?>)#{', difference: <?>' if @response_diff}",
  8626. @response.redirected_to || @response.redirect_url, @response_diff)
  8627. assert_block(msg) do
  8628. if options.is_a?(Symbol)
  8629. @response.redirected_to == options
  8630. else
  8631. options.keys.all? do |k|
  8632. if k == :controller then options[k] == ActionController::Routing.controller_relative_to(@response.redirected_to[k], @controller.class.controller_path)
  8633. 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?)
  8634. end
  8635. end
  8636. end
  8637. end
  8638. end
  8639. end
  8640. end
  8641. # Asserts that the request was rendered with the appropriate template file.
  8642. def assert_template(expected = nil, message=nil)
  8643. clean_backtrace do
  8644. rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file
  8645. msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
  8646. assert_block(msg) do
  8647. if expected.nil?
  8648. !@response.rendered_with_file?
  8649. else
  8650. expected == rendered
  8651. end
  8652. end
  8653. end
  8654. end
  8655. # Asserts that the routing of the given path was handled correctly and that the parsed options match.
  8656. def assert_recognizes(expected_options, path, extras={}, message=nil)
  8657. clean_backtrace do
  8658. path = "/#{path}" unless path[0..0] == '/'
  8659. # Load routes.rb if it hasn't been loaded.
  8660. ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
  8661. # Assume given controller
  8662. request = ActionController::TestRequest.new({}, {}, nil)
  8663. request.path = path
  8664. ActionController::Routing::Routes.recognize!(request)
  8665. expected_options = expected_options.clone
  8666. extras.each_key { |key| expected_options.delete key } unless extras.nil?
  8667. expected_options.stringify_keys!
  8668. msg = build_message(message, "The recognized options <?> did not match <?>",
  8669. request.path_parameters, expected_options)
  8670. assert_block(msg) { request.path_parameters == expected_options }
  8671. end
  8672. end
  8673. # Asserts that the provided options can be used to generate the provided path.
  8674. def assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)
  8675. clean_backtrace do
  8676. expected_path = "/#{expected_path}" unless expected_path[0] == ?/
  8677. # Load routes.rb if it hasn't been loaded.
  8678. ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
  8679. generated_path, extra_keys = ActionController::Routing::Routes.generate(options, extras)
  8680. found_extras = options.reject {|k, v| ! extra_keys.include? k}
  8681. msg = build_message(message, "found extras <?>, not <?>", found_extras, extras)
  8682. assert_block(msg) { found_extras == extras }
  8683. msg = build_message(message, "The generated path <?> did not match <?>", generated_path,
  8684. expected_path)
  8685. assert_block(msg) { expected_path == generated_path }
  8686. end
  8687. end
  8688. # Asserts that path and options match both ways; in other words, the URL generated from
  8689. # options is the same as path, and also that the options recognized from path are the same as options
  8690. def assert_routing(path, options, defaults={}, extras={}, message=nil)
  8691. assert_recognizes(options, path, extras, message)
  8692. controller, default_controller = options[:controller], defaults[:controller]
  8693. if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
  8694. options[:controller] = "/#{controller}"
  8695. end
  8696. assert_generates(path, options, defaults, extras, message)
  8697. end
  8698. # Asserts that there is a tag/node/element in the body of the response
  8699. # that meets all of the given conditions. The +conditions+ parameter must
  8700. # be a hash of any of the following keys (all are optional):
  8701. #
  8702. # * <tt>:tag</tt>: the node type must match the corresponding value
  8703. # * <tt>:attributes</tt>: a hash. The node's attributes must match the
  8704. # corresponding values in the hash.
  8705. # * <tt>:parent</tt>: a hash. The node's parent must match the
  8706. # corresponding hash.
  8707. # * <tt>:child</tt>: a hash. At least one of the node's immediate children
  8708. # must meet the criteria described by the hash.
  8709. # * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
  8710. # meet the criteria described by the hash.
  8711. # * <tt>:descendant</tt>: a hash. At least one of the node's descendants
  8712. # must meet the criteria described by the hash.
  8713. # * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
  8714. # meet the criteria described by the hash.
  8715. # * <tt>:after</tt>: a hash. The node must be after any sibling meeting
  8716. # the criteria described by the hash, and at least one sibling must match.
  8717. # * <tt>:before</tt>: a hash. The node must be before any sibling meeting
  8718. # the criteria described by the hash, and at least one sibling must match.
  8719. # * <tt>:children</tt>: a hash, for counting children of a node. Accepts
  8720. # the keys:
  8721. # * <tt>:count</tt>: either a number or a range which must equal (or
  8722. # include) the number of children that match.
  8723. # * <tt>:less_than</tt>: the number of matching children must be less
  8724. # than this number.
  8725. # * <tt>:greater_than</tt>: the number of matching children must be
  8726. # greater than this number.
  8727. # * <tt>:only</tt>: another hash consisting of the keys to use
  8728. # to match on the children, and only matching children will be
  8729. # counted.
  8730. # * <tt>:content</tt>: the textual content of the node must match the
  8731. # given value. This will not match HTML tags in the body of a
  8732. # tag--only text.
  8733. #
  8734. # Conditions are matched using the following algorithm:
  8735. #
  8736. # * if the condition is a string, it must be a substring of the value.
  8737. # * if the condition is a regexp, it must match the value.
  8738. # * if the condition is a number, the value must match number.to_s.
  8739. # * if the condition is +true+, the value must not be +nil+.
  8740. # * if the condition is +false+ or +nil+, the value must be +nil+.
  8741. #
  8742. # Usage:
  8743. #
  8744. # # assert that there is a "span" tag
  8745. # assert_tag :tag => "span"
  8746. #
  8747. # # assert that there is a "span" tag with id="x"
  8748. # assert_tag :tag => "span", :attributes => { :id => "x" }
  8749. #
  8750. # # assert that there is a "span" tag using the short-hand
  8751. # assert_tag :span
  8752. #
  8753. # # assert that there is a "span" tag with id="x" using the short-hand
  8754. # assert_tag :span, :attributes => { :id => "x" }
  8755. #
  8756. # # assert that there is a "span" inside of a "div"
  8757. # assert_tag :tag => "span", :parent => { :tag => "div" }
  8758. #
  8759. # # assert that there is a "span" somewhere inside a table
  8760. # assert_tag :tag => "span", :ancestor => { :tag => "table" }
  8761. #
  8762. # # assert that there is a "span" with at least one "em" child
  8763. # assert_tag :tag => "span", :child => { :tag => "em" }
  8764. #
  8765. # # assert that there is a "span" containing a (possibly nested)
  8766. # # "strong" tag.
  8767. # assert_tag :tag => "span", :descendant => { :tag => "strong" }
  8768. #
  8769. # # assert that there is a "span" containing between 2 and 4 "em" tags
  8770. # # as immediate children
  8771. # assert_tag :tag => "span",
  8772. # :children => { :count => 2..4, :only => { :tag => "em" } }
  8773. #
  8774. # # get funky: assert that there is a "div", with an "ul" ancestor
  8775. # # and an "li" parent (with "class" = "enum"), and containing a
  8776. # # "span" descendant that contains text matching /hello world/
  8777. # assert_tag :tag => "div",
  8778. # :ancestor => { :tag => "ul" },
  8779. # :parent => { :tag => "li",
  8780. # :attributes => { :class => "enum" } },
  8781. # :descendant => { :tag => "span",
  8782. # :child => /hello world/ }
  8783. #
  8784. # <strong>Please note</strong: #assert_tag and #assert_no_tag only work
  8785. # with well-formed XHTML. They recognize a few tags as implicitly self-closing
  8786. # (like br and hr and such) but will not work correctly with tags
  8787. # that allow optional closing tags (p, li, td). <em>You must explicitly
  8788. # close all of your tags to use these assertions.</em>
  8789. def assert_tag(*opts)
  8790. clean_backtrace do
  8791. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  8792. tag = find_tag(opts)
  8793. assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
  8794. end
  8795. end
  8796. # Identical to #assert_tag, but asserts that a matching tag does _not_
  8797. # exist. (See #assert_tag for a full discussion of the syntax.)
  8798. def assert_no_tag(*opts)
  8799. clean_backtrace do
  8800. opts = opts.size > 1 ? opts.last.merge({ :tag => opts.first.to_s }) : opts.first
  8801. tag = find_tag(opts)
  8802. assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
  8803. end
  8804. end
  8805. # test 2 html strings to be equivalent, i.e. identical up to reordering of attributes
  8806. def assert_dom_equal(expected, actual, message="")
  8807. clean_backtrace do
  8808. expected_dom = HTML::Document.new(expected).root
  8809. actual_dom = HTML::Document.new(actual).root
  8810. full_message = build_message(message, "<?> expected to be == to\n<?>.", expected_dom.to_s, actual_dom.to_s)
  8811. assert_block(full_message) { expected_dom == actual_dom }
  8812. end
  8813. end
  8814. # negated form of +assert_dom_equivalent+
  8815. def assert_dom_not_equal(expected, actual, message="")
  8816. clean_backtrace do
  8817. expected_dom = HTML::Document.new(expected).root
  8818. actual_dom = HTML::Document.new(actual).root
  8819. full_message = build_message(message, "<?> expected to be != to\n<?>.", expected_dom.to_s, actual_dom.to_s)
  8820. assert_block(full_message) { expected_dom != actual_dom }
  8821. end
  8822. end
  8823. # ensures that the passed record is valid by active record standards. returns the error messages if not
  8824. def assert_valid(record)
  8825. clean_backtrace do
  8826. assert record.valid?, record.errors.full_messages.join("\n")
  8827. end
  8828. end
  8829. def clean_backtrace(&block)
  8830. yield
  8831. rescue AssertionFailedError => e
  8832. path = File.expand_path(__FILE__)
  8833. raise AssertionFailedError, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
  8834. end
  8835. end
  8836. end
  8837. end
  8838. require 'action_controller/mime_type'
  8839. require 'action_controller/request'
  8840. require 'action_controller/response'
  8841. require 'action_controller/routing'
  8842. require 'action_controller/code_generation'
  8843. require 'action_controller/url_rewriter'
  8844. require 'drb'
  8845. require 'set'
  8846. module ActionController #:nodoc:
  8847. class ActionControllerError < StandardError #:nodoc:
  8848. end
  8849. class SessionRestoreError < ActionControllerError #:nodoc:
  8850. end
  8851. class MissingTemplate < ActionControllerError #:nodoc:
  8852. end
  8853. class RoutingError < ActionControllerError #:nodoc:
  8854. attr_reader :failures
  8855. def initialize(message, failures=[])
  8856. super(message)
  8857. @failures = failures
  8858. end
  8859. end
  8860. class UnknownController < ActionControllerError #:nodoc:
  8861. end
  8862. class UnknownAction < ActionControllerError #:nodoc:
  8863. end
  8864. class MissingFile < ActionControllerError #:nodoc:
  8865. end
  8866. class SessionOverflowError < ActionControllerError #:nodoc:
  8867. DEFAULT_MESSAGE = 'Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.'
  8868. def initialize(message = nil)
  8869. super(message || DEFAULT_MESSAGE)
  8870. end
  8871. end
  8872. class DoubleRenderError < ActionControllerError #:nodoc:
  8873. DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and only once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\". Finally, note that to cause a before filter to halt execution of the rest of the filter chain, the filter must return false, explicitly, so \"render(...) and return false\"."
  8874. def initialize(message = nil)
  8875. super(message || DEFAULT_MESSAGE)
  8876. end
  8877. end
  8878. class RedirectBackError < ActionControllerError #:nodoc:
  8879. DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify @request.env["HTTP_REFERER"].'
  8880. def initialize(message = nil)
  8881. super(message || DEFAULT_MESSAGE)
  8882. end
  8883. end
  8884. # Action Controllers are the core of a web request in Rails. They are made up of one or more actions that are executed
  8885. # on request and then either render a template or redirect to another action. An action is defined as a public method
  8886. # on the controller, which will automatically be made accessible to the web-server through Rails Routes.
  8887. #
  8888. # A sample controller could look like this:
  8889. #
  8890. # class GuestBookController < ActionController::Base
  8891. # def index
  8892. # @entries = Entry.find(:all)
  8893. # end
  8894. #
  8895. # def sign
  8896. # Entry.create(params[:entry])
  8897. # redirect_to :action => "index"
  8898. # end
  8899. # end
  8900. #
  8901. # Actions, by default, render a template in the <tt>app/views</tt> directory corresponding to the name of the controller and action
  8902. # after executing code in the action. For example, the +index+ action of the +GuestBookController+ would render the
  8903. # template <tt>app/views/guestbook/index.rhtml</tt> by default after populating the <tt>@entries</tt> instance variable.
  8904. #
  8905. # Unlike index, the sign action will not render a template. After performing its main purpose (creating a
  8906. # new entry in the guest book), it initiates a redirect instead. This redirect works by returning an external
  8907. # "302 Moved" HTTP response that takes the user to the index action.
  8908. #
  8909. # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
  8910. # Most actions are variations of these themes.
  8911. #
  8912. # == Requests
  8913. #
  8914. # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
  8915. # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
  8916. # request parameters, the session (if one is available), and the full request with all the http headers are made available to
  8917. # the action through instance variables. Then the action is performed.
  8918. #
  8919. # The full request object is available with the request accessor and is primarily used to query for http headers. These queries
  8920. # are made by accessing the environment hash, like this:
  8921. #
  8922. # def server_ip
  8923. # location = request.env["SERVER_ADDR"]
  8924. # render :text => "This server hosted at #{location}"
  8925. # end
  8926. #
  8927. # == Parameters
  8928. #
  8929. # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
  8930. # which returns a hash. For example, an action that was performed through <tt>/weblog/list?category=All&limit=5</tt> will include
  8931. # <tt>{ "category" => "All", "limit" => 5 }</tt> in params.
  8932. #
  8933. # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
  8934. #
  8935. # <input type="text" name="post[name]" value="david">
  8936. # <input type="text" name="post[address]" value="hyacintvej">
  8937. #
  8938. # A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
  8939. # If the address input had been named "post[address][street]", the params would have included
  8940. # <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
  8941. #
  8942. # == Sessions
  8943. #
  8944. # Sessions allows you to store objects in between requests. This is useful for objects that are not yet ready to be persisted,
  8945. # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
  8946. # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
  8947. # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
  8948. #
  8949. # You can place objects in the session by using the <tt>session</tt> method, which accesses a hash:
  8950. #
  8951. # session[:person] = Person.authenticate(user_name, password)
  8952. #
  8953. # And retrieved again through the same hash:
  8954. #
  8955. # Hello #{session[:person]}
  8956. #
  8957. # For removing objects from the session, you can either assign a single key to nil, like <tt>session[:person] = nil</tt>, or you can
  8958. # remove the entire session with reset_session.
  8959. #
  8960. # By default, sessions are stored on the file system in <tt>RAILS_ROOT/tmp/sessions</tt>. Any object can be placed in the session
  8961. # (as long as it can be Marshalled). But remember that 1000 active sessions each storing a 50kb object could lead to a 50MB store on the filesystem.
  8962. # In other words, think carefully about size and caching before resorting to the use of the session on the filesystem.
  8963. #
  8964. # An alternative to storing sessions on disk is to use ActiveRecordStore to store sessions in your database, which can solve problems
  8965. # caused by storing sessions in the file system and may speed up your application. To use ActiveRecordStore, uncomment the line:
  8966. #
  8967. # config.action_controller.session_store = :active_record_store
  8968. #
  8969. # in your <tt>environment.rb</tt> and run <tt>rake db:sessions:create</tt>.
  8970. #
  8971. # == Responses
  8972. #
  8973. # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
  8974. # object is generated automatically through the use of renders and redirects and requires no user intervention.
  8975. #
  8976. # == Renders
  8977. #
  8978. # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
  8979. # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
  8980. # The controller passes objects to the view by assigning instance variables:
  8981. #
  8982. # def show
  8983. # @post = Post.find(params[:id])
  8984. # end
  8985. #
  8986. # Which are then automatically available to the view:
  8987. #
  8988. # Title: <%= @post.title %>
  8989. #
  8990. # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
  8991. # the manual rendering methods:
  8992. #
  8993. # def search
  8994. # @results = Search.find(params[:query])
  8995. # case @results
  8996. # when 0 then render :action => "no_results"
  8997. # when 1 then render :action => "show"
  8998. # when 2..10 then render :action => "show_many"
  8999. # end
  9000. # end
  9001. #
  9002. # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
  9003. #
  9004. # == Redirects
  9005. #
  9006. # Redirects are used to move from one action to another. For example, after a <tt>create</tt> action, which stores a blog entry to a database,
  9007. # we might like to show the user the new entry. Because we're following good DRY principles (Don't Repeat Yourself), we're going to reuse (and redirect to)
  9008. # a <tt>show</tt> action that we'll assume has already been created. The code might look like this:
  9009. #
  9010. # def create
  9011. # @entry = Entry.new(params[:entry])
  9012. # if @entry.save
  9013. # # The entry was saved correctly, redirect to show
  9014. # redirect_to :action => 'show', :id => @entry.id
  9015. # else
  9016. # # things didn't go so well, do something else
  9017. # end
  9018. # end
  9019. #
  9020. # In this case, after saving our new entry to the database, the user is redirected to the <tt>show</tt> method which is then executed.
  9021. #
  9022. # == Calling multiple redirects or renders
  9023. #
  9024. # An action should conclude with a single render or redirect. Attempting to try to do either again will result in a DoubleRenderError:
  9025. #
  9026. # def do_something
  9027. # redirect_to :action => "elsewhere"
  9028. # render :action => "overthere" # raises DoubleRenderError
  9029. # end
  9030. #
  9031. # If you need to redirect on the condition of something, then be sure to add "and return" to halt execution.
  9032. #
  9033. # def do_something
  9034. # redirect_to(:action => "elsewhere") and return if monkeys.nil?
  9035. # render :action => "overthere" # won't be called unless monkeys is nil
  9036. # end
  9037. #
  9038. class Base
  9039. DEFAULT_RENDER_STATUS_CODE = "200 OK"
  9040. include Reloadable::Subclasses
  9041. # Determines whether the view has access to controller internals @request, @response, @session, and @template.
  9042. # By default, it does.
  9043. @@view_controller_internals = true
  9044. cattr_accessor :view_controller_internals
  9045. # Protected instance variable cache
  9046. @@protected_variables_cache = nil
  9047. cattr_accessor :protected_variables_cache
  9048. # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets,
  9049. # and images to a dedicated asset server away from the main web server. Example:
  9050. # ActionController::Base.asset_host = "http://assets.example.com"
  9051. @@asset_host = ""
  9052. cattr_accessor :asset_host
  9053. # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
  9054. # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
  9055. # should instead be implemented in the controller to determine when debugging screens should be shown.
  9056. @@consider_all_requests_local = true
  9057. cattr_accessor :consider_all_requests_local
  9058. # Enable or disable the collection of failure information for RoutingErrors.
  9059. # This information can be extremely useful when tweaking custom routes, but is
  9060. # pointless once routes have been tested and verified.
  9061. @@debug_routes = true
  9062. cattr_accessor :debug_routes
  9063. # Controls whether the application is thread-safe, so multi-threaded servers like WEBrick know whether to apply a mutex
  9064. # around the performance of each action. Action Pack and Active Record are by default thread-safe, but many applications
  9065. # may not be. Turned off by default.
  9066. @@allow_concurrency = false
  9067. cattr_accessor :allow_concurrency
  9068. # Modern REST web services often need to submit complex data to the web application.
  9069. # The param_parsers hash lets you register handlers wich will process the http body and add parameters to the
  9070. # <tt>params</tt> hash. These handlers are invoked for post and put requests.
  9071. #
  9072. # By default application/xml is enabled. A XmlSimple class with the same param name as the root will be instanciated
  9073. # in the <tt>params</tt>. This allows XML requests to mask themselves as regular form submissions, so you can have one
  9074. # action serve both regular forms and web service requests.
  9075. #
  9076. # Example of doing your own parser for a custom content type:
  9077. #
  9078. # ActionController::Base.param_parsers[Mime::Type.lookup('application/atom+xml')] = Proc.new do |data|
  9079. # node = REXML::Document.new(post)
  9080. # { node.root.name => node.root }
  9081. # end
  9082. #
  9083. # Note: Up until release 1.1 of Rails, Action Controller would default to using XmlSimple configured to discard the
  9084. # root node for such requests. The new default is to keep the root, such that "<r><name>David</name></r>" results
  9085. # in params[:r][:name] for "David" instead of params[:name]. To get the old behavior, you can
  9086. # re-register XmlSimple as application/xml handler ike this:
  9087. #
  9088. # ActionController::Base.param_parsers[Mime::XML] =
  9089. # Proc.new { |data| XmlSimple.xml_in(data, 'ForceArray' => false) }
  9090. #
  9091. # A YAML parser is also available and can be turned on with:
  9092. #
  9093. # ActionController::Base.param_parsers[Mime::YAML] = :yaml
  9094. @@param_parsers = { Mime::XML => :xml_simple }
  9095. cattr_accessor :param_parsers
  9096. # Template root determines the base from which template references will be made. So a call to render("test/template")
  9097. # will be converted to "#{template_root}/test/template.rhtml".
  9098. class_inheritable_accessor :template_root
  9099. # The logger is used for generating information on the action run-time (including benchmarking) if available.
  9100. # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
  9101. cattr_accessor :logger
  9102. # Determines which template class should be used by ActionController.
  9103. cattr_accessor :template_class
  9104. # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
  9105. cattr_accessor :ignore_missing_templates
  9106. # Holds the request object that's primarily used to get environment variables through access like
  9107. # <tt>request.env["REQUEST_URI"]</tt>.
  9108. attr_accessor :request
  9109. # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>params["post_id"]</tt>
  9110. # to get the post_id. No type casts are made, so all values are returned as strings.
  9111. attr_accessor :params
  9112. # Holds the response object that's primarily used to set additional HTTP headers through access like
  9113. # <tt>response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
  9114. # has been rendered through response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
  9115. # such as a OutputCompressionFilter.
  9116. attr_accessor :response
  9117. # Holds a hash of objects in the session. Accessed like <tt>session[:person]</tt> to get the object tied to the "person"
  9118. # key. The session will hold any type of object as values, but the key should be a string or symbol.
  9119. attr_accessor :session
  9120. # Holds a hash of header names and values. Accessed like <tt>headers["Cache-Control"]</tt> to get the value of the Cache-Control
  9121. # directive. Values should always be specified as strings.
  9122. attr_accessor :headers
  9123. # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
  9124. # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
  9125. attr_accessor :assigns
  9126. # Returns the name of the action this controller is processing.
  9127. attr_accessor :action_name
  9128. class << self
  9129. # Factory for the standard create, process loop where the controller is discarded after processing.
  9130. def process(request, response) #:nodoc:
  9131. new.process(request, response)
  9132. end
  9133. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
  9134. def controller_class_name
  9135. @controller_class_name ||= name.demodulize
  9136. end
  9137. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
  9138. def controller_name
  9139. @controller_name ||= controller_class_name.sub(/Controller$/, '').underscore
  9140. end
  9141. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".
  9142. def controller_path
  9143. @controller_path ||= name.gsub(/Controller$/, '').underscore
  9144. end
  9145. # Return an array containing the names of public methods that have been marked hidden from the action processor.
  9146. # By default, all methods defined in ActionController::Base and included modules are hidden.
  9147. # More methods can be hidden using <tt>hide_actions</tt>.
  9148. def hidden_actions
  9149. write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods) unless read_inheritable_attribute(:hidden_actions)
  9150. read_inheritable_attribute(:hidden_actions)
  9151. end
  9152. # Hide each of the given methods from being callable as actions.
  9153. def hide_action(*names)
  9154. write_inheritable_attribute(:hidden_actions, hidden_actions | names.collect { |n| n.to_s })
  9155. end
  9156. # Replace sensitive paramater data from the request log.
  9157. # Filters paramaters that have any of the arguments as a substring.
  9158. # Looks in all subhashes of the param hash for keys to filter.
  9159. # If a block is given, each key and value of the paramater hash and all
  9160. # subhashes is passed to it, the value or key
  9161. # can be replaced using String#replace or similar method.
  9162. #
  9163. # Examples:
  9164. # filter_parameter_logging
  9165. # => Does nothing, just slows the logging process down
  9166. #
  9167. # filter_parameter_logging :password
  9168. # => replaces the value to all keys matching /password/i with "[FILTERED]"
  9169. #
  9170. # filter_parameter_logging :foo, "bar"
  9171. # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
  9172. #
  9173. # filter_parameter_logging { |k,v| v.reverse! if k =~ /secret/i }
  9174. # => reverses the value to all keys matching /secret/i
  9175. #
  9176. # filter_parameter_logging(:foo, "bar") { |k,v| v.reverse! if k =~ /secret/i }
  9177. # => reverses the value to all keys matching /secret/i, and
  9178. # replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
  9179. def filter_parameter_logging(*filter_words, &block)
  9180. parameter_filter = Regexp.new(filter_words.collect{ |s| s.to_s }.join('|'), true) if filter_words.length > 0
  9181. define_method(:filter_parameters) do |unfiltered_parameters|
  9182. filtered_parameters = {}
  9183. unfiltered_parameters.each do |key, value|
  9184. if key =~ parameter_filter
  9185. filtered_parameters[key] = '[FILTERED]'
  9186. elsif value.is_a?(Hash)
  9187. filtered_parameters[key] = filter_parameters(value)
  9188. elsif block_given?
  9189. key, value = key.dup, value.dup
  9190. yield key, value
  9191. filtered_parameters[key] = value
  9192. else
  9193. filtered_parameters[key] = value
  9194. end
  9195. end
  9196. filtered_parameters
  9197. end
  9198. end
  9199. end
  9200. public
  9201. # Extracts the action_name from the request parameters and performs that action.
  9202. def process(request, response, method = :perform_action, *arguments) #:nodoc:
  9203. initialize_template_class(response)
  9204. assign_shortcuts(request, response)
  9205. initialize_current_url
  9206. assign_names
  9207. forget_variables_added_to_assigns
  9208. log_processing
  9209. send(method, *arguments)
  9210. response
  9211. ensure
  9212. process_cleanup
  9213. end
  9214. # Returns a URL that has been rewritten according to the options hash and the defined Routes.
  9215. # (For doing a complete redirect, use redirect_to).
  9216. #  
  9217. # <tt>url_for</tt> is used to:
  9218. #  
  9219. # All keys given to url_for are forwarded to the Route module, save for the following:
  9220. # * <tt>:anchor</tt> -- specifies the anchor name to be appended to the path. For example,
  9221. # <tt>url_for :controller => 'posts', :action => 'show', :id => 10, :anchor => 'comments'</tt>
  9222. # will produce "/posts/show/10#comments".
  9223. # * <tt>:only_path</tt> -- if true, returns the absolute URL (omitting the protocol, host name, and port)
  9224. # * <tt>:trailing_slash</tt> -- if true, adds a trailing slash, as in "/archive/2005/". Note that this
  9225. # is currently not recommended since it breaks caching.
  9226. # * <tt>:host</tt> -- overrides the default (current) host if provided
  9227. # * <tt>:protocol</tt> -- overrides the default (current) protocol if provided
  9228. #
  9229. # The URL is generated from the remaining keys in the hash. A URL contains two key parts: the <base> and a query string.
  9230. # Routes composes a query string as the key/value pairs not included in the <base>.
  9231. #
  9232. # The default Routes setup supports a typical Rails path of "controller/action/id" where action and id are optional, with
  9233. # action defaulting to 'index' when not given. Here are some typical url_for statements and their corresponding URLs:
  9234. #  
  9235. # url_for :controller => 'posts', :action => 'recent' # => 'proto://host.com/posts/recent'
  9236. # url_for :controller => 'posts', :action => 'index' # => 'proto://host.com/posts'
  9237. # url_for :controller => 'posts', :action => 'show', :id => 10 # => 'proto://host.com/posts/show/10'
  9238. #
  9239. # When generating a new URL, missing values may be filled in from the current request's parameters. For example,
  9240. # <tt>url_for :action => 'some_action'</tt> will retain the current controller, as expected. This behavior extends to
  9241. # other parameters, including <tt>:controller</tt>, <tt>:id</tt>, and any other parameters that are placed into a Route's
  9242. # path.
  9243. #  
  9244. # The URL helpers such as <tt>url_for</tt> have a limited form of memory: when generating a new URL, they can look for
  9245. # missing values in the current request's parameters. Routes attempts to guess when a value should and should not be
  9246. # taken from the defaults. There are a few simple rules on how this is performed:
  9247. #
  9248. # * If the controller name begins with a slash, no defaults are used: <tt>url_for :controller => '/home'</tt>
  9249. # * If the controller changes, the action will default to index unless provided
  9250. #
  9251. # The final rule is applied while the URL is being generated and is best illustrated by an example. Let us consider the
  9252. # route given by <tt>map.connect 'people/:last/:first/:action', :action => 'bio', :controller => 'people'</tt>.
  9253. #
  9254. # Suppose that the current URL is "people/hh/david/contacts". Let's consider a few different cases of URLs which are generated
  9255. # from this page.
  9256. #
  9257. # * <tt>url_for :action => 'bio'</tt> -- During the generation of this URL, default values will be used for the first and
  9258. # last components, and the action shall change. The generated URL will be, "people/hh/david/bio".
  9259. # * <tt>url_for :first => 'davids-little-brother'</tt> This generates the URL 'people/hh/davids-little-brother' -- note
  9260. # that this URL leaves out the assumed action of 'bio'.
  9261. #
  9262. # However, you might ask why the action from the current request, 'contacts', isn't carried over into the new URL. The
  9263. # answer has to do with the order in which the parameters appear in the generated path. In a nutshell, since the
  9264. # value that appears in the slot for <tt>:first</tt> is not equal to default value for <tt>:first</tt> we stop using
  9265. # defaults. On it's own, this rule can account for much of the typical Rails URL behavior.
  9266. #  
  9267. # Although a convienence, defaults can occasionaly get in your way. In some cases a default persists longer than desired.
  9268. # The default may be cleared by adding <tt>:name => nil</tt> to <tt>url_for</tt>'s options.
  9269. # This is often required when writing form helpers, since the defaults in play may vary greatly depending upon where the
  9270. # helper is used from. The following line will redirect to PostController's default action, regardless of the page it is
  9271. # displayed on:
  9272. #
  9273. # url_for :controller => 'posts', :action => nil
  9274. #
  9275. # If you explicitly want to create a URL that's almost the same as the current URL, you can do so using the
  9276. # :overwrite_params options. Say for your posts you have different views for showing and printing them.
  9277. # Then, in the show view, you get the URL for the print view like this
  9278. #
  9279. # url_for :overwrite_params => { :action => 'print' }
  9280. #
  9281. # This takes the current URL as is and only exchanges the action. In contrast, <tt>url_for :action => 'print'</tt>
  9282. # would have slashed-off the path components after the changed action.
  9283. def url_for(options = {}, *parameters_for_method_reference) #:doc:
  9284. case options
  9285. when String then options
  9286. when Symbol then send(options, *parameters_for_method_reference)
  9287. when Hash then @url.rewrite(rewrite_options(options))
  9288. end
  9289. end
  9290. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
  9291. def controller_class_name
  9292. self.class.controller_class_name
  9293. end
  9294. # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
  9295. def controller_name
  9296. self.class.controller_name
  9297. end
  9298. def session_enabled?
  9299. request.session_options[:disabled] != false
  9300. end
  9301. protected
  9302. # Renders the content that will be returned to the browser as the response body.
  9303. #
  9304. # === Rendering an action
  9305. #
  9306. # Action rendering is the most common form and the type used automatically by Action Controller when nothing else is
  9307. # specified. By default, actions are rendered within the current layout (if one exists).
  9308. #
  9309. # # Renders the template for the action "goal" within the current controller
  9310. # render :action => "goal"
  9311. #
  9312. # # Renders the template for the action "short_goal" within the current controller,
  9313. # # but without the current active layout
  9314. # render :action => "short_goal", :layout => false
  9315. #
  9316. # # Renders the template for the action "long_goal" within the current controller,
  9317. # # but with a custom layout
  9318. # render :action => "long_goal", :layout => "spectacular"
  9319. #
  9320. # _Deprecation_ _notice_: This used to have the signatures <tt>render_action("action", status = 200)</tt>,
  9321. # <tt>render_without_layout("controller/action", status = 200)</tt>, and
  9322. # <tt>render_with_layout("controller/action", status = 200, layout)</tt>.
  9323. #
  9324. # === Rendering partials
  9325. #
  9326. # Partial rendering is most commonly used together with Ajax calls that only update one or a few elements on a page
  9327. # without reloading. Rendering of partials from the controller makes it possible to use the same partial template in
  9328. # both the full-page rendering (by calling it from within the template) and when sub-page updates happen (from the
  9329. # controller action responding to Ajax calls). By default, the current layout is not used.
  9330. #
  9331. # # Renders the partial located at app/views/controller/_win.r(html|xml)
  9332. # render :partial => "win"
  9333. #
  9334. # # Renders the partial with a status code of 500 (internal error)
  9335. # render :partial => "broken", :status => 500
  9336. #
  9337. # # Renders the same partial but also makes a local variable available to it
  9338. # render :partial => "win", :locals => { :name => "david" }
  9339. #
  9340. # # Renders a collection of the same partial by making each element of @wins available through
  9341. # # the local variable "win" as it builds the complete response
  9342. # render :partial => "win", :collection => @wins
  9343. #
  9344. # # Renders the same collection of partials, but also renders the win_divider partial in between
  9345. # # each win partial.
  9346. # render :partial => "win", :collection => @wins, :spacer_template => "win_divider"
  9347. #
  9348. # _Deprecation_ _notice_: This used to have the signatures
  9349. # <tt>render_partial(partial_path = default_template_name, object = nil, local_assigns = {})</tt> and
  9350. # <tt>render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})</tt>.
  9351. #
  9352. # === Rendering a template
  9353. #
  9354. # Template rendering works just like action rendering except that it takes a path relative to the template root.
  9355. # The current layout is automatically applied.
  9356. #
  9357. # # Renders the template located in [TEMPLATE_ROOT]/weblog/show.r(html|xml) (in Rails, app/views/weblog/show.rhtml)
  9358. # render :template => "weblog/show"
  9359. #
  9360. # === Rendering a file
  9361. #
  9362. # File rendering works just like action rendering except that it takes a filesystem path. By default, the path
  9363. # is assumed to be absolute, and the current layout is not applied.
  9364. #
  9365. # # Renders the template located at the absolute filesystem path
  9366. # render :file => "/path/to/some/template.rhtml"
  9367. # render :file => "c:/path/to/some/template.rhtml"
  9368. #
  9369. # # Renders a template within the current layout, and with a 404 status code
  9370. # render :file => "/path/to/some/template.rhtml", :layout => true, :status => 404
  9371. # render :file => "c:/path/to/some/template.rhtml", :layout => true, :status => 404
  9372. #
  9373. # # Renders a template relative to the template root and chooses the proper file extension
  9374. # render :file => "some/template", :use_full_path => true
  9375. #
  9376. # _Deprecation_ _notice_: This used to have the signature <tt>render_file(path, status = 200)</tt>
  9377. #
  9378. # === Rendering text
  9379. #
  9380. # Rendering of text is usually used for tests or for rendering prepared content, such as a cache. By default, text
  9381. # rendering is not done within the active layout.
  9382. #
  9383. # # Renders the clear text "hello world" with status code 200
  9384. # render :text => "hello world!"
  9385. #
  9386. # # Renders the clear text "Explosion!" with status code 500
  9387. # render :text => "Explosion!", :status => 500
  9388. #
  9389. # # Renders the clear text "Hi there!" within the current active layout (if one exists)
  9390. # render :text => "Explosion!", :layout => true
  9391. #
  9392. # # Renders the clear text "Hi there!" within the layout
  9393. # # placed in "app/views/layouts/special.r(html|xml)"
  9394. # render :text => "Explosion!", :layout => "special"
  9395. #
  9396. # _Deprecation_ _notice_: This used to have the signature <tt>render_text("text", status = 200)</tt>
  9397. #
  9398. # === Rendering an inline template
  9399. #
  9400. # Rendering of an inline template works as a cross between text and action rendering where the source for the template
  9401. # is supplied inline, like text, but its interpreted with ERb or Builder, like action. By default, ERb is used for rendering
  9402. # and the current layout is not used.
  9403. #
  9404. # # Renders "hello, hello, hello, again"
  9405. # render :inline => "<%= 'hello, ' * 3 + 'again' %>"
  9406. #
  9407. # # Renders "<p>Good seeing you!</p>" using Builder
  9408. # render :inline => "xml.p { 'Good seeing you!' }", :type => :rxml
  9409. #
  9410. # # Renders "hello david"
  9411. # render :inline => "<%= 'hello ' + name %>", :locals => { :name => "david" }
  9412. #
  9413. # _Deprecation_ _notice_: This used to have the signature <tt>render_template(template, status = 200, type = :rhtml)</tt>
  9414. #
  9415. # === Rendering inline JavaScriptGenerator page updates
  9416. #
  9417. # In addition to rendering JavaScriptGenerator page updates with Ajax in RJS templates (see ActionView::Base for details),
  9418. # you can also pass the <tt>:update</tt> parameter to +render+, along with a block, to render page updates inline.
  9419. #
  9420. # render :update do |page|
  9421. # page.replace_html 'user_list', :partial => 'user', :collection => @users
  9422. # page.visual_effect :highlight, 'user_list'
  9423. # end
  9424. #
  9425. # === Rendering nothing
  9426. #
  9427. # Rendering nothing is often convenient in combination with Ajax calls that perform their effect client-side or
  9428. # when you just want to communicate a status code. Due to a bug in Safari, nothing actually means a single space.
  9429. #
  9430. # # Renders an empty response with status code 200
  9431. # render :nothing => true
  9432. #
  9433. # # Renders an empty response with status code 401 (access denied)
  9434. # render :nothing => true, :status => 401
  9435. def render(options = nil, deprecated_status = nil, &block) #:doc:
  9436. raise DoubleRenderError, "Can only render or redirect once per action" if performed?
  9437. # Backwards compatibility
  9438. unless options.is_a?(Hash)
  9439. if options == :update
  9440. options = {:update => true}
  9441. else
  9442. return render_file(options || default_template_name, deprecated_status, true)
  9443. end
  9444. end
  9445. if content_type = options[:content_type]
  9446. headers["Content-Type"] = content_type
  9447. end
  9448. if text = options[:text]
  9449. render_text(text, options[:status])
  9450. else
  9451. if file = options[:file]
  9452. render_file(file, options[:status], options[:use_full_path], options[:locals] || {})
  9453. elsif template = options[:template]
  9454. render_file(template, options[:status], true)
  9455. elsif inline = options[:inline]
  9456. render_template(inline, options[:status], options[:type], options[:locals] || {})
  9457. elsif action_name = options[:action]
  9458. render_action(action_name, options[:status], options[:layout])
  9459. elsif xml = options[:xml]
  9460. render_xml(xml, options[:status])
  9461. elsif partial = options[:partial]
  9462. partial = default_template_name if partial == true
  9463. if collection = options[:collection]
  9464. render_partial_collection(partial, collection, options[:spacer_template], options[:locals], options[:status])
  9465. else
  9466. render_partial(partial, ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals], options[:status])
  9467. end
  9468. elsif options[:update]
  9469. add_variables_to_assigns
  9470. @template.send :evaluate_assigns
  9471. generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(@template, &block)
  9472. render_javascript(generator.to_s)
  9473. elsif options[:nothing]
  9474. # Safari doesn't pass the headers of the return if the response is zero length
  9475. render_text(" ", options[:status])
  9476. else
  9477. render_file(default_template_name, options[:status], true)
  9478. end
  9479. end
  9480. end
  9481. # Renders according to the same rules as <tt>render</tt>, but returns the result in a string instead
  9482. # of sending it as the response body to the browser.
  9483. def render_to_string(options = nil, &block) #:doc:
  9484. result = render(options, &block)
  9485. erase_render_results
  9486. forget_variables_added_to_assigns
  9487. reset_variables_added_to_assigns
  9488. result
  9489. end
  9490. def render_action(action_name, status = nil, with_layout = true) #:nodoc:
  9491. template = default_template_name(action_name.to_s)
  9492. if with_layout && !template_exempt_from_layout?(template)
  9493. render_with_layout(template, status)
  9494. else
  9495. render_without_layout(template, status)
  9496. end
  9497. end
  9498. def render_file(template_path, status = nil, use_full_path = false, locals = {}) #:nodoc:
  9499. add_variables_to_assigns
  9500. assert_existence_of_template_file(template_path) if use_full_path
  9501. logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger
  9502. render_text(@template.render_file(template_path, use_full_path, locals), status)
  9503. end
  9504. def render_template(template, status = nil, type = :rhtml, local_assigns = {}) #:nodoc:
  9505. add_variables_to_assigns
  9506. render_text(@template.render_template(type, template, nil, local_assigns), status)
  9507. end
  9508. def render_text(text = nil, status = nil) #:nodoc:
  9509. @performed_render = true
  9510. @response.headers['Status'] = (status || DEFAULT_RENDER_STATUS_CODE).to_s
  9511. @response.body = text
  9512. end
  9513. def render_javascript(javascript, status = nil) #:nodoc:
  9514. @response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'
  9515. render_text(javascript, status)
  9516. end
  9517. def render_xml(xml, status = nil) #:nodoc:
  9518. @response.headers['Content-Type'] = 'application/xml'
  9519. render_text(xml, status)
  9520. end
  9521. def render_nothing(status = nil) #:nodoc:
  9522. render_text(' ', status)
  9523. end
  9524. def render_partial(partial_path = default_template_name, object = nil, local_assigns = nil, status = nil) #:nodoc:
  9525. add_variables_to_assigns
  9526. render_text(@template.render_partial(partial_path, object, local_assigns), status)
  9527. end
  9528. def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = nil, status = nil) #:nodoc:
  9529. add_variables_to_assigns
  9530. render_text(@template.render_partial_collection(partial_name, collection, partial_spacer_template, local_assigns), status)
  9531. end
  9532. def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
  9533. render_with_a_layout(template_name, status, layout)
  9534. end
  9535. def render_without_layout(template_name = default_template_name, status = nil) #:nodoc:
  9536. render_with_no_layout(template_name, status)
  9537. end
  9538. # Clears the rendered results, allowing for another render to be performed.
  9539. def erase_render_results #:nodoc:
  9540. @response.body = nil
  9541. @performed_render = false
  9542. end
  9543. # Clears the redirected results from the headers, resets the status to 200 and returns
  9544. # the URL that was used to redirect or nil if there was no redirected URL
  9545. # Note that +redirect_to+ will change the body of the response to indicate a redirection.
  9546. # The response body is not reset here, see +erase_render_results+
  9547. def erase_redirect_results #:nodoc:
  9548. @performed_redirect = false
  9549. response.redirected_to = nil
  9550. response.redirected_to_method_params = nil
  9551. response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE
  9552. response.headers.delete('location')
  9553. end
  9554. # Erase both render and redirect results
  9555. def erase_results #:nodoc:
  9556. erase_render_results
  9557. erase_redirect_results
  9558. end
  9559. def rewrite_options(options) #:nodoc:
  9560. if defaults = default_url_options(options)
  9561. defaults.merge(options)
  9562. else
  9563. options
  9564. end
  9565. end
  9566. # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
  9567. # the form of a hash, just like the one you would use for url_for directly. Example:
  9568. #
  9569. # def default_url_options(options)
  9570. # { :project => @project.active? ? @project.url_name : "unknown" }
  9571. # end
  9572. #
  9573. # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
  9574. # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
  9575. # by this method.
  9576. def default_url_options(options) #:doc:
  9577. end
  9578. # Redirects the browser to the target specified in +options+. This parameter can take one of three forms:
  9579. #
  9580. # * <tt>Hash</tt>: The URL will be generated by calling url_for with the +options+.
  9581. # * <tt>String starting with protocol:// (like http://)</tt>: Is passed straight through as the target for redirection.
  9582. # * <tt>String not containing a protocol</tt>: The current protocol and host is prepended to the string.
  9583. # * <tt>:back</tt>: Back to the page that issued the request. Useful for forms that are triggered from multiple places.
  9584. # Short-hand for redirect_to(request.env["HTTP_REFERER"])
  9585. #
  9586. # Examples:
  9587. # redirect_to :action => "show", :id => 5
  9588. # redirect_to "http://www.rubyonrails.org"
  9589. # redirect_to "/images/screenshot.jpg"
  9590. # redirect_to :back
  9591. #
  9592. # The redirection happens as a "302 Moved" header.
  9593. #
  9594. # When using <tt>redirect_to :back</tt>, if there is no referrer,
  9595. # RedirectBackError will be raised. You may specify some fallback
  9596. # behavior for this case by rescueing RedirectBackError.
  9597. def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
  9598. case options
  9599. when %r{^\w+://.*}
  9600. raise DoubleRenderError if performed?
  9601. logger.info("Redirected to #{options}") if logger
  9602. response.redirect(options)
  9603. response.redirected_to = options
  9604. @performed_redirect = true
  9605. when String
  9606. redirect_to(request.protocol + request.host_with_port + options)
  9607. when :back
  9608. request.env["HTTP_REFERER"] ? redirect_to(request.env["HTTP_REFERER"]) : raise(RedirectBackError)
  9609. else
  9610. if parameters_for_method_reference.empty?
  9611. redirect_to(url_for(options))
  9612. response.redirected_to = options
  9613. else
  9614. redirect_to(url_for(options, *parameters_for_method_reference))
  9615. response.redirected_to, response.redirected_to_method_params = options, parameters_for_method_reference
  9616. end
  9617. end
  9618. end
  9619. # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that
  9620. # intermediate caches shouldn't cache the response.
  9621. #
  9622. # Examples:
  9623. # expires_in 20.minutes
  9624. # expires_in 3.hours, :private => false
  9625. # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true
  9626. #
  9627. # This method will overwrite an existing Cache-Control header.
  9628. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities.
  9629. def expires_in(seconds, options = {}) #:doc:
  9630. cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys)
  9631. cache_options.delete_if { |k,v| v.nil? or v == false }
  9632. cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"}
  9633. @response.headers["Cache-Control"] = cache_control.join(', ')
  9634. end
  9635. # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or
  9636. # intermediate caches (like caching proxy servers).
  9637. def expires_now #:doc:
  9638. @response.headers["Cache-Control"] = "no-cache"
  9639. end
  9640. # Resets the session by clearing out all the objects stored within and initializing a new session object.
  9641. def reset_session #:doc:
  9642. @request.reset_session
  9643. @session = @request.session
  9644. @response.session = @session
  9645. end
  9646. private
  9647. def self.view_class
  9648. @view_class ||=
  9649. # create a new class based on the default template class and include helper methods
  9650. returning Class.new(ActionView::Base) do |view_class|
  9651. view_class.send(:include, master_helper_module)
  9652. end
  9653. end
  9654. def self.view_root
  9655. @view_root ||= template_root
  9656. end
  9657. def initialize_template_class(response)
  9658. raise "You must assign a template class through ActionController.template_class= before processing a request" unless @@template_class
  9659. response.template = self.class.view_class.new(self.class.view_root, {}, self)
  9660. response.redirected_to = nil
  9661. @performed_render = @performed_redirect = false
  9662. end
  9663. def assign_shortcuts(request, response)
  9664. @request, @params, @cookies = request, request.parameters, request.cookies
  9665. @response = response
  9666. @response.session = request.session
  9667. @session = @response.session
  9668. @template = @response.template
  9669. @assigns = @response.template.assigns
  9670. @headers = @response.headers
  9671. end
  9672. def initialize_current_url
  9673. @url = UrlRewriter.new(@request, @params.clone())
  9674. end
  9675. def log_processing
  9676. if logger
  9677. logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]"
  9678. logger.info " Session ID: #{@session.session_id}" if @session and @session.respond_to?(:session_id)
  9679. logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(@params).inspect : @params.inspect}"
  9680. end
  9681. end
  9682. def perform_action
  9683. if self.class.action_methods.include?(action_name) || self.class.action_methods.include?('method_missing')
  9684. send(action_name)
  9685. render unless performed?
  9686. elsif template_exists? && template_public?
  9687. render
  9688. else
  9689. raise UnknownAction, "No action responded to #{action_name}", caller
  9690. end
  9691. end
  9692. def performed?
  9693. @performed_render || @performed_redirect
  9694. end
  9695. def assign_names
  9696. @action_name = (params['action'] || 'index')
  9697. end
  9698. def action_methods
  9699. self.class.action_methods
  9700. end
  9701. def self.action_methods
  9702. @action_methods ||= Set.new(public_instance_methods - hidden_actions)
  9703. end
  9704. def add_variables_to_assigns
  9705. unless @variables_added
  9706. add_instance_variables_to_assigns
  9707. add_class_variables_to_assigns if view_controller_internals
  9708. @variables_added = true
  9709. end
  9710. end
  9711. def forget_variables_added_to_assigns
  9712. @variables_added = nil
  9713. end
  9714. def reset_variables_added_to_assigns
  9715. @template.instance_variable_set("@assigns_added", nil)
  9716. end
  9717. def add_instance_variables_to_assigns
  9718. @@protected_variables_cache ||= protected_instance_variables.inject({}) { |h, k| h[k] = true; h }
  9719. instance_variables.each do |var|
  9720. next if @@protected_variables_cache.include?(var)
  9721. @assigns[var[1..-1]] = instance_variable_get(var)
  9722. end
  9723. end
  9724. def add_class_variables_to_assigns
  9725. %w( template_root logger template_class ignore_missing_templates ).each do |cvar|
  9726. @assigns[cvar] = self.send(cvar)
  9727. end
  9728. end
  9729. def protected_instance_variables
  9730. if view_controller_internals
  9731. [ "@assigns", "@performed_redirect", "@performed_render" ]
  9732. else
  9733. [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template", "@request_origin", "@parent_controller" ]
  9734. end
  9735. end
  9736. def request_origin
  9737. # this *needs* to be cached!
  9738. # otherwise you'd get different results if calling it more than once
  9739. @request_origin ||= "#{@request.remote_ip} at #{Time.now.to_s(:db)}"
  9740. end
  9741. def complete_request_uri
  9742. "#{@request.protocol}#{@request.host}#{@request.request_uri}"
  9743. end
  9744. def close_session
  9745. @session.close unless @session.nil? || Hash === @session
  9746. end
  9747. def template_exists?(template_name = default_template_name)
  9748. @template.file_exists?(template_name)
  9749. end
  9750. def template_public?(template_name = default_template_name)
  9751. @template.file_public?(template_name)
  9752. end
  9753. def template_exempt_from_layout?(template_name = default_template_name)
  9754. template_name =~ /\.rjs$/ || (@template.pick_template_extension(template_name) == :rjs rescue false)
  9755. end
  9756. def assert_existence_of_template_file(template_name)
  9757. unless template_exists?(template_name) || ignore_missing_templates
  9758. full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
  9759. template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
  9760. raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
  9761. end
  9762. end
  9763. def default_template_name(action_name = self.action_name)
  9764. if action_name
  9765. action_name = action_name.to_s
  9766. if action_name.include?('/') && template_path_includes_controller?(action_name)
  9767. action_name = strip_out_controller(action_name)
  9768. end
  9769. end
  9770. "#{self.class.controller_path}/#{action_name}"
  9771. end
  9772. def strip_out_controller(path)
  9773. path.split('/', 2).last
  9774. end
  9775. def template_path_includes_controller?(path)
  9776. self.class.controller_path.split('/')[-1] == path.split('/')[0]
  9777. end
  9778. def process_cleanup
  9779. close_session
  9780. end
  9781. end
  9782. end
  9783. require 'benchmark'
  9784. module ActionController #:nodoc:
  9785. # The benchmarking module times the performance of actions and reports to the logger. If the Active Record
  9786. # package has been included, a separate timing section for database calls will be added as well.
  9787. module Benchmarking #:nodoc:
  9788. def self.included(base)
  9789. base.extend(ClassMethods)
  9790. base.class_eval do
  9791. alias_method :perform_action_without_benchmark, :perform_action
  9792. alias_method :perform_action, :perform_action_with_benchmark
  9793. alias_method :render_without_benchmark, :render
  9794. alias_method :render, :render_with_benchmark
  9795. end
  9796. end
  9797. module ClassMethods
  9798. # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
  9799. # (unless <tt>use_silence</tt> is set to false).
  9800. #
  9801. # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
  9802. # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
  9803. # will only be conducted if the log level is low enough.
  9804. def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
  9805. if logger && logger.level == log_level
  9806. result = nil
  9807. seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
  9808. logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
  9809. result
  9810. else
  9811. yield
  9812. end
  9813. end
  9814. # Silences the logger for the duration of the block.
  9815. def silence
  9816. old_logger_level, logger.level = logger.level, Logger::ERROR if logger
  9817. yield
  9818. ensure
  9819. logger.level = old_logger_level if logger
  9820. end
  9821. end
  9822. def render_with_benchmark(options = nil, deprecated_status = nil, &block)
  9823. unless logger
  9824. render_without_benchmark(options, deprecated_status, &block)
  9825. else
  9826. db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9827. render_output = nil
  9828. @rendering_runtime = Benchmark::measure{ render_output = render_without_benchmark(options, deprecated_status, &block) }.real
  9829. if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9830. @db_rt_before_render = db_runtime
  9831. @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime
  9832. @rendering_runtime -= @db_rt_after_render
  9833. end
  9834. render_output
  9835. end
  9836. end
  9837. def perform_action_with_benchmark
  9838. unless logger
  9839. perform_action_without_benchmark
  9840. else
  9841. runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
  9842. log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
  9843. log_message << rendering_runtime(runtime) if @rendering_runtime
  9844. log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
  9845. log_message << " | #{headers["Status"]}"
  9846. log_message << " [#{complete_request_uri rescue "unknown"}]"
  9847. logger.info(log_message)
  9848. end
  9849. end
  9850. private
  9851. def rendering_runtime(runtime)
  9852. " | Rendering: #{sprintf("%.5f", @rendering_runtime)} (#{sprintf("%d", (@rendering_runtime * 100) / runtime)}%)"
  9853. end
  9854. def active_record_runtime(runtime)
  9855. db_runtime = ActiveRecord::Base.connection.reset_runtime
  9856. db_runtime += @db_rt_before_render if @db_rt_before_render
  9857. db_runtime += @db_rt_after_render if @db_rt_after_render
  9858. db_percentage = (db_runtime * 100) / runtime
  9859. " | DB: #{sprintf("%.5f", db_runtime)} (#{sprintf("%d", db_percentage)}%)"
  9860. end
  9861. end
  9862. end
  9863. require 'fileutils'
  9864. module ActionController #:nodoc:
  9865. # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
  9866. # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
  9867. #
  9868. # You can read more about each approach and the sweeping assistance by clicking the modules below.
  9869. #
  9870. # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
  9871. module Caching
  9872. def self.included(base) #:nodoc:
  9873. base.send(:include, Pages, Actions, Fragments, Sweeping)
  9874. base.class_eval do
  9875. @@perform_caching = true
  9876. cattr_accessor :perform_caching
  9877. end
  9878. end
  9879. # Page caching is an approach to caching where the entire action output of is stored as a HTML file that the web server
  9880. # can serve without going through the Action Pack. This can be as much as 100 times faster than going through the process of dynamically
  9881. # generating the content. Unfortunately, this incredible speed-up is only available to stateless pages where all visitors
  9882. # are treated the same. Content management systems -- including weblogs and wikis -- have many pages that are a great fit
  9883. # for this approach, but account-based systems where people log in and manipulate their own data are often less likely candidates.
  9884. #
  9885. # Specifying which actions to cache is done through the <tt>caches</tt> class method:
  9886. #
  9887. # class WeblogController < ActionController::Base
  9888. # caches_page :show, :new
  9889. # end
  9890. #
  9891. # This will generate cache files such as weblog/show/5 and weblog/new, which match the URLs used to trigger the dynamic
  9892. # 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
  9893. # the Action Pack to generate it.
  9894. #
  9895. # Expiration of the cache is handled by deleting the cached file, which results in a lazy regeneration approach where the cache
  9896. # is not restored before another hit is made against it. The API for doing so mimics the options from url_for and friends:
  9897. #
  9898. # class WeblogController < ActionController::Base
  9899. # def update
  9900. # List.update(params[:list][:id], params[:list])
  9901. # expire_page :action => "show", :id => params[:list][:id]
  9902. # redirect_to :action => "show", :id => params[:list][:id]
  9903. # end
  9904. # end
  9905. #
  9906. # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
  9907. # expired.
  9908. #
  9909. # == Setting the cache directory
  9910. #
  9911. # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
  9912. # For Rails, this directory has already been set to RAILS_ROOT + "/public".
  9913. #
  9914. # == Setting the cache extension
  9915. #
  9916. # 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
  9917. # something else, like .php or .shtml, just set Base.page_cache_extension.
  9918. module Pages
  9919. def self.included(base) #:nodoc:
  9920. base.extend(ClassMethods)
  9921. base.class_eval do
  9922. @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
  9923. cattr_accessor :page_cache_directory
  9924. @@page_cache_extension = '.html'
  9925. cattr_accessor :page_cache_extension
  9926. end
  9927. end
  9928. module ClassMethods
  9929. # Expires the page that was cached with the +path+ as a key. Example:
  9930. # expire_page "/lists/show"
  9931. def expire_page(path)
  9932. return unless perform_caching
  9933. benchmark "Expired page: #{page_cache_file(path)}" do
  9934. File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
  9935. end
  9936. end
  9937. # Manually cache the +content+ in the key determined by +path+. Example:
  9938. # cache_page "I'm the cached content", "/lists/show"
  9939. def cache_page(content, path)
  9940. return unless perform_caching
  9941. benchmark "Cached page: #{page_cache_file(path)}" do
  9942. FileUtils.makedirs(File.dirname(page_cache_path(path)))
  9943. File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
  9944. end
  9945. end
  9946. # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
  9947. # matches the triggering url.
  9948. def caches_page(*actions)
  9949. return unless perform_caching
  9950. actions.each do |action|
  9951. class_eval "after_filter { |c| c.cache_page if c.action_name == '#{action}' }"
  9952. end
  9953. end
  9954. private
  9955. def page_cache_file(path)
  9956. name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
  9957. name << page_cache_extension unless (name.split('/').last || name).include? '.'
  9958. return name
  9959. end
  9960. def page_cache_path(path)
  9961. page_cache_directory + page_cache_file(path)
  9962. end
  9963. end
  9964. # Expires the page that was cached with the +options+ as a key. Example:
  9965. # expire_page :controller => "lists", :action => "show"
  9966. def expire_page(options = {})
  9967. return unless perform_caching
  9968. if options[:action].is_a?(Array)
  9969. options[:action].dup.each do |action|
  9970. self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true, :action => action })))
  9971. end
  9972. else
  9973. self.class.expire_page(url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
  9974. end
  9975. end
  9976. # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
  9977. # If no options are provided, the current +options+ for this action is used. Example:
  9978. # cache_page "I'm the cached content", :controller => "lists", :action => "show"
  9979. def cache_page(content = nil, options = {})
  9980. return unless perform_caching && caching_allowed
  9981. self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true, :skip_relative_url_root => true })))
  9982. end
  9983. private
  9984. def caching_allowed
  9985. !@request.post? && @response.headers['Status'] && @response.headers['Status'].to_i < 400
  9986. end
  9987. end
  9988. # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
  9989. # every request still goes through the Action Pack. The key benefit of this is that filters are run before the cache is served, which
  9990. # allows for authentication and other restrictions on whether someone is allowed to see the cache. Example:
  9991. #
  9992. # class ListsController < ApplicationController
  9993. # before_filter :authenticate, :except => :public
  9994. # caches_page :public
  9995. # caches_action :show, :feed
  9996. # end
  9997. #
  9998. # In this example, the public action doesn't require authentication, so it's possible to use the faster page caching method. But both the
  9999. # show and feed action are to be shielded behind the authenticate filter, so we need to implement those as action caches.
  10000. #
  10001. # Action caching internally uses the fragment caching and an around filter to do the job. The fragment cache is named according to both
  10002. # 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
  10003. # "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
  10004. # "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
  10005. module Actions
  10006. def self.append_features(base) #:nodoc:
  10007. super
  10008. base.extend(ClassMethods)
  10009. base.send(:attr_accessor, :rendered_action_cache)
  10010. end
  10011. module ClassMethods #:nodoc:
  10012. def caches_action(*actions)
  10013. return unless perform_caching
  10014. around_filter(ActionCacheFilter.new(*actions))
  10015. end
  10016. end
  10017. def expire_action(options = {})
  10018. return unless perform_caching
  10019. if options[:action].is_a?(Array)
  10020. options[:action].dup.each do |action|
  10021. expire_fragment(url_for(options.merge({ :action => action })).split("://").last)
  10022. end
  10023. else
  10024. expire_fragment(url_for(options).split("://").last)
  10025. end
  10026. end
  10027. class ActionCacheFilter #:nodoc:
  10028. def initialize(*actions)
  10029. @actions = actions
  10030. end
  10031. def before(controller)
  10032. return unless @actions.include?(controller.action_name.intern)
  10033. if cache = controller.read_fragment(controller.url_for.split("://").last)
  10034. controller.rendered_action_cache = true
  10035. controller.send(:render_text, cache)
  10036. false
  10037. end
  10038. end
  10039. def after(controller)
  10040. return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
  10041. controller.write_fragment(controller.url_for.split("://").last, controller.response.body)
  10042. end
  10043. end
  10044. end
  10045. # Fragment caching is used for caching various blocks within templates without caching the entire action as a whole. This is useful when
  10046. # certain elements of an action change frequently or depend on complicated state while other parts rarely change or can be shared amongst multiple
  10047. # parties. The caching is doing using the cache helper available in the Action View. A template with caching might look something like:
  10048. #
  10049. # <b>Hello <%= @name %></b>
  10050. # <% cache do %>
  10051. # All the topics in the system:
  10052. # <%= render_collection_of_partials "topic", Topic.find_all %>
  10053. # <% end %>
  10054. #
  10055. # This cache will bind to the name of action that called it. So you would be able to invalidate it using
  10056. # <tt>expire_fragment(:controller => "topics", :action => "list")</tt> -- if that was the controller/action used. This is not too helpful
  10057. # 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
  10058. # qualify the name of the action used with something like:
  10059. #
  10060. # <% cache(:action => "list", :action_suffix => "all_topics") do %>
  10061. #
  10062. # That would result in a name such as "/topics/list/all_topics", which wouldn't conflict with any action cache and neither with another
  10063. # 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
  10064. # to generate unique cache names that we can refer to later for expirations. The expiration call for this example would be
  10065. # <tt>expire_fragment(:controller => "topics", :action => "list", :action_suffix => "all_topics")</tt>.
  10066. #
  10067. # == Fragment stores
  10068. #
  10069. # 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
  10070. # of which there are four different kinds:
  10071. #
  10072. # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and shares the fragments for
  10073. # all the web server processes running off the same application directory.
  10074. # * 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
  10075. # 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
  10076. # up a lot of memory since each process keeps all the caches in memory.
  10077. # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
  10078. # around for all processes, but requires that you run and manage a separate DRb process.
  10079. # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
  10080. # Requires the ruby-memcache library: gem install ruby-memcache.
  10081. #
  10082. # Configuration examples (MemoryStore is the default):
  10083. #
  10084. # ActionController::Base.fragment_cache_store = :memory_store
  10085. # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
  10086. # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
  10087. # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
  10088. # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
  10089. module Fragments
  10090. def self.append_features(base) #:nodoc:
  10091. super
  10092. base.class_eval do
  10093. @@fragment_cache_store = MemoryStore.new
  10094. cattr_reader :fragment_cache_store
  10095. def self.fragment_cache_store=(store_option)
  10096. store, *parameters = *([ store_option ].flatten)
  10097. @@fragment_cache_store = if store.is_a?(Symbol)
  10098. store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
  10099. store_class = ActionController::Caching::Fragments.const_get(store_class_name)
  10100. store_class.new(*parameters)
  10101. else
  10102. store
  10103. end
  10104. end
  10105. end
  10106. end
  10107. def fragment_cache_key(name)
  10108. name.is_a?(Hash) ? url_for(name).split("://").last : name
  10109. end
  10110. # Called by CacheHelper#cache
  10111. def cache_erb_fragment(block, name = {}, options = nil)
  10112. unless perform_caching then block.call; return end
  10113. buffer = eval("_erbout", block.binding)
  10114. if cache = read_fragment(name, options)
  10115. buffer.concat(cache)
  10116. else
  10117. pos = buffer.length
  10118. block.call
  10119. write_fragment(name, buffer[pos..-1], options)
  10120. end
  10121. end
  10122. def write_fragment(name, content, options = nil)
  10123. return unless perform_caching
  10124. key = fragment_cache_key(name)
  10125. self.class.benchmark "Cached fragment: #{key}" do
  10126. fragment_cache_store.write(key, content, options)
  10127. end
  10128. content
  10129. end
  10130. def read_fragment(name, options = nil)
  10131. return unless perform_caching
  10132. key = fragment_cache_key(name)
  10133. self.class.benchmark "Fragment read: #{key}" do
  10134. fragment_cache_store.read(key, options)
  10135. end
  10136. end
  10137. # Name can take one of three forms:
  10138. # * String: This would normally take the form of a path like "pages/45/notes"
  10139. # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
  10140. # * 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
  10141. def expire_fragment(name, options = nil)
  10142. return unless perform_caching
  10143. key = fragment_cache_key(name)
  10144. if key.is_a?(Regexp)
  10145. self.class.benchmark "Expired fragments matching: #{key.source}" do
  10146. fragment_cache_store.delete_matched(key, options)
  10147. end
  10148. else
  10149. self.class.benchmark "Expired fragment: #{key}" do
  10150. fragment_cache_store.delete(key, options)
  10151. end
  10152. end
  10153. end
  10154. # Deprecated -- just call expire_fragment with a regular expression
  10155. def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
  10156. expire_fragment(matcher, options)
  10157. end
  10158. class UnthreadedMemoryStore #:nodoc:
  10159. def initialize #:nodoc:
  10160. @data = {}
  10161. end
  10162. def read(name, options=nil) #:nodoc:
  10163. @data[name]
  10164. end
  10165. def write(name, value, options=nil) #:nodoc:
  10166. @data[name] = value
  10167. end
  10168. def delete(name, options=nil) #:nodoc:
  10169. @data.delete(name)
  10170. end
  10171. def delete_matched(matcher, options=nil) #:nodoc:
  10172. @data.delete_if { |k,v| k =~ matcher }
  10173. end
  10174. end
  10175. module ThreadSafety #:nodoc:
  10176. def read(name, options=nil) #:nodoc:
  10177. @mutex.synchronize { super }
  10178. end
  10179. def write(name, value, options=nil) #:nodoc:
  10180. @mutex.synchronize { super }
  10181. end
  10182. def delete(name, options=nil) #:nodoc:
  10183. @mutex.synchronize { super }
  10184. end
  10185. def delete_matched(matcher, options=nil) #:nodoc:
  10186. @mutex.synchronize { super }
  10187. end
  10188. end
  10189. class MemoryStore < UnthreadedMemoryStore #:nodoc:
  10190. def initialize #:nodoc:
  10191. super
  10192. if ActionController::Base.allow_concurrency
  10193. @mutex = Mutex.new
  10194. MemoryStore.send(:include, ThreadSafety)
  10195. end
  10196. end
  10197. end
  10198. class DRbStore < MemoryStore #:nodoc:
  10199. attr_reader :address
  10200. def initialize(address = 'druby://localhost:9192')
  10201. super()
  10202. @address = address
  10203. @data = DRbObject.new(nil, address)
  10204. end
  10205. end
  10206. class MemCacheStore < MemoryStore #:nodoc:
  10207. attr_reader :addresses
  10208. def initialize(*addresses)
  10209. super()
  10210. addresses = addresses.flatten
  10211. addresses = ["localhost"] if addresses.empty?
  10212. @addresses = addresses
  10213. @data = MemCache.new(*addresses)
  10214. end
  10215. end
  10216. class UnthreadedFileStore #:nodoc:
  10217. attr_reader :cache_path
  10218. def initialize(cache_path)
  10219. @cache_path = cache_path
  10220. end
  10221. def write(name, value, options = nil) #:nodoc:
  10222. ensure_cache_path(File.dirname(real_file_path(name)))
  10223. File.open(real_file_path(name), "wb+") { |f| f.write(value) }
  10224. rescue => e
  10225. Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
  10226. end
  10227. def read(name, options = nil) #:nodoc:
  10228. File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
  10229. end
  10230. def delete(name, options) #:nodoc:
  10231. File.delete(real_file_path(name))
  10232. rescue SystemCallError => e
  10233. # If there's no cache, then there's nothing to complain about
  10234. end
  10235. def delete_matched(matcher, options) #:nodoc:
  10236. search_dir(@cache_path) do |f|
  10237. if f =~ matcher
  10238. begin
  10239. File.delete(f)
  10240. rescue Object => e
  10241. # If there's no cache, then there's nothing to complain about
  10242. end
  10243. end
  10244. end
  10245. end
  10246. private
  10247. def real_file_path(name)
  10248. '%s/%s.cache' % [@cache_path, name.gsub('?', '.').gsub(':', '.')]
  10249. end
  10250. def ensure_cache_path(path)
  10251. FileUtils.makedirs(path) unless File.exists?(path)
  10252. end
  10253. def search_dir(dir, &callback)
  10254. Dir.foreach(dir) do |d|
  10255. next if d == "." || d == ".."
  10256. name = File.join(dir, d)
  10257. if File.directory?(name)
  10258. search_dir(name, &callback)
  10259. else
  10260. callback.call name
  10261. end
  10262. end
  10263. end
  10264. end
  10265. class FileStore < UnthreadedFileStore #:nodoc:
  10266. def initialize(cache_path)
  10267. super(cache_path)
  10268. if ActionController::Base.allow_concurrency
  10269. @mutex = Mutex.new
  10270. FileStore.send(:include, ThreadSafety)
  10271. end
  10272. end
  10273. end
  10274. end
  10275. # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
  10276. # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
  10277. #
  10278. # class ListSweeper < ActionController::Caching::Sweeper
  10279. # observe List, Item
  10280. #
  10281. # def after_save(record)
  10282. # list = record.is_a?(List) ? record : record.list
  10283. # expire_page(:controller => "lists", :action => %w( show public feed ), :id => list.id)
  10284. # expire_action(:controller => "lists", :action => "all")
  10285. # list.shares.each { |share| expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
  10286. # end
  10287. # end
  10288. #
  10289. # The sweeper is assigned in the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
  10290. #
  10291. # class ListsController < ApplicationController
  10292. # caches_action :index, :show, :public, :feed
  10293. # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
  10294. # end
  10295. #
  10296. # In the example above, four actions are cached and three actions are responsible for expiring those caches.
  10297. module Sweeping
  10298. def self.append_features(base) #:nodoc:
  10299. super
  10300. base.extend(ClassMethods)
  10301. end
  10302. module ClassMethods #:nodoc:
  10303. def cache_sweeper(*sweepers)
  10304. return unless perform_caching
  10305. configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
  10306. sweepers.each do |sweeper|
  10307. observer(sweeper)
  10308. sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
  10309. if sweeper_instance.is_a?(Sweeper)
  10310. around_filter(sweeper_instance, :only => configuration[:only])
  10311. else
  10312. after_filter(sweeper_instance, :only => configuration[:only])
  10313. end
  10314. end
  10315. end
  10316. end
  10317. end
  10318. if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
  10319. class Sweeper < ActiveRecord::Observer #:nodoc:
  10320. attr_accessor :controller
  10321. # ActiveRecord::Observer will mark this class as reloadable even though it should not be.
  10322. # However, subclasses of ActionController::Caching::Sweeper should be Reloadable
  10323. include Reloadable::Subclasses
  10324. def before(controller)
  10325. self.controller = controller
  10326. callback(:before)
  10327. end
  10328. def after(controller)
  10329. callback(:after)
  10330. # Clean up, so that the controller can be collected after this request
  10331. self.controller = nil
  10332. end
  10333. private
  10334. def callback(timing)
  10335. controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
  10336. action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
  10337. send(controller_callback_method_name) if respond_to?(controller_callback_method_name)
  10338. send(action_callback_method_name) if respond_to?(action_callback_method_name)
  10339. end
  10340. def method_missing(method, *arguments)
  10341. return if @controller.nil?
  10342. @controller.send(method, *arguments)
  10343. end
  10344. end
  10345. end
  10346. end
  10347. end
  10348. require 'cgi'
  10349. require 'cgi/session'
  10350. require 'cgi/session/pstore'
  10351. require 'action_controller/cgi_ext/cgi_methods'
  10352. # Wrapper around the CGIMethods that have been secluded to allow testing without
  10353. # an instantiated CGI object
  10354. class CGI #:nodoc:
  10355. class << self
  10356. alias :escapeHTML_fail_on_nil :escapeHTML
  10357. def escapeHTML(string)
  10358. escapeHTML_fail_on_nil(string) unless string.nil?
  10359. end
  10360. end
  10361. # Returns a parameter hash including values from both the request (POST/GET)
  10362. # and the query string with the latter taking precedence.
  10363. def parameters
  10364. request_parameters.update(query_parameters)
  10365. end
  10366. def query_parameters
  10367. CGIMethods.parse_query_parameters(query_string)
  10368. end
  10369. def request_parameters
  10370. CGIMethods.parse_request_parameters(params, env_table)
  10371. end
  10372. def redirect(where)
  10373. header({
  10374. "Status" => "302 Moved",
  10375. "location" => "#{where}"
  10376. })
  10377. end
  10378. def session(parameters = nil)
  10379. parameters = {} if parameters.nil?
  10380. parameters['database_manager'] = CGI::Session::PStore
  10381. CGI::Session.new(self, parameters)
  10382. end
  10383. end
  10384. require 'cgi'
  10385. require 'action_controller/vendor/xml_simple'
  10386. require 'action_controller/vendor/xml_node'
  10387. # Static methods for parsing the query and request parameters that can be used in
  10388. # a CGI extension class or testing in isolation.
  10389. class CGIMethods #:nodoc:
  10390. public
  10391. # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
  10392. # parse_request_params is not done here.
  10393. def CGIMethods.parse_query_parameters(query_string)
  10394. parsed_params = {}
  10395. query_string.split(/[&;]/).each { |p|
  10396. # Ignore repeated delimiters.
  10397. next if p.empty?
  10398. k, v = p.split('=',2)
  10399. v = nil if (v && v.empty?)
  10400. k = CGI.unescape(k) if k
  10401. v = CGI.unescape(v) if v
  10402. unless k.include?(?[)
  10403. parsed_params[k] = v
  10404. else
  10405. keys = split_key(k)
  10406. last_key = keys.pop
  10407. last_key = keys.pop if (use_array = last_key.empty?)
  10408. parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
  10409. if use_array then (parent[last_key] ||= []) << v
  10410. else parent[last_key] = v
  10411. end
  10412. end
  10413. }
  10414. parsed_params
  10415. end
  10416. # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
  10417. # "Somewhere cool!" are translated into a full hash hierarchy, like
  10418. # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
  10419. def CGIMethods.parse_request_parameters(params)
  10420. parsed_params = {}
  10421. for key, value in params
  10422. value = [value] if key =~ /.*\[\]$/
  10423. unless key.include?('[')
  10424. # much faster to test for the most common case first (GET)
  10425. # and avoid the call to build_deep_hash
  10426. parsed_params[key] = get_typed_value(value[0])
  10427. else
  10428. build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
  10429. end
  10430. end
  10431. parsed_params
  10432. end
  10433. def self.parse_formatted_request_parameters(mime_type, raw_post_data)
  10434. params = case strategy = ActionController::Base.param_parsers[mime_type]
  10435. when Proc
  10436. strategy.call(raw_post_data)
  10437. when :xml_simple
  10438. raw_post_data.blank? ? nil :
  10439. typecast_xml_value(XmlSimple.xml_in(raw_post_data,
  10440. 'forcearray' => false,
  10441. 'forcecontent' => true,
  10442. 'keeproot' => true,
  10443. 'contentkey' => '__content__'))
  10444. when :yaml
  10445. YAML.load(raw_post_data)
  10446. when :xml_node
  10447. node = XmlNode.from_xml(raw_post_data)
  10448. { node.node_name => node }
  10449. end
  10450. dasherize_keys(params || {})
  10451. rescue Object => e
  10452. { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
  10453. "raw_post_data" => raw_post_data, "format" => mime_type }
  10454. end
  10455. def self.typecast_xml_value(value)
  10456. case value
  10457. when Hash
  10458. if value.has_key?("__content__")
  10459. content = translate_xml_entities(value["__content__"])
  10460. case value["type"]
  10461. when "integer" then content.to_i
  10462. when "boolean" then content == "true"
  10463. when "datetime" then Time.parse(content)
  10464. when "date" then Date.parse(content)
  10465. else content
  10466. end
  10467. else
  10468. value.empty? ? nil : value.inject({}) do |h,(k,v)|
  10469. h[k] = typecast_xml_value(v)
  10470. h
  10471. end
  10472. end
  10473. when Array
  10474. value.map! { |i| typecast_xml_value(i) }
  10475. case value.length
  10476. when 0 then nil
  10477. when 1 then value.first
  10478. else value
  10479. end
  10480. else
  10481. raise "can't typecast #{value.inspect}"
  10482. end
  10483. end
  10484. private
  10485. def self.translate_xml_entities(value)
  10486. value.gsub(/&lt;/, "<").
  10487. gsub(/&gt;/, ">").
  10488. gsub(/&quot;/, '"').
  10489. gsub(/&apos;/, "'").
  10490. gsub(/&amp;/, "&")
  10491. end
  10492. def self.dasherize_keys(params)
  10493. case params.class.to_s
  10494. when "Hash"
  10495. params.inject({}) do |h,(k,v)|
  10496. h[k.to_s.tr("-", "_")] = dasherize_keys(v)
  10497. h
  10498. end
  10499. when "Array"
  10500. params.map { |v| dasherize_keys(v) }
  10501. else
  10502. params
  10503. end
  10504. end
  10505. # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
  10506. # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
  10507. # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
  10508. def CGIMethods.split_key(key)
  10509. if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
  10510. keys = [$1]
  10511. keys.concat($2[1..-2].split(']['))
  10512. keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
  10513. keys
  10514. else
  10515. [key]
  10516. end
  10517. end
  10518. def CGIMethods.get_typed_value(value)
  10519. # test most frequent case first
  10520. if value.is_a?(String)
  10521. value
  10522. elsif value.respond_to?(:content_type) && ! value.content_type.blank?
  10523. # Uploaded file
  10524. unless value.respond_to?(:full_original_filename)
  10525. class << value
  10526. alias_method :full_original_filename, :original_filename
  10527. # Take the basename of the upload's original filename.
  10528. # This handles the full Windows paths given by Internet Explorer
  10529. # (and perhaps other broken user agents) without affecting
  10530. # those which give the lone filename.
  10531. # The Windows regexp is adapted from Perl's File::Basename.
  10532. def original_filename
  10533. if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
  10534. md.captures.first
  10535. else
  10536. File.basename full_original_filename
  10537. end
  10538. end
  10539. end
  10540. end
  10541. # Return the same value after overriding original_filename.
  10542. value
  10543. elsif value.respond_to?(:read)
  10544. # Value as part of a multipart request
  10545. value.read
  10546. elsif value.class == Array
  10547. value.collect { |v| CGIMethods.get_typed_value(v) }
  10548. else
  10549. # other value (neither string nor a multipart request)
  10550. value.to_s
  10551. end
  10552. end
  10553. PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
  10554. def CGIMethods.get_levels(key)
  10555. all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
  10556. if main.nil?
  10557. []
  10558. elsif trailing
  10559. [key]
  10560. elsif bracketed
  10561. [main] + bracketed.slice(1...-1).split('][')
  10562. else
  10563. [main]
  10564. end
  10565. end
  10566. def CGIMethods.build_deep_hash(value, hash, levels)
  10567. if levels.length == 0
  10568. value
  10569. elsif hash.nil?
  10570. { levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
  10571. else
  10572. hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
  10573. end
  10574. end
  10575. end
  10576. CGI.module_eval { remove_const "Cookie" }
  10577. class CGI #:nodoc:
  10578. # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.
  10579. # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on
  10580. # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link14
  10581. class Cookie < DelegateClass(Array)
  10582. # Create a new CGI::Cookie object.
  10583. #
  10584. # The contents of the cookie can be specified as a +name+ and one
  10585. # or more +value+ arguments. Alternatively, the contents can
  10586. # be specified as a single hash argument. The possible keywords of
  10587. # this hash are as follows:
  10588. #
  10589. # name:: the name of the cookie. Required.
  10590. # value:: the cookie's value or list of values.
  10591. # path:: the path for which this cookie applies. Defaults to the
  10592. # base directory of the CGI script.
  10593. # domain:: the domain for which this cookie applies.
  10594. # expires:: the time at which this cookie expires, as a +Time+ object.
  10595. # secure:: whether this cookie is a secure cookie or not (default to
  10596. # false). Secure cookies are only transmitted to HTTPS
  10597. # servers.
  10598. #
  10599. # These keywords correspond to attributes of the cookie object.
  10600. def initialize(name = '', *value)
  10601. if name.kind_of?(String)
  10602. @name = name
  10603. @value = Array(value)
  10604. @domain = nil
  10605. @expires = nil
  10606. @secure = false
  10607. @path = nil
  10608. else
  10609. @name = name['name']
  10610. @value = Array(name['value'])
  10611. @domain = name['domain']
  10612. @expires = name['expires']
  10613. @secure = name['secure'] || false
  10614. @path = name['path']
  10615. end
  10616. unless @name
  10617. raise ArgumentError, "`name' required"
  10618. end
  10619. # simple support for IE
  10620. unless @path
  10621. %r|^(.*/)|.match(ENV['SCRIPT_NAME'])
  10622. @path = ($1 or '')
  10623. end
  10624. super(@value)
  10625. end
  10626. def __setobj__(obj)
  10627. @_dc_obj = obj
  10628. end
  10629. attr_accessor("name", "value", "path", "domain", "expires")
  10630. attr_reader("secure")
  10631. # Set whether the Cookie is a secure cookie or not.
  10632. #
  10633. # +val+ must be a boolean.
  10634. def secure=(val)
  10635. @secure = val if val == true or val == false
  10636. @secure
  10637. end
  10638. # Convert the Cookie to its string representation.
  10639. def to_s
  10640. buf = ""
  10641. buf << @name << '='
  10642. if @value.kind_of?(String)
  10643. buf << CGI::escape(@value)
  10644. else
  10645. buf << @value.collect{|v| CGI::escape(v) }.join("&")
  10646. end
  10647. if @domain
  10648. buf << '; domain=' << @domain
  10649. end
  10650. if @path
  10651. buf << '; path=' << @path
  10652. end
  10653. if @expires
  10654. buf << '; expires=' << CGI::rfc1123_date(@expires)
  10655. end
  10656. if @secure == true
  10657. buf << '; secure'
  10658. end
  10659. buf
  10660. end
  10661. # Parse a raw cookie string into a hash of cookie-name=>Cookie
  10662. # pairs.
  10663. #
  10664. # cookies = CGI::Cookie::parse("raw_cookie_string")
  10665. # # { "name1" => cookie1, "name2" => cookie2, ... }
  10666. #
  10667. def self.parse(raw_cookie)
  10668. cookies = Hash.new([])
  10669. if raw_cookie
  10670. raw_cookie.split(/; ?/).each do |pairs|
  10671. name, values = pairs.split('=',2)
  10672. next unless name and values
  10673. name = CGI::unescape(name)
  10674. values = values.split('&').collect!{|v| CGI::unescape(v) }
  10675. unless cookies.has_key?(name)
  10676. cookies[name] = new(name, *values)
  10677. end
  10678. end
  10679. end
  10680. cookies
  10681. end
  10682. end # class Cookie
  10683. end
  10684. class CGI #:nodoc:
  10685. # Add @request.env['RAW_POST_DATA'] for the vegans.
  10686. module QueryExtension
  10687. # Initialize the data from the query.
  10688. #
  10689. # Handles multipart forms (in particular, forms that involve file uploads).
  10690. # Reads query parameters in the @params field, and cookies into @cookies.
  10691. def initialize_query()
  10692. @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
  10693. #fix some strange request environments
  10694. if method = env_table['REQUEST_METHOD']
  10695. method = method.to_s.downcase.intern
  10696. else
  10697. method = :get
  10698. end
  10699. if method == :post && (boundary = multipart_form_boundary)
  10700. @multipart = true
  10701. @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
  10702. else
  10703. @multipart = false
  10704. @params = CGI::parse(read_query_params(method) || "")
  10705. end
  10706. end
  10707. private
  10708. unless defined?(MULTIPART_FORM_BOUNDARY_RE)
  10709. MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
  10710. end
  10711. def multipart_form_boundary
  10712. MULTIPART_FORM_BOUNDARY_RE.match(env_table['CONTENT_TYPE']).to_a.pop
  10713. end
  10714. if defined? MOD_RUBY
  10715. def read_params_from_query
  10716. Apache::request.args || ''
  10717. end
  10718. else
  10719. def read_params_from_query
  10720. # fixes CGI querystring parsing for lighttpd
  10721. env_qs = env_table['QUERY_STRING']
  10722. if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
  10723. uri.split('?', 2)[1] || ''
  10724. else
  10725. env_qs
  10726. end
  10727. end
  10728. end
  10729. def read_params_from_post
  10730. stdinput.binmode if stdinput.respond_to?(:binmode)
  10731. content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || ''
  10732. # fix for Safari Ajax postings that always append \000
  10733. content.chop! if content[-1] == 0
  10734. content.gsub! /&_=$/, ''
  10735. env_table['RAW_POST_DATA'] = content.freeze
  10736. end
  10737. def read_query_params(method)
  10738. case method
  10739. when :get
  10740. read_params_from_query
  10741. when :post, :put
  10742. read_params_from_post
  10743. when :cmd
  10744. read_from_cmdline
  10745. else # when :head, :delete, :options
  10746. read_params_from_query
  10747. end
  10748. end
  10749. end # module QueryExtension
  10750. end
  10751. require 'action_controller/cgi_ext/cgi_ext'
  10752. require 'action_controller/cgi_ext/cookie_performance_fix'
  10753. require 'action_controller/cgi_ext/raw_post_data_fix'
  10754. module ActionController #:nodoc:
  10755. class Base
  10756. # Process a request extracted from an CGI object and return a response. Pass false as <tt>session_options</tt> to disable
  10757. # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
  10758. #
  10759. # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
  10760. # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
  10761. # lib/action_controller/session.
  10762. # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
  10763. # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ parameter
  10764. # of the request, or automatically generated for a new session.
  10765. # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
  10766. # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
  10767. # an ArgumentError is raised.
  10768. # * <tt>:session_expires</tt> - the time the current session expires, as a +Time+ object. If not set, the session will continue
  10769. # indefinitely.
  10770. # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
  10771. # server.
  10772. # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
  10773. # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
  10774. def self.process_cgi(cgi = CGI.new, session_options = {})
  10775. new.process_cgi(cgi, session_options)
  10776. end
  10777. def process_cgi(cgi, session_options = {}) #:nodoc:
  10778. process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
  10779. end
  10780. end
  10781. class CgiRequest < AbstractRequest #:nodoc:
  10782. attr_accessor :cgi, :session_options
  10783. DEFAULT_SESSION_OPTIONS = {
  10784. :database_manager => CGI::Session::PStore,
  10785. :prefix => "ruby_sess.",
  10786. :session_path => "/"
  10787. } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
  10788. def initialize(cgi, session_options = {})
  10789. @cgi = cgi
  10790. @session_options = session_options
  10791. @env = @cgi.send(:env_table)
  10792. super()
  10793. end
  10794. def query_string
  10795. if (qs = @cgi.query_string) && !qs.empty?
  10796. qs
  10797. elsif uri = @env['REQUEST_URI']
  10798. parts = uri.split('?')
  10799. parts.shift
  10800. parts.join('?')
  10801. else
  10802. @env['QUERY_STRING'] || ''
  10803. end
  10804. end
  10805. def query_parameters
  10806. (qs = self.query_string).empty? ? {} : CGIMethods.parse_query_parameters(qs)
  10807. end
  10808. def request_parameters
  10809. @request_parameters ||=
  10810. if ActionController::Base.param_parsers.has_key?(content_type)
  10811. CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])
  10812. else
  10813. CGIMethods.parse_request_parameters(@cgi.params)
  10814. end
  10815. end
  10816. def cookies
  10817. @cgi.cookies.freeze
  10818. end
  10819. def host_with_port
  10820. if forwarded = env["HTTP_X_FORWARDED_HOST"]
  10821. forwarded.split(/,\s?/).last
  10822. elsif http_host = env['HTTP_HOST']
  10823. http_host
  10824. elsif server_name = env['SERVER_NAME']
  10825. server_name
  10826. else
  10827. "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
  10828. end
  10829. end
  10830. def host
  10831. host_with_port[/^[^:]+/]
  10832. end
  10833. def port
  10834. if host_with_port =~ /:(\d+)$/
  10835. $1.to_i
  10836. else
  10837. standard_port
  10838. end
  10839. end
  10840. def session
  10841. unless @session
  10842. if @session_options == false
  10843. @session = Hash.new
  10844. else
  10845. stale_session_check! do
  10846. if session_options_with_string_keys['new_session'] == true
  10847. @session = new_session
  10848. else
  10849. @session = CGI::Session.new(@cgi, session_options_with_string_keys)
  10850. end
  10851. @session['__valid_session']
  10852. end
  10853. end
  10854. end
  10855. @session
  10856. end
  10857. def reset_session
  10858. @session.delete if CGI::Session === @session
  10859. @session = new_session
  10860. end
  10861. def method_missing(method_id, *arguments)
  10862. @cgi.send(method_id, *arguments) rescue super
  10863. end
  10864. private
  10865. # Delete an old session if it exists then create a new one.
  10866. def new_session
  10867. if @session_options == false
  10868. Hash.new
  10869. else
  10870. CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
  10871. CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
  10872. end
  10873. end
  10874. def stale_session_check!
  10875. yield
  10876. rescue ArgumentError => argument_error
  10877. if argument_error.message =~ %r{undefined class/module (\w+)}
  10878. begin
  10879. Module.const_missing($1)
  10880. rescue LoadError, NameError => const_error
  10881. raise ActionController::SessionRestoreError, <<end_msg
  10882. Session contains objects whose class definition isn\'t available.
  10883. Remember to require the classes for all objects kept in the session.
  10884. (Original exception: #{const_error.message} [#{const_error.class}])
  10885. end_msg
  10886. end
  10887. retry
  10888. else
  10889. raise
  10890. end
  10891. end
  10892. def session_options_with_string_keys
  10893. @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).inject({}) { |options, (k,v)| options[k.to_s] = v; options }
  10894. end
  10895. end
  10896. class CgiResponse < AbstractResponse #:nodoc:
  10897. def initialize(cgi)
  10898. @cgi = cgi
  10899. super()
  10900. end
  10901. def out(output = $stdout)
  10902. convert_content_type!(@headers)
  10903. output.binmode if output.respond_to?(:binmode)
  10904. output.sync = false if output.respond_to?(:sync=)
  10905. begin
  10906. output.write(@cgi.header(@headers))
  10907. if @cgi.send(:env_table)['REQUEST_METHOD'] == 'HEAD'
  10908. return
  10909. elsif @body.respond_to?(:call)
  10910. @body.call(self, output)
  10911. else
  10912. output.write(@body)
  10913. end
  10914. output.flush if output.respond_to?(:flush)
  10915. rescue Errno::EPIPE => e
  10916. # lost connection to the FCGI process -- ignore the output, then
  10917. end
  10918. end
  10919. private
  10920. def convert_content_type!(headers)
  10921. if header = headers.delete("Content-Type")
  10922. headers["type"] = header
  10923. end
  10924. if header = headers.delete("Content-type")
  10925. headers["type"] = header
  10926. end
  10927. if header = headers.delete("content-type")
  10928. headers["type"] = header
  10929. end
  10930. end
  10931. end
  10932. end
  10933. module ActionController
  10934. module CodeGeneration #:nodoc:
  10935. class GenerationError < StandardError #:nodoc:
  10936. end
  10937. class Source #:nodoc:
  10938. attr_reader :lines, :indentation_level
  10939. IndentationString = ' '
  10940. def initialize
  10941. @lines, @indentation_level = [], 0
  10942. end
  10943. def line(line)
  10944. @lines << (IndentationString * @indentation_level + line)
  10945. end
  10946. alias :<< :line
  10947. def indent
  10948. @indentation_level += 1
  10949. yield
  10950. ensure
  10951. @indentation_level -= 1
  10952. end
  10953. def to_s() lines.join("\n") end
  10954. end
  10955. class CodeGenerator #:nodoc:
  10956. attr_accessor :source, :locals
  10957. def initialize(source = nil)
  10958. @locals = []
  10959. @source = source || Source.new
  10960. end
  10961. BeginKeywords = %w(if unless begin until while def).collect {|kw| kw.to_sym}
  10962. ResumeKeywords = %w(elsif else rescue).collect {|kw| kw.to_sym}
  10963. Keywords = BeginKeywords + ResumeKeywords
  10964. def method_missing(keyword, *text)
  10965. if Keywords.include? keyword
  10966. if ResumeKeywords.include? keyword
  10967. raise GenerationError, "Can only resume with #{keyword} immediately after an end" unless source.lines.last =~ /^\s*end\s*$/
  10968. source.lines.pop # Remove the 'end'
  10969. end
  10970. line "#{keyword} #{text.join ' '}"
  10971. begin source.indent { yield(self.dup) }
  10972. ensure line 'end'
  10973. end
  10974. else
  10975. super(keyword, *text)
  10976. end
  10977. end
  10978. def line(*args) self.source.line(*args) end
  10979. alias :<< :line
  10980. def indent(*args, &block) source(*args, &block) end
  10981. def to_s() source.to_s end
  10982. def share_locals_with(other)
  10983. other.locals = self.locals = (other.locals | locals)
  10984. end
  10985. FieldsToDuplicate = [:locals]
  10986. def dup
  10987. copy = self.class.new(source)
  10988. self.class::FieldsToDuplicate.each do |sym|
  10989. value = self.send(sym)
  10990. value = value.dup unless value.nil? || value.is_a?(Numeric)
  10991. copy.send("#{sym}=", value)
  10992. end
  10993. return copy
  10994. end
  10995. end
  10996. class RecognitionGenerator < CodeGenerator #:nodoc:
  10997. Attributes = [:after, :before, :current, :results, :constants, :depth, :move_ahead, :finish_statement]
  10998. attr_accessor(*Attributes)
  10999. FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
  11000. def initialize(*args)
  11001. super(*args)
  11002. @after, @before = [], []
  11003. @current = nil
  11004. @results, @constants = {}, {}
  11005. @depth = 0
  11006. @move_ahead = nil
  11007. @finish_statement = Proc.new {|hash_expr| hash_expr}
  11008. end
  11009. def if_next_matches(string, &block)
  11010. test = Routing.test_condition(next_segment(true), string)
  11011. self.if(test, &block)
  11012. end
  11013. def move_forward(places = 1)
  11014. dup = self.dup
  11015. dup.depth += 1
  11016. dup.move_ahead = places
  11017. yield dup
  11018. end
  11019. def next_segment(assign_inline = false, default = nil)
  11020. if locals.include?(segment_name)
  11021. code = segment_name
  11022. else
  11023. code = "#{segment_name} = #{path_name}[#{index_name}]"
  11024. if assign_inline
  11025. code = "(#{code})"
  11026. else
  11027. line(code)
  11028. code = segment_name
  11029. end
  11030. locals << segment_name
  11031. end
  11032. code = "(#{code} || #{default.inspect})" if default
  11033. return code.to_s
  11034. end
  11035. def segment_name() "segment#{depth}".to_sym end
  11036. def path_name() :path end
  11037. def index_name
  11038. move_ahead, @move_ahead = @move_ahead, nil
  11039. move_ahead ? "index += #{move_ahead}" : 'index'
  11040. end
  11041. def continue
  11042. dup = self.dup
  11043. dup.before << dup.current
  11044. dup.current = dup.after.shift
  11045. dup.go
  11046. end
  11047. def go
  11048. if current then current.write_recognition(self)
  11049. else self.finish
  11050. end
  11051. end
  11052. def result(key, expression, delay = false)
  11053. unless delay
  11054. line "#{key}_value = #{expression}"
  11055. expression = "#{key}_value"
  11056. end
  11057. results[key] = expression
  11058. end
  11059. def constant_result(key, object)
  11060. constants[key] = object
  11061. end
  11062. def finish(ensure_traversal_finished = true)
  11063. pairs = []
  11064. (results.keys + constants.keys).uniq.each do |key|
  11065. pairs << "#{key.to_s.inspect} => #{results[key] ? results[key] : constants[key].inspect}"
  11066. end
  11067. hash_expr = "{#{pairs.join(', ')}}"
  11068. statement = finish_statement.call(hash_expr)
  11069. if ensure_traversal_finished then self.if("! #{next_segment(true)}") {|gp| gp << statement}
  11070. else self << statement
  11071. end
  11072. end
  11073. end
  11074. class GenerationGenerator < CodeGenerator #:nodoc:
  11075. Attributes = [:after, :before, :current, :segments]
  11076. attr_accessor(*Attributes)
  11077. FieldsToDuplicate = CodeGenerator::FieldsToDuplicate + Attributes
  11078. def initialize(*args)
  11079. super(*args)
  11080. @after, @before = [], []
  11081. @current = nil
  11082. @segments = []
  11083. end
  11084. def hash_name() 'hash' end
  11085. def local_name(key) "#{key}_value" end
  11086. def hash_value(key, assign = true, default = nil)
  11087. if locals.include?(local_name(key)) then code = local_name(key)
  11088. else
  11089. code = "hash[#{key.to_sym.inspect}]"
  11090. if assign
  11091. code = "(#{local_name(key)} = #{code})"
  11092. locals << local_name(key)
  11093. end
  11094. end
  11095. code = "(#{code} || (#{default.inspect}))" if default
  11096. return code
  11097. end
  11098. def expire_for_keys(*keys)
  11099. return if keys.empty?
  11100. conds = keys.collect {|key| "expire_on[#{key.to_sym.inspect}]"}
  11101. line "not_expired, #{hash_name} = false, options if not_expired && #{conds.join(' && ')}"
  11102. end
  11103. def add_segment(*segments)
  11104. d = dup
  11105. d.segments.concat segments
  11106. yield d
  11107. end
  11108. def go
  11109. if current then current.write_generation(self)
  11110. else self.finish
  11111. end
  11112. end
  11113. def continue
  11114. d = dup
  11115. d.before << d.current
  11116. d.current = d.after.shift
  11117. d.go
  11118. end
  11119. def finish
  11120. line %("/#{segments.join('/')}")
  11121. end
  11122. def check_conditions(conditions)
  11123. tests = []
  11124. generator = nil
  11125. conditions.each do |key, condition|
  11126. tests << (generator || self).hash_value(key, true) if condition.is_a? Regexp
  11127. tests << Routing.test_condition((generator || self).hash_value(key, false), condition)
  11128. generator = self.dup unless generator
  11129. end
  11130. return tests.join(' && ')
  11131. end
  11132. end
  11133. end
  11134. end
  11135. module ActionController #:nodoc:
  11136. # Components allow you to call other actions for their rendered response while executing another action. You can either delegate
  11137. # the entire response rendering or you can mix a partial response in with your other content.
  11138. #
  11139. # class WeblogController < ActionController::Base
  11140. # # Performs a method and then lets hello_world output its render
  11141. # def delegate_action
  11142. # do_other_stuff_before_hello_world
  11143. # render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" }
  11144. # end
  11145. # end
  11146. #
  11147. # class GreeterController < ActionController::Base
  11148. # def hello_world
  11149. # render :text => "#{params[:person]} says, Hello World!"
  11150. # end
  11151. # end
  11152. #
  11153. # The same can be done in a view to do a partial rendering:
  11154. #
  11155. # Let's see a greeting:
  11156. # <%= render_component :controller => "greeter", :action => "hello_world" %>
  11157. #
  11158. # It is also possible to specify the controller as a class constant, bypassing the inflector
  11159. # code to compute the controller class at runtime:
  11160. #
  11161. # <%= render_component :controller => GreeterController, :action => "hello_world" %>
  11162. #
  11163. # == When to use components
  11164. #
  11165. # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and
  11166. # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead,
  11167. # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed
  11168. # across many applications at once.
  11169. #
  11170. # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters.
  11171. module Components
  11172. def self.included(base) #:nodoc:
  11173. base.send :include, InstanceMethods
  11174. base.extend(ClassMethods)
  11175. base.helper do
  11176. def render_component(options)
  11177. @controller.send(:render_component_as_string, options)
  11178. end
  11179. end
  11180. # If this controller was instantiated to process a component request,
  11181. # +parent_controller+ points to the instantiator of this controller.
  11182. base.send :attr_accessor, :parent_controller
  11183. base.class_eval do
  11184. alias_method :process_cleanup_without_components, :process_cleanup
  11185. alias_method :process_cleanup, :process_cleanup_with_components
  11186. alias_method :set_session_options_without_components, :set_session_options
  11187. alias_method :set_session_options, :set_session_options_with_components
  11188. alias_method :flash_without_components, :flash
  11189. alias_method :flash, :flash_with_components
  11190. alias_method :component_request?, :parent_controller
  11191. end
  11192. end
  11193. module ClassMethods
  11194. # Track parent controller to identify component requests
  11195. def process_with_components(request, response, parent_controller = nil) #:nodoc:
  11196. controller = new
  11197. controller.parent_controller = parent_controller
  11198. controller.process(request, response)
  11199. end
  11200. # Set the template root to be one directory behind the root dir of the controller. Examples:
  11201. # /code/weblog/components/admin/users_controller.rb with Admin::UsersController
  11202. # will use /code/weblog/components as template root
  11203. # and find templates in /code/weblog/components/admin/users/
  11204. #
  11205. # /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController
  11206. # will also use /code/weblog/components as template root
  11207. # and find templates in /code/weblog/components/admin/parties/users/
  11208. def uses_component_template_root
  11209. path_of_calling_controller = File.dirname(caller[0].split(/:\d+:/).first)
  11210. path_of_controller_root = path_of_calling_controller.sub(/#{controller_path.split("/")[0..-2]}$/, "") # " (for ruby-mode)
  11211. self.template_root = path_of_controller_root
  11212. end
  11213. end
  11214. module InstanceMethods
  11215. # Extracts the action_name from the request parameters and performs that action.
  11216. def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
  11217. flash.discard if component_request?
  11218. process_without_components(request, response, method, *arguments)
  11219. end
  11220. protected
  11221. # Renders the component specified as the response for the current method
  11222. def render_component(options) #:doc:
  11223. component_logging(options) do
  11224. render_text(component_response(options, true).body, response.headers["Status"])
  11225. end
  11226. end
  11227. # Returns the component response as a string
  11228. def render_component_as_string(options) #:doc:
  11229. component_logging(options) do
  11230. response = component_response(options, false)
  11231. if redirected = response.redirected_to
  11232. render_component_as_string(redirected)
  11233. else
  11234. response.body
  11235. end
  11236. end
  11237. end
  11238. def flash_with_components(refresh = false) #:nodoc:
  11239. if @flash.nil? || refresh
  11240. @flash =
  11241. if @parent_controller
  11242. @parent_controller.flash
  11243. else
  11244. flash_without_components
  11245. end
  11246. end
  11247. @flash
  11248. end
  11249. private
  11250. def component_response(options, reuse_response)
  11251. klass = component_class(options)
  11252. request = request_for_component(klass.controller_name, options)
  11253. response = reuse_response ? @response : @response.dup
  11254. klass.process_with_components(request, response, self)
  11255. end
  11256. # determine the controller class for the component request
  11257. def component_class(options)
  11258. if controller = options[:controller]
  11259. controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
  11260. else
  11261. self.class
  11262. end
  11263. end
  11264. # Create a new request object based on the current request.
  11265. # The new request inherits the session from the current request,
  11266. # bypassing any session options set for the component controller's class
  11267. def request_for_component(controller_name, options)
  11268. request = @request.dup
  11269. request.session = @request.session
  11270. request.instance_variable_set(
  11271. :@parameters,
  11272. (options[:params] || {}).with_indifferent_access.update(
  11273. "controller" => controller_name, "action" => options[:action], "id" => options[:id]
  11274. )
  11275. )
  11276. request
  11277. end
  11278. def component_logging(options)
  11279. if logger
  11280. logger.info "Start rendering component (#{options.inspect}): "
  11281. result = yield
  11282. logger.info "\n\nEnd of component rendering"
  11283. result
  11284. else
  11285. yield
  11286. end
  11287. end
  11288. def set_session_options_with_components(request)
  11289. set_session_options_without_components(request) unless component_request?
  11290. end
  11291. def process_cleanup_with_components
  11292. process_cleanup_without_components unless component_request?
  11293. end
  11294. end
  11295. end
  11296. end
  11297. module ActionController #:nodoc:
  11298. # Cookies are read and written through ActionController#cookies. The cookies being read are what were received along with the request,
  11299. # the cookies being written are what will be sent out with the response. Cookies are read by value (so you won't get the cookie object
  11300. # itself back -- just the value it holds). Examples for writing:
  11301. #
  11302. # cookies[:user_name] = "david" # => Will set a simple session cookie
  11303. # cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
  11304. #
  11305. # Examples for reading:
  11306. #
  11307. # cookies[:user_name] # => "david"
  11308. # cookies.size # => 2
  11309. #
  11310. # Example for deleting:
  11311. #
  11312. # cookies.delete :user_name
  11313. #
  11314. # All the option symbols for setting cookies are:
  11315. #
  11316. # * <tt>value</tt> - the cookie's value or list of values (as an array).
  11317. # * <tt>path</tt> - the path for which this cookie applies. Defaults to the root of the application.
  11318. # * <tt>domain</tt> - the domain for which this cookie applies.
  11319. # * <tt>expires</tt> - the time at which this cookie expires, as a +Time+ object.
  11320. # * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
  11321. # Secure cookies are only transmitted to HTTPS servers.
  11322. module Cookies
  11323. protected
  11324. # Returns the cookie container, which operates as described above.
  11325. def cookies
  11326. CookieJar.new(self)
  11327. end
  11328. # Deprecated cookie writer method
  11329. def cookie(*options)
  11330. @response.headers["cookie"] << CGI::Cookie.new(*options)
  11331. end
  11332. end
  11333. class CookieJar < Hash #:nodoc:
  11334. def initialize(controller)
  11335. @controller, @cookies = controller, controller.instance_variable_get("@cookies")
  11336. super()
  11337. update(@cookies)
  11338. end
  11339. # Returns the value of the cookie by +name+ -- or nil if no such cookie exists. You set new cookies using either the cookie method
  11340. # or cookies[]= (for simple name/value cookies without options).
  11341. def [](name)
  11342. @cookies[name.to_s].value.first if @cookies[name.to_s] && @cookies[name.to_s].respond_to?(:value)
  11343. end
  11344. def []=(name, options)
  11345. if options.is_a?(Hash)
  11346. options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options }
  11347. options["name"] = name.to_s
  11348. else
  11349. options = { "name" => name.to_s, "value" => options }
  11350. end
  11351. set_cookie(options)
  11352. end
  11353. # Removes the cookie on the client machine by setting the value to an empty string
  11354. # and setting its expiration date into the past
  11355. def delete(name)
  11356. set_cookie("name" => name.to_s, "value" => "", "expires" => Time.at(0))
  11357. end
  11358. private
  11359. def set_cookie(options) #:doc:
  11360. options["path"] = "/" unless options["path"]
  11361. cookie = CGI::Cookie.new(options)
  11362. @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil?
  11363. @controller.response.headers["cookie"] << cookie
  11364. end
  11365. end
  11366. end
  11367. module ActionController #:nodoc:
  11368. module Dependencies #:nodoc:
  11369. def self.append_features(base)
  11370. super
  11371. base.extend(ClassMethods)
  11372. end
  11373. # Dependencies control what classes are needed for the controller to run its course. This is an alternative to doing explicit
  11374. # +require+ statements that bring a number of benefits. It's more succinct, communicates what type of dependency we're talking about,
  11375. # can trigger special behavior (as in the case of +observer+), and enables Rails to be clever about reloading in cached environments
  11376. # like FCGI. Example:
  11377. #
  11378. # class ApplicationController < ActionController::Base
  11379. # model :account, :company, :person, :project, :category
  11380. # helper :access_control
  11381. # service :notifications, :billings
  11382. # observer :project_change_observer
  11383. # end
  11384. #
  11385. # Please note that a controller like ApplicationController will automatically attempt to require_dependency on a model of its
  11386. # singuralized name and a helper of its name. If nothing is found, no error is raised. This is especially useful for concrete
  11387. # controllers like PostController:
  11388. #
  11389. # class PostController < ApplicationController
  11390. # # model :post (already required)
  11391. # # helper :post (already required)
  11392. # end
  11393. #
  11394. # Also note, that if the models follow the pattern of just 1 class per file in the form of MyClass => my_class.rb, then these
  11395. # classes don't have to be required as Active Support will auto-require them.
  11396. module ClassMethods #:nodoc:
  11397. # Specifies a variable number of models that this controller depends on. Models are normally Active Record classes or a similar
  11398. # backend for modelling entity classes.
  11399. def model(*models)
  11400. require_dependencies(:model, models)
  11401. depend_on(:model, models)
  11402. end
  11403. # Specifies a variable number of services that this controller depends on. Services are normally singletons or factories, like
  11404. # Action Mailer service or a Payment Gateway service.
  11405. def service(*services)
  11406. require_dependencies(:service, services)
  11407. depend_on(:service, services)
  11408. end
  11409. # Specifies a variable number of observers that are to govern when this controller is handling actions. The observers will
  11410. # automatically have .instance called on them to make them active on assignment.
  11411. def observer(*observers)
  11412. require_dependencies(:observer, observers)
  11413. depend_on(:observer, observers)
  11414. instantiate_observers(observers)
  11415. end
  11416. # Returns an array of symbols that specify the dependencies on a given layer. For the example at the top, calling
  11417. # <tt>ApplicationController.dependencies_on(:model)</tt> would return <tt>[:account, :company, :person, :project, :category]</tt>
  11418. def dependencies_on(layer)
  11419. read_inheritable_attribute("#{layer}_dependencies")
  11420. end
  11421. def depend_on(layer, dependencies) #:nodoc:
  11422. write_inheritable_array("#{layer}_dependencies", dependencies)
  11423. end
  11424. private
  11425. def instantiate_observers(observers)
  11426. observers.flatten.each { |observer| Object.const_get(Inflector.classify(observer.to_s)).instance }
  11427. end
  11428. def require_dependencies(layer, dependencies)
  11429. dependencies.flatten.each do |dependency|
  11430. begin
  11431. require_dependency(dependency.to_s)
  11432. rescue LoadError => e
  11433. raise LoadError.new("Missing #{layer} #{dependency}.rb").copy_blame!(e)
  11434. rescue Object => exception
  11435. exception.blame_file! "=> #{layer} #{dependency}.rb"
  11436. raise
  11437. end
  11438. end
  11439. end
  11440. end
  11441. end
  11442. end
  11443. require 'test/unit'
  11444. require 'test/unit/assertions'
  11445. require 'rexml/document'
  11446. module Test #:nodoc:
  11447. module Unit #:nodoc:
  11448. module Assertions
  11449. def assert_success(message=nil) #:nodoc:
  11450. assert_response(:success, message)
  11451. end
  11452. def assert_redirect(message=nil) #:nodoc:
  11453. assert_response(:redirect, message)
  11454. end
  11455. def assert_rendered_file(expected=nil, message=nil) #:nodoc:
  11456. assert_template(expected, message)
  11457. end
  11458. # ensure that the session has an object with the specified name
  11459. def assert_session_has(key=nil, message=nil) #:nodoc:
  11460. msg = build_message(message, "<?> is not in the session <?>", key, @response.session)
  11461. assert_block(msg) { @response.has_session_object?(key) }
  11462. end
  11463. # ensure that the session has no object with the specified name
  11464. def assert_session_has_no(key=nil, message=nil) #:nodoc:
  11465. msg = build_message(message, "<?> is in the session <?>", key, @response.session)
  11466. assert_block(msg) { !@response.has_session_object?(key) }
  11467. end
  11468. def assert_session_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11469. msg = build_message(message, "<?> expected in session['?'] but was <?>", expected, key, @response.session[key])
  11470. assert_block(msg) { expected == @response.session[key] }
  11471. end
  11472. # -- cookie assertions ---------------------------------------------------
  11473. def assert_no_cookie(key = nil, message = nil) #:nodoc:
  11474. actual = @response.cookies[key]
  11475. msg = build_message(message, "<?> not expected in cookies['?']", actual, key)
  11476. assert_block(msg) { actual.nil? or actual.empty? }
  11477. end
  11478. def assert_cookie_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11479. actual = @response.cookies[key]
  11480. actual = actual.first if actual
  11481. msg = build_message(message, "<?> expected in cookies['?'] but was <?>", expected, key, actual)
  11482. assert_block(msg) { expected == actual }
  11483. end
  11484. # -- flash assertions ---------------------------------------------------
  11485. # ensure that the flash has an object with the specified name
  11486. def assert_flash_has(key=nil, message=nil) #:nodoc:
  11487. msg = build_message(message, "<?> is not in the flash <?>", key, @response.flash)
  11488. assert_block(msg) { @response.has_flash_object?(key) }
  11489. end
  11490. # ensure that the flash has no object with the specified name
  11491. def assert_flash_has_no(key=nil, message=nil) #:nodoc:
  11492. msg = build_message(message, "<?> is in the flash <?>", key, @response.flash)
  11493. assert_block(msg) { !@response.has_flash_object?(key) }
  11494. end
  11495. # ensure the flash exists
  11496. def assert_flash_exists(message=nil) #:nodoc:
  11497. msg = build_message(message, "the flash does not exist <?>", @response.session['flash'] )
  11498. assert_block(msg) { @response.has_flash? }
  11499. end
  11500. # ensure the flash does not exist
  11501. def assert_flash_not_exists(message=nil) #:nodoc:
  11502. msg = build_message(message, "the flash exists <?>", @response.flash)
  11503. assert_block(msg) { !@response.has_flash? }
  11504. end
  11505. # ensure the flash is empty but existent
  11506. def assert_flash_empty(message=nil) #:nodoc:
  11507. msg = build_message(message, "the flash is not empty <?>", @response.flash)
  11508. assert_block(msg) { !@response.has_flash_with_contents? }
  11509. end
  11510. # ensure the flash is not empty
  11511. def assert_flash_not_empty(message=nil) #:nodoc:
  11512. msg = build_message(message, "the flash is empty")
  11513. assert_block(msg) { @response.has_flash_with_contents? }
  11514. end
  11515. def assert_flash_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11516. msg = build_message(message, "<?> expected in flash['?'] but was <?>", expected, key, @response.flash[key])
  11517. assert_block(msg) { expected == @response.flash[key] }
  11518. end
  11519. # ensure our redirection url is an exact match
  11520. def assert_redirect_url(url=nil, message=nil) #:nodoc:
  11521. assert_redirect(message)
  11522. msg = build_message(message, "<?> is not the redirected location <?>", url, @response.redirect_url)
  11523. assert_block(msg) { @response.redirect_url == url }
  11524. end
  11525. # ensure our redirection url matches a pattern
  11526. def assert_redirect_url_match(pattern=nil, message=nil) #:nodoc:
  11527. assert_redirect(message)
  11528. msg = build_message(message, "<?> was not found in the location: <?>", pattern, @response.redirect_url)
  11529. assert_block(msg) { @response.redirect_url_match?(pattern) }
  11530. end
  11531. # -- template assertions ------------------------------------------------
  11532. # ensure that a template object with the given name exists
  11533. def assert_template_has(key=nil, message=nil) #:nodoc:
  11534. msg = build_message(message, "<?> is not a template object", key )
  11535. assert_block(msg) { @response.has_template_object?(key) }
  11536. end
  11537. # ensure that a template object with the given name does not exist
  11538. def assert_template_has_no(key=nil,message=nil) #:nodoc:
  11539. msg = build_message(message, "<?> is a template object <?>", key, @response.template_objects[key])
  11540. assert_block(msg) { !@response.has_template_object?(key) }
  11541. end
  11542. # ensures that the object assigned to the template on +key+ is equal to +expected+ object.
  11543. def assert_template_equal(expected = nil, key = nil, message = nil) #:nodoc:
  11544. msg = build_message(message, "<?> expected in assigns['?'] but was <?>", expected, key, @response.template.assigns[key.to_s])
  11545. assert_block(msg) { expected == @response.template.assigns[key.to_s] }
  11546. end
  11547. alias_method :assert_assigned_equal, :assert_template_equal
  11548. # Asserts that the template returns the +expected+ string or array based on the XPath +expression+.
  11549. # This will only work if the template rendered a valid XML document.
  11550. def assert_template_xpath_match(expression=nil, expected=nil, message=nil) #:nodoc:
  11551. xml, matches = REXML::Document.new(@response.body), []
  11552. xml.elements.each(expression) { |e| matches << e.text }
  11553. if matches.empty? then
  11554. msg = build_message(message, "<?> not found in document", expression)
  11555. flunk(msg)
  11556. return
  11557. elsif matches.length < 2 then
  11558. matches = matches.first
  11559. end
  11560. msg = build_message(message, "<?> found <?>, not <?>", expression, matches, expected)
  11561. assert_block(msg) { matches == expected }
  11562. end
  11563. # Assert the template object with the given name is an Active Record descendant and is valid.
  11564. def assert_valid_record(key = nil, message = nil) #:nodoc:
  11565. record = find_record_in_template(key)
  11566. msg = build_message(message, "Active Record is invalid <?>)", record.errors.full_messages)
  11567. assert_block(msg) { record.valid? }
  11568. end
  11569. # Assert the template object with the given name is an Active Record descendant and is invalid.
  11570. def assert_invalid_record(key = nil, message = nil) #:nodoc:
  11571. record = find_record_in_template(key)
  11572. msg = build_message(message, "Active Record is valid)")
  11573. assert_block(msg) { !record.valid? }
  11574. end
  11575. # Assert the template object with the given name is an Active Record descendant and the specified column(s) are valid.
  11576. def assert_valid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
  11577. record = find_record_in_template(key)
  11578. record.send(:validate)
  11579. cols = glue_columns(columns)
  11580. cols.delete_if { |col| !record.errors.invalid?(col) }
  11581. msg = build_message(message, "Active Record has invalid columns <?>)", cols.join(",") )
  11582. assert_block(msg) { cols.empty? }
  11583. end
  11584. # Assert the template object with the given name is an Active Record descendant and the specified column(s) are invalid.
  11585. def assert_invalid_column_on_record(key = nil, columns = "", message = nil) #:nodoc:
  11586. record = find_record_in_template(key)
  11587. record.send(:validate)
  11588. cols = glue_columns(columns)
  11589. cols.delete_if { |col| record.errors.invalid?(col) }
  11590. msg = build_message(message, "Active Record has valid columns <?>)", cols.join(",") )
  11591. assert_block(msg) { cols.empty? }
  11592. end
  11593. private
  11594. def glue_columns(columns)
  11595. cols = []
  11596. cols << columns if columns.class == String
  11597. cols += columns if columns.class == Array
  11598. cols
  11599. end
  11600. def find_record_in_template(key = nil)
  11601. assert_template_has(key)
  11602. record = @response.template_objects[key]
  11603. assert_not_nil(record)
  11604. assert_kind_of ActiveRecord::Base, record
  11605. return record
  11606. end
  11607. end
  11608. end
  11609. endmodule ActionController
  11610. class Base
  11611. protected
  11612. # Deprecated in favor of calling redirect_to directly with the path.
  11613. def redirect_to_path(path) #:nodoc:
  11614. redirect_to(path)
  11615. end
  11616. # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
  11617. # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
  11618. # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
  11619. def redirect_to_url(url, permanently = false) #:nodoc:
  11620. headers["Status"] = "301 Moved Permanently" if permanently
  11621. redirect_to(url)
  11622. end
  11623. end
  11624. end
  11625. module ActionController
  11626. class AbstractRequest
  11627. # Determine whether the body of a HTTP call is URL-encoded (default)
  11628. # or matches one of the registered param_parsers.
  11629. #
  11630. # For backward compatibility, the post format is extracted from the
  11631. # X-Post-Data-Format HTTP header if present.
  11632. def post_format
  11633. case content_type.to_s
  11634. when 'application/xml'
  11635. :xml
  11636. when 'application/x-yaml'
  11637. :yaml
  11638. else
  11639. :url_encoded
  11640. end
  11641. end
  11642. # Is this a POST request formatted as XML or YAML?
  11643. def formatted_post?
  11644. post? && (post_format == :yaml || post_format == :xml)
  11645. end
  11646. # Is this a POST request formatted as XML?
  11647. def xml_post?
  11648. post? && post_format == :xml
  11649. end
  11650. # Is this a POST request formatted as YAML?
  11651. def yaml_post?
  11652. post? && post_format == :yaml
  11653. end
  11654. end
  11655. end
  11656. module ActionController #:nodoc:
  11657. module Filters #:nodoc:
  11658. def self.included(base)
  11659. base.extend(ClassMethods)
  11660. base.send(:include, ActionController::Filters::InstanceMethods)
  11661. end
  11662. # Filters enable controllers to run shared pre and post processing code for its actions. These filters can be used to do
  11663. # authentication, caching, or auditing before the intended action is performed. Or to do localization or output
  11664. # compression after the action has been performed.
  11665. #
  11666. # Filters have access to the request, response, and all the instance variables set by other filters in the chain
  11667. # or by the action (in the case of after filters). Additionally, it's possible for a pre-processing <tt>before_filter</tt>
  11668. # to halt the processing before the intended action is processed by returning false or performing a redirect or render.
  11669. # This is especially useful for filters like authentication where you're not interested in allowing the action to be
  11670. # performed if the proper credentials are not in order.
  11671. #
  11672. # == Filter inheritance
  11673. #
  11674. # Controller inheritance hierarchies share filters downwards, but subclasses can also add new filters without
  11675. # affecting the superclass. For example:
  11676. #
  11677. # class BankController < ActionController::Base
  11678. # before_filter :audit
  11679. #
  11680. # private
  11681. # def audit
  11682. # # record the action and parameters in an audit log
  11683. # end
  11684. # end
  11685. #
  11686. # class VaultController < BankController
  11687. # before_filter :verify_credentials
  11688. #
  11689. # private
  11690. # def verify_credentials
  11691. # # make sure the user is allowed into the vault
  11692. # end
  11693. # end
  11694. #
  11695. # Now any actions performed on the BankController will have the audit method called before. On the VaultController,
  11696. # first the audit method is called, then the verify_credentials method. If the audit method returns false, then
  11697. # verify_credentials and the intended action are never called.
  11698. #
  11699. # == Filter types
  11700. #
  11701. # A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first
  11702. # is the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of
  11703. # the controller by use of a symbol. In the bank example above, both BankController and VaultController use this form.
  11704. #
  11705. # Using an external class makes for more easily reused generic filters, such as output compression. External filter classes
  11706. # are implemented by having a static +filter+ method on any class and then passing this class to the filter method. Example:
  11707. #
  11708. # class OutputCompressionFilter
  11709. # def self.filter(controller)
  11710. # controller.response.body = compress(controller.response.body)
  11711. # end
  11712. # end
  11713. #
  11714. # class NewspaperController < ActionController::Base
  11715. # after_filter OutputCompressionFilter
  11716. # end
  11717. #
  11718. # The filter method is passed the controller instance and is hence granted access to all aspects of the controller and can
  11719. # manipulate them as it sees fit.
  11720. #
  11721. # The inline method (using a proc) can be used to quickly do something small that doesn't require a lot of explanation.
  11722. # Or just as a quick test. It works like this:
  11723. #
  11724. # class WeblogController < ActionController::Base
  11725. # before_filter { |controller| false if controller.params["stop_action"] }
  11726. # end
  11727. #
  11728. # As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables.
  11729. # This means that the block has access to both the request and response objects complete with convenience methods for params,
  11730. # session, template, and assigns. Note: The inline method doesn't strictly have to be a block; any object that responds to call
  11731. # and returns 1 or -1 on arity will do (such as a Proc or an Method object).
  11732. #
  11733. # == Filter chain ordering
  11734. #
  11735. # Using <tt>before_filter</tt> and <tt>after_filter</tt> appends the specified filters to the existing chain. That's usually
  11736. # just fine, but some times you care more about the order in which the filters are executed. When that's the case, you
  11737. # can use <tt>prepend_before_filter</tt> and <tt>prepend_after_filter</tt>. Filters added by these methods will be put at the
  11738. # beginning of their respective chain and executed before the rest. For example:
  11739. #
  11740. # class ShoppingController
  11741. # before_filter :verify_open_shop
  11742. #
  11743. # class CheckoutController
  11744. # prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock
  11745. #
  11746. # The filter chain for the CheckoutController is now <tt>:ensure_items_in_cart, :ensure_items_in_stock,</tt>
  11747. # <tt>:verify_open_shop</tt>. So if either of the ensure filters return false, we'll never get around to see if the shop
  11748. # is open or not.
  11749. #
  11750. # You may pass multiple filter arguments of each type as well as a filter block.
  11751. # If a block is given, it is treated as the last argument.
  11752. #
  11753. # == Around filters
  11754. #
  11755. # In addition to the individual before and after filters, it's also possible to specify that a single object should handle
  11756. # both the before and after call. That's especially useful when you need to keep state active between the before and after,
  11757. # such as the example of a benchmark filter below:
  11758. #
  11759. # class WeblogController < ActionController::Base
  11760. # around_filter BenchmarkingFilter.new
  11761. #
  11762. # # Before this action is performed, BenchmarkingFilter#before(controller) is executed
  11763. # def index
  11764. # end
  11765. # # After this action has been performed, BenchmarkingFilter#after(controller) is executed
  11766. # end
  11767. #
  11768. # class BenchmarkingFilter
  11769. # def initialize
  11770. # @runtime
  11771. # end
  11772. #
  11773. # def before
  11774. # start_timer
  11775. # end
  11776. #
  11777. # def after
  11778. # stop_timer
  11779. # report_result
  11780. # end
  11781. # end
  11782. #
  11783. # == Filter chain skipping
  11784. #
  11785. # Some times its convenient to specify a filter chain in a superclass that'll hold true for the majority of the
  11786. # subclasses, but not necessarily all of them. The subclasses that behave in exception can then specify which filters
  11787. # they would like to be relieved of. Examples
  11788. #
  11789. # class ApplicationController < ActionController::Base
  11790. # before_filter :authenticate
  11791. # end
  11792. #
  11793. # class WeblogController < ApplicationController
  11794. # # will run the :authenticate filter
  11795. # end
  11796. #
  11797. # class SignupController < ApplicationController
  11798. # # will not run the :authenticate filter
  11799. # skip_before_filter :authenticate
  11800. # end
  11801. #
  11802. # == Filter conditions
  11803. #
  11804. # Filters can be limited to run for only specific actions. This can be expressed either by listing the actions to
  11805. # exclude or the actions to include when executing the filter. Available conditions are +:only+ or +:except+, both
  11806. # of which accept an arbitrary number of method references. For example:
  11807. #
  11808. # class Journal < ActionController::Base
  11809. # # only require authentication if the current action is edit or delete
  11810. # before_filter :authorize, :only => [ :edit, :delete ]
  11811. #
  11812. # private
  11813. # def authorize
  11814. # # redirect to login unless authenticated
  11815. # end
  11816. # end
  11817. #
  11818. # When setting conditions on inline method (proc) filters the condition must come first and be placed in parentheses.
  11819. #
  11820. # class UserPreferences < ActionController::Base
  11821. # before_filter(:except => :new) { # some proc ... }
  11822. # # ...
  11823. # end
  11824. #
  11825. module ClassMethods
  11826. # The passed <tt>filters</tt> will be appended to the array of filters that's run _before_ actions
  11827. # on this controller are performed.
  11828. def append_before_filter(*filters, &block)
  11829. conditions = extract_conditions!(filters)
  11830. filters << block if block_given?
  11831. add_action_conditions(filters, conditions)
  11832. append_filter_to_chain('before', filters)
  11833. end
  11834. # The passed <tt>filters</tt> will be prepended to the array of filters that's run _before_ actions
  11835. # on this controller are performed.
  11836. def prepend_before_filter(*filters, &block)
  11837. conditions = extract_conditions!(filters)
  11838. filters << block if block_given?
  11839. add_action_conditions(filters, conditions)
  11840. prepend_filter_to_chain('before', filters)
  11841. end
  11842. # Short-hand for append_before_filter since that's the most common of the two.
  11843. alias :before_filter :append_before_filter
  11844. # The passed <tt>filters</tt> will be appended to the array of filters that's run _after_ actions
  11845. # on this controller are performed.
  11846. def append_after_filter(*filters, &block)
  11847. conditions = extract_conditions!(filters)
  11848. filters << block if block_given?
  11849. add_action_conditions(filters, conditions)
  11850. append_filter_to_chain('after', filters)
  11851. end
  11852. # The passed <tt>filters</tt> will be prepended to the array of filters that's run _after_ actions
  11853. # on this controller are performed.
  11854. def prepend_after_filter(*filters, &block)
  11855. conditions = extract_conditions!(filters)
  11856. filters << block if block_given?
  11857. add_action_conditions(filters, conditions)
  11858. prepend_filter_to_chain("after", filters)
  11859. end
  11860. # Short-hand for append_after_filter since that's the most common of the two.
  11861. alias :after_filter :append_after_filter
  11862. # The passed <tt>filters</tt> will have their +before+ method appended to the array of filters that's run both before actions
  11863. # on this controller are performed and have their +after+ method prepended to the after actions. The filter objects must all
  11864. # respond to both +before+ and +after+. So if you do append_around_filter A.new, B.new, the callstack will look like:
  11865. #
  11866. # B#before
  11867. # A#before
  11868. # A#after
  11869. # B#after
  11870. def append_around_filter(*filters)
  11871. conditions = extract_conditions!(filters)
  11872. for filter in filters.flatten
  11873. ensure_filter_responds_to_before_and_after(filter)
  11874. append_before_filter(conditions || {}) { |c| filter.before(c) }
  11875. prepend_after_filter(conditions || {}) { |c| filter.after(c) }
  11876. end
  11877. end
  11878. # The passed <tt>filters</tt> will have their +before+ method prepended to the array of filters that's run both before actions
  11879. # on this controller are performed and have their +after+ method appended to the after actions. The filter objects must all
  11880. # respond to both +before+ and +after+. So if you do prepend_around_filter A.new, B.new, the callstack will look like:
  11881. #
  11882. # A#before
  11883. # B#before
  11884. # B#after
  11885. # A#after
  11886. def prepend_around_filter(*filters)
  11887. for filter in filters.flatten
  11888. ensure_filter_responds_to_before_and_after(filter)
  11889. prepend_before_filter { |c| filter.before(c) }
  11890. append_after_filter { |c| filter.after(c) }
  11891. end
  11892. end
  11893. # Short-hand for append_around_filter since that's the most common of the two.
  11894. alias :around_filter :append_around_filter
  11895. # Removes the specified filters from the +before+ filter chain. Note that this only works for skipping method-reference
  11896. # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
  11897. # of many sub-controllers need a different hierarchy.
  11898. #
  11899. # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
  11900. # just like when you apply the filters.
  11901. def skip_before_filter(*filters)
  11902. if conditions = extract_conditions!(filters)
  11903. remove_contradicting_conditions!(filters, conditions)
  11904. conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
  11905. add_action_conditions(filters, conditions)
  11906. else
  11907. for filter in filters.flatten
  11908. write_inheritable_attribute("before_filters", read_inheritable_attribute("before_filters") - [ filter ])
  11909. end
  11910. end
  11911. end
  11912. # Removes the specified filters from the +after+ filter chain. Note that this only works for skipping method-reference
  11913. # filters, not procs. This is especially useful for managing the chain in inheritance hierarchies where only one out
  11914. # of many sub-controllers need a different hierarchy.
  11915. #
  11916. # You can control the actions to skip the filter for with the <tt>:only</tt> and <tt>:except</tt> options,
  11917. # just like when you apply the filters.
  11918. def skip_after_filter(*filters)
  11919. if conditions = extract_conditions!(filters)
  11920. remove_contradicting_conditions!(filters, conditions)
  11921. conditions[:only], conditions[:except] = conditions[:except], conditions[:only]
  11922. add_action_conditions(filters, conditions)
  11923. else
  11924. for filter in filters.flatten
  11925. write_inheritable_attribute("after_filters", read_inheritable_attribute("after_filters") - [ filter ])
  11926. end
  11927. end
  11928. end
  11929. # Returns all the before filters for this class and all its ancestors.
  11930. def before_filters #:nodoc:
  11931. @before_filters ||= read_inheritable_attribute("before_filters") || []
  11932. end
  11933. # Returns all the after filters for this class and all its ancestors.
  11934. def after_filters #:nodoc:
  11935. @after_filters ||= read_inheritable_attribute("after_filters") || []
  11936. end
  11937. # Returns a mapping between filters and the actions that may run them.
  11938. def included_actions #:nodoc:
  11939. @included_actions ||= read_inheritable_attribute("included_actions") || {}
  11940. end
  11941. # Returns a mapping between filters and actions that may not run them.
  11942. def excluded_actions #:nodoc:
  11943. @excluded_actions ||= read_inheritable_attribute("excluded_actions") || {}
  11944. end
  11945. private
  11946. def append_filter_to_chain(condition, filters)
  11947. write_inheritable_array("#{condition}_filters", filters)
  11948. end
  11949. def prepend_filter_to_chain(condition, filters)
  11950. old_filters = read_inheritable_attribute("#{condition}_filters") || []
  11951. write_inheritable_attribute("#{condition}_filters", filters + old_filters)
  11952. end
  11953. def ensure_filter_responds_to_before_and_after(filter)
  11954. unless filter.respond_to?(:before) && filter.respond_to?(:after)
  11955. raise ActionControllerError, "Filter object must respond to both before and after"
  11956. end
  11957. end
  11958. def extract_conditions!(filters)
  11959. return nil unless filters.last.is_a? Hash
  11960. filters.pop
  11961. end
  11962. def add_action_conditions(filters, conditions)
  11963. return unless conditions
  11964. included, excluded = conditions[:only], conditions[:except]
  11965. write_inheritable_hash('included_actions', condition_hash(filters, included)) && return if included
  11966. write_inheritable_hash('excluded_actions', condition_hash(filters, excluded)) if excluded
  11967. end
  11968. def condition_hash(filters, *actions)
  11969. filters.inject({}) {|hash, filter| hash.merge(filter => actions.flatten.map {|action| action.to_s})}
  11970. end
  11971. def remove_contradicting_conditions!(filters, conditions)
  11972. return unless conditions[:only]
  11973. filters.each do |filter|
  11974. next unless included_actions_for_filter = (read_inheritable_attribute('included_actions') || {})[filter]
  11975. [*conditions[:only]].each do |conditional_action|
  11976. conditional_action = conditional_action.to_s
  11977. included_actions_for_filter.delete(conditional_action) if included_actions_for_filter.include?(conditional_action)
  11978. end
  11979. end
  11980. end
  11981. end
  11982. module InstanceMethods # :nodoc:
  11983. def self.included(base)
  11984. base.class_eval do
  11985. alias_method :perform_action_without_filters, :perform_action
  11986. alias_method :perform_action, :perform_action_with_filters
  11987. alias_method :process_without_filters, :process
  11988. alias_method :process, :process_with_filters
  11989. alias_method :process_cleanup_without_filters, :process_cleanup
  11990. alias_method :process_cleanup, :process_cleanup_with_filters
  11991. end
  11992. end
  11993. def perform_action_with_filters
  11994. before_action_result = before_action
  11995. unless before_action_result == false || performed?
  11996. perform_action_without_filters
  11997. after_action
  11998. end
  11999. @before_filter_chain_aborted = (before_action_result == false)
  12000. end
  12001. def process_with_filters(request, response, method = :perform_action, *arguments) #:nodoc:
  12002. @before_filter_chain_aborted = false
  12003. process_without_filters(request, response, method, *arguments)
  12004. end
  12005. # Calls all the defined before-filter filters, which are added by using "before_filter :method".
  12006. # If any of the filters return false, no more filters will be executed and the action is aborted.
  12007. def before_action #:doc:
  12008. call_filters(self.class.before_filters)
  12009. end
  12010. # Calls all the defined after-filter filters, which are added by using "after_filter :method".
  12011. # If any of the filters return false, no more filters will be executed.
  12012. def after_action #:doc:
  12013. call_filters(self.class.after_filters)
  12014. end
  12015. private
  12016. def call_filters(filters)
  12017. filters.each do |filter|
  12018. next if action_exempted?(filter)
  12019. filter_result = case
  12020. when filter.is_a?(Symbol)
  12021. self.send(filter)
  12022. when filter_block?(filter)
  12023. filter.call(self)
  12024. when filter_class?(filter)
  12025. filter.filter(self)
  12026. else
  12027. raise(
  12028. ActionControllerError,
  12029. 'Filters need to be either a symbol, proc/method, or class implementing a static filter method'
  12030. )
  12031. end
  12032. if filter_result == false
  12033. logger.info "Filter chain halted as [#{filter}] returned false" if logger
  12034. return false
  12035. end
  12036. end
  12037. end
  12038. def filter_block?(filter)
  12039. filter.respond_to?('call') && (filter.arity == 1 || filter.arity == -1)
  12040. end
  12041. def filter_class?(filter)
  12042. filter.respond_to?('filter')
  12043. end
  12044. def action_exempted?(filter)
  12045. case
  12046. when ia = self.class.included_actions[filter]
  12047. !ia.include?(action_name)
  12048. when ea = self.class.excluded_actions[filter]
  12049. ea.include?(action_name)
  12050. end
  12051. end
  12052. def process_cleanup_with_filters
  12053. if @before_filter_chain_aborted
  12054. close_session
  12055. else
  12056. process_cleanup_without_filters
  12057. end
  12058. end
  12059. end
  12060. end
  12061. end
  12062. module ActionController #:nodoc:
  12063. # The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed
  12064. # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create action
  12065. # that sets <tt>flash[:notice] = "Successfully created"</tt> before redirecting to a display action that can then expose
  12066. # the flash to its template. Actually, that exposure is automatically done. Example:
  12067. #
  12068. # class WeblogController < ActionController::Base
  12069. # def create
  12070. # # save post
  12071. # flash[:notice] = "Successfully created post"
  12072. # redirect_to :action => "display", :params => { :id => post.id }
  12073. # end
  12074. #
  12075. # def display
  12076. # # doesn't need to assign the flash notice to the template, that's done automatically
  12077. # end
  12078. # end
  12079. #
  12080. # display.rhtml
  12081. # <% if @flash[:notice] %><div class="notice"><%= @flash[:notice] %></div><% end %>
  12082. #
  12083. # This example just places a string in the flash, but you can put any object in there. And of course, you can put as many
  12084. # as you like at a time too. Just remember: They'll be gone by the time the next action has been performed.
  12085. #
  12086. # See docs on the FlashHash class for more details about the flash.
  12087. module Flash
  12088. def self.included(base)
  12089. base.send :include, InstanceMethods
  12090. base.class_eval do
  12091. alias_method :assign_shortcuts_without_flash, :assign_shortcuts
  12092. alias_method :assign_shortcuts, :assign_shortcuts_with_flash
  12093. alias_method :process_cleanup_without_flash, :process_cleanup
  12094. alias_method :process_cleanup, :process_cleanup_with_flash
  12095. end
  12096. end
  12097. class FlashNow #:nodoc:
  12098. def initialize(flash)
  12099. @flash = flash
  12100. end
  12101. def []=(k, v)
  12102. @flash[k] = v
  12103. @flash.discard(k)
  12104. v
  12105. end
  12106. def [](k)
  12107. @flash[k]
  12108. end
  12109. end
  12110. class FlashHash < Hash
  12111. def initialize #:nodoc:
  12112. super
  12113. @used = {}
  12114. end
  12115. def []=(k, v) #:nodoc:
  12116. keep(k)
  12117. super
  12118. end
  12119. def update(h) #:nodoc:
  12120. h.keys.each{ |k| discard(k) }
  12121. super
  12122. end
  12123. alias :merge! :update
  12124. def replace(h) #:nodoc:
  12125. @used = {}
  12126. super
  12127. end
  12128. # Sets a flash that will not be available to the next action, only to the current.
  12129. #
  12130. # flash.now[:message] = "Hello current action"
  12131. #
  12132. # This method enables you to use the flash as a central messaging system in your app.
  12133. # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
  12134. # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
  12135. # vanish when the current action is done.
  12136. #
  12137. # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
  12138. def now
  12139. FlashNow.new self
  12140. end
  12141. # Keeps either the entire current flash or a specific flash entry available for the next action:
  12142. #
  12143. # flash.keep # keeps the entire flash
  12144. # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
  12145. def keep(k=nil)
  12146. use(k, false)
  12147. end
  12148. # Marks the entire flash or a single flash entry to be discarded by the end of the current action
  12149. #
  12150. # flash.keep # keep entire flash available for the next action
  12151. # flash.discard(:warning) # discard the "warning" entry (it'll still be available for the current action)
  12152. def discard(k=nil)
  12153. use(k)
  12154. end
  12155. # Mark for removal entries that were kept, and delete unkept ones.
  12156. #
  12157. # This method is called automatically by filters, so you generally don't need to care about it.
  12158. def sweep #:nodoc:
  12159. keys.each do |k|
  12160. unless @used[k]
  12161. use(k)
  12162. else
  12163. delete(k)
  12164. @used.delete(k)
  12165. end
  12166. end
  12167. (@used.keys - keys).each{|k| @used.delete k } # clean up after keys that could have been left over by calling reject! or shift on the flash
  12168. end
  12169. private
  12170. # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods
  12171. # use() # marks the entire flash as used
  12172. # use('msg') # marks the "msg" entry as used
  12173. # use(nil, false) # marks the entire flash as unused (keeps it around for one more action)
  12174. # use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
  12175. def use(k=nil, v=true)
  12176. unless k.nil?
  12177. @used[k] = v
  12178. else
  12179. keys.each{|key| use key, v }
  12180. end
  12181. end
  12182. end
  12183. module InstanceMethods #:nodoc:
  12184. def assign_shortcuts_with_flash(request, response) #:nodoc:
  12185. assign_shortcuts_without_flash(request, response)
  12186. flash(:refresh)
  12187. end
  12188. def process_cleanup_with_flash
  12189. flash.sweep if @session
  12190. process_cleanup_without_flash
  12191. end
  12192. protected
  12193. # Access the contents of the flash. Use <tt>flash["notice"]</tt> to read a notice you put there or
  12194. # <tt>flash["notice"] = "hello"</tt> to put a new one.
  12195. # Note that if sessions are disabled only flash.now will work.
  12196. def flash(refresh = false) #:doc:
  12197. if @flash.nil? || refresh
  12198. @flash =
  12199. if @session.is_a?(Hash)
  12200. # @session is a Hash, if sessions are disabled
  12201. # we don't put the flash in the session in this case
  12202. FlashHash.new
  12203. else
  12204. # otherwise, @session is a CGI::Session or a TestSession
  12205. # so make sure it gets retrieved from/saved to session storage after request processing
  12206. @session["flash"] ||= FlashHash.new
  12207. end
  12208. end
  12209. @flash
  12210. end
  12211. # deprecated. use <tt>flash.keep</tt> instead
  12212. def keep_flash #:doc:
  12213. warn 'keep_flash is deprecated; use flash.keep instead.'
  12214. flash.keep
  12215. end
  12216. end
  12217. end
  12218. endmodule ActionController #:nodoc:
  12219. module Helpers #:nodoc:
  12220. def self.append_features(base)
  12221. super
  12222. # Initialize the base module to aggregate its helpers.
  12223. base.class_inheritable_accessor :master_helper_module
  12224. base.master_helper_module = Module.new
  12225. # Extend base with class methods to declare helpers.
  12226. base.extend(ClassMethods)
  12227. base.class_eval do
  12228. # Wrap inherited to create a new master helper module for subclasses.
  12229. class << self
  12230. alias_method :inherited_without_helper, :inherited
  12231. alias_method :inherited, :inherited_with_helper
  12232. end
  12233. end
  12234. end
  12235. # The template helpers serve to relieve the templates from including the same inline code again and again. It's a
  12236. # set of standardized methods for working with forms (FormHelper), dates (DateHelper), texts (TextHelper), and
  12237. # Active Records (ActiveRecordHelper) that's available to all templates by default.
  12238. #
  12239. # It's also really easy to make your own helpers and it's much encouraged to keep the template files free
  12240. # from complicated logic. It's even encouraged to bundle common compositions of methods from other helpers
  12241. # (often the common helpers) as they're used by the specific application.
  12242. #
  12243. # module MyHelper
  12244. # def hello_world() "hello world" end
  12245. # end
  12246. #
  12247. # MyHelper can now be included in a controller, like this:
  12248. #
  12249. # class MyController < ActionController::Base
  12250. # helper :my_helper
  12251. # end
  12252. #
  12253. # ...and, same as above, used in any template rendered from MyController, like this:
  12254. #
  12255. # Let's hear what the helper has to say: <tt><%= hello_world %></tt>
  12256. module ClassMethods
  12257. # Makes all the (instance) methods in the helper module available to templates rendered through this controller.
  12258. # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
  12259. # available to the templates.
  12260. def add_template_helper(helper_module) #:nodoc:
  12261. master_helper_module.send(:include, helper_module)
  12262. end
  12263. # Declare a helper:
  12264. # helper :foo
  12265. # requires 'foo_helper' and includes FooHelper in the template class.
  12266. # helper FooHelper
  12267. # includes FooHelper in the template class.
  12268. # helper { def foo() "#{bar} is the very best" end }
  12269. # evaluates the block in the template class, adding method #foo.
  12270. # helper(:three, BlindHelper) { def mice() 'mice' end }
  12271. # does all three.
  12272. def helper(*args, &block)
  12273. args.flatten.each do |arg|
  12274. case arg
  12275. when Module
  12276. add_template_helper(arg)
  12277. when String, Symbol
  12278. file_name = arg.to_s.underscore + '_helper'
  12279. class_name = file_name.camelize
  12280. begin
  12281. require_dependency(file_name)
  12282. rescue LoadError => load_error
  12283. requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
  12284. msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
  12285. raise LoadError.new(msg).copy_blame!(load_error)
  12286. end
  12287. add_template_helper(class_name.constantize)
  12288. else
  12289. raise ArgumentError, 'helper expects String, Symbol, or Module argument'
  12290. end
  12291. end
  12292. # Evaluate block in template class if given.
  12293. master_helper_module.module_eval(&block) if block_given?
  12294. end
  12295. # Declare a controller method as a helper. For example,
  12296. # helper_method :link_to
  12297. # def link_to(name, options) ... end
  12298. # makes the link_to controller method available in the view.
  12299. def helper_method(*methods)
  12300. methods.flatten.each do |method|
  12301. master_helper_module.module_eval <<-end_eval
  12302. def #{method}(*args, &block)
  12303. controller.send(%(#{method}), *args, &block)
  12304. end
  12305. end_eval
  12306. end
  12307. end
  12308. # Declare a controller attribute as a helper. For example,
  12309. # helper_attr :name
  12310. # attr_accessor :name
  12311. # makes the name and name= controller methods available in the view.
  12312. # The is a convenience wrapper for helper_method.
  12313. def helper_attr(*attrs)
  12314. attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
  12315. end
  12316. private
  12317. def default_helper_module!
  12318. module_name = name.sub(/Controller$|$/, 'Helper')
  12319. module_path = module_name.split('::').map { |m| m.underscore }.join('/')
  12320. require_dependency module_path
  12321. helper module_name.constantize
  12322. rescue LoadError
  12323. logger.debug("#{name}: missing default helper path #{module_path}") if logger
  12324. rescue NameError
  12325. logger.debug("#{name}: missing default helper module #{module_name}") if logger
  12326. end
  12327. def inherited_with_helper(child)
  12328. inherited_without_helper(child)
  12329. begin
  12330. child.master_helper_module = Module.new
  12331. child.master_helper_module.send :include, master_helper_module
  12332. child.send :default_helper_module!
  12333. rescue MissingSourceFile => e
  12334. raise unless e.is_missing?("helpers/#{child.controller_path}_helper")
  12335. end
  12336. end
  12337. end
  12338. end
  12339. end
  12340. require 'dispatcher'
  12341. require 'stringio'
  12342. require 'uri'
  12343. module ActionController
  12344. module Integration #:nodoc:
  12345. # An integration Session instance represents a set of requests and responses
  12346. # performed sequentially by some virtual user. Becase you can instantiate
  12347. # multiple sessions and run them side-by-side, you can also mimic (to some
  12348. # limited extent) multiple simultaneous users interacting with your system.
  12349. #
  12350. # Typically, you will instantiate a new session using IntegrationTest#open_session,
  12351. # rather than instantiating Integration::Session directly.
  12352. class Session
  12353. include Test::Unit::Assertions
  12354. include ActionController::TestProcess
  12355. # The integer HTTP status code of the last request.
  12356. attr_reader :status
  12357. # The status message that accompanied the status code of the last request.
  12358. attr_reader :status_message
  12359. # The URI of the last request.
  12360. attr_reader :path
  12361. # The hostname used in the last request.
  12362. attr_accessor :host
  12363. # The remote_addr used in the last request.
  12364. attr_accessor :remote_addr
  12365. # The Accept header to send.
  12366. attr_accessor :accept
  12367. # A map of the cookies returned by the last response, and which will be
  12368. # sent with the next request.
  12369. attr_reader :cookies
  12370. # A map of the headers returned by the last response.
  12371. attr_reader :headers
  12372. # A reference to the controller instance used by the last request.
  12373. attr_reader :controller
  12374. # A reference to the request instance used by the last request.
  12375. attr_reader :request
  12376. # A reference to the response instance used by the last request.
  12377. attr_reader :response
  12378. # Create an initialize a new Session instance.
  12379. def initialize
  12380. reset!
  12381. end
  12382. # Resets the instance. This can be used to reset the state information
  12383. # in an existing session instance, so it can be used from a clean-slate
  12384. # condition.
  12385. #
  12386. # session.reset!
  12387. def reset!
  12388. @status = @path = @headers = nil
  12389. @result = @status_message = nil
  12390. @https = false
  12391. @cookies = {}
  12392. @controller = @request = @response = nil
  12393. self.host = "www.example.com"
  12394. self.remote_addr = "127.0.0.1"
  12395. self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
  12396. unless @named_routes_configured
  12397. # install the named routes in this session instance.
  12398. klass = class<<self; self; end
  12399. Routing::NamedRoutes.install(klass)
  12400. # the helpers are made protected by default--we make them public for
  12401. # easier access during testing and troubleshooting.
  12402. klass.send(:public, *Routing::NamedRoutes::Helpers)
  12403. @named_routes_configured = true
  12404. end
  12405. end
  12406. # Specify whether or not the session should mimic a secure HTTPS request.
  12407. #
  12408. # session.https!
  12409. # session.https!(false)
  12410. def https!(flag=true)
  12411. @https = flag
  12412. end
  12413. # Return +true+ if the session is mimicing a secure HTTPS request.
  12414. #
  12415. # if session.https?
  12416. # ...
  12417. # end
  12418. def https?
  12419. @https
  12420. end
  12421. # Set the host name to use in the next request.
  12422. #
  12423. # session.host! "www.example.com"
  12424. def host!(name)
  12425. @host = name
  12426. end
  12427. # Follow a single redirect response. If the last response was not a
  12428. # redirect, an exception will be raised. Otherwise, the redirect is
  12429. # performed on the location header.
  12430. def follow_redirect!
  12431. raise "not a redirect! #{@status} #{@status_message}" unless redirect?
  12432. get(interpret_uri(headers["location"].first))
  12433. status
  12434. end
  12435. # Performs a GET request, following any subsequent redirect. Note that
  12436. # the redirects are followed until the response is not a redirect--this
  12437. # means you may run into an infinite loop if your redirect loops back to
  12438. # itself.
  12439. def get_via_redirect(path, args={})
  12440. get path, args
  12441. follow_redirect! while redirect?
  12442. status
  12443. end
  12444. # Performs a POST request, following any subsequent redirect. This is
  12445. # vulnerable to infinite loops, the same as #get_via_redirect.
  12446. def post_via_redirect(path, args={})
  12447. post path, args
  12448. follow_redirect! while redirect?
  12449. status
  12450. end
  12451. # Returns +true+ if the last response was a redirect.
  12452. def redirect?
  12453. status/100 == 3
  12454. end
  12455. # Performs a GET request with the given parameters. The parameters may
  12456. # be +nil+, a Hash, or a string that is appropriately encoded
  12457. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12458. # should be a hash. The keys will automatically be upcased, with the
  12459. # prefix 'HTTP_' added if needed.
  12460. def get(path, parameters=nil, headers=nil)
  12461. process :get, path, parameters, headers
  12462. end
  12463. # Performs a POST request with the given parameters. The parameters may
  12464. # be +nil+, a Hash, or a string that is appropriately encoded
  12465. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12466. # should be a hash. The keys will automatically be upcased, with the
  12467. # prefix 'HTTP_' added if needed.
  12468. def post(path, parameters=nil, headers=nil)
  12469. process :post, path, parameters, headers
  12470. end
  12471. # Performs an XMLHttpRequest request with the given parameters, mimicing
  12472. # the request environment created by the Prototype library. The parameters
  12473. # may be +nil+, a Hash, or a string that is appropriately encoded
  12474. # (application/x-www-form-urlencoded or multipart/form-data). The headers
  12475. # should be a hash. The keys will automatically be upcased, with the
  12476. # prefix 'HTTP_' added if needed.
  12477. def xml_http_request(path, parameters=nil, headers=nil)
  12478. headers = (headers || {}).merge("X-Requested-With" => "XMLHttpRequest")
  12479. post(path, parameters, headers)
  12480. end
  12481. # Returns the URL for the given options, according to the rules specified
  12482. # in the application's routes.
  12483. def url_for(options)
  12484. controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
  12485. end
  12486. private
  12487. class MockCGI < CGI #:nodoc:
  12488. attr_accessor :stdinput, :stdoutput, :env_table
  12489. def initialize(env, input=nil)
  12490. self.env_table = env
  12491. self.stdinput = StringIO.new(input || "")
  12492. self.stdoutput = StringIO.new
  12493. super()
  12494. end
  12495. end
  12496. # Tailors the session based on the given URI, setting the HTTPS value
  12497. # and the hostname.
  12498. def interpret_uri(path)
  12499. location = URI.parse(path)
  12500. https! URI::HTTPS === location if location.scheme
  12501. host! location.host if location.host
  12502. location.query ? "#{location.path}?#{location.query}" : location.path
  12503. end
  12504. # Performs the actual request.
  12505. def process(method, path, parameters=nil, headers=nil)
  12506. data = requestify(parameters)
  12507. path = interpret_uri(path) if path =~ %r{://}
  12508. path = "/#{path}" unless path[0] == ?/
  12509. @path = path
  12510. env = {}
  12511. if method == :get
  12512. env["QUERY_STRING"] = data
  12513. data = nil
  12514. end
  12515. env.update(
  12516. "REQUEST_METHOD" => method.to_s.upcase,
  12517. "REQUEST_URI" => path,
  12518. "HTTP_HOST" => host,
  12519. "REMOTE_ADDR" => remote_addr,
  12520. "SERVER_PORT" => (https? ? "443" : "80"),
  12521. "CONTENT_TYPE" => "application/x-www-form-urlencoded",
  12522. "CONTENT_LENGTH" => data ? data.length.to_s : nil,
  12523. "HTTP_COOKIE" => encode_cookies,
  12524. "HTTPS" => https? ? "on" : "off",
  12525. "HTTP_ACCEPT" => accept
  12526. )
  12527. (headers || {}).each do |key, value|
  12528. key = key.to_s.upcase.gsub(/-/, "_")
  12529. key = "HTTP_#{key}" unless env.has_key?(key) || env =~ /^X|HTTP/
  12530. env[key] = value
  12531. end
  12532. unless ActionController::Base.respond_to?(:clear_last_instantiation!)
  12533. ActionController::Base.send(:include, ControllerCapture)
  12534. end
  12535. ActionController::Base.clear_last_instantiation!
  12536. cgi = MockCGI.new(env, data)
  12537. Dispatcher.dispatch(cgi, ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS, cgi.stdoutput)
  12538. @result = cgi.stdoutput.string
  12539. @controller = ActionController::Base.last_instantiation
  12540. @request = @controller.request
  12541. @response = @controller.response
  12542. # Decorate the response with the standard behavior of the TestResponse
  12543. # so that things like assert_response can be used in integration
  12544. # tests.
  12545. @response.extend(TestResponseBehavior)
  12546. parse_result
  12547. return status
  12548. end
  12549. # Parses the result of the response and extracts the various values,
  12550. # like cookies, status, headers, etc.
  12551. def parse_result
  12552. headers, result_body = @result.split(/\r\n\r\n/, 2)
  12553. @headers = Hash.new { |h,k| h[k] = [] }
  12554. headers.each_line do |line|
  12555. key, value = line.strip.split(/:\s*/, 2)
  12556. @headers[key.downcase] << value
  12557. end
  12558. (@headers['set-cookie'] || [] ).each do |string|
  12559. name, value = string.match(/^(.*?)=(.*?);/)[1,2]
  12560. @cookies[name] = value
  12561. end
  12562. @status, @status_message = @headers["status"].first.split(/ /)
  12563. @status = @status.to_i
  12564. end
  12565. # Encode the cookies hash in a format suitable for passing to a
  12566. # request.
  12567. def encode_cookies
  12568. cookies.inject("") do |string, (name, value)|
  12569. string << "#{name}=#{value}; "
  12570. end
  12571. end
  12572. # Get a temporarly URL writer object
  12573. def generic_url_rewriter
  12574. cgi = MockCGI.new('REQUEST_METHOD' => "GET",
  12575. 'QUERY_STRING' => "",
  12576. "REQUEST_URI" => "/",
  12577. "HTTP_HOST" => host,
  12578. "SERVER_PORT" => https? ? "443" : "80",
  12579. "HTTPS" => https? ? "on" : "off")
  12580. ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
  12581. end
  12582. def name_with_prefix(prefix, name)
  12583. prefix ? "#{prefix}[#{name}]" : name.to_s
  12584. end
  12585. # Convert the given parameters to a request string. The parameters may
  12586. # be a string, +nil+, or a Hash.
  12587. def requestify(parameters, prefix=nil)
  12588. if Hash === parameters
  12589. return nil if parameters.empty?
  12590. parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
  12591. elsif Array === parameters
  12592. parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
  12593. elsif prefix.nil?
  12594. parameters
  12595. else
  12596. "#{CGI.escape(prefix)}=#{CGI.escape(parameters.to_s)}"
  12597. end
  12598. end
  12599. end
  12600. # A module used to extend ActionController::Base, so that integration tests
  12601. # can capture the controller used to satisfy a request.
  12602. module ControllerCapture #:nodoc:
  12603. def self.included(base)
  12604. base.extend(ClassMethods)
  12605. base.class_eval do
  12606. class <<self
  12607. alias_method :new_without_capture, :new
  12608. alias_method :new, :new_with_capture
  12609. end
  12610. end
  12611. end
  12612. module ClassMethods #:nodoc:
  12613. mattr_accessor :last_instantiation
  12614. def clear_last_instantiation!
  12615. self.last_instantiation = nil
  12616. end
  12617. def new_with_capture(*args)
  12618. self.last_instantiation ||= new_without_capture(*args)
  12619. end
  12620. end
  12621. end
  12622. end
  12623. # An IntegrationTest is one that spans multiple controllers and actions,
  12624. # tying them all together to ensure they work together as expected. It tests
  12625. # more completely than either unit or functional tests do, exercising the
  12626. # entire stack, from the dispatcher to the database.
  12627. #
  12628. # At its simplest, you simply extend IntegrationTest and write your tests
  12629. # using the get/post methods:
  12630. #
  12631. # require "#{File.dirname(__FILE__)}/test_helper"
  12632. #
  12633. # class ExampleTest < ActionController::IntegrationTest
  12634. # fixtures :people
  12635. #
  12636. # def test_login
  12637. # # get the login page
  12638. # get "/login"
  12639. # assert_equal 200, status
  12640. #
  12641. # # post the login and follow through to the home page
  12642. # post "/login", :username => people(:jamis).username,
  12643. # :password => people(:jamis).password
  12644. # follow_redirect!
  12645. # assert_equal 200, status
  12646. # assert_equal "/home", path
  12647. # end
  12648. # end
  12649. #
  12650. # However, you can also have multiple session instances open per test, and
  12651. # even extend those instances with assertions and methods to create a very
  12652. # powerful testing DSL that is specific for your application. You can even
  12653. # reference any named routes you happen to have defined!
  12654. #
  12655. # require "#{File.dirname(__FILE__)}/test_helper"
  12656. #
  12657. # class AdvancedTest < ActionController::IntegrationTest
  12658. # fixtures :people, :rooms
  12659. #
  12660. # def test_login_and_speak
  12661. # jamis, david = login(:jamis), login(:david)
  12662. # room = rooms(:office)
  12663. #
  12664. # jamis.enter(room)
  12665. # jamis.speak(room, "anybody home?")
  12666. #
  12667. # david.enter(room)
  12668. # david.speak(room, "hello!")
  12669. # end
  12670. #
  12671. # private
  12672. #
  12673. # module CustomAssertions
  12674. # def enter(room)
  12675. # # reference a named route, for maximum internal consistency!
  12676. # get(room_url(:id => room.id))
  12677. # assert(...)
  12678. # ...
  12679. # end
  12680. #
  12681. # def speak(room, message)
  12682. # xml_http_request "/say/#{room.id}", :message => message
  12683. # assert(...)
  12684. # ...
  12685. # end
  12686. # end
  12687. #
  12688. # def login(who)
  12689. # open_session do |sess|
  12690. # sess.extend(CustomAssertions)
  12691. # who = people(who)
  12692. # sess.post "/login", :username => who.username,
  12693. # :password => who.password
  12694. # assert(...)
  12695. # end
  12696. # end
  12697. # end
  12698. class IntegrationTest < Test::Unit::TestCase
  12699. # Work around a bug in test/unit caused by the default test being named
  12700. # as a symbol (:default_test), which causes regex test filters
  12701. # (like "ruby test.rb -n /foo/") to fail because =~ doesn't work on
  12702. # symbols.
  12703. def initialize(name) #:nodoc:
  12704. super(name.to_s)
  12705. end
  12706. # Work around test/unit's requirement that every subclass of TestCase have
  12707. # at least one test method. Note that this implementation extends to all
  12708. # subclasses, as well, so subclasses of IntegrationTest may also exist
  12709. # without any test methods.
  12710. def run(*args) #:nodoc:
  12711. return if @method_name == "default_test"
  12712. super
  12713. end
  12714. # Because of how use_instantiated_fixtures and use_transactional_fixtures
  12715. # are defined, we need to treat them as special cases. Otherwise, users
  12716. # would potentially have to set their values for both Test::Unit::TestCase
  12717. # ActionController::IntegrationTest, since by the time the value is set on
  12718. # TestCase, IntegrationTest has already been defined and cannot inherit
  12719. # changes to those variables. So, we make those two attributes copy-on-write.
  12720. class << self
  12721. def use_transactional_fixtures=(flag) #:nodoc:
  12722. @_use_transactional_fixtures = true
  12723. @use_transactional_fixtures = flag
  12724. end
  12725. def use_instantiated_fixtures=(flag) #:nodoc:
  12726. @_use_instantiated_fixtures = true
  12727. @use_instantiated_fixtures = flag
  12728. end
  12729. def use_transactional_fixtures #:nodoc:
  12730. @_use_transactional_fixtures ?
  12731. @use_transactional_fixtures :
  12732. superclass.use_transactional_fixtures
  12733. end
  12734. def use_instantiated_fixtures #:nodoc:
  12735. @_use_instantiated_fixtures ?
  12736. @use_instantiated_fixtures :
  12737. superclass.use_instantiated_fixtures
  12738. end
  12739. end
  12740. # Reset the current session. This is useful for testing multiple sessions
  12741. # in a single test case.
  12742. def reset!
  12743. @integration_session = open_session
  12744. end
  12745. %w(get post cookies assigns xml_http_request).each do |method|
  12746. define_method(method) do |*args|
  12747. reset! unless @integration_session
  12748. returning @integration_session.send(method, *args) do
  12749. copy_session_variables!
  12750. end
  12751. end
  12752. end
  12753. # Open a new session instance. If a block is given, the new session is
  12754. # yielded to the block before being returned.
  12755. #
  12756. # session = open_session do |sess|
  12757. # sess.extend(CustomAssertions)
  12758. # end
  12759. #
  12760. # By default, a single session is automatically created for you, but you
  12761. # can use this method to open multiple sessions that ought to be tested
  12762. # simultaneously.
  12763. def open_session
  12764. session = Integration::Session.new
  12765. # delegate the fixture accessors back to the test instance
  12766. extras = Module.new { attr_accessor :delegate, :test_result }
  12767. self.class.fixture_table_names.each do |table_name|
  12768. name = table_name.tr(".", "_")
  12769. next unless respond_to?(name)
  12770. extras.send(:define_method, name) { |*args| delegate.send(name, *args) }
  12771. end
  12772. # delegate add_assertion to the test case
  12773. extras.send(:define_method, :add_assertion) { test_result.add_assertion }
  12774. session.extend(extras)
  12775. session.delegate = self
  12776. session.test_result = @_result
  12777. yield session if block_given?
  12778. session
  12779. end
  12780. # Copy the instance variables from the current session instance into the
  12781. # test instance.
  12782. def copy_session_variables! #:nodoc:
  12783. return unless @integration_session
  12784. %w(controller response request).each do |var|
  12785. instance_variable_set("@#{var}", @integration_session.send(var))
  12786. end
  12787. end
  12788. # Delegate unhandled messages to the current session instance.
  12789. def method_missing(sym, *args, &block)
  12790. reset! unless @integration_session
  12791. returning @integration_session.send(sym, *args, &block) do
  12792. copy_session_variables!
  12793. end
  12794. end
  12795. end
  12796. end
  12797. module ActionController #:nodoc:
  12798. module Layout #:nodoc:
  12799. def self.included(base)
  12800. base.extend(ClassMethods)
  12801. base.class_eval do
  12802. alias_method :render_with_no_layout, :render
  12803. alias_method :render, :render_with_a_layout
  12804. class << self
  12805. alias_method :inherited_without_layout, :inherited
  12806. alias_method :inherited, :inherited_with_layout
  12807. end
  12808. end
  12809. end
  12810. # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
  12811. # repeated setups. The inclusion pattern has pages that look like this:
  12812. #
  12813. # <%= render "shared/header" %>
  12814. # Hello World
  12815. # <%= render "shared/footer" %>
  12816. #
  12817. # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
  12818. # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
  12819. #
  12820. # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
  12821. # that the header and footer are only mentioned in one place, like this:
  12822. #
  12823. # <!-- The header part of this layout -->
  12824. # <%= yield %>
  12825. # <!-- The footer part of this layout -->
  12826. #
  12827. # And then you have content pages that look like this:
  12828. #
  12829. # hello world
  12830. #
  12831. # Not a word about common structures. At rendering time, the content page is computed and then inserted in the layout,
  12832. # like this:
  12833. #
  12834. # <!-- The header part of this layout -->
  12835. # hello world
  12836. # <!-- The footer part of this layout -->
  12837. #
  12838. # == Accessing shared variables
  12839. #
  12840. # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
  12841. # references that won't materialize before rendering time:
  12842. #
  12843. # <h1><%= @page_title %></h1>
  12844. # <%= yield %>
  12845. #
  12846. # ...and content pages that fulfill these references _at_ rendering time:
  12847. #
  12848. # <% @page_title = "Welcome" %>
  12849. # Off-world colonies offers you a chance to start a new life
  12850. #
  12851. # The result after rendering is:
  12852. #
  12853. # <h1>Welcome</h1>
  12854. # Off-world colonies offers you a chance to start a new life
  12855. #
  12856. # == Automatic layout assignment
  12857. #
  12858. # If there is a template in <tt>app/views/layouts/</tt> with the same name as the current controller then it will be automatically
  12859. # set as that controller's layout unless explicitly told otherwise. Say you have a WeblogController, for example. If a template named
  12860. # <tt>app/views/layouts/weblog.rhtml</tt> or <tt>app/views/layouts/weblog.rxml</tt> exists then it will be automatically set as
  12861. # the layout for your WeblogController. You can create a layout with the name <tt>application.rhtml</tt> or <tt>application.rxml</tt>
  12862. # and this will be set as the default controller if there is no layout with the same name as the current controller and there is
  12863. # no layout explicitly assigned with the +layout+ method. Nested controllers use the same folder structure for automatic layout.
  12864. # assignment. So an Admin::WeblogController will look for a template named <tt>app/views/layouts/admin/weblog.rhtml</tt>.
  12865. # Setting a layout explicitly will always override the automatic behaviour for the controller where the layout is set.
  12866. # Explicitly setting the layout in a parent class, though, will not override the child class's layout assignement if the child
  12867. # class has a layout with the same name.
  12868. #
  12869. # == Inheritance for layouts
  12870. #
  12871. # Layouts are shared downwards in the inheritance hierarchy, but not upwards. Examples:
  12872. #
  12873. # class BankController < ActionController::Base
  12874. # layout "bank_standard"
  12875. #
  12876. # class InformationController < BankController
  12877. #
  12878. # class VaultController < BankController
  12879. # layout :access_level_layout
  12880. #
  12881. # class EmployeeController < BankController
  12882. # layout nil
  12883. #
  12884. # The InformationController uses "bank_standard" inherited from the BankController, the VaultController overwrites
  12885. # and picks the layout dynamically, and the EmployeeController doesn't want to use a layout at all.
  12886. #
  12887. # == Types of layouts
  12888. #
  12889. # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
  12890. # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
  12891. # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
  12892. #
  12893. # The method reference is the preferred approach to variable layouts and is used like this:
  12894. #
  12895. # class WeblogController < ActionController::Base
  12896. # layout :writers_and_readers
  12897. #
  12898. # def index
  12899. # # fetching posts
  12900. # end
  12901. #
  12902. # private
  12903. # def writers_and_readers
  12904. # logged_in? ? "writer_layout" : "reader_layout"
  12905. # end
  12906. #
  12907. # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
  12908. # is logged in or not.
  12909. #
  12910. # If you want to use an inline method, such as a proc, do something like this:
  12911. #
  12912. # class WeblogController < ActionController::Base
  12913. # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
  12914. #
  12915. # Of course, the most common way of specifying a layout is still just as a plain template name:
  12916. #
  12917. # class WeblogController < ActionController::Base
  12918. # layout "weblog_standard"
  12919. #
  12920. # If no directory is specified for the template name, the template will by default by looked for in +app/views/layouts/+.
  12921. #
  12922. # == Conditional layouts
  12923. #
  12924. # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
  12925. # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
  12926. # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
  12927. #
  12928. # class WeblogController < ActionController::Base
  12929. # layout "weblog_standard", :except => :rss
  12930. #
  12931. # # ...
  12932. #
  12933. # end
  12934. #
  12935. # This will assign "weblog_standard" as the WeblogController's layout except for the +rss+ action, which will not wrap a layout
  12936. # around the rendered view.
  12937. #
  12938. # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
  12939. # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
  12940. #
  12941. # == Using a different layout in the action render call
  12942. #
  12943. # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
  12944. # Some times you'll have exceptions, though, where one action wants to use a different layout than the rest of the controller.
  12945. # This is possible using the <tt>render</tt> method. It's just a bit more manual work as you'll have to supply fully
  12946. # qualified template and layout names as this example shows:
  12947. #
  12948. # class WeblogController < ActionController::Base
  12949. # def help
  12950. # render :action => "help/index", :layout => "help"
  12951. # end
  12952. # end
  12953. #
  12954. # As you can see, you pass the template as the first parameter, the status code as the second ("200" is OK), and the layout
  12955. # as the third.
  12956. #
  12957. # NOTE: The old notation for rendering the view from a layout was to expose the magic <tt>@content_for_layout</tt> instance
  12958. # variable. The preferred notation now is to use <tt>yield</tt>, as documented above.
  12959. module ClassMethods
  12960. # If a layout is specified, all rendered actions will have their result rendered
  12961. # when the layout<tt>yield</tt>'s. This layout can itself depend on instance variables assigned during action
  12962. # performance and have access to them as any normal template would.
  12963. def layout(template_name, conditions = {})
  12964. add_layout_conditions(conditions)
  12965. write_inheritable_attribute "layout", template_name
  12966. end
  12967. def layout_conditions #:nodoc:
  12968. @layout_conditions ||= read_inheritable_attribute("layout_conditions")
  12969. end
  12970. def default_layout #:nodoc:
  12971. @default_layout ||= read_inheritable_attribute("layout")
  12972. end
  12973. private
  12974. def inherited_with_layout(child)
  12975. inherited_without_layout(child)
  12976. child.send :include, Reloadable
  12977. layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '')
  12978. child.layout(layout_match) unless layout_list.grep(%r{layouts/#{layout_match}\.[a-z][0-9a-z]*$}).empty?
  12979. end
  12980. def layout_list
  12981. Dir.glob("#{template_root}/layouts/**/*")
  12982. end
  12983. def add_layout_conditions(conditions)
  12984. write_inheritable_hash "layout_conditions", normalize_conditions(conditions)
  12985. end
  12986. def normalize_conditions(conditions)
  12987. conditions.inject({}) {|hash, (key, value)| hash.merge(key => [value].flatten.map {|action| action.to_s})}
  12988. end
  12989. def layout_directory_exists_cache
  12990. @@layout_directory_exists_cache ||= Hash.new do |h, dirname|
  12991. h[dirname] = File.directory? dirname
  12992. end
  12993. end
  12994. end
  12995. # Returns the name of the active layout. If the layout was specified as a method reference (through a symbol), this method
  12996. # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method
  12997. # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return
  12998. # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard.
  12999. def active_layout(passed_layout = nil)
  13000. layout = passed_layout || self.class.default_layout
  13001. active_layout = case layout
  13002. when String then layout
  13003. when Symbol then send(layout)
  13004. when Proc then layout.call(self)
  13005. end
  13006. # Explicitly passed layout names with slashes are looked up relative to the template root,
  13007. # but auto-discovered layouts derived from a nested controller will contain a slash, though be relative
  13008. # to the 'layouts' directory so we have to check the file system to infer which case the layout name came from.
  13009. if active_layout
  13010. if active_layout.include?('/') && ! layout_directory?(active_layout)
  13011. active_layout
  13012. else
  13013. "layouts/#{active_layout}"
  13014. end
  13015. end
  13016. end
  13017. def render_with_a_layout(options = nil, deprecated_status = nil, deprecated_layout = nil, &block) #:nodoc:
  13018. template_with_options = options.is_a?(Hash)
  13019. if apply_layout?(template_with_options, options) && (layout = pick_layout(template_with_options, options, deprecated_layout))
  13020. options = options.merge :layout => false if template_with_options
  13021. logger.info("Rendering #{options} within #{layout}") if logger
  13022. if template_with_options
  13023. content_for_layout = render_with_no_layout(options, &block)
  13024. deprecated_status = options[:status] || deprecated_status
  13025. else
  13026. content_for_layout = render_with_no_layout(options, deprecated_status, &block)
  13027. end
  13028. erase_render_results
  13029. add_variables_to_assigns
  13030. @template.instance_variable_set("@content_for_layout", content_for_layout)
  13031. render_text(@template.render_file(layout, true), deprecated_status)
  13032. else
  13033. render_with_no_layout(options, deprecated_status, &block)
  13034. end
  13035. end
  13036. private
  13037. def apply_layout?(template_with_options, options)
  13038. return false if options == :update
  13039. template_with_options ? candidate_for_layout?(options) : !template_exempt_from_layout?
  13040. end
  13041. def candidate_for_layout?(options)
  13042. (options.has_key?(:layout) && options[:layout] != false) ||
  13043. options.values_at(:text, :xml, :file, :inline, :partial, :nothing).compact.empty? &&
  13044. !template_exempt_from_layout?(default_template_name(options[:action] || options[:template]))
  13045. end
  13046. def pick_layout(template_with_options, options, deprecated_layout)
  13047. if deprecated_layout
  13048. deprecated_layout
  13049. elsif template_with_options
  13050. case layout = options[:layout]
  13051. when FalseClass
  13052. nil
  13053. when NilClass, TrueClass
  13054. active_layout if action_has_layout?
  13055. else
  13056. active_layout(layout)
  13057. end
  13058. else
  13059. active_layout if action_has_layout?
  13060. end
  13061. end
  13062. def action_has_layout?
  13063. if conditions = self.class.layout_conditions
  13064. case
  13065. when only = conditions[:only]
  13066. only.include?(action_name)
  13067. when except = conditions[:except]
  13068. !except.include?(action_name)
  13069. else
  13070. true
  13071. end
  13072. else
  13073. true
  13074. end
  13075. end
  13076. # Does a layout directory for this class exist?
  13077. # we cache this info in a class level hash
  13078. def layout_directory?(layout_name)
  13079. template_path = File.join(self.class.view_root, 'layouts', layout_name)
  13080. dirname = File.dirname(template_path)
  13081. self.class.send(:layout_directory_exists_cache)[dirname]
  13082. end
  13083. end
  13084. end
  13085. module ActionController
  13086. # Macros are class-level calls that add pre-defined actions to the controller based on the parameters passed in.
  13087. # Currently, they're used to bridge the JavaScript macros, like autocompletion and in-place editing, with the controller
  13088. # backing.
  13089. module Macros
  13090. module AutoComplete #:nodoc:
  13091. def self.append_features(base) #:nodoc:
  13092. super
  13093. base.extend(ClassMethods)
  13094. end
  13095. # Example:
  13096. #
  13097. # # Controller
  13098. # class BlogController < ApplicationController
  13099. # auto_complete_for :post, :title
  13100. # end
  13101. #
  13102. # # View
  13103. # <%= text_field_with_auto_complete :post, title %>
  13104. #
  13105. # By default, auto_complete_for limits the results to 10 entries,
  13106. # and sorts by the given field.
  13107. #
  13108. # auto_complete_for takes a third parameter, an options hash to
  13109. # the find method used to search for the records:
  13110. #
  13111. # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC'
  13112. #
  13113. # For help on defining text input fields with autocompletion,
  13114. # see ActionView::Helpers::JavaScriptHelper.
  13115. #
  13116. # For more examples, see script.aculo.us:
  13117. # * http://script.aculo.us/demos/ajax/autocompleter
  13118. # * http://script.aculo.us/demos/ajax/autocompleter_customized
  13119. module ClassMethods
  13120. def auto_complete_for(object, method, options = {})
  13121. define_method("auto_complete_for_#{object}_#{method}") do
  13122. find_options = {
  13123. :conditions => [ "LOWER(#{method}) LIKE ?", '%' + params[object][method].downcase + '%' ],
  13124. :order => "#{method} ASC",
  13125. :limit => 10 }.merge!(options)
  13126. @items = object.to_s.camelize.constantize.find(:all, find_options)
  13127. render :inline => "<%= auto_complete_result @items, '#{method}' %>"
  13128. end
  13129. end
  13130. end
  13131. end
  13132. end
  13133. endmodule ActionController
  13134. module Macros
  13135. module InPlaceEditing #:nodoc:
  13136. def self.append_features(base) #:nodoc:
  13137. super
  13138. base.extend(ClassMethods)
  13139. end
  13140. # Example:
  13141. #
  13142. # # Controller
  13143. # class BlogController < ApplicationController
  13144. # in_place_edit_for :post, :title
  13145. # end
  13146. #
  13147. # # View
  13148. # <%= in_place_editor_field :post, 'title' %>
  13149. #
  13150. # For help on defining an in place editor in the browser,
  13151. # see ActionView::Helpers::JavaScriptHelper.
  13152. module ClassMethods
  13153. def in_place_edit_for(object, attribute, options = {})
  13154. define_method("set_#{object}_#{attribute}") do
  13155. @item = object.to_s.camelize.constantize.find(params[:id])
  13156. @item.update_attribute(attribute, params[:value])
  13157. render :text => @item.send(attribute)
  13158. end
  13159. end
  13160. end
  13161. end
  13162. end
  13163. end
  13164. module ActionController #:nodoc:
  13165. module MimeResponds #:nodoc:
  13166. def self.included(base)
  13167. base.send(:include, ActionController::MimeResponds::InstanceMethods)
  13168. end
  13169. module InstanceMethods
  13170. # Without web-service support, an action which collects the data for displaying a list of people
  13171. # might look something like this:
  13172. #
  13173. # def list
  13174. # @people = Person.find(:all)
  13175. # end
  13176. #
  13177. # Here's the same action, with web-service support baked in:
  13178. #
  13179. # def list
  13180. # @people = Person.find(:all)
  13181. #
  13182. # respond_to do |wants|
  13183. # wants.html
  13184. # wants.xml { render :xml => @people.to_xml }
  13185. # end
  13186. # end
  13187. #
  13188. # What that says is, "if the client wants HTML in response to this action, just respond as we
  13189. # would have before, but if the client wants XML, return them the list of people in XML format."
  13190. # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
  13191. #
  13192. # Supposing you have an action that adds a new person, optionally creating their company
  13193. # (by name) if it does not already exist, without web-services, it might look like this:
  13194. #
  13195. # def add
  13196. # @company = Company.find_or_create_by_name(params[:company][:name])
  13197. # @person = @company.people.create(params[:person])
  13198. #
  13199. # redirect_to(person_list_url)
  13200. # end
  13201. #
  13202. # Here's the same action, with web-service support baked in:
  13203. #
  13204. # def add
  13205. # company = params[:person].delete(:company)
  13206. # @company = Company.find_or_create_by_name(company[:name])
  13207. # @person = @company.people.create(params[:person])
  13208. #
  13209. # respond_to do |wants|
  13210. # wants.html { redirect_to(person_list_url) }
  13211. # wants.js
  13212. # wants.xml { render :xml => @person.to_xml(:include => @company) }
  13213. # end
  13214. # end
  13215. #
  13216. # If the client wants HTML, we just redirect them back to the person list. If they want Javascript
  13217. # (wants.js), then it is an RJS request and we render the RJS template associated with this action.
  13218. # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
  13219. # include the persons company in the rendered XML, so you get something like this:
  13220. #
  13221. # <person>
  13222. # <id>...</id>
  13223. # ...
  13224. # <company>
  13225. # <id>...</id>
  13226. # <name>...</name>
  13227. # ...
  13228. # </company>
  13229. # </person>
  13230. #
  13231. # Note, however, the extra bit at the top of that action:
  13232. #
  13233. # company = params[:person].delete(:company)