Froze the Rails gems and RedCloth into the vendor directory, so that these will be used instead of any local version of Rails which might be incompatible. Should also mean that you don't need to install RedCloth locally to make Tracks work (I'm not sure whether this also applies to Rails, and don't want to uninstall my local Rails gems to find out!)

Effectively fixes #159.



git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@176 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
bsag 2006-01-11 22:50:43 +00:00
parent 825bad76a7
commit c392296680
751 changed files with 94570 additions and 4 deletions

View file

@ -0,0 +1,34 @@
desc "Copy third-party gems into ./lib"
task :freeze_other_gems do
# TODO Get this list from parsing environment.rb
libraries = %w(redcloth)
require 'rubygems'
require 'find'
libraries.each do |library|
library_gem = Gem.cache.search(library).sort_by { |g| g.version }.last
puts "Freezing #{library} for #{library_gem.version}..."
# TODO Add dependencies to list of libraries to freeze
#library_gem.dependencies.each { |g| libraries << g }
folder_for_library = "#{library_gem.name}-#{library_gem.version}"
system "cd vendor; gem unpack -v '#{library_gem.version}' #{library_gem.name};"
# Copy files recursively to ./lib
folder_for_library_with_lib = "vendor/#{folder_for_library}/lib/"
Find.find(folder_for_library_with_lib) do |original_file|
destination_file = "./lib/" + original_file.gsub(folder_for_library_with_lib, '')
if File.directory?(original_file)
if !File.exist?(destination_file)
Dir.mkdir destination_file
end
else
File.copy original_file, destination_file
end
end
system "rm -r vendor/#{folder_for_library}"
end
end

View file

@ -525,8 +525,8 @@ div.message {
}
#ErrorExplanation {
width: 400px;
border: 2px solid red;
width: 335px;
border: 2px solid #ff0000;
padding: 7px;
padding-bottom: 12px;
margin-bottom: 20px;
@ -550,8 +550,8 @@ div.message {
}
#ErrorExplanation ul li {
font-size: 12px;
list-style: square;
font-size: 1em;
list-style: disc;
}
ul.warning { list-style-type: circle; font-size: 1em; }

View file

@ -0,0 +1,3 @@
#!/usr/local/bin/ruby18
require 'redcloth'
puts RedCloth.new( ARGF.read ).to_html

View file

@ -0,0 +1,26 @@
#!/usr/bin/env ruby
require 'lib/redcloth'
require 'yaml'
Dir["tests/*.yml"].each do |testfile|
YAML::load_documents( File.open( testfile ) ) do |doc|
if doc['in'] and doc['out']
red = RedCloth.new( doc['in'] )
html = if testfile =~ /markdown/
red.to_html( :markdown )
else
red.to_html
end
puts "---"
html.gsub!( /\n+/, "\n" )
doc['out'].gsub!( /\n+/, "\n" )
if html == doc['out']
puts "success: true"
else
puts "out: "; p html
puts "expected: "; p doc['out']
end
end
end
end

View file

@ -0,0 +1,105 @@
---
in: 'This is an empty dictionary: @{}@'
out: '<p>This is an empty dictionary: <code>{}</code></p>'
---
in: |-
Testing nested pre tags...
<pre>
<code>
Good code here.
<pre>
a = 1
</pre>
Bad code here.
<script language="JavaScript">
window.open( "about:blank" );
</script>
</code>
</pre>
out: |-
<p>Testing nested pre tags&#8230;</p>
<pre>
<code>
Good code here.
&lt;pre&gt;
a = 1
&lt;/pre&gt;
Bad code here.
&lt;script language="JavaScript"&gt;
window.open( "about:blank" );
&lt;/script&gt;
</code>
</pre>
---
in: |-
<pre>
*** test
</pre>
out: |-
<pre>
*** test
</pre>
---
in: |-
<notextile>
*** test
</notextile>
out: |-
*** test
---
in: '*this <span></span> is strong*'
out: '<p><strong>this <span></span> is strong</strong></p>'
---
in: '*this <span>test</span> is strong*'
out: '<p><strong>this <span>test</span> is strong</strong></p>'
---
in: <pre class="code"> __inline__</pre>
out: <pre class="code"> __inline__</pre>
---
in: |-
* @foo@
* @bar@
* and @x@ is also.
out: "<ul>\n\t<li><code>foo</code></li>\n\t\t<li><code>bar</code></li>\n\t\t<li>and <code>x</code> is also.</li>\n\t</ul>"
---
in: |-
<pre class="code"> <hello> </pre>
<pre class="code"> <hello> </pre>
out: |-
<pre class="code"> &lt;hello&gt; </pre>
<pre class="code"> &lt;hello&gt; </pre>
---
in: |
Test of Markdown-style indented code.
a = [1, 2, 3]
a.each do |x|
puts "test number", x,
"and more!"
end
Paragraph 2.
Paragraph 3.
out: |-
<p>Test of Markdown-style indented code.</p>
<pre><code>a = [1, 2, 3]
a.each do |x|
puts "test number", x,
"and more!"
end</code></pre>
<p>Paragraph 2.</p>
<p>Paragraph 3.</p>

View file

@ -0,0 +1,171 @@
---
in: This is an !image.jpg!
out: <p>This is an <img src="image.jpg" alt="" /></p>
---
in: This is an !image.jpg(with alt text)!
out: <p>This is an <img src="image.jpg" title="with alt text" alt="with alt text" /></p>
---
in: This is an !http://example.com/i/image.jpg!
out: <p>This is an <img src="http://example.com/i/image.jpg" alt="" /></p>
---
in: This is an !http://example.com/i/image.jpg#a1!
out: <p>This is an <img src="http://example.com/i/image.jpg#a1" alt="" /></p>
---
in: This is an !image.jpg!.
out: <p>This is an <img src="image.jpg" alt="" />.</p>
---
in: This is an !image.jpg(with alt text)!.
out: <p>This is an <img src="image.jpg" title="with alt text" alt="with alt text" />.</p>
---
in: This is an !http://example.com/i/image.jpg!.
out: <p>This is an <img src="http://example.com/i/image.jpg" alt="" />.</p>
---
in: This is an !http://example.com/i/image.jpg#a1!.
out: <p>This is an <img src="http://example.com/i/image.jpg#a1" alt="" />.</p>
---
in: This is not an image!!!
out: <p>This is not an image!!!</p>
---
in: This is an !http://example.com/i/image.jpg!:#1
out: <p>This is an <a href="#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:#a
out: <p>This is an <a href="#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:#a1
out: <p>This is an <a href="#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:#a10
out: <p>This is an <a href="#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html
out: <p>This is an <a href="index.html"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html#1
out: <p>This is an <a href="index.html#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html#a1
out: <p>This is an <a href="index.html#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html#a10
out: <p>This is an <a href="index.html#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar
out: <p>This is an <a href="index.html?foo=bar"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#1
out: <p>This is an <a href="index.html?foo=bar#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a
out: <p>This is an <a href="index.html?foo=bar#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a1
out: <p>This is an <a href="index.html?foo=bar#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:index.html?foo=bar#a10
out: <p>This is an <a href="index.html?foo=bar#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/
out: <p>This is an <a href="http://example.com/"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/#1
out: <p>This is an <a href="http://example.com/#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/#a
out: <p>This is an <a href="http://example.com/#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/#a1
out: <p>This is an <a href="http://example.com/#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/#a10
out: <p>This is an <a href="http://example.com/#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html
out: <p>This is an <a href="http://example.com/index.html"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#1
out: <p>This is an <a href="http://example.com/index.html#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a
out: <p>This is an <a href="http://example.com/index.html#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a1
out: <p>This is an <a href="http://example.com/index.html#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html#a10
out: <p>This is an <a href="http://example.com/index.html#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar
out: <p>This is an <a href="http://example.com/index.html?foo=bar"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#1
out: <p>This is an <a href="http://example.com/index.html?foo=bar#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a
out: <p>This is an <a href="http://example.com/index.html?foo=bar#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a1
out: <p>This is an <a href="http://example.com/index.html?foo=bar#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar#a10
out: <p>This is an <a href="http://example.com/index.html?foo=bar#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a1"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a10"><img src="http://example.com/i/image.jpg" alt="" /></a></p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b"><img src="http://example.com/i/image.jpg" alt="" /></a>.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#1"><img src="http://example.com/i/image.jpg" alt="" /></a>.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a"><img src="http://example.com/i/image.jpg" alt="" /></a>.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a1"><img src="http://example.com/i/image.jpg" alt="" /></a>.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a10"><img src="http://example.com/i/image.jpg" alt="" /></a>.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b, but this is not.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b"><img src="http://example.com/i/image.jpg" alt="" /></a>, but this is not.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1, but this is not.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#1"><img src="http://example.com/i/image.jpg" alt="" /></a>, but this is not.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a, but this is not.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a"><img src="http://example.com/i/image.jpg" alt="" /></a>, but this is not.</p>
---
in: This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1, but this is not.
out: <p>This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a1"><img src="http://example.com/i/image.jpg" alt="" /></a>, but this is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a10"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#1) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#1"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a1) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a1"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>
---
in: (This is an !http://example.com/i/image.jpg!:http://example.com/index.html?foo=bar&a=b#a10) This is not.
out: <p>(This is an <a href="http://example.com/index.html?foo=bar&#38;a=b#a10"><img src="http://example.com/i/image.jpg" alt="" /></a>) This is not.</p>

View file

@ -0,0 +1,39 @@
--- # Bugs filed at http://www.instiki.org/show/BugReports
in: |-
_Hi, <span class="newWikiWord">Joe Bob<a href="../show/JoeBob">?</a></span>, this should all be in italic!_
out: |-
<p><em>Hi, <span class="newWikiWord">Joe Bob<a href="../show/JoeBob">?</a></span>, this should all be in italic!</em></p>
---
in: '*this <span>span</span> is strong*'
out: '<p><strong>this <span>span</span> is strong</strong></p>'
---
in: '*this <span>Camel Thing<a href="../show/CamelThing">?</a></span> is strong*'
out: '<p><strong>this <span>Camel Thing<a href="../show/CamelThing">?</a></span> is strong</strong></p>'
---
in: '_this <span>span</span> is italic_'
out: '<p><em>this <span>span</span> is italic</em></p>'
---
in: '%{color:red}nested span because of <span><span class="newWikiWord">Camel Word<a href="../show/CamelWord">?</a></span></span>%'
out: '<p><span style="color:red;">nested span because of <span><span class="newWikiWord">Camel Word<a href="../show/CamelWord">?</a></span></span></span></p>'
---
in: |-
h2. Version History
* "Version
0.0":http://www.threewordslong.com/render-0-8-9b.patch - Early version using MD5 hashes.
* "Version
0.1":http://www.threewordslong.com/chunk-0-1.patch.gz - First cut of new system. Much cleaner.
* "Version 0.2":http://www.threewordslong.com/chunk-0-2.patch.gz - Fixed problem with "authors" page and some tests.
out: |-
<h2>Version History</h2>
<ul>
<li><a href="http://www.threewordslong.com/render-0-8-9b.patch">Version
0.0</a> &#8211; Early version using <span class="caps">MD5</span> hashes.</li>
<li><a href="http://www.threewordslong.com/chunk-0-1.patch.gz">Version
0.1</a> &#8211; First cut of new system. Much cleaner.</li>
<li><a href="http://www.threewordslong.com/chunk-0-2.patch.gz">Version 0.2</a> &#8211; Fixed problem with &#8220;authors&#8221; page and some tests.</li>
</ul>
---
in: "--richSeymour --whyTheLuckyStiff"
out: "<p>&#8212;richSeymour&#8212;whyTheLuckyStiff</p>"

View file

@ -0,0 +1,155 @@
---
in: '"link text":#1'
out: <p><a href="#1">link text</a></p>
---
in: '"link text":#a'
out: <p><a href="#a">link text</a></p>
---
in: '"link text":#a1'
out: <p><a href="#a1">link text</a></p>
---
in: '"link text":#a10'
out: <p><a href="#a10">link text</a></p>
---
in: '"link text":index.html'
out: <p><a href="index.html">link text</a></p>
---
in: '"link text":index.html#1'
out: <p><a href="index.html#1">link text</a></p>
---
in: '"link text":index.html#a'
out: <p><a href="index.html#a">link text</a></p>
---
in: '"link text":index.html#a1'
out: <p><a href="index.html#a1">link text</a></p>
---
in: '"link text":index.html#a10'
out: <p><a href="index.html#a10">link text</a></p>
---
in: '"link text":http://example.com/'
out: <p><a href="http://example.com/">link text</a></p>
---
in: '"link text":http://example.com/#1'
out: <p><a href="http://example.com/#1">link text</a></p>
---
in: '"link text":http://example.com/#a'
out: <p><a href="http://example.com/#a">link text</a></p>
---
in: '"link text":http://example.com/#a1'
out: <p><a href="http://example.com/#a1">link text</a></p>
---
in: '"link text":http://example.com/#a10'
out: <p><a href="http://example.com/#a10">link text</a></p>
---
in: '"link text":http://example.com/index.html'
out: <p><a href="http://example.com/index.html">link text</a></p>
---
in: '"link text":http://example.com/index.html#a'
out: <p><a href="http://example.com/index.html#a">link text</a></p>
---
in: '"link text":http://example.com/index.html#1'
out: <p><a href="http://example.com/index.html#1">link text</a></p>
---
in: '"link text":http://example.com/index.html#a1'
out: <p><a href="http://example.com/index.html#a1">link text</a></p>
---
in: '"link text":http://example.com/index.html#a10'
out: <p><a href="http://example.com/index.html#a10">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar'
out: <p><a href="http://example.com/?foo=bar">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar#a'
out: <p><a href="http://example.com/?foo=bar#a">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar#1'
out: <p><a href="http://example.com/?foo=bar#1">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar#a1'
out: <p><a href="http://example.com/?foo=bar#a1">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar#a10'
out: <p><a href="http://example.com/?foo=bar#a10">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar&a=b'
out: <p><a href="http://example.com/?foo=bar&#38;a=b">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar&a=b#1'
out: <p><a href="http://example.com/?foo=bar&#38;a=b#1">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar&a=b#a'
out: <p><a href="http://example.com/?foo=bar&#38;a=b#a">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar&a=b#a1'
out: <p><a href="http://example.com/?foo=bar&#38;a=b#a1">link text</a></p>
---
in: '"link text":http://example.com/?foo=bar&a=b#a10'
out: <p><a href="http://example.com/?foo=bar&#38;a=b#a10">link text</a></p>
---
in: 'This is a "link":http://example.com/'
out: <p>This is a <a href="http://example.com/">link</a></p>
---
in: 'This is a "link":http://example.com/.'
out: <p>This is a <a href="http://example.com/">link</a>.</p>
---
in: 'This is a "link":http://example.com/index.html.'
out: <p>This is a <a href="http://example.com/index.html">link</a>.</p>
---
in: 'This is a "link":http://example.com/index.html#a.'
out: <p>This is a <a href="http://example.com/index.html#a">link</a>.</p>
---
in: 'This is a "link":http://example.com/index.html#1.'
out: <p>This is a <a href="http://example.com/index.html#1">link</a>.</p>
---
in: 'This is a "link":http://example.com/index.html#a1.'
out: <p>This is a <a href="http://example.com/index.html#a1">link</a>.</p>
---
in: 'This is a "link":http://example.com/index.html#a10.'
out: <p>This is a <a href="http://example.com/index.html#a10">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar.'
out: <p>This is a <a href="http://example.com/?foo=bar">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar#1.'
out: <p>This is a <a href="http://example.com/?foo=bar#1">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar#a.'
out: <p>This is a <a href="http://example.com/?foo=bar#a">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar#a1.'
out: <p>This is a <a href="http://example.com/?foo=bar#a1">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar#a10.'
out: <p>This is a <a href="http://example.com/?foo=bar#a10">link</a>.</p>
---
in: 'This is a "link":http://example.com/?foo=bar#a10, but this is not.'
out: <p>This is a <a href="http://example.com/?foo=bar#a10">link</a>, but this is not.</p>
---
in: '(This is a "link":http://example.com/?foo=bar#a10) but this is not.'
out: <p>(This is a <a href="http://example.com/?foo=bar#a10">link</a>) but this is not.</p>
---
in: '"link text(link title)":http://example.com/'
out: <p><a href="http://example.com/" title="link title">link text</a></p>
# ---
# in: '"link text(link title) ":http://example.com/'
# out: <p>&#8220;link text(link title) &#8220;:http://example.com/</p>
# comments: this is a real test and should pass
---
in: '"(link) text(link title)":http://example.com/'
out: <p><a href="http://example.com/" class="link" title="link title"> text</a></p>
comments: link text can not contain parentheses
---
in: '"Dive Into XML":http://www.xml.com/pub/au/164'
out: <p><a href="http://www.xml.com/pub/au/164">Dive Into <span class="caps">XML</span></a></p>
---
in: '"Lab Exercises":../lab/exercises/exercises.html.'
out: <p><a href="../lab/exercises/exercises.html">Lab Exercises</a>.</p>
---
in: 'Go to "discuss":http://www.dreammoods.com/cgibin/cutecast/cutecast.pl?forum=1&thread=26627 to discuss.'
out: <p>Go to <a href="http://www.dreammoods.com/cgibin/cutecast/cutecast.pl?forum=1&#38;thread=26627">discuss</a> to discuss.</p>
---
in: '* "rubylang":http://www.ruby-lang.org/en/'
out: "<ul>\n\t<li><a href=\"http://www.ruby-lang.org/en/\">rubylang</a></li>\n\t</ul>"
---
in: 'The ION coding style document found at "IONCodingStyleGuide.doc":http://perforce:8081/@md=d&cd=//&c=82E@//depot/systest/system/main/pub/doc/IONCodingStyleGuide.doc?ac=22 codifies a couple of rules to ensure reasonably consistent code and documentation of libraries in ION. Test text'
out: <p>The <span class="caps">ION</span> coding style document found at <a href="http://perforce:8081/@md=d&#38;cd=//&#38;c=82E@//depot/systest/system/main/pub/doc/IONCodingStyleGuide.doc?ac=22">IONCodingStyleGuide.doc</a> codifies a couple of rules to ensure reasonably consistent code and documentation of libraries in <span class="caps">ION</span>. Test text</p>

View file

@ -0,0 +1,77 @@
--- # Bret Pettichord, Thanks.
in: |-
* first line
* second
line
* third line
out: |-
<ul>
<li>first line</li>
<li>second
line</li>
<li>third line</li>
</ul>
---
in: |-
p. start
* one
and one
* two
and two
* three
p. end
out: |-
<p>start</p>
<ul>
<li>one
and one</li>
<li>two
and two</li>
<li>three</li>
</ul>
<p>end</p>
---
in: |-
Funky:
* Testing
*# number
*##* bullet
*# number
*# number
yeah number
#* bullet
*** okay
****# what
out: |-
<p>Funky:</p>
<ul>
<li>Testing
<ol>
<li>number
<ul>
<li>bullet</li>
</ul>
</li>
<li>number</li>
<li>number
yeah number</li>
<ul>
<li>bullet
<ul>
<li>okay
<ol>
<li>what</li>
</ul></li>
</ol></li>
</ul></li>
</ul></li>
</ol>
---
in: "* command run: @time ruby run-tests.rb > toto@"
out: "<ul>\n\t<li>command run: <code>time ruby run-tests.rb &gt; toto</code></li>\n\t</ul>"

View file

@ -0,0 +1,218 @@
in: |
This is a regular paragraph.
<table>
<tr>
<td>Foo</td>
</tr>
</table>
This is another regular paragraph.
out: |-
<p>This is a regular paragraph.</p>
<table>
<tr>
<td>Foo</td>
</tr>
</table>
<p>This is another regular paragraph.</p>
---
in: '"Larry Bird":http://images.google.com/images?num=30&q=larry+bird'
out: '<p>"Larry Bird":http://images.google.com/images?num=30&#38;q=larry+bird</p>'
---
in: '&copy;'
out: <p>&copy;</p>
---
in: AT&T
out: <p>AT&#38;T</p>
# We don't do this.
# ---
# in: 4 < 5
# out: 4 &lt; 5
---
in: |
This is an H1
=============
This is an H2
-------------
out: |-
<h1>This is an H1</h1>
<h2>This is an H2</h2>
---
in: |
# This is an H1
## This is an H2
###### This is an H6
out: |-
<h1>This is an H1</h1>
<h2>This is an H2</h2>
<h6>This is an H6</h6>
---
in: |
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
>
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
> id sem consectetuer libero luctus adipiscing.
out: |-
<blockquote>
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.</p>
</blockquote>
---
in: |
> This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
>
> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.
out: |-
<blockquote>
<p>This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.</p>
<p>Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
id sem consectetuer libero luctus adipiscing.</p>
</blockquote>
---
in: |
> This is the first level of quoting.
>
> > This is nested blockquote.
>
> Back to the first level.
out: |-
<blockquote>
<p>This is the first level of quoting.</p>
<blockquote>
<p>This is nested blockquote.</p>
</blockquote>
<p>Back to the first level.</p>
</blockquote>
---
in: |
> ## This is a header.
>
> 1. This is the first list item.
> 2. This is the second list item.
>
> Here's some example code:
>
> return shell_exec("echo $input | $markdown_script");
out: |-
<blockquote>
<h2>This is a header.</h2>
<p>1. This is the first list item.
2. This is the second list item.</p>
<p>Here's some example code:</p>
<pre><code>return shell_exec("echo $input | $markdown_script");</code></pre>
</blockquote>
---
in: |
* * *
***
*****
- - -
---------------------------------------
_ _ _
out: |-
<hr />
<hr />
<hr />
<hr />
<hr />
<hr />
---
in: |
This is [an example](http://example.com/ "Title") inline link.
[This link](http://example.net/) has no title attribute.
out: |-
<p>This is <a href="http://example.com/" title="Title">an example</a> inline link.</p>
<p><a href="http://example.net/">This link</a> has no title attribute.</p>
---
in: See my [About](/about/) page for details.
out: <p>See my <a href="/about/">About</a> page for details.</p>
---
in: |
This is [an example][id] reference-style link.
This is [an example] [id] reference-style link.
[id]: http://example.com/ "Optional Title Here"
out: |-
<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.</p>
<p>This is <a href="http://example.com/" title="Optional Title Here">an example</a> reference-style link.</p>
---
in: |
[Google][]
[Google]: http://google.com/
out: <p><a href="http://google.com/">Google</a></p>
---
in: |
Visit [Daring Fireball][] for more information.
[Daring Fireball]: http://daringfireball.net/
out: <p>Visit <a href="http://daringfireball.net/">Daring Fireball</a> for more information.</p>
---
in: |
I get 10 times more traffic from [Google] [1] than from
[Yahoo] [2] or [MSN] [3].
[1]: http://google.com/ "Google"
[2]: http://search.yahoo.com/ "Yahoo Search"
[3]: http://search.msn.com/ "MSN Search"
out: |-
<p>I get 10 times more traffic from <a href="http://google.com/" title="Google">Google</a> than from
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
---
in: |
I get 10 times more traffic from [Google][] than from
[Yahoo][] or [MSN][].
[google]: http://google.com/ "Google"
[yahoo]: http://search.yahoo.com/ "Yahoo Search"
[msn]: http://search.msn.com/ "MSN Search"
out: |-
<p>I get 10 times more traffic from <a href="http://google.com/" title="Google">Google</a> than from
<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>

View file

@ -0,0 +1,64 @@
--- # Tests from the (Poignant Guide)
in: >
h3. False
!<i/blix-neg.gif(Shape of a cat.)!
_The cat Trady Blix. Frozen in emptiness. Immaculate whiskers rigid. Placid
eyes of lake. Tail of warm icicle. Sponsored by a Very Powerful Pause Button._
The darkness surrounding Blix can be called *negative space*. Hang on to that phrase.
Let it suggest that the emptiness has a negative connotation. In a similar way,
@nil@ has a slightly sour note that it whistles.
Generally speaking, everything in Ruby has a positive charge to it. This spark
flows through strings, numbers, regexps, all of it. Only two keywords wear a
shady cloak: @nil@ and @false@ draggin us down.
You can test that charge with an @if@ keyword. It looks very much like the
@do@ blocks we saw in the last chapter, in that both end with an @end@.
<pre>
if plastic_cup
print "Plastic cup is on the up 'n' up!"
end
</pre>
If @plastic_cup@ contains either @nil@ or @false@, you won't see anything print
to the screen. They're not on the @if@ guest list. So @if@ isn't going to run
any of the code it's protecting.
But @nil@ and @false@ need not walk away in shame. They may be of questionable
character, but @unless@ runs a smaller establishment that caters to the bedraggled.
The @unless@ keyword has a policy of only allowing those with a negative charge in.
Who are: @nil@ and @false@.
<pre>
unless plastic_cup
print "Plastic cup is on the down low."
end
</pre>
You can also use @if@ and @unless@ at the end of a single line of code, if that's
all that is being protected.
<pre>
print "Yeah, plastic cup is up again!" if plastic_cup
print "Hardly. It's down." unless plastic_cup
</pre>
Now that you've met @false@, I'm sure you can see what's on next.
out: "<h3>False</h3>\n\n\t<p style=\"float:left\"><img src=\"i/blix-neg.gif\" title=\"Shape of a cat.\" alt=\"Shape of a cat.\" /></p>\n\n\t<p><em>The cat Trady Blix. Frozen in emptiness. Immaculate whiskers rigid. Placid eyes of lake. Tail of warm icicle. Sponsored by a Very Powerful Pause Button.</em></p>\n\n\t<p>The darkness surrounding Blix can be called <strong>negative space</strong>. Hang on to that phrase. Let it suggest that the emptiness has a negative connotation. In a similar way, <code>nil</code> has a slightly sour note that it whistles.</p>\n\n\t<p>Generally speaking, everything in Ruby has a positive charge to it. This spark flows through strings, numbers, regexps, all of it. Only two keywords wear a shady cloak: <code>nil</code> and <code>false</code> draggin us down.</p>\n\n\t<p>You can test that charge with an <code>if</code> keyword. It looks very much like the <code>do</code> blocks we saw in the last chapter, in that both end with an <code>end</code>.</p>\n\n\n<pre>\n if plastic_cup\n print \"Plastic cup is on the up 'n' up!\" \n end\n</pre>\n\t<p>If <code>plastic_cup</code> contains either <code>nil</code> or <code>false</code>, you won&#8217;t see anything print to the screen. They&#8217;re not on the <code>if</code> guest list. So <code>if</code> isn&#8217;t going to run any of the code it&#8217;s protecting.</p>\n\n\t<p>But <code>nil</code> and <code>false</code> need not walk away in shame. They may be of questionable character, but <code>unless</code> runs a smaller establishment that caters to the bedraggled. The <code>unless</code> keyword has a policy of only allowing those with a negative charge in. Who are: <code>nil</code> and <code>false</code>.</p>\n\n\n<pre>\n unless plastic_cup\n print \"Plastic cup is on the down low.\" \n end\n</pre>\n\t<p>You can also use <code>if</code> and <code>unless</code> at the end of a single line of code, if that&#8217;s all that is being protected.</p>\n\n\n<pre>\n print \"Yeah, plastic cup is up again!\" if plastic_cup\n print \"Hardly. It's down.\" unless plastic_cup\n</pre>\n\t<p>Now that you&#8217;ve met <code>false</code>, I&#8217;m sure you can see what&#8217;s on next.</p>"

View file

@ -0,0 +1,198 @@
in: |
{background:#ddd}. |S|Target|Complete|App|Milestone|
|!/i/g.gif!|11/18/04|11/18/04|070|XML spec complete|
|!/i/g.gif!|11/29/04|11/29/04|011|XML spec complete (KH is on schedule)|
|!/i/g.gif!|11/29/04|11/29/04|051|XML spec complete (KH is on schedule)|
|!/i/g.gif!|11/29/04|11/29/04|081|XML spec complete (KH is on schedule)|
|!/i/g.gif!|11/19/04|11/22/04|070|Preprocessor complete|
|!/i/g.gif!|11/22/04|11/22/04|070|Dialog pass 1 builds an index file|
|!/i/g.gif!|11/24/04|11/24/04|070|Dialog pass 2 98% complete|
|!/i/g.gif!|11/30/04|11/30/04|070|Feature complete. Passes end-to-end smoke test.|
|!/i/g.gif!|11/30/04|11/30/04|011|Preprocessor updates complete|
|!/i/g.gif!|11/30/04|11/30/04|051|Preprocessor updates complete|
|!/i/g.gif!|11/30/04|11/29/04|081|Preprocessor updates complete|
|!/i/w.gif!|12/02/04|.|011|Dialog pass 1 and 2 complete (98+%)|
|!/i/w.gif!|12/02/04|.|051|Dialog pass 1 and 2 complete (98+%)|
|!/i/w.gif!|12/02/04|.|081|Dialog pass 1 and 2 complete (98+%)|
|!/i/w.gif!|12/03/04|.|011|Feature complete|
|!/i/w.gif!|12/03/04|.|051|Feature complete|
|!/i/w.gif!|12/03/04|.|081|Feature complete|
|!/i/w.gif!|12/10/04|.|011|Deployed to Napa test workstation. Passes smoke test.|
|!/i/w.gif!|12/10/04|.|051|Deployed to Napa test workstation. Passes smoke test.|
|!/i/w.gif!|12/10/04|.|081|Deployed to Napa test workstation. Passes smoke test.|
|!/i/w.gif!|12/10/04|.|070|Deployed to Napa test workstation. Passes smoke test.|
|!/i/w.gif!|12/17/04|.|011|System testing complete. Begin testing with live customer data.|
|!/i/w.gif!|12/17/04|.|051|System testing complete. Begin testing with live customer data.|
|!/i/w.gif!|12/17/04|.|081|System testing complete. Begin testing with live customer data.|
|!/i/w.gif!|12/17/04|.|070|System testing complete. Begin testing with live customer data.|
out: |-
<table>
<tr style="background:#ddd;">
<td>S</td>
<td>Target</td>
<td>Complete</td>
<td>App</td>
<td>Milestone</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/18/04</td>
<td>11/18/04</td>
<td>070</td>
<td>XML spec complete</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/29/04</td>
<td>11/29/04</td>
<td>011</td>
<td>XML spec complete (KH is on schedule)</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/29/04</td>
<td>11/29/04</td>
<td>051</td>
<td>XML spec complete (KH is on schedule)</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/29/04</td>
<td>11/29/04</td>
<td>081</td>
<td>XML spec complete (KH is on schedule)</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/19/04</td>
<td>11/22/04</td>
<td>070</td>
<td>Preprocessor complete</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/22/04</td>
<td>11/22/04</td>
<td>070</td>
<td>Dialog pass 1 builds an index file</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/24/04</td>
<td>11/24/04</td>
<td>070</td>
<td>Dialog pass 2 98% complete</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/30/04</td>
<td>11/30/04</td>
<td>070</td>
<td>Feature complete. Passes end-to-end smoke test.</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/30/04</td>
<td>11/30/04</td>
<td>011</td>
<td>Preprocessor updates complete</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/30/04</td>
<td>11/30/04</td>
<td>051</td>
<td>Preprocessor updates complete</td>
</tr>
<tr>
<td><img src="/i/g.gif" alt="" /></td>
<td>11/30/04</td>
<td>11/29/04</td>
<td>081</td>
<td>Preprocessor updates complete</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/02/04</td>
<td>011</td>
<td>Dialog pass 1 and 2 complete (98+%)</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/02/04</td>
<td>051</td>
<td>Dialog pass 1 and 2 complete (98+%)</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/02/04</td>
<td>081</td>
<td>Dialog pass 1 and 2 complete (98+%)</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/03/04</td>
<td>011</td>
<td>Feature complete</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/03/04</td>
<td>051</td>
<td>Feature complete</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/03/04</td>
<td>081</td>
<td>Feature complete</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/10/04</td>
<td>011</td>
<td>Deployed to Napa test workstation. Passes smoke test.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/10/04</td>
<td>051</td>
<td>Deployed to Napa test workstation. Passes smoke test.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/10/04</td>
<td>081</td>
<td>Deployed to Napa test workstation. Passes smoke test.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/10/04</td>
<td>070</td>
<td>Deployed to Napa test workstation. Passes smoke test.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/17/04</td>
<td>011</td>
<td>System testing complete. Begin testing with live customer data.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/17/04</td>
<td>051</td>
<td>System testing complete. Begin testing with live customer data.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/17/04</td>
<td>081</td>
<td>System testing complete. Begin testing with live customer data.</td>
</tr>
<tr>
<td><img src="/i/w.gif" alt="" /></td>
<td>12/17/04</td>
<td>070</td>
<td>System testing complete. Begin testing with live customer data.</td>
</tr>
</table>

View file

@ -0,0 +1,397 @@
---
in: h1. Header 1
out: <h1>Header 1</h1>
---
in: h2. Header 2
out: <h2>Header 2</h2>
---
in: h3. Header 3
out: <h3>Header 3</h3>
---
in: |-
Any old text.
bq. A block quotation.
Any old text.
out: |-
<p>Any old text.</p>
<blockquote>
<p>A block quotation.</p>
</blockquote>
<p>Any old text.</p>
---
in: This is covered elsewhere[1].
out: <p>This is covered elsewhere<sup><a href="#fn1">1</a></sup>.</p>
---
in: fn1. Down here, in fact.
out: <p id="fn1"><sup>1</sup> Down here, in fact.</p>
---
in: |-
# A first item
# A second item
# A third item
# A fourth item
out: |-
<ol>
<li>A first item</li>
<li>A second item</li>
<li>A third item</li>
<li>A fourth item</li>
</ol>
---
in: |-
* A first item
* A second item
* A third item
* A fourth item
out: |-
<ul>
<li>A first item</li>
<li>A second item</li>
<li>A third item</li>
<li>A fourth item</li>
</ul>
---
in: _a phrase_
out: <p><em>a phrase</em></p>
---
in: __a phrase__
out: <p><i>a phrase</i></p>
---
in: '*a phrase*'
out: <p><strong>a phrase</strong></p>
---
in: '**a phrase**'
out: <p><b>a phrase</b></p>
---
in: Nabokov's ??Pnin??
out: <p>Nabokov&#8217;s <cite>Pnin</cite></p>
---
in: -a phrase-
out: <p><del>a phrase</del></p>
---
in: +a phrase+
out: <p><ins>a phrase</ins></p>
---
in: ^a phrase^
out: <p><sup>a phrase</sup></p>
---
in: ~a phrase~
out: <p><sub>a phrase</sub></p>
# ---
# in: %(caps)SPAN%
# out: <p><span class="caps">SPAN</span>
---
in: %{color:red}red%
out: <p><span style="color:red;">red</span></p>
---
in: %[fr]rouge%
out: <p><span lang="fr">rouge</span></p>
---
in: _(big)red_
out: <p><em class="big">red</em></p>
---
in: p(bob). A paragraph
out: <p class="bob">A paragraph</p>
---
in: p{color:#ddd}. A paragraph
out: <p style="color:#ddd;">A paragraph</p>
---
in: p[fr]. A paragraph
out: <p lang="fr">A paragraph</p>
---
in: h2()>. right-aligned header2, indented 1em both side
out: <h2 style="padding-left:1em;padding-right:1em;text-align:right;">right-aligned header2, indented 1em both side</h2>
---
in: h3=. centered header
out: <h3 style="text-align:center;">centered header</h3>
---
in: '!>/image.gif! right-aligned image'
out: <p style="float:right"><img src="/image.gif" alt="" /> right-aligned image</p>
---
in: p[no]{color:red}. A Norse of a different colour.
out: <p style="color:red;" lang="no">A Norse of a different colour.</p>
---
in: |-
|This|is|a|simple|table|
|This|is|a|simple|row|
out: |-
<table>
<tr>
<td>This</td>
<td>is</td>
<td>a</td>
<td>simple</td>
<td>table</td>
</tr>
<tr>
<td>This</td>
<td>is</td>
<td>a</td>
<td>simple</td>
<td>row</td>
</tr>
</table>
---
in: |-
table{border:1px solid black}.
|This|is|a|row|
|This|is|a|row|
out: |-
<table style="border:1px solid black;">
<tr>
<td>This</td>
<td>is</td>
<td>a</td>
<td>row</td>
</tr>
<tr>
<td>This</td>
<td>is</td>
<td>a</td>
<td>row</td>
</tr>
</table>
---
in: '{background:#ddd}. |This|is|a|row|'
out: |-
<table>
<tr style="background:#ddd;">
<td>This</td>
<td>is</td>
<td>a</td>
<td>row</td>
</tr>
</table>
---
in: |-
|{background:#ddd}. Cell with gray background|
|\2. Cell spanning 2 columns|
|/3. Cell spanning 3 rows|
|>. Right-aligned cell|
out: |-
<table>
<tr>
<td style="background:#ddd;">Cell with gray background</td>
</tr>
<tr>
<td colspan="2">Cell spanning 2 columns</td>
</tr>
<tr>
<td rowspan="3">Cell spanning 3 rows</td>
</tr>
<tr>
<td style="text-align:right;">Right-aligned cell</td>
</tr>
</table>
# ---
# in: |-
# This is a "link":bob to Bob's website.
#
# [bob]http://itsbob.com/index.html
---
in: ACLU(American Civil Liberties Union)
out: <p><acronym title="American Civil Liberties Union">ACLU</acronym></p>
---
in: |-
h2{color:green}. This is a title
h3. This is a subhead
p{color:red}. This is some text of dubious character. Isn't the use of "quotes" just lazy writing -- and theft of 'intellectual property' besides? I think the time has come to see a block quote.
bq[fr]. This is a block quote. I'll admit it's not the most exciting block quote ever devised.
Simple list:
#{color:blue} one
# two
# three
Multi-level list:
# one
## aye
## bee
## see
# two
## x
## y
# three
Mixed list:
* Point one
* Point two
## Step 1
## Step 2
## Step 3
* Point three
** Sub point 1
** Sub point 2
Well, that went well. How about we insert an <a href="/" title="watch out">old-fashioned hypertext link</a>? Will the quote marks in the tags get messed up? No!
"This is a link (optional title)":http://www.textism.com
table{border:1px solid black}.
|_. this|_. is|_. a|_. header|
<{background:gray}. |\2. this is|{background:red;width:200px}. a|^<>{height:200px}. row|
|this|<>{padding:10px}. is|^. another|(bob#bob). row|
An image:
!/common/textist.gif(optional alt text)!
# Librarians rule
# Yes they do
# But you knew that
Some more text of dubious character. Here is a noisome string of CAPITAL letters. Here is something we want to _emphasize_.
That was a linebreak. And something to indicate *strength*. Of course I could use <em>my own HTML tags</em> if I <strong>felt</strong> like it.
h3. Coding
This <code>is some code, "isn't it"</code>. Watch those quote marks! Now for some preformatted text:
<pre>
<code>
$text = str_replace("<p>%::%</p>","",$text);
$text = str_replace("%::%</p>","",$text);
$text = str_replace("%::%","",$text);
</code>
</pre>
This isn't code.
So you see, my friends:
* The time is now
* The time is not later
* The time is not yesterday
* We must act
out: |-
<h2 style="color:green;">This is a title</h2>
<h3>This is a subhead</h3>
<p style="color:red;">This is some text of dubious character. Isn&#8217;t the use of &#8220;quotes&#8221; just lazy writing&#8212;and theft of &#8216;intellectual property&#8217; besides? I think the time has come to see a block quote.</p>
<blockquote>
<p lang="fr">This is a block quote. I&#8217;ll admit it&#8217;s not the most exciting block quote ever devised.</p>
</blockquote>
<p>Simple list:</p>
<ol style="color:blue;">
<li>one</li>
<li>two</li>
<li>three</li>
</ol>
<p>Multi-level list:</p>
<ol>
<li>one
<ol>
<li>aye</li>
<li>bee</li>
<li>see</li>
</ol>
</li>
<li>two
<ol>
<li>x</li>
<li>y</li>
</ol>
</li>
<li>three</li>
</ol>
<p>Mixed list:</p>
<ul>
<li>Point one</li>
<li>Point two
<ol>
<li>Step 1</li>
<li>Step 2</li>
<li>Step 3</li>
</ol>
</li>
<li>Point three
<ul>
<li>Sub point 1</li>
<li>Sub point 2</li>
</ul></li>
</ul>
<p>Well, that went well. How about we insert an <a href="/" title="watch out">old-fashioned hypertext link</a>? Will the quote marks in the tags get messed up? No!</p>
<p><a href="http://www.textism.com" title="optional title">This is a link</a></p>
<table style="border:1px solid black;">
<tr>
<th>this</th>
<th>is</th>
<th>a</th>
<th>header</th>
</tr>
<tr style="background:gray;text-align:left;">
<td colspan="2">this is</td>
<td style="background:red;width:200px;">a</td>
<td style="vertical-align:top;height:200px;text-align:justify;">row</td>
</tr>
<tr>
<td>this</td>
<td style="padding:10px;text-align:justify;">is</td>
<td style="vertical-align:top;">another</td>
<td class="bob" id="bob">row</td>
</tr>
</table>
<p>An image:</p>
<p><img src="/common/textist.gif" title="optional alt text" alt="optional alt text" /></p>
<ol>
<li>Librarians rule</li>
<li>Yes they do</li>
<li>But you knew that</li>
</ol>
<p>Some more text of dubious character. Here is a noisome string of <span class="caps">CAPITAL</span> letters. Here is something we want to <em>emphasize</em>.
That was a linebreak. And something to indicate <strong>strength</strong>. Of course I could use <em>my own <span class="caps">HTML</span> tags</em> if I <strong>felt</strong> like it.</p>
<h3>Coding</h3>
<p>This <code>is some code, "isn't it"</code>. Watch those quote marks! Now for some preformatted text:</p>
<pre>
<code>
$text = str_replace("&lt;p&gt;%::%&lt;/p&gt;","",$text);
$text = str_replace("%::%&lt;/p&gt;","",$text);
$text = str_replace("%::%","",$text);
</code>
</pre>
<p>This isn&#8217;t code.</p>
<p>So you see, my friends:</p>
<ul>
<li>The time is now</li>
<li>The time is not later</li>
<li>The time is not yesterday</li>
<li>We must act</li>
</ul>

View file

@ -0,0 +1,211 @@
*1.1.5* (December 13th, 2005)
* Become part of Rails 1.0
*1.1.4* (December 7th, 2005)
* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
*1.1.3* (November 7th, 2005)
* Allow Mailers to have custom initialize methods that set default instance variables for all mail actions #2563 [mrj@bigpond.net.au]
*1.1.2* (October 26th, 2005)
* Upgraded to Action Pack 1.10.2
*1.1.1* (October 19th, 2005)
* Upgraded to Action Pack 1.10.1
*1.1.0* (October 16th, 2005)
* Update and extend documentation (rdoc)
* Minero Aoki made TMail available to Rails/ActionMailer under the MIT license (instead of LGPL) [RubyConf '05]
* Austin Ziegler made Text::Simple available to Rails/ActionMailer under a MIT-like licens [See rails ML, subject "Text::Format Licence Exception" on Oct 15, 2005]
* Fix vendor require paths to prevent files being required twice
* Don't add charset to content-type header for a part that contains subparts (for AOL compatibility) #2013 [John Long]
* Preserve underscores when unquoting message bodies #1930
* Encode multibyte characters correctly #1894
* Multipart messages specify a MIME-Version header automatically #2003 [John Long]
* Add a unified render method to ActionMailer (delegates to ActionView::Base#render)
* Move mailer initialization to a separate (overridable) method, so that subclasses may alter the various defaults #1727
* Look at content-location header (if available) to determine filename of attachments #1670
* ActionMailer::Base.deliver(email) had been accidentally removed, but was documented in the Rails book #1849
* Fix problem with sendmail delivery where headers should be delimited by \n characters instead of \r\n, which confuses some mail readers #1742 [Kent Sibilev]
*1.0.1* (11 July, 2005)
* Bind to Action Pack 1.9.1
*1.0.0* (6 July, 2005)
* Avoid adding nil header values #1392
* Better multipart support with implicit multipart/alternative and sorting of subparts [John Long]
* Allow for nested parts in multipart mails #1570 [Flurin Egger]
* Normalize line endings in outgoing mail bodies to "\n" #1536 [John Long]
* Allow template to be explicitly specified #1448 [tuxie@dekadance.se]
* Allow specific "multipart/xxx" content-type to be set on multipart messages #1412 [Flurin Egger]
* Unquoted @ characters in headers are now accepted in spite of RFC 822 #1206
* Helper support (borrowed from ActionPack)
* Silently ignore Errno::EINVAL errors when converting text.
* Don't cause an error when parsing an encoded attachment name #1340 [lon@speedymac.com]
* Nested multipart message parts are correctly processed in TMail::Mail#body
* BCC headers are removed when sending via SMTP #1402
* Added 'content_type' accessor, to allow content type to be set on a per-message basis. content_type defaults to "text/plain".
* Silently ignore Iconv::IllegalSequence errors when converting text #1341 [lon@speedymac.com]
* Support attachments and multipart messages.
* Added new accessors for the various mail properties.
* Fix to only perform the charset conversion if a 'from' and a 'to' charset are given (make no assumptions about what the charset was) #1276 [Jamis Buck]
* Fix attachments and content-type problems #1276 [Jamis Buck]
* Fixed the TMail#body method to look at the content-transfer-encoding header and unquote the body according to the rules it specifies #1265 [Jamis Buck]
* Added unquoting even if the iconv lib can't be loaded--in that case, only the charset conversion is skipped #1265 [Jamis Buck]
* Added automatic decoding of base64 bodies #1214 [Jamis Buck]
* Added that delivery errors are caught in a way so the mail is still returned whether the delivery was successful or not
* Fixed that email address like "Jamis Buck, M.D." <wild.medicine@example.net> would cause the quoter to generate emails resulting in "bad address" errors from the mail server #1220 [Jamis Buck]
*0.9.1* (20th April, 2005)
* Depend on Action Pack 1.8.1
*0.9.0* (19th April, 2005)
* Added that deliver_* will now return the email that was sent
* Added that quoting to UTF-8 only happens if the characters used are in that range #955 [Jamis Buck]
* Fixed quoting for all address headers, not just to #955 [Jamis Buck]
* Fixed unquoting of emails that doesn't have an explicit charset #1036 [wolfgang@stufenlos.net]
*0.8.1* (27th March, 2005)
* Fixed that if charset was found that the end of a mime part declaration TMail would throw an error #919 [lon@speedymac.com]
* Fixed that TMail::Unquoter would fail to recognize quoting method if it was in lowercase #919 [lon@speedymac.com]
* Fixed that TMail::Encoder would fail when it attempts to parse e-mail addresses which are encoded using something other than the messages encoding method #919 [lon@speedymac.com]
* Added rescue for missing iconv library and throws warnings if subject/body is called on a TMail object without it instead
*0.8.0* (22th March, 2005)
* Added framework support for processing incoming emails with an Action Mailer class. See example in README.
*0.7.1* (7th March, 2005)
* Bind to newest Action Pack (1.5.1)
*0.7.0* (24th February, 2005)
* Added support for charsets for both subject and body. The default charset is now UTF-8 #673 [Jamis Buck]. Examples:
def iso_charset(recipient)
@recipients = recipient
@subject = "testing iso charsets"
@from = "system@loudthinking.com"
@body = "Nothing to see here."
@charset = "iso-8859-1"
end
def unencoded_subject(recipient)
@recipients = recipient
@subject = "testing unencoded subject"
@from = "system@loudthinking.com"
@body = "Nothing to see here."
@encode_subject = false
@charset = "iso-8859-1"
end
*0.6.1* (January 18th, 2005)
* Fixed sending of emails to use Tmail#from not the deprecated Tmail#from_address
*0.6* (January 17th, 2005)
* Fixed that bcc and cc should be settable through @bcc and @cc -- not just @headers["Bcc"] and @headers["Cc"] #453 [Eric Hodel]
* Fixed Action Mailer to be "warnings safe" so you can run with ruby -w and not get framework warnings #453 [Eric Hodel]
*0.5*
* Added access to custom headers, like cc, bcc, and reply-to #268 [Andreas Schwarz]. Example:
def post_notification(recipients, post)
@recipients = recipients
@from = post.author.email_address_with_name
@headers["bcc"] = SYSTEM_ADMINISTRATOR_EMAIL
@headers["reply-to"] = "notifications@example.com"
@subject = "[#{post.account.name} #{post.title}]"
@body["post"] = post
end
*0.4* (5)
* Consolidated the server configuration options into Base#server_settings= and expanded that with controls for authentication and more [Marten]
NOTE: This is an API change that could potentially break your application if you used the old application form. Please do change!
* Added Base#deliveries as an accessor for an array of emails sent out through that ActionMailer class when using the :test delivery option. [Jeremy Kemper]
* Added Base#perform_deliveries= which can be set to false to turn off the actual delivery of the email through smtp or sendmail.
This is especially useful for functional testing that shouldn't send off real emails, but still trigger delivery_* methods.
* Added option to specify delivery method with Base#delivery_method=. Default is :smtp and :sendmail is currently the only other option.
Sendmail is assumed to be present at "/usr/sbin/sendmail" if that option is used. [Kent Sibilev]
* Dropped "include TMail" as it added to much baggage into the default namespace (like Version) [Chad Fowler]
*0.3*
* First release

View file

@ -0,0 +1,21 @@
Copyright (c) 2004 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

148
tracks/vendor/rails/actionmailer/README vendored Normal file
View file

@ -0,0 +1,148 @@
= Action Mailer -- Easy email delivery and testing
Action Mailer is a framework for designing email-service layers. These layers
are used to consolidate code for sending out forgotten passwords, welcoming
wishes on signup, invoices for billing, and any other use case that requires
a written notification to either a person or another system.
Additionally, an Action Mailer class can be used to process incoming email,
such as allowing a weblog to accept new posts from an email (which could even
have been sent from a phone).
== Sending emails
The framework works by setting up all the email details, except the body,
in methods on the service layer. Subject, recipients, sender, and timestamp
are all set up this way. An example of such a method:
def signed_up(recipient)
recipients recipient
subject "[Signed up] Welcome #{recipient}"
from "system@loudthinking.com"
body(:recipient => recipient)
end
The body of the email is created by using an Action View template (regular
ERb) that has the content of the body hash parameter available as instance variables.
So the corresponding body template for the method above could look like this:
Hello there,
Mr. <%= @recipient %>
And if the recipient was given as "david@loudthinking.com", the email
generated would look like this:
Date: Sun, 12 Dec 2004 00:00:00 +0100
From: system@loudthinking.com
To: david@loudthinking.com
Subject: [Signed up] Welcome david@loudthinking.com
Hello there,
Mr. david@loudthinking.com
You never actually call the instance methods like signed_up directly. Instead,
you call class methods like deliver_* and create_* that are automatically
created for each instance method. So if the signed_up method sat on
ApplicationMailer, it would look like this:
ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing
ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email
ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work!
== Receiving emails
To receive emails, you need to implement a public instance method called receive that takes a
tmail object as its single parameter. The Action Mailer framework has a corresponding class method,
which is also called receive, that accepts a raw, unprocessed email as a string, which it then turns
into the tmail object and calls the receive instance method.
Example:
class Mailman < ActionMailer::Base
def receive(email)
page = Page.find_by_address(email.to.first)
page.emails.create(
:subject => email.subject, :body => email.body
)
if email.has_attachments?
for attachment in email.attachments
page.attachments.create({
:file => attachment, :description => email.subject
})
end
end
end
end
This Mailman can be the target for Postfix. In Rails, you would use the runner like this:
./script/runner 'Mailman.receive(STDIN.read)'
== Configuration
The Base class has the full list of configuration options. Here's an example:
ActionMailer::Base.server_settings = {
:address=>'smtp.yourserver.com', # default: localhost
:port=>'25', # default: 25
:user_name=>'user',
:password=>'pass',
:authentication=>:plain # :plain, :login or :cram_md5
}
== Dependencies
Action Mailer requires that the Action Pack is either available to be required immediately
or is accessible as a GEM.
== Bundled software
* tmail 0.10.8 by Minero Aoki released under LGPL
Read more on http://i.loveruby.net/en/prog/tmail.html
* Text::Format 0.63 by Austin Ziegler released under OpenSource
Read more on http://www.halostatue.ca/ruby/Text__Format.html
== Download
The latest version of Action Mailer can be found at
* http://rubyforge.org/project/showfiles.php?group_id=361
Documentation can be found at
* http://actionmailer.rubyonrails.org
== Installation
You can install Action Mailer with the following command.
% [sudo] ruby install.rb
from its distribution directory.
== License
Action Mailer is released under the MIT license.
== Support
The Action Mailer homepage is http://actionmailer.rubyonrails.org. You can find
the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
And as Jim from Rake says:
Feel free to submit commits or feature requests. If you send a patch,
remember to update the corresponding unit tests. If fact, I prefer
new feature to be submitted in the form of new unit tests.
For other information, feel free to ask on the ruby-talk mailing list (which
is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.

View file

@ -0,0 +1,30 @@
require 'rbconfig'
require 'find'
require 'ftools'
include Config
# this was adapted from rdoc's install.rb by way of Log4r
$sitedir = CONFIG["sitelibdir"]
unless $sitedir
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
$libdir = File.join(CONFIG["libdir"], "ruby", version)
$sitedir = $:.find {|x| x =~ /site_ruby/ }
if !$sitedir
$sitedir = File.join($libdir, "site_ruby")
elsif $sitedir !~ Regexp.quote(version)
$sitedir = File.join($sitedir, version)
end
end
# the acual gruntwork
Dir.chdir("lib")
Find.find("action_mailer", "action_mailer.rb") { |f|
if f[-3..-1] == ".rb"
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
else
File::makedirs(File.join($sitedir, *f.split(/\//)))
end
}

View file

@ -0,0 +1,51 @@
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
begin
require 'action_controller'
rescue LoadError
begin
require File.dirname(__FILE__) + '/../../actionpack/lib/action_controller'
rescue LoadError
require 'rubygems'
require_gem 'actionpack', '>= 1.9.1'
end
end
$:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
require 'action_mailer/base'
require 'action_mailer/helpers'
require 'action_mailer/mail_helper'
require 'action_mailer/quoting'
require 'tmail'
require 'net/smtp'
ActionMailer::Base.class_eval do
include ActionMailer::Quoting
include ActionMailer::Helpers
helper MailHelper
end
silence_warnings { TMail::Encoder.const_set("MAX_LINE_LEN", 200) }

View file

@ -0,0 +1,27 @@
module ActionMailer
module AdvAttrAccessor #:nodoc:
def self.append_features(base)
super
base.extend(ClassMethods)
end
module ClassMethods #:nodoc:
def adv_attr_accessor(*names)
names.each do |name|
define_method("#{name}=") do |value|
instance_variable_set("@#{name}", value)
end
define_method(name) do |*parameters|
raise ArgumentError, "expected 0 or 1 parameters" unless parameters.length <= 1
if parameters.empty?
instance_variable_get("@#{name}")
else
instance_variable_set("@#{name}", parameters.first)
end
end
end
end
end
end
end

View file

@ -0,0 +1,453 @@
require 'action_mailer/adv_attr_accessor'
require 'action_mailer/part'
require 'action_mailer/part_container'
require 'action_mailer/utils'
require 'tmail/net'
module ActionMailer
# Usage:
#
# class ApplicationMailer < ActionMailer::Base
# # Set up properties
# # Properties can also be specified via accessor methods
# # (i.e. self.subject = "foo") and instance variables (@subject = "foo").
# def signup_notification(recipient)
# recipients recipient.email_address_with_name
# subject "New account information"
# body { "account" => recipient }
# from "system@example.com"
# end
#
# # explicitly specify multipart messages
# def signup_notification(recipient)
# recipients recipient.email_address_with_name
# subject "New account information"
# from "system@example.com"
#
# part :content_type => "text/html",
# :body => render_message("signup-as-html", :account => recipient)
#
# part "text/plain" do |p|
# p.body = render_message("signup-as-plain", :account => recipient)
# p.transfer_encoding = "base64"
# end
# end
#
# # attachments
# def signup_notification(recipient)
# recipients recipient.email_address_with_name
# subject "New account information"
# from "system@example.com"
#
# attachment :content_type => "image/jpeg",
# :body => File.read("an-image.jpg")
#
# attachment "application/pdf" do |a|
# a.body = generate_your_pdf_here()
# end
# end
#
# # implicitly multipart messages
# def signup_notification(recipient)
# recipients recipient.email_address_with_name
# subject "New account information"
# from "system@example.com"
# body(:account => "recipient")
#
# # ActionMailer will automatically detect and use multipart templates,
# # where each template is named after the name of the action, followed
# # by the content type. Each such detected template will be added as
# # a separate part to the message.
# #
# # for example, if the following templates existed:
# # * signup_notification.text.plain.rhtml
# # * signup_notification.text.html.rhtml
# # * signup_notification.text.xml.rxml
# # * signup_notification.text.x-yaml.rhtml
# #
# # Each would be rendered and added as a separate part to the message,
# # with the corresponding content type. The same body hash is passed to
# # each template.
# end
# end
#
# # After this, post_notification will look for "templates/application_mailer/post_notification.rhtml"
# ApplicationMailer.template_root = "templates"
#
# ApplicationMailer.create_comment_notification(david, hello_world) # => a tmail object
# ApplicationMailer.deliver_comment_notification(david, hello_world) # sends the email
#
# = Configuration options
#
# These options are specified on the class level, like <tt>ActionMailer::Base.template_root = "/my/templates"</tt>
#
# * <tt>template_root</tt> - template root determines the base from which template references will be made.
#
# * <tt>logger</tt> - the logger is used for generating information on the mailing run if available.
# Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
#
# * <tt>server_settings</tt> - Allows detailed configuration of the server:
# * <tt>:address</tt> Allows you to use a remote mail server. Just change it from its default "localhost" setting.
# * <tt>:port</tt> On the off chance that your mail server doesn't run on port 25, you can change it.
# * <tt>:domain</tt> If you need to specify a HELO domain, you can do it here.
# * <tt>:user_name</tt> If your mail server requires authentication, set the username in this setting.
# * <tt>:password</tt> If your mail server requires authentication, set the password in this setting.
# * <tt>:authentication</tt> If your mail server requires authentication, you need to specify the authentication type here.
# This is a symbol and one of :plain, :login, :cram_md5
#
# * <tt>raise_delivery_errors</tt> - whether or not errors should be raised if the email fails to be delivered.
#
# * <tt>delivery_method</tt> - Defines a delivery method. Possible values are :smtp (default), :sendmail, and :test.
# Sendmail is assumed to be present at "/usr/sbin/sendmail".
#
# * <tt>perform_deliveries</tt> - Determines whether deliver_* methods are actually carried out. By default they are,
# but this can be turned off to help functional testing.
#
# * <tt>deliveries</tt> - Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful
# for unit and functional testing.
#
# * <tt>default_charset</tt> - The default charset used for the body and to encode the subject. Defaults to UTF-8. You can also
# pick a different charset from inside a method with <tt>@charset</tt>.
# * <tt>default_content_type</tt> - The default content type used for the main part of the message. Defaults to "text/plain". You
# can also pick a different content type from inside a method with <tt>@content_type</tt>.
# * <tt>default_mime_version</tt> - The default mime version used for the message. Defaults to nil. You
# can also pick a different value from inside a method with <tt>@mime_version</tt>. When multipart messages are in
# use, <tt>@mime_version</tt> will be set to "1.0" if it is not set inside a method.
# * <tt>default_implicit_parts_order</tt> - When a message is built implicitly (i.e. multiple parts are assembled from templates
# which specify the content type in their filenames) this variable controls how the parts are ordered. Defaults to
# ["text/html", "text/enriched", "text/plain"]. Items that appear first in the array have higher priority in the mail client
# and appear last in the mime encoded message. You can also pick a different order from inside a method with
# <tt>@implicit_parts_order</tt>.
class Base
include AdvAttrAccessor, PartContainer
private_class_method :new #:nodoc:
cattr_accessor :template_root
cattr_accessor :logger
@@server_settings = {
:address => "localhost",
:port => 25,
:domain => 'localhost.localdomain',
:user_name => nil,
:password => nil,
:authentication => nil
}
cattr_accessor :server_settings
@@raise_delivery_errors = true
cattr_accessor :raise_delivery_errors
@@delivery_method = :smtp
cattr_accessor :delivery_method
@@perform_deliveries = true
cattr_accessor :perform_deliveries
@@deliveries = []
cattr_accessor :deliveries
@@default_charset = "utf-8"
cattr_accessor :default_charset
@@default_content_type = "text/plain"
cattr_accessor :default_content_type
@@default_mime_version = nil
cattr_accessor :default_mime_version
@@default_implicit_parts_order = [ "text/html", "text/enriched", "text/plain" ]
cattr_accessor :default_implicit_parts_order
# Specify the BCC addresses for the message
adv_attr_accessor :bcc
# Define the body of the message. This is either a Hash (in which case it
# specifies the variables to pass to the template when it is rendered),
# or a string, in which case it specifies the actual text of the message.
adv_attr_accessor :body
# Specify the CC addresses for the message.
adv_attr_accessor :cc
# Specify the charset to use for the message. This defaults to the
# +default_charset+ specified for ActionMailer::Base.
adv_attr_accessor :charset
# Specify the content type for the message. This defaults to <tt>text/plain</tt>
# in most cases, but can be automatically set in some situations.
adv_attr_accessor :content_type
# Specify the from address for the message.
adv_attr_accessor :from
# Specify additional headers to be added to the message.
adv_attr_accessor :headers
# Specify the order in which parts should be sorted, based on content-type.
# This defaults to the value for the +default_implicit_parts_order+.
adv_attr_accessor :implicit_parts_order
# Override the mailer name, which defaults to an inflected version of the
# mailer's class name. If you want to use a template in a non-standard
# location, you can use this to specify that location.
adv_attr_accessor :mailer_name
# Defaults to "1.0", but may be explicitly given if needed.
adv_attr_accessor :mime_version
# The recipient addresses for the message, either as a string (for a single
# address) or an array (for multiple addresses).
adv_attr_accessor :recipients
# The date on which the message was sent. If not set (the default), the
# header will be set by the delivery agent.
adv_attr_accessor :sent_on
# Specify the subject of the message.
adv_attr_accessor :subject
# Specify the template name to use for current message. This is the "base"
# template name, without the extension or directory, and may be used to
# have multiple mailer methods share the same template.
adv_attr_accessor :template
# The mail object instance referenced by this mailer.
attr_reader :mail
class << self
def method_missing(method_symbol, *parameters)#:nodoc:
case method_symbol.id2name
when /^create_([_a-z]\w*)/ then new($1, *parameters).mail
when /^deliver_([_a-z]\w*)/ then new($1, *parameters).deliver!
when "new" then nil
else super
end
end
# Receives a raw email, parses it into an email object, decodes it,
# instantiates a new mailer, and passes the email object to the mailer
# object's #receive method. If you want your mailer to be able to
# process incoming messages, you'll need to implement a #receive
# method that accepts the email object as a parameter:
#
# class MyMailer < ActionMailer::Base
# def receive(mail)
# ...
# end
# end
def receive(raw_email)
logger.info "Received mail:\n #{raw_email}" unless logger.nil?
mail = TMail::Mail.parse(raw_email)
mail.base64_decode
new.receive(mail)
end
# Deliver the given mail object directly. This can be used to deliver
# a preconstructed mail object, like:
#
# email = MyMailer.create_some_mail(parameters)
# email.set_some_obscure_header "frobnicate"
# MyMailer.deliver(email)
def deliver(mail)
new.deliver!(mail)
end
end
# Instantiate a new mailer object. If +method_name+ is not +nil+, the mailer
# will be initialized according to the named method. If not, the mailer will
# remain uninitialized (useful when you only need to invoke the "receive"
# method, for instance).
def initialize(method_name=nil, *parameters) #:nodoc:
create!(method_name, *parameters) if method_name
end
# Initialize the mailer via the given +method_name+. The body will be
# rendered and a new TMail::Mail object created.
def create!(method_name, *parameters) #:nodoc:
initialize_defaults(method_name)
send(method_name, *parameters)
# If an explicit, textual body has not been set, we check assumptions.
unless String === @body
# First, we look to see if there are any likely templates that match,
# which include the content-type in their file name (i.e.,
# "the_template_file.text.html.rhtml", etc.). Only do this if parts
# have not already been specified manually.
if @parts.empty?
templates = Dir.glob("#{template_path}/#{@template}.*")
templates.each do |path|
type = (File.basename(path).split(".")[1..-2] || []).join("/")
next if type.empty?
@parts << Part.new(:content_type => type,
:disposition => "inline", :charset => charset,
:body => render_message(File.basename(path).split(".")[0..-2].join('.'), @body))
end
unless @parts.empty?
@content_type = "multipart/alternative"
@charset = nil
@parts = sort_parts(@parts, @implicit_parts_order)
end
end
# Then, if there were such templates, we check to see if we ought to
# also render a "normal" template (without the content type). If a
# normal template exists (or if there were no implicit parts) we render
# it.
template_exists = @parts.empty?
template_exists ||= Dir.glob("#{template_path}/#{@template}.*").any? { |i| i.split(".").length == 2 }
@body = render_message(@template, @body) if template_exists
# Finally, if there are other message parts and a textual body exists,
# we shift it onto the front of the parts and set the body to nil (so
# that create_mail doesn't try to render it in addition to the parts).
if !@parts.empty? && String === @body
@parts.unshift Part.new(:charset => charset, :body => @body)
@body = nil
end
end
# If this is a multipart e-mail add the mime_version if it is not
# already set.
@mime_version ||= "1.0" if !@parts.empty?
# build the mail object itself
@mail = create_mail
end
# Delivers a TMail::Mail object. By default, it delivers the cached mail
# object (from the #create! method). If no cached mail object exists, and
# no alternate has been given as the parameter, this will fail.
def deliver!(mail = @mail)
raise "no mail object available for delivery!" unless mail
logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
begin
send("perform_delivery_#{delivery_method}", mail) if perform_deliveries
rescue Object => e
raise e if raise_delivery_errors
end
return mail
end
private
# Set up the default values for the various instance variables of this
# mailer. Subclasses may override this method to provide different
# defaults.
def initialize_defaults(method_name)
@charset ||= @@default_charset.dup
@content_type ||= @@default_content_type.dup
@implicit_parts_order ||= @@default_implicit_parts_order.dup
@template ||= method_name
@mailer_name ||= Inflector.underscore(self.class.name)
@parts ||= []
@headers ||= {}
@body ||= {}
@mime_version = @@default_mime_version.dup if @@default_mime_version
end
def render_message(method_name, body)
render :file => method_name, :body => body
end
def render(opts)
body = opts.delete(:body)
initialize_template_class(body).render(opts)
end
def template_path
"#{template_root}/#{mailer_name}"
end
def initialize_template_class(assigns)
ActionView::Base.new(template_path, assigns, self)
end
def sort_parts(parts, order = [])
order = order.collect { |s| s.downcase }
parts = parts.sort do |a, b|
a_ct = a.content_type.downcase
b_ct = b.content_type.downcase
a_in = order.include? a_ct
b_in = order.include? b_ct
s = case
when a_in && b_in
order.index(a_ct) <=> order.index(b_ct)
when a_in
-1
when b_in
1
else
a_ct <=> b_ct
end
# reverse the ordering because parts that come last are displayed
# first in mail clients
(s * -1)
end
parts
end
def create_mail
m = TMail::Mail.new
m.subject, = quote_any_if_necessary(charset, subject)
m.to, m.from = quote_any_address_if_necessary(charset, recipients, from)
m.bcc = quote_address_if_necessary(bcc, charset) unless bcc.nil?
m.cc = quote_address_if_necessary(cc, charset) unless cc.nil?
m.mime_version = mime_version unless mime_version.nil?
m.date = sent_on.to_time rescue sent_on if sent_on
headers.each { |k, v| m[k] = v }
if @parts.empty?
m.set_content_type content_type, nil, { "charset" => charset }
m.body = Utils.normalize_new_lines(body)
else
if String === body
part = TMail::Mail.new
part.body = Utils.normalize_new_lines(body)
part.set_content_type content_type, nil, { "charset" => charset }
part.set_content_disposition "inline"
m.parts << part
end
@parts.each do |p|
part = (TMail::Mail === p ? p : p.to_mail(self))
m.parts << part
end
m.set_content_type(content_type, nil, { "charset" => charset }) if content_type =~ /multipart/
end
@mail = m
end
def perform_delivery_smtp(mail)
destinations = mail.destinations
mail.ready_to_send
Net::SMTP.start(server_settings[:address], server_settings[:port], server_settings[:domain],
server_settings[:user_name], server_settings[:password], server_settings[:authentication]) do |smtp|
smtp.sendmail(mail.encoded, mail.from, destinations)
end
end
def perform_delivery_sendmail(mail)
IO.popen("/usr/sbin/sendmail -i -t","w+") do |sm|
sm.print(mail.encoded.gsub(/\r/, ''))
sm.flush
end
end
def perform_delivery_test(mail)
deliveries << mail
end
end
end

View file

@ -0,0 +1,115 @@
module ActionMailer
module Helpers #:nodoc:
def self.append_features(base) #:nodoc:
super
# Initialize the base module to aggregate its helpers.
base.class_inheritable_accessor :master_helper_module
base.master_helper_module = Module.new
# Extend base with class methods to declare helpers.
base.extend(ClassMethods)
base.class_eval do
# Wrap inherited to create a new master helper module for subclasses.
class << self
alias_method :inherited_without_helper, :inherited
alias_method :inherited, :inherited_with_helper
end
# Wrap initialize_template_class to extend new template class
# instances with the master helper module.
alias_method :initialize_template_class_without_helper, :initialize_template_class
alias_method :initialize_template_class, :initialize_template_class_with_helper
end
end
module ClassMethods
# Makes all the (instance) methods in the helper module available to templates rendered through this controller.
# See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules
# available to the templates.
def add_template_helper(helper_module) #:nodoc:
master_helper_module.module_eval "include #{helper_module}"
end
# Declare a helper:
# helper :foo
# requires 'foo_helper' and includes FooHelper in the template class.
# helper FooHelper
# includes FooHelper in the template class.
# helper { def foo() "#{bar} is the very best" end }
# evaluates the block in the template class, adding method #foo.
# helper(:three, BlindHelper) { def mice() 'mice' end }
# does all three.
def helper(*args, &block)
args.flatten.each do |arg|
case arg
when Module
add_template_helper(arg)
when String, Symbol
file_name = arg.to_s.underscore + '_helper'
class_name = file_name.camelize
begin
require_dependency(file_name)
rescue LoadError => load_error
requiree = / -- (.*?)(\.rb)?$/.match(load_error).to_a[1]
msg = (requiree == file_name) ? "Missing helper file helpers/#{file_name}.rb" : "Can't load file: #{requiree}"
raise LoadError.new(msg).copy_blame!(load_error)
end
add_template_helper(class_name.constantize)
else
raise ArgumentError, 'helper expects String, Symbol, or Module argument'
end
end
# Evaluate block in template class if given.
master_helper_module.module_eval(&block) if block_given?
end
# Declare a controller method as a helper. For example,
# helper_method :link_to
# def link_to(name, options) ... end
# makes the link_to controller method available in the view.
def helper_method(*methods)
methods.flatten.each do |method|
master_helper_module.module_eval <<-end_eval
def #{method}(*args, &block)
controller.send(%(#{method}), *args, &block)
end
end_eval
end
end
# Declare a controller attribute as a helper. For example,
# helper_attr :name
# attr_accessor :name
# makes the name and name= controller methods available in the view.
# The is a convenience wrapper for helper_method.
def helper_attr(*attrs)
attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") }
end
private
def inherited_with_helper(child)
inherited_without_helper(child)
begin
child.master_helper_module = Module.new
child.master_helper_module.send :include, master_helper_module
child.helper child.name.underscore
rescue MissingSourceFile => e
raise unless e.is_missing?("helpers/#{child.name.underscore}_helper")
end
end
end
private
# Extend the template class instance with our controller's helper module.
def initialize_template_class_with_helper(assigns)
returning(template = initialize_template_class_without_helper(assigns)) do
template.extend self.class.master_helper_module
end
end
end
end

View file

@ -0,0 +1,19 @@
require 'text/format'
module MailHelper
# Uses Text::Format to take the text and format it, indented two spaces for
# each line, and wrapped at 72 columns.
def block_format(text)
formatted = text.split(/\n\r\n/).collect { |paragraph|
Text::Format.new(
:columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
).format
}.join("\n")
# Make list points stand on their own line
formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
formatted
end
end

View file

@ -0,0 +1,108 @@
require 'action_mailer/adv_attr_accessor'
require 'action_mailer/part_container'
require 'action_mailer/utils'
module ActionMailer
# Represents a subpart of an email message. It shares many similar
# attributes of ActionMailer::Base. Although you can create parts manually
# and add them to the #parts list of the mailer, it is easier
# to use the helper methods in ActionMailer::PartContainer.
class Part
include ActionMailer::AdvAttrAccessor
include ActionMailer::PartContainer
# Represents the body of the part, as a string. This should not be a
# Hash (like ActionMailer::Base), but if you want a template to be rendered
# into the body of a subpart you can do it with the mailer's #render method
# and assign the result here.
adv_attr_accessor :body
# Specify the charset for this subpart. By default, it will be the charset
# of the containing part or mailer.
adv_attr_accessor :charset
# The content disposition of this part, typically either "inline" or
# "attachment".
adv_attr_accessor :content_disposition
# The content type of the part.
adv_attr_accessor :content_type
# The filename to use for this subpart (usually for attachments).
adv_attr_accessor :filename
# Accessor for specifying additional headers to include with this part.
adv_attr_accessor :headers
# The transfer encoding to use for this subpart, like "base64" or
# "quoted-printable".
adv_attr_accessor :transfer_encoding
# Create a new part from the given +params+ hash. The valid params keys
# correspond to the accessors.
def initialize(params)
@content_type = params[:content_type]
@content_disposition = params[:disposition] || "inline"
@charset = params[:charset]
@body = params[:body]
@filename = params[:filename]
@transfer_encoding = params[:transfer_encoding] || "quoted-printable"
@headers = params[:headers] || {}
@parts = []
end
# Convert the part to a mail object which can be included in the parts
# list of another mail object.
def to_mail(defaults)
part = TMail::Mail.new
if @parts.empty?
part.content_transfer_encoding = transfer_encoding || "quoted-printable"
case (transfer_encoding || "").downcase
when "base64" then
part.body = TMail::Base64.folding_encode(body)
when "quoted-printable"
part.body = [Utils.normalize_new_lines(body)].pack("M*")
else
part.body = body
end
# Always set the content_type after setting the body and or parts!
# Also don't set filename and name when there is none (like in
# non-attachment parts)
if content_disposition == "attachment"
part.set_content_type(content_type || defaults.content_type, nil,
squish("charset" => nil, "name" => filename))
part.set_content_disposition(content_disposition,
squish("filename" => filename))
else
part.set_content_type(content_type || defaults.content_type, nil,
"charset" => (charset || defaults.charset))
part.set_content_disposition(content_disposition)
end
else
if String === body
part = TMail::Mail.new
part.body = body
part.set_content_type content_type, nil, { "charset" => charset }
part.set_content_disposition "inline"
m.parts << part
end
@parts.each do |p|
prt = (TMail::Mail === p ? p : p.to_mail(defaults))
part.parts << prt
end
part.set_content_type(content_type, nil, { "charset" => charset }) if content_type =~ /multipart/
end
part
end
private
def squish(values={})
values.delete_if { |k,v| v.nil? }
end
end
end

View file

@ -0,0 +1,42 @@
module ActionMailer
# Accessors and helpers that ActionMailer::Base and ActionMailer::Part have
# in common. Using these helpers you can easily add subparts or attachments
# to your message:
#
# def my_mail_message(...)
# ...
# part "text/plain" do |p|
# p.body "hello, world"
# p.transfer_encoding "base64"
# end
#
# attachment "image/jpg" do |a|
# a.body = File.read("hello.jpg")
# a.filename = "hello.jpg"
# end
# end
module PartContainer
# The list of subparts of this container
attr_reader :parts
# Add a part to a multipart message, with the given content-type. The
# part itself is yielded to the block so that other properties (charset,
# body, headers, etc.) can be set on it.
def part(params)
params = {:content_type => params} if String === params
part = Part.new(params)
yield part if block_given?
@parts << part
end
# Add an attachment to a multipart message. This is simply a part with the
# content-disposition set to "attachment".
def attachment(params, &block)
params = { :content_type => params } if String === params
params = { :disposition => "attachment",
:transfer_encoding => "base64" }.merge(params)
part(params, &block)
end
end
end

View file

@ -0,0 +1,59 @@
module ActionMailer
module Quoting #:nodoc:
# Convert the given text into quoted printable format, with an instruction
# that the text be eventually interpreted in the given charset.
def quoted_printable(text, charset)
text = text.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.
gsub( / /, "_" )
"=?#{charset}?Q?#{text}?="
end
# Convert the given character to quoted printable format, taking into
# account multi-byte characters (if executing with $KCODE="u", for instance)
def quoted_printable_encode(character)
result = ""
character.each_byte { |b| result << "=%02x" % b }
result
end
# A quick-and-dirty regexp for determining whether a string contains any
# characters that need escaping.
if !defined?(CHARS_NEEDING_QUOTING)
CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
end
# Quote the given text if it contains any "illegal" characters
def quote_if_necessary(text, charset)
(text =~ CHARS_NEEDING_QUOTING) ?
quoted_printable(text, charset) :
text
end
# Quote any of the given strings if they contain any "illegal" characters
def quote_any_if_necessary(charset, *args)
args.map { |v| quote_if_necessary(v, charset) }
end
# Quote the given address if it needs to be. The address may be a
# regular email address, or it can be a phrase followed by an address in
# brackets. The phrase is the only part that will be quoted, and only if
# it needs to be. This allows extended characters to be used in the
# "to", "from", "cc", and "bcc" headers.
def quote_address_if_necessary(address, charset)
if Array === address
address.map { |a| quote_address_if_necessary(a, charset) }
elsif address =~ /^(\S.*)\s+(<.*>)$/
address = $2
phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
"\"#{phrase}\" #{address}"
else
address
end
end
# Quote any of the given addresses, if they need to be.
def quote_any_address_if_necessary(charset, *args)
args.map { |v| quote_address_if_necessary(v, charset) }
end
end
end

View file

@ -0,0 +1,8 @@
module ActionMailer
module Utils #:nodoc:
def normalize_new_lines(text)
text.to_s.gsub(/\r\n?/, "\n")
end
module_function :normalize_new_lines
end
end

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,242 @@
#
# address.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/encode'
require 'tmail/parser'
module TMail
class Address
include TextUtils
def Address.parse( str )
Parser.parse :ADDRESS, str
end
def address_group?
false
end
def initialize( local, domain )
if domain
domain.each do |s|
raise SyntaxError, 'empty word in domain' if s.empty?
end
end
@local = local
@domain = domain
@name = nil
@routes = []
end
attr_reader :name
def name=( str )
@name = str
@name = nil if str and str.empty?
end
alias phrase name
alias phrase= name=
attr_reader :routes
def inspect
"#<#{self.class} #{address()}>"
end
def local
return nil unless @local
return '""' if @local.size == 1 and @local[0].empty?
@local.map {|i| quote_atom(i) }.join('.')
end
def domain
return nil unless @domain
join_domain(@domain)
end
def spec
s = self.local
d = self.domain
if s and d
s + '@' + d
else
s
end
end
alias address spec
def ==( other )
other.respond_to? :spec and self.spec == other.spec
end
alias eql? ==
def hash
@local.hash ^ @domain.hash
end
def dup
obj = self.class.new(@local.dup, @domain.dup)
obj.name = @name.dup if @name
obj.routes.replace @routes
obj
end
include StrategyInterface
def accept( strategy, dummy1 = nil, dummy2 = nil )
unless @local
strategy.meta '<>' # empty return-path
return
end
spec_p = (not @name and @routes.empty?)
if @name
strategy.phrase @name
strategy.space
end
tmp = spec_p ? '' : '<'
unless @routes.empty?
tmp << @routes.map {|i| '@' + i }.join(',') << ':'
end
tmp << self.spec
tmp << '>' unless spec_p
strategy.meta tmp
strategy.lwsp ''
end
end
class AddressGroup
include Enumerable
def address_group?
true
end
def initialize( name, addrs )
@name = name
@addresses = addrs
end
attr_reader :name
def ==( other )
other.respond_to? :to_a and @addresses == other.to_a
end
alias eql? ==
def hash
map {|i| i.hash }.hash
end
def []( idx )
@addresses[idx]
end
def size
@addresses.size
end
def empty?
@addresses.empty?
end
def each( &block )
@addresses.each(&block)
end
def to_a
@addresses.dup
end
alias to_ary to_a
def include?( a )
@addresses.include? a
end
def flatten
set = []
@addresses.each do |a|
if a.respond_to? :flatten
set.concat a.flatten
else
set.push a
end
end
set
end
def each_address( &block )
flatten.each(&block)
end
def add( a )
@addresses.push a
end
alias push add
def delete( a )
@addresses.delete a
end
include StrategyInterface
def accept( strategy, dummy1 = nil, dummy2 = nil )
strategy.phrase @name
strategy.meta ':'
strategy.space
first = true
each do |mbox|
if first
first = false
else
strategy.meta ','
end
strategy.space
mbox.accept strategy
end
strategy.meta ';'
strategy.lwsp ''
end
end
end # module TMail

View file

@ -0,0 +1,34 @@
require 'stringio'
module TMail
class Attachment < StringIO
attr_accessor :original_filename, :content_type
end
class Mail
def has_attachments?
multipart? && parts.any? { |part| part.header["content-type"].main_type != "text" }
end
def attachments
if multipart?
parts.collect { |part|
if part.header["content-type"].main_type != "text"
content = part.body # unquoted automatically by TMail#body
file_name = (part['content-location'] &&
part['content-location'].body) ||
part.sub_header("content-type", "name") ||
part.sub_header("content-disposition", "filename")
next if file_name.blank? || content.blank?
attachment = Attachment.new(content)
attachment.original_filename = file_name.strip
attachment.content_type = part.content_type
attachment
end
}.compact
end
end
end
end

View file

@ -0,0 +1,71 @@
#
# base64.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
module TMail
module Base64
module_function
def rb_folding_encode( str, eol = "\n", limit = 60 )
[str].pack('m')
end
def rb_encode( str )
[str].pack('m').tr( "\r\n", '' )
end
def rb_decode( str, strict = false )
str.unpack('m')
end
begin
require 'tmail/base64.so'
alias folding_encode c_folding_encode
alias encode c_encode
alias decode c_decode
class << self
alias folding_encode c_folding_encode
alias encode c_encode
alias decode c_decode
end
rescue LoadError
alias folding_encode rb_folding_encode
alias encode rb_encode
alias decode rb_decode
class << self
alias folding_encode rb_folding_encode
alias encode rb_encode
alias decode rb_decode
end
end
end
end

View file

@ -0,0 +1,69 @@
#
# config.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
module TMail
class Config
def initialize( strict )
@strict_parse = strict
@strict_base64decode = strict
end
def strict_parse?
@strict_parse
end
attr_writer :strict_parse
def strict_base64decode?
@strict_base64decode
end
attr_writer :strict_base64decode
def new_body_port( mail )
StringPort.new
end
alias new_preamble_port new_body_port
alias new_part_port new_body_port
end
DEFAULT_CONFIG = Config.new(false)
DEFAULT_STRICT_CONFIG = Config.new(true)
def Config.to_config( arg )
return DEFAULT_STRICT_CONFIG if arg == true
return DEFAULT_CONFIG if arg == false
arg or DEFAULT_CONFIG
end
end

View file

@ -0,0 +1,467 @@
#
# encode.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'nkf'
require 'tmail/base64.rb'
require 'tmail/stringio'
require 'tmail/utils'
module TMail
module StrategyInterface
def create_dest( obj )
case obj
when nil
StringOutput.new
when String
StringOutput.new(obj)
when IO, StringOutput
obj
else
raise TypeError, 'cannot handle this type of object for dest'
end
end
module_function :create_dest
def encoded( eol = "\r\n", charset = 'j', dest = nil )
accept_strategy Encoder, eol, charset, dest
end
def decoded( eol = "\n", charset = 'e', dest = nil )
accept_strategy Decoder, eol, charset, dest
end
alias to_s decoded
def accept_strategy( klass, eol, charset, dest = nil )
dest ||= ''
accept klass.new(create_dest(dest), charset, eol)
dest
end
end
###
### MIME B encoding decoder
###
class Decoder
include TextUtils
encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
OUTPUT_ENCODING = {
'EUC' => 'e',
'SJIS' => 's',
}
def self.decode( str, encoding = nil )
encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
opt = '-m' + encoding
str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
end
def initialize( dest, encoding = nil, eol = "\n" )
@f = StrategyInterface.create_dest(dest)
@encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
@eol = eol
end
def decode( str )
self.class.decode(str, @encoding)
end
private :decode
def terminate
end
def header_line( str )
@f << decode(str)
end
def header_name( nm )
@f << nm << ': '
end
def header_body( str )
@f << decode(str)
end
def space
@f << ' '
end
alias spc space
def lwsp( str )
@f << str
end
def meta( str )
@f << str
end
def text( str )
@f << decode(str)
end
def phrase( str )
@f << quote_phrase(decode(str))
end
def kv_pair( k, v )
@f << k << '=' << v
end
def puts( str = nil )
@f << str if str
@f << @eol
end
def write( str )
@f << str
end
end
###
### MIME B-encoding encoder
###
#
# FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
#
class Encoder
include TextUtils
BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
def Encoder.encode( str )
e = new()
e.header_body str
e.terminate
e.dest.string
end
SPACER = "\t"
MAX_LINE_LEN = 70
OPTIONS = {
'EUC' => '-Ej -m0',
'SJIS' => '-Sj -m0',
'UTF8' => nil, # FIXME
'NONE' => nil
}
def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
@f = StrategyInterface.create_dest(dest)
@opt = OPTIONS[$KCODE]
@eol = eol
reset
end
def normalize_encoding( str )
if @opt
then NKF.nkf(@opt, str)
else str
end
end
def reset
@text = ''
@lwsp = ''
@curlen = 0
end
def terminate
add_lwsp ''
reset
end
def dest
@f
end
def puts( str = nil )
@f << str if str
@f << @eol
end
def write( str )
@f << str
end
#
# add
#
def header_line( line )
scanadd line
end
def header_name( name )
add_text name.split(/-/).map {|i| i.capitalize }.join('-')
add_text ':'
add_lwsp ' '
end
def header_body( str )
scanadd normalize_encoding(str)
end
def space
add_lwsp ' '
end
alias spc space
def lwsp( str )
add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
end
def meta( str )
add_text str
end
def text( str )
scanadd normalize_encoding(str)
end
def phrase( str )
str = normalize_encoding(str)
if CONTROL_CHAR === str
scanadd str
else
add_text quote_phrase(str)
end
end
# FIXME: implement line folding
#
def kv_pair( k, v )
return if v.nil?
v = normalize_encoding(v)
if token_safe?(v)
add_text k + '=' + v
elsif not CONTROL_CHAR === v
add_text k + '=' + quote_token(v)
else
# apply RFC2231 encoding
kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
add_text kv
end
end
def encode_value( str )
str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
end
private
def scanadd( str, force = false )
types = ''
strs = []
until str.empty?
if m = /\A[^\e\t\r\n ]+/.match(str)
types << (force ? 'j' : 'a')
strs.push m[0]
elsif m = /\A[\t\r\n ]+/.match(str)
types << 's'
strs.push m[0]
elsif m = /\A\e../.match(str)
esc = m[0]
str = m.post_match
if esc != "\e(B" and m = /\A[^\e]+/.match(str)
types << 'j'
strs.push m[0]
end
else
raise 'TMail FATAL: encoder scan fail'
end
(str = m.post_match) unless m.nil?
end
do_encode types, strs
end
def do_encode( types, strs )
#
# result : (A|E)(S(A|E))*
# E : W(SW)*
# W : (J|A)+ but must contain J # (J|A)*J(J|A)*
# A : <<A character string not to be encoded>>
# J : <<A character string to be encoded>>
# S : <<LWSP>>
#
# An encoding unit is `E'.
# Input (parameter `types') is (J|A)(J|A|S)*(J|A)
#
if BENCODE_DEBUG
puts
puts '-- do_encode ------------'
puts types.split(//).join(' ')
p strs
end
e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
while m = e.match(types)
pre = m.pre_match
concat_A_S pre, strs[0, pre.size] unless pre.empty?
concat_E m[0], strs[m.begin(0) ... m.end(0)]
types = m.post_match
strs.slice! 0, m.end(0)
end
concat_A_S types, strs
end
def concat_A_S( types, strs )
i = 0
types.each_byte do |t|
case t
when ?a then add_text strs[i]
when ?s then add_lwsp strs[i]
else
raise "TMail FATAL: unknown flag: #{t.chr}"
end
i += 1
end
end
METHOD_ID = {
?j => :extract_J,
?e => :extract_E,
?a => :extract_A,
?s => :extract_S
}
def concat_E( types, strs )
if BENCODE_DEBUG
puts '---- concat_E'
puts "types=#{types.split(//).join(' ')}"
puts "strs =#{strs.inspect}"
end
flush() unless @text.empty?
chunk = ''
strs.each_with_index do |s,i|
mid = METHOD_ID[types[i]]
until s.empty?
unless c = __send__(mid, chunk.size, s)
add_with_encode chunk unless chunk.empty?
flush
chunk = ''
fold
c = __send__(mid, 0, s)
raise 'TMail FATAL: extract fail' unless c
end
chunk << c
end
end
add_with_encode chunk unless chunk.empty?
end
def extract_J( chunksize, str )
size = max_bytes(chunksize, str.size) - 6
size = (size % 2 == 0) ? (size) : (size - 1)
return nil if size <= 0
"\e$B#{str.slice!(0, size)}\e(B"
end
def extract_A( chunksize, str )
size = max_bytes(chunksize, str.size)
return nil if size <= 0
str.slice!(0, size)
end
alias extract_S extract_A
def max_bytes( chunksize, ssize )
(restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
end
#
# free length buffer
#
def add_text( str )
@text << str
# puts '---- text -------------------------------------'
# puts "+ #{str.inspect}"
# puts "txt >>>#{@text.inspect}<<<"
end
def add_with_encode( str )
@text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
end
def add_lwsp( lwsp )
# puts '---- lwsp -------------------------------------'
# puts "+ #{lwsp.inspect}"
fold if restsize() <= 0
flush
@lwsp = lwsp
end
def flush
# puts '---- flush ----'
# puts "spc >>>#{@lwsp.inspect}<<<"
# puts "txt >>>#{@text.inspect}<<<"
@f << @lwsp << @text
@curlen += (@lwsp.size + @text.size)
@text = ''
@lwsp = ''
end
def fold
# puts '---- fold ----'
@f << @eol
@curlen = 0
@lwsp = SPACER
end
def restsize
MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
end
end
end # module TMail

View file

@ -0,0 +1,552 @@
#
# facade.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/utils'
module TMail
class Mail
def header_string( name, default = nil )
h = @header[name.downcase] or return default
h.to_s
end
###
### attributes
###
include TextUtils
def set_string_array_attr( key, strs )
strs.flatten!
if strs.empty?
@header.delete key.downcase
else
store key, strs.join(', ')
end
strs
end
private :set_string_array_attr
def set_string_attr( key, str )
if str
store key, str
else
@header.delete key.downcase
end
str
end
private :set_string_attr
def set_addrfield( name, arg )
if arg
h = HeaderField.internal_new(name, @config)
h.addrs.replace [arg].flatten
@header[name] = h
else
@header.delete name
end
arg
end
private :set_addrfield
def addrs2specs( addrs )
return nil unless addrs
list = addrs.map {|addr|
if addr.address_group?
then addr.map {|a| a.spec }
else addr.spec
end
}.flatten
return nil if list.empty?
list
end
private :addrs2specs
#
# date time
#
def date( default = nil )
if h = @header['date']
h.date
else
default
end
end
def date=( time )
if time
store 'Date', time2str(time)
else
@header.delete 'date'
end
time
end
def strftime( fmt, default = nil )
if t = date
t.strftime(fmt)
else
default
end
end
#
# destination
#
def to_addrs( default = nil )
if h = @header['to']
h.addrs
else
default
end
end
def cc_addrs( default = nil )
if h = @header['cc']
h.addrs
else
default
end
end
def bcc_addrs( default = nil )
if h = @header['bcc']
h.addrs
else
default
end
end
def to_addrs=( arg )
set_addrfield 'to', arg
end
def cc_addrs=( arg )
set_addrfield 'cc', arg
end
def bcc_addrs=( arg )
set_addrfield 'bcc', arg
end
def to( default = nil )
addrs2specs(to_addrs(nil)) || default
end
def cc( default = nil )
addrs2specs(cc_addrs(nil)) || default
end
def bcc( default = nil )
addrs2specs(bcc_addrs(nil)) || default
end
def to=( *strs )
set_string_array_attr 'To', strs
end
def cc=( *strs )
set_string_array_attr 'Cc', strs
end
def bcc=( *strs )
set_string_array_attr 'Bcc', strs
end
#
# originator
#
def from_addrs( default = nil )
if h = @header['from']
h.addrs
else
default
end
end
def from_addrs=( arg )
set_addrfield 'from', arg
end
def from( default = nil )
addrs2specs(from_addrs(nil)) || default
end
def from=( *strs )
set_string_array_attr 'From', strs
end
def friendly_from( default = nil )
h = @header['from']
a, = h.addrs
return default unless a
return a.phrase if a.phrase
return h.comments.join(' ') unless h.comments.empty?
a.spec
end
def reply_to_addrs( default = nil )
if h = @header['reply-to']
h.addrs
else
default
end
end
def reply_to_addrs=( arg )
set_addrfield 'reply-to', arg
end
def reply_to( default = nil )
addrs2specs(reply_to_addrs(nil)) || default
end
def reply_to=( *strs )
set_string_array_attr 'Reply-To', strs
end
def sender_addr( default = nil )
f = @header['sender'] or return default
f.addr or return default
end
def sender_addr=( addr )
if addr
h = HeaderField.internal_new('sender', @config)
h.addr = addr
@header['sender'] = h
else
@header.delete 'sender'
end
addr
end
def sender( default )
f = @header['sender'] or return default
a = f.addr or return default
a.spec
end
def sender=( str )
set_string_attr 'Sender', str
end
#
# subject
#
def subject( default = nil )
if h = @header['subject']
h.body
else
default
end
end
alias quoted_subject subject
def subject=( str )
set_string_attr 'Subject', str
end
#
# identity & threading
#
def message_id( default = nil )
if h = @header['message-id']
h.id || default
else
default
end
end
def message_id=( str )
set_string_attr 'Message-Id', str
end
def in_reply_to( default = nil )
if h = @header['in-reply-to']
h.ids
else
default
end
end
def in_reply_to=( *idstrs )
set_string_array_attr 'In-Reply-To', idstrs
end
def references( default = nil )
if h = @header['references']
h.refs
else
default
end
end
def references=( *strs )
set_string_array_attr 'References', strs
end
#
# MIME headers
#
def mime_version( default = nil )
if h = @header['mime-version']
h.version || default
else
default
end
end
def mime_version=( m, opt = nil )
if opt
if h = @header['mime-version']
h.major = m
h.minor = opt
else
store 'Mime-Version', "#{m}.#{opt}"
end
else
store 'Mime-Version', m
end
m
end
def content_type( default = nil )
if h = @header['content-type']
h.content_type || default
else
default
end
end
def main_type( default = nil )
if h = @header['content-type']
h.main_type || default
else
default
end
end
def sub_type( default = nil )
if h = @header['content-type']
h.sub_type || default
else
default
end
end
def set_content_type( str, sub = nil, param = nil )
if sub
main, sub = str, sub
else
main, sub = str.split(%r</>, 2)
raise ArgumentError, "sub type missing: #{str.inspect}" unless sub
end
if h = @header['content-type']
h.main_type = main
h.sub_type = sub
h.params.clear
else
store 'Content-Type', "#{main}/#{sub}"
end
@header['content-type'].params.replace param if param
str
end
alias content_type= set_content_type
def type_param( name, default = nil )
if h = @header['content-type']
h[name] || default
else
default
end
end
def charset( default = nil )
if h = @header['content-type']
h['charset'] or default
else
default
end
end
def charset=( str )
if str
if h = @header[ 'content-type' ]
h['charset'] = str
else
store 'Content-Type', "text/plain; charset=#{str}"
end
end
str
end
def transfer_encoding( default = nil )
if h = @header['content-transfer-encoding']
h.encoding || default
else
default
end
end
def transfer_encoding=( str )
set_string_attr 'Content-Transfer-Encoding', str
end
alias encoding transfer_encoding
alias encoding= transfer_encoding=
alias content_transfer_encoding transfer_encoding
alias content_transfer_encoding= transfer_encoding=
def disposition( default = nil )
if h = @header['content-disposition']
h.disposition || default
else
default
end
end
alias content_disposition disposition
def set_disposition( str, params = nil )
if h = @header['content-disposition']
h.disposition = str
h.params.clear
else
store('Content-Disposition', str)
h = @header['content-disposition']
end
h.params.replace params if params
end
alias disposition= set_disposition
alias set_content_disposition set_disposition
alias content_disposition= set_disposition
def disposition_param( name, default = nil )
if h = @header['content-disposition']
h[name] || default
else
default
end
end
###
### utils
###
def create_reply
mail = TMail::Mail.parse('')
mail.subject = 'Re: ' + subject('').sub(/\A(?:\[[^\]]+\])?(?:\s*Re:)*\s*/i, '')
mail.to_addrs = reply_addresses([])
mail.in_reply_to = [message_id(nil)].compact
mail.references = references([]) + [message_id(nil)].compact
mail.mime_version = '1.0'
mail
end
def base64_encode
store 'Content-Transfer-Encoding', 'Base64'
self.body = Base64.folding_encode(self.body)
end
def base64_decode
if /base64/i === self.transfer_encoding('')
store 'Content-Transfer-Encoding', '8bit'
self.body = Base64.decode(self.body, @config.strict_base64decode?)
end
end
def destinations( default = nil )
ret = []
%w( to cc bcc ).each do |nm|
if h = @header[nm]
h.addrs.each {|i| ret.push i.address }
end
end
ret.empty? ? default : ret
end
def each_destination( &block )
destinations([]).each do |i|
if Address === i
yield i
else
i.each(&block)
end
end
end
alias each_dest each_destination
def reply_addresses( default = nil )
reply_to_addrs(nil) or from_addrs(nil) or default
end
def error_reply_addresses( default = nil )
if s = sender(nil)
[s]
else
from_addrs(default)
end
end
def multipart?
main_type('').downcase == 'multipart'
end
end # class Mail
end # module TMail

View file

@ -0,0 +1,914 @@
#
# header.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/encode'
require 'tmail/address'
require 'tmail/parser'
require 'tmail/config'
require 'tmail/utils'
module TMail
class HeaderField
include TextUtils
class << self
alias newobj new
def new( name, body, conf = DEFAULT_CONFIG )
klass = FNAME_TO_CLASS[name.downcase] || UnstructuredHeader
klass.newobj body, conf
end
def new_from_port( port, name, conf = DEFAULT_CONFIG )
re = Regep.new('\A(' + Regexp.quote(name) + '):', 'i')
str = nil
port.ropen {|f|
f.each do |line|
if m = re.match(line) then str = m.post_match.strip
elsif str and /\A[\t ]/ === line then str << ' ' << line.strip
elsif /\A-*\s*\z/ === line then break
elsif str then break
end
end
}
new(name, str, Config.to_config(conf))
end
def internal_new( name, conf )
FNAME_TO_CLASS[name].newobj('', conf, true)
end
end # class << self
def initialize( body, conf, intern = false )
@body = body
@config = conf
@illegal = false
@parsed = false
if intern
@parsed = true
parse_init
end
end
def inspect
"#<#{self.class} #{@body.inspect}>"
end
def illegal?
@illegal
end
def empty?
ensure_parsed
return true if @illegal
isempty?
end
private
def ensure_parsed
return if @parsed
@parsed = true
parse
end
# defabstract parse
# end
def clear_parse_status
@parsed = false
@illegal = false
end
public
def body
ensure_parsed
v = Decoder.new(s = '')
do_accept v
v.terminate
s
end
def body=( str )
@body = str
clear_parse_status
end
include StrategyInterface
def accept( strategy, dummy1 = nil, dummy2 = nil )
ensure_parsed
do_accept strategy
strategy.terminate
end
# abstract do_accept
end
class UnstructuredHeader < HeaderField
def body
ensure_parsed
@body
end
def body=( arg )
ensure_parsed
@body = arg
end
private
def parse_init
end
def parse
@body = Decoder.decode(@body.gsub(/\n|\r\n|\r/, ''))
end
def isempty?
not @body
end
def do_accept( strategy )
strategy.text @body
end
end
class StructuredHeader < HeaderField
def comments
ensure_parsed
@comments
end
private
def parse
save = nil
begin
parse_init
do_parse
rescue SyntaxError
if not save and mime_encoded? @body
save = @body
@body = Decoder.decode(save)
retry
elsif save
@body = save
end
@illegal = true
raise if @config.strict_parse?
end
end
def parse_init
@comments = []
init
end
def do_parse
obj = Parser.parse(self.class::PARSE_TYPE, @body, @comments)
set obj if obj
end
end
class DateTimeHeader < StructuredHeader
PARSE_TYPE = :DATETIME
def date
ensure_parsed
@date
end
def date=( arg )
ensure_parsed
@date = arg
end
private
def init
@date = nil
end
def set( t )
@date = t
end
def isempty?
not @date
end
def do_accept( strategy )
strategy.meta time2str(@date)
end
end
class AddressHeader < StructuredHeader
PARSE_TYPE = :MADDRESS
def addrs
ensure_parsed
@addrs
end
private
def init
@addrs = []
end
def set( a )
@addrs = a
end
def isempty?
@addrs.empty?
end
def do_accept( strategy )
first = true
@addrs.each do |a|
if first
first = false
else
strategy.meta ','
strategy.space
end
a.accept strategy
end
@comments.each do |c|
strategy.space
strategy.meta '('
strategy.text c
strategy.meta ')'
end
end
end
class ReturnPathHeader < AddressHeader
PARSE_TYPE = :RETPATH
def addr
addrs()[0]
end
def spec
a = addr() or return nil
a.spec
end
def routes
a = addr() or return nil
a.routes
end
private
def do_accept( strategy )
a = addr()
strategy.meta '<'
unless a.routes.empty?
strategy.meta a.routes.map {|i| '@' + i }.join(',')
strategy.meta ':'
end
spec = a.spec
strategy.meta spec if spec
strategy.meta '>'
end
end
class SingleAddressHeader < AddressHeader
def addr
addrs()[0]
end
private
def do_accept( strategy )
a = addr()
a.accept strategy
@comments.each do |c|
strategy.space
strategy.meta '('
strategy.text c
strategy.meta ')'
end
end
end
class MessageIdHeader < StructuredHeader
def id
ensure_parsed
@id
end
def id=( arg )
ensure_parsed
@id = arg
end
private
def init
@id = nil
end
def isempty?
not @id
end
def do_parse
@id = @body.slice(MESSAGE_ID) or
raise SyntaxError, "wrong Message-ID format: #{@body}"
end
def do_accept( strategy )
strategy.meta @id
end
end
class ReferencesHeader < StructuredHeader
def refs
ensure_parsed
@refs
end
def each_id
self.refs.each do |i|
yield i if MESSAGE_ID === i
end
end
def ids
ensure_parsed
@ids
end
def each_phrase
self.refs.each do |i|
yield i unless MESSAGE_ID === i
end
end
def phrases
ret = []
each_phrase {|i| ret.push i }
ret
end
private
def init
@refs = []
@ids = []
end
def isempty?
@ids.empty?
end
def do_parse
str = @body
while m = MESSAGE_ID.match(str)
pre = m.pre_match.strip
@refs.push pre unless pre.empty?
@refs.push s = m[0]
@ids.push s
str = m.post_match
end
str = str.strip
@refs.push str unless str.empty?
end
def do_accept( strategy )
first = true
@ids.each do |i|
if first
first = false
else
strategy.space
end
strategy.meta i
end
end
end
class ReceivedHeader < StructuredHeader
PARSE_TYPE = :RECEIVED
def from
ensure_parsed
@from
end
def from=( arg )
ensure_parsed
@from = arg
end
def by
ensure_parsed
@by
end
def by=( arg )
ensure_parsed
@by = arg
end
def via
ensure_parsed
@via
end
def via=( arg )
ensure_parsed
@via = arg
end
def with
ensure_parsed
@with
end
def id
ensure_parsed
@id
end
def id=( arg )
ensure_parsed
@id = arg
end
def _for
ensure_parsed
@_for
end
def _for=( arg )
ensure_parsed
@_for = arg
end
def date
ensure_parsed
@date
end
def date=( arg )
ensure_parsed
@date = arg
end
private
def init
@from = @by = @via = @with = @id = @_for = nil
@with = []
@date = nil
end
def set( args )
@from, @by, @via, @with, @id, @_for, @date = *args
end
def isempty?
@with.empty? and not (@from or @by or @via or @id or @_for or @date)
end
def do_accept( strategy )
list = []
list.push 'from ' + @from if @from
list.push 'by ' + @by if @by
list.push 'via ' + @via if @via
@with.each do |i|
list.push 'with ' + i
end
list.push 'id ' + @id if @id
list.push 'for <' + @_for + '>' if @_for
first = true
list.each do |i|
strategy.space unless first
strategy.meta i
first = false
end
if @date
strategy.meta ';'
strategy.space
strategy.meta time2str(@date)
end
end
end
class KeywordsHeader < StructuredHeader
PARSE_TYPE = :KEYWORDS
def keys
ensure_parsed
@keys
end
private
def init
@keys = []
end
def set( a )
@keys = a
end
def isempty?
@keys.empty?
end
def do_accept( strategy )
first = true
@keys.each do |i|
if first
first = false
else
strategy.meta ','
end
strategy.meta i
end
end
end
class EncryptedHeader < StructuredHeader
PARSE_TYPE = :ENCRYPTED
def encrypter
ensure_parsed
@encrypter
end
def encrypter=( arg )
ensure_parsed
@encrypter = arg
end
def keyword
ensure_parsed
@keyword
end
def keyword=( arg )
ensure_parsed
@keyword = arg
end
private
def init
@encrypter = nil
@keyword = nil
end
def set( args )
@encrypter, @keyword = args
end
def isempty?
not (@encrypter or @keyword)
end
def do_accept( strategy )
if @key
strategy.meta @encrypter + ','
strategy.space
strategy.meta @keyword
else
strategy.meta @encrypter
end
end
end
class MimeVersionHeader < StructuredHeader
PARSE_TYPE = :MIMEVERSION
def major
ensure_parsed
@major
end
def major=( arg )
ensure_parsed
@major = arg
end
def minor
ensure_parsed
@minor
end
def minor=( arg )
ensure_parsed
@minor = arg
end
def version
sprintf('%d.%d', major, minor)
end
private
def init
@major = nil
@minor = nil
end
def set( args )
@major, @minor = *args
end
def isempty?
not (@major or @minor)
end
def do_accept( strategy )
strategy.meta sprintf('%d.%d', @major, @minor)
end
end
class ContentTypeHeader < StructuredHeader
PARSE_TYPE = :CTYPE
def main_type
ensure_parsed
@main
end
def main_type=( arg )
ensure_parsed
@main = arg.downcase
end
def sub_type
ensure_parsed
@sub
end
def sub_type=( arg )
ensure_parsed
@sub = arg.downcase
end
def content_type
ensure_parsed
@sub ? sprintf('%s/%s', @main, @sub) : @main
end
def params
ensure_parsed
@params
end
def []( key )
ensure_parsed
@params and @params[key]
end
def []=( key, val )
ensure_parsed
(@params ||= {})[key] = val
end
private
def init
@main = @sub = @params = nil
end
def set( args )
@main, @sub, @params = *args
end
def isempty?
not (@main or @sub)
end
def do_accept( strategy )
if @sub
strategy.meta sprintf('%s/%s', @main, @sub)
else
strategy.meta @main
end
@params.each do |k,v|
if v
strategy.meta ';'
strategy.space
strategy.kv_pair k, v
end
end
end
end
class ContentTransferEncodingHeader < StructuredHeader
PARSE_TYPE = :CENCODING
def encoding
ensure_parsed
@encoding
end
def encoding=( arg )
ensure_parsed
@encoding = arg
end
private
def init
@encoding = nil
end
def set( s )
@encoding = s
end
def isempty?
not @encoding
end
def do_accept( strategy )
strategy.meta @encoding.capitalize
end
end
class ContentDispositionHeader < StructuredHeader
PARSE_TYPE = :CDISPOSITION
def disposition
ensure_parsed
@disposition
end
def disposition=( str )
ensure_parsed
@disposition = str.downcase
end
def params
ensure_parsed
@params
end
def []( key )
ensure_parsed
@params and @params[key]
end
def []=( key, val )
ensure_parsed
(@params ||= {})[key] = val
end
private
def init
@disposition = @params = nil
end
def set( args )
@disposition, @params = *args
end
def isempty?
not @disposition and (not @params or @params.empty?)
end
def do_accept( strategy )
strategy.meta @disposition
@params.each do |k,v|
strategy.meta ';'
strategy.space
strategy.kv_pair k, v
end
end
end
class HeaderField # redefine
FNAME_TO_CLASS = {
'date' => DateTimeHeader,
'resent-date' => DateTimeHeader,
'to' => AddressHeader,
'cc' => AddressHeader,
'bcc' => AddressHeader,
'from' => AddressHeader,
'reply-to' => AddressHeader,
'resent-to' => AddressHeader,
'resent-cc' => AddressHeader,
'resent-bcc' => AddressHeader,
'resent-from' => AddressHeader,
'resent-reply-to' => AddressHeader,
'sender' => SingleAddressHeader,
'resent-sender' => SingleAddressHeader,
'return-path' => ReturnPathHeader,
'message-id' => MessageIdHeader,
'resent-message-id' => MessageIdHeader,
'in-reply-to' => ReferencesHeader,
'received' => ReceivedHeader,
'references' => ReferencesHeader,
'keywords' => KeywordsHeader,
'encrypted' => EncryptedHeader,
'mime-version' => MimeVersionHeader,
'content-type' => ContentTypeHeader,
'content-transfer-encoding' => ContentTransferEncodingHeader,
'content-disposition' => ContentDispositionHeader,
'content-id' => MessageIdHeader,
'subject' => UnstructuredHeader,
'comments' => UnstructuredHeader,
'content-description' => UnstructuredHeader
}
end
end # module TMail

View file

@ -0,0 +1,35 @@
#
# info.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
module TMail
Version = '0.10.7'
Copyright = 'Copyright (c) 1998-2002 Minero Aoki'
end

View file

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

View file

@ -0,0 +1,447 @@
#
# mail.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/facade'
require 'tmail/encode'
require 'tmail/header'
require 'tmail/port'
require 'tmail/config'
require 'tmail/utils'
require 'tmail/attachments'
require 'tmail/quoting'
require 'socket'
module TMail
class Mail
class << self
def load( fname )
new(FilePort.new(fname))
end
alias load_from load
alias loadfrom load
def parse( str )
new(StringPort.new(str))
end
end
def initialize( port = nil, conf = DEFAULT_CONFIG )
@port = port || StringPort.new
@config = Config.to_config(conf)
@header = {}
@body_port = nil
@body_parsed = false
@epilogue = ''
@parts = []
@port.ropen {|f|
parse_header f
parse_body f unless @port.reproducible?
}
end
attr_reader :port
def inspect
"\#<#{self.class} port=#{@port.inspect} bodyport=#{@body_port.inspect}>"
end
#
# to_s interfaces
#
public
include StrategyInterface
def write_back( eol = "\n", charset = 'e' )
parse_body
@port.wopen {|stream| encoded eol, charset, stream }
end
def accept( strategy )
with_multipart_encoding(strategy) {
ordered_each do |name, field|
next if field.empty?
strategy.header_name canonical(name)
field.accept strategy
strategy.puts
end
strategy.puts
body_port().ropen {|r|
strategy.write r.read
}
}
end
private
def canonical( name )
name.split(/-/).map {|s| s.capitalize }.join('-')
end
def with_multipart_encoding( strategy )
if parts().empty? # DO NOT USE @parts
yield
else
bound = ::TMail.new_boundary
if @header.key? 'content-type'
@header['content-type'].params['boundary'] = bound
else
store 'Content-Type', %<multipart/mixed; boundary="#{bound}">
end
yield
parts().each do |tm|
strategy.puts
strategy.puts '--' + bound
tm.accept strategy
end
strategy.puts
strategy.puts '--' + bound + '--'
strategy.write epilogue()
end
end
###
### header
###
public
ALLOW_MULTIPLE = {
'received' => true,
'resent-date' => true,
'resent-from' => true,
'resent-sender' => true,
'resent-to' => true,
'resent-cc' => true,
'resent-bcc' => true,
'resent-message-id' => true,
'comments' => true,
'keywords' => true
}
USE_ARRAY = ALLOW_MULTIPLE
def header
@header.dup
end
def []( key )
@header[key.downcase]
end
def sub_header(key, param)
(hdr = self[key]) ? hdr[param] : nil
end
alias fetch []
def []=( key, val )
dkey = key.downcase
if val.nil?
@header.delete dkey
return nil
end
case val
when String
header = new_hf(key, val)
when HeaderField
;
when Array
ALLOW_MULTIPLE.include? dkey or
raise ArgumentError, "#{key}: Header must not be multiple"
@header[dkey] = val
return val
else
header = new_hf(key, val.to_s)
end
if ALLOW_MULTIPLE.include? dkey
(@header[dkey] ||= []).push header
else
@header[dkey] = header
end
val
end
alias store []=
def each_header
@header.each do |key, val|
[val].flatten.each {|v| yield key, v }
end
end
alias each_pair each_header
def each_header_name( &block )
@header.each_key(&block)
end
alias each_key each_header_name
def each_field( &block )
@header.values.flatten.each(&block)
end
alias each_value each_field
FIELD_ORDER = %w(
return-path received
resent-date resent-from resent-sender resent-to
resent-cc resent-bcc resent-message-id
date from sender reply-to to cc bcc
message-id in-reply-to references
subject comments keywords
mime-version content-type content-transfer-encoding
content-disposition content-description
)
def ordered_each
list = @header.keys
FIELD_ORDER.each do |name|
if list.delete(name)
[@header[name]].flatten.each {|v| yield name, v }
end
end
list.each do |name|
[@header[name]].flatten.each {|v| yield name, v }
end
end
def clear
@header.clear
end
def delete( key )
@header.delete key.downcase
end
def delete_if
@header.delete_if do |key,val|
if Array === val
val.delete_if {|v| yield key, v }
val.empty?
else
yield key, val
end
end
end
def keys
@header.keys
end
def key?( key )
@header.key? key.downcase
end
def values_at( *args )
args.map {|k| @header[k.downcase] }.flatten
end
alias indexes values_at
alias indices values_at
private
def parse_header( f )
name = field = nil
unixfrom = nil
while line = f.gets
case line
when /\A[ \t]/ # continue from prev line
raise SyntaxError, 'mail is began by space' unless field
field << ' ' << line.strip
when /\A([^\: \t]+):\s*/ # new header line
add_hf name, field if field
name = $1
field = $' #.strip
when /\A\-*\s*\z/ # end of header
add_hf name, field if field
name = field = nil
break
when /\AFrom (\S+)/
unixfrom = $1
when /^charset=.*/
else
raise SyntaxError, "wrong mail header: '#{line.inspect}'"
end
end
add_hf name, field if name
if unixfrom
add_hf 'Return-Path', "<#{unixfrom}>" unless @header['return-path']
end
end
def add_hf( name, field )
key = name.downcase
field = new_hf(name, field)
if ALLOW_MULTIPLE.include? key
(@header[key] ||= []).push field
else
@header[key] = field
end
end
def new_hf( name, field )
HeaderField.new(name, field, @config)
end
###
### body
###
public
def body_port
parse_body
@body_port
end
def each( &block )
body_port().ropen {|f| f.each(&block) }
end
def quoted_body
parse_body
@body_port.ropen {|f|
return f.read
}
end
def body=( str )
parse_body
@body_port.wopen {|f| f.write str }
str
end
alias preamble body
alias preamble= body=
def epilogue
parse_body
@epilogue.dup
end
def epilogue=( str )
parse_body
@epilogue = str
str
end
def parts
parse_body
@parts
end
def each_part( &block )
parts().each(&block)
end
private
def parse_body( f = nil )
return if @body_parsed
if f
parse_body_0 f
else
@port.ropen {|f|
skip_header f
parse_body_0 f
}
end
@body_parsed = true
end
def skip_header( f )
while line = f.gets
return if /\A[\r\n]*\z/ === line
end
end
def parse_body_0( f )
if multipart?
read_multipart f
else
@body_port = @config.new_body_port(self)
@body_port.wopen {|w|
w.write f.read
}
end
end
def read_multipart( src )
bound = @header['content-type'].params['boundary']
is_sep = /\A--#{Regexp.quote bound}(?:--)?[ \t]*(?:\n|\r\n|\r)/
lastbound = "--#{bound}--"
ports = [ @config.new_preamble_port(self) ]
begin
f = ports.last.wopen
while line = src.gets
if is_sep === line
f.close
break if line.strip == lastbound
ports.push @config.new_part_port(self)
f = ports.last.wopen
else
f << line
end
end
@epilogue = (src.read || '')
ensure
f.close if f and not f.closed?
end
@body_port = ports.shift
@parts = ports.map {|p| self.class.new(p, @config) }
end
end # class Mail
end # module TMail

View file

@ -0,0 +1,433 @@
#
# mailbox.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/port'
require 'socket'
require 'mutex_m'
unless [].respond_to?(:sort_by)
module Enumerable#:nodoc:
def sort_by
map {|i| [yield(i), i] }.sort {|a,b| a.first <=> b.first }.map {|i| i[1] }
end
end
end
module TMail
class MhMailbox
PORT_CLASS = MhPort
def initialize( dir )
edir = File.expand_path(dir)
raise ArgumentError, "not directory: #{dir}"\
unless FileTest.directory? edir
@dirname = edir
@last_file = nil
@last_atime = nil
end
def directory
@dirname
end
alias dirname directory
attr_accessor :last_atime
def inspect
"#<#{self.class} #{@dirname}>"
end
def close
end
def new_port
PORT_CLASS.new(next_file_name())
end
def each_port
mail_files().each do |path|
yield PORT_CLASS.new(path)
end
@last_atime = Time.now
end
alias each each_port
def reverse_each_port
mail_files().reverse_each do |path|
yield PORT_CLASS.new(path)
end
@last_atime = Time.now
end
alias reverse_each reverse_each_port
# old #each_mail returns Port
#def each_mail
# each_port do |port|
# yield Mail.new(port)
# end
#end
def each_new_port( mtime = nil, &block )
mtime ||= @last_atime
return each_port(&block) unless mtime
return unless File.mtime(@dirname) >= mtime
mail_files().each do |path|
yield PORT_CLASS.new(path) if File.mtime(path) > mtime
end
@last_atime = Time.now
end
private
def mail_files
Dir.entries(@dirname)\
.select {|s| /\A\d+\z/ === s }\
.map {|s| s.to_i }\
.sort\
.map {|i| "#{@dirname}/#{i}" }\
.select {|path| FileTest.file? path }
end
def next_file_name
unless n = @last_file
n = 0
Dir.entries(@dirname)\
.select {|s| /\A\d+\z/ === s }\
.map {|s| s.to_i }.sort\
.each do |i|
next unless FileTest.file? "#{@dirname}/#{i}"
n = i
end
end
begin
n += 1
end while FileTest.exist? "#{@dirname}/#{n}"
@last_file = n
"#{@dirname}/#{n}"
end
end # MhMailbox
MhLoader = MhMailbox
class UNIXMbox
def UNIXMbox.lock( fname )
begin
f = File.open(fname)
f.flock File::LOCK_EX
yield f
ensure
f.flock File::LOCK_UN
f.close if f and not f.closed?
end
end
class << self
alias newobj new
end
def UNIXMbox.new( fname, tmpdir = nil, readonly = false )
tmpdir = ENV['TEMP'] || ENV['TMP'] || '/tmp'
newobj(fname, "#{tmpdir}/ruby_tmail_#{$$}_#{rand()}", readonly, false)
end
def UNIXMbox.static_new( fname, dir, readonly = false )
newobj(fname, dir, readonly, true)
end
def initialize( fname, mhdir, readonly, static )
@filename = fname
@readonly = readonly
@closed = false
Dir.mkdir mhdir
@real = MhMailbox.new(mhdir)
@finalizer = UNIXMbox.mkfinal(@real, @filename, !@readonly, !static)
ObjectSpace.define_finalizer self, @finalizer
end
def UNIXMbox.mkfinal( mh, mboxfile, writeback_p, cleanup_p )
lambda {
if writeback_p
lock(mboxfile) {|f|
mh.each_port do |port|
f.puts create_from_line(port)
port.ropen {|r|
f.puts r.read
}
end
}
end
if cleanup_p
Dir.foreach(mh.dirname) do |fname|
next if /\A\.\.?\z/ === fname
File.unlink "#{mh.dirname}/#{fname}"
end
Dir.rmdir mh.dirname
end
}
end
# make _From line
def UNIXMbox.create_from_line( port )
sprintf 'From %s %s',
fromaddr(), TextUtils.time2str(File.mtime(port.filename))
end
def UNIXMbox.fromaddr
h = HeaderField.new_from_port(port, 'Return-Path') ||
HeaderField.new_from_port(port, 'From') or return 'nobody'
a = h.addrs[0] or return 'nobody'
a.spec
end
private_class_method :fromaddr
def close
return if @closed
ObjectSpace.undefine_finalizer self
@finalizer.call
@finalizer = nil
@real = nil
@closed = true
@updated = nil
end
def each_port( &block )
close_check
update
@real.each_port(&block)
end
alias each each_port
def reverse_each_port( &block )
close_check
update
@real.reverse_each_port(&block)
end
alias reverse_each reverse_each_port
# old #each_mail returns Port
#def each_mail( &block )
# each_port do |port|
# yield Mail.new(port)
# end
#end
def each_new_port( mtime = nil )
close_check
update
@real.each_new_port(mtime) {|p| yield p }
end
def new_port
close_check
@real.new_port
end
private
def close_check
@closed and raise ArgumentError, 'accessing already closed mbox'
end
def update
return if FileTest.zero?(@filename)
return if @updated and File.mtime(@filename) < @updated
w = nil
port = nil
time = nil
UNIXMbox.lock(@filename) {|f|
begin
f.each do |line|
if /\AFrom / === line
w.close if w
File.utime time, time, port.filename if time
port = @real.new_port
w = port.wopen
time = fromline2time(line)
else
w.print line if w
end
end
ensure
if w and not w.closed?
w.close
File.utime time, time, port.filename if time
end
end
f.truncate(0) unless @readonly
@updated = Time.now
}
end
def fromline2time( line )
m = /\AFrom \S+ \w+ (\w+) (\d+) (\d+):(\d+):(\d+) (\d+)/.match(line) \
or return nil
Time.local(m[6].to_i, m[1], m[2].to_i, m[3].to_i, m[4].to_i, m[5].to_i)
end
end # UNIXMbox
MboxLoader = UNIXMbox
class Maildir
extend Mutex_m
PORT_CLASS = MaildirPort
@seq = 0
def Maildir.unique_number
synchronize {
@seq += 1
return @seq
}
end
def initialize( dir = nil )
@dirname = dir || ENV['MAILDIR']
raise ArgumentError, "not directory: #{@dirname}"\
unless FileTest.directory? @dirname
@new = "#{@dirname}/new"
@tmp = "#{@dirname}/tmp"
@cur = "#{@dirname}/cur"
end
def directory
@dirname
end
def inspect
"#<#{self.class} #{@dirname}>"
end
def close
end
def each_port
mail_files(@cur).each do |path|
yield PORT_CLASS.new(path)
end
end
alias each each_port
def reverse_each_port
mail_files(@cur).reverse_each do |path|
yield PORT_CLASS.new(path)
end
end
alias reverse_each reverse_each_port
def new_port
fname = nil
tmpfname = nil
newfname = nil
begin
fname = "#{Time.now.to_i}.#{$$}_#{Maildir.unique_number}.#{Socket.gethostname}"
tmpfname = "#{@tmp}/#{fname}"
newfname = "#{@new}/#{fname}"
end while FileTest.exist? tmpfname
if block_given?
File.open(tmpfname, 'w') {|f| yield f }
File.rename tmpfname, newfname
PORT_CLASS.new(newfname)
else
File.open(tmpfname, 'w') {|f| f.write "\n\n" }
PORT_CLASS.new(tmpfname)
end
end
def each_new_port
mail_files(@new).each do |path|
dest = @cur + '/' + File.basename(path)
File.rename path, dest
yield PORT_CLASS.new(dest)
end
check_tmp
end
TOO_OLD = 60 * 60 * 36 # 36 hour
def check_tmp
old = Time.now.to_i - TOO_OLD
each_filename(@tmp) do |full, fname|
if FileTest.file? full and
File.stat(full).mtime.to_i < old
File.unlink full
end
end
end
private
def mail_files( dir )
Dir.entries(dir)\
.select {|s| s[0] != ?. }\
.sort_by {|s| s.slice(/\A\d+/).to_i }\
.map {|s| "#{dir}/#{s}" }\
.select {|path| FileTest.file? path }
end
def each_filename( dir )
Dir.foreach(dir) do |fname|
path = "#{dir}/#{fname}"
if fname[0] != ?. and FileTest.file? path
yield path, fname
end
end
end
end # Maildir
MaildirLoader = Maildir
end # module TMail

View file

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

View file

@ -0,0 +1,280 @@
#
# net.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'nkf'
module TMail
class Mail
def send_to( smtp )
do_send_to(smtp) do
ready_to_send
end
end
def send_text_to( smtp )
do_send_to(smtp) do
ready_to_send
mime_encode
end
end
def do_send_to( smtp )
from = from_address or raise ArgumentError, 'no from address'
(dests = destinations).empty? and raise ArgumentError, 'no receipient'
yield
send_to_0 smtp, from, dests
end
private :do_send_to
def send_to_0( smtp, from, to )
smtp.ready(from, to) do |f|
encoded "\r\n", 'j', f, ''
end
end
def ready_to_send
delete_no_send_fields
add_message_id
add_date
end
NOSEND_FIELDS = %w(
received
bcc
)
def delete_no_send_fields
NOSEND_FIELDS.each do |nm|
delete nm
end
delete_if {|n,v| v.empty? }
end
def add_message_id( fqdn = nil )
self.message_id = ::TMail::new_message_id(fqdn)
end
def add_date
self.date = Time.now
end
def mime_encode
if parts.empty?
mime_encode_singlepart
else
mime_encode_multipart true
end
end
def mime_encode_singlepart
self.mime_version = '1.0'
b = body
if NKF.guess(b) != NKF::BINARY
mime_encode_text b
else
mime_encode_binary b
end
end
def mime_encode_text( body )
self.body = NKF.nkf('-j -m0', body)
self.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
self.encoding = '7bit'
end
def mime_encode_binary( body )
self.body = [body].pack('m')
self.set_content_type 'application', 'octet-stream'
self.encoding = 'Base64'
end
def mime_encode_multipart( top = true )
self.mime_version = '1.0' if top
self.set_content_type 'multipart', 'mixed'
e = encoding(nil)
if e and not /\A(?:7bit|8bit|binary)\z/i === e
raise ArgumentError,
'using C.T.Encoding with multipart mail is not permitted'
end
end
def create_empty_mail
self.class.new(StringPort.new(''), @config)
end
def create_reply
setup_reply create_empty_mail()
end
def setup_reply( m )
if tmp = reply_addresses(nil)
m.to_addrs = tmp
end
mid = message_id(nil)
tmp = references(nil) || []
tmp.push mid if mid
m.in_reply_to = [mid] if mid
m.references = tmp unless tmp.empty?
m.subject = 'Re: ' + subject('').sub(/\A(?:\s*re:)+/i, '')
m
end
def create_forward
setup_forward create_empty_mail()
end
def setup_forward( mail )
m = Mail.new(StringPort.new(''))
m.body = decoded
m.set_content_type 'message', 'rfc822'
m.encoding = encoding('7bit')
mail.parts.push m
end
end
class DeleteFields
NOSEND_FIELDS = %w(
received
bcc
)
def initialize( nosend = nil, delempty = true )
@no_send_fields = nosend || NOSEND_FIELDS.dup
@delete_empty_fields = delempty
end
attr :no_send_fields
attr :delete_empty_fields, true
def exec( mail )
@no_send_fields.each do |nm|
delete nm
end
delete_if {|n,v| v.empty? } if @delete_empty_fields
end
end
class AddMessageId
def initialize( fqdn = nil )
@fqdn = fqdn
end
attr :fqdn, true
def exec( mail )
mail.message_id = ::TMail::new_msgid(@fqdn)
end
end
class AddDate
def exec( mail )
mail.date = Time.now
end
end
class MimeEncodeAuto
def initialize( s = nil, m = nil )
@singlepart_composer = s || MimeEncodeSingle.new
@multipart_composer = m || MimeEncodeMulti.new
end
attr :singlepart_composer
attr :multipart_composer
def exec( mail )
if mail._builtin_multipart?
then @multipart_composer
else @singlepart_composer end.exec mail
end
end
class MimeEncodeSingle
def exec( mail )
mail.mime_version = '1.0'
b = mail.body
if NKF.guess(b) != NKF::BINARY
on_text b
else
on_binary b
end
end
def on_text( body )
mail.body = NKF.nkf('-j -m0', body)
mail.set_content_type 'text', 'plain', {'charset' => 'iso-2022-jp'}
mail.encoding = '7bit'
end
def on_binary( body )
mail.body = [body].pack('m')
mail.set_content_type 'application', 'octet-stream'
mail.encoding = 'Base64'
end
end
class MimeEncodeMulti
def exec( mail, top = true )
mail.mime_version = '1.0' if top
mail.set_content_type 'multipart', 'mixed'
e = encoding(nil)
if e and not /\A(?:7bit|8bit|binary)\z/i === e
raise ArgumentError,
'using C.T.Encoding with multipart mail is not permitted'
end
mail.parts.each do |m|
exec m, false if m._builtin_multipart?
end
end
end
end # module TMail

View file

@ -0,0 +1,135 @@
#
# obsolete.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
module TMail
# mail.rb
class Mail
alias include? key?
alias has_key? key?
def values
ret = []
each_field {|v| ret.push v }
ret
end
def value?( val )
HeaderField === val or return false
[ @header[val.name.downcase] ].flatten.include? val
end
alias has_value? value?
end
# facade.rb
class Mail
def from_addr( default = nil )
addr, = from_addrs(nil)
addr || default
end
def from_address( default = nil )
if a = from_addr(nil)
a.spec
else
default
end
end
alias from_address= from_addrs=
def from_phrase( default = nil )
if a = from_addr(nil)
a.phrase
else
default
end
end
alias msgid message_id
alias msgid= message_id=
alias each_dest each_destination
end
# address.rb
class Address
alias route routes
alias addr spec
def spec=( str )
@local, @domain = str.split(/@/,2).map {|s| s.split(/\./) }
end
alias addr= spec=
alias address= spec=
end
# mbox.rb
class MhMailbox
alias new_mail new_port
alias each_mail each_port
alias each_newmail each_new_port
end
class UNIXMbox
alias new_mail new_port
alias each_mail each_port
alias each_newmail each_new_port
end
class Maildir
alias new_mail new_port
alias each_mail each_port
alias each_newmail each_new_port
end
# utils.rb
extend TextUtils
class << self
alias msgid? message_id?
alias boundary new_boundary
alias msgid new_message_id
alias new_msgid new_message_id
end
def Mail.boundary
::TMail.new_boundary
end
def Mail.msgid
::TMail.new_message_id
end
end # module TMail

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,377 @@
#
# port.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/stringio'
module TMail
class Port
def reproducible?
false
end
end
###
### FilePort
###
class FilePort < Port
def initialize( fname )
@filename = File.expand_path(fname)
super()
end
attr_reader :filename
alias ident filename
def ==( other )
other.respond_to?(:filename) and @filename == other.filename
end
alias eql? ==
def hash
@filename.hash
end
def inspect
"#<#{self.class}:#{@filename}>"
end
def reproducible?
true
end
def size
File.size @filename
end
def ropen( &block )
File.open(@filename, &block)
end
def wopen( &block )
File.open(@filename, 'w', &block)
end
def aopen( &block )
File.open(@filename, 'a', &block)
end
def read_all
ropen {|f|
return f.read
}
end
def remove
File.unlink @filename
end
def move_to( port )
begin
File.link @filename, port.filename
rescue Errno::EXDEV
copy_to port
end
File.unlink @filename
end
alias mv move_to
def copy_to( port )
if FilePort === port
copy_file @filename, port.filename
else
File.open(@filename) {|r|
port.wopen {|w|
while s = r.sysread(4096)
w.write << s
end
} }
end
end
alias cp copy_to
private
# from fileutils.rb
def copy_file( src, dest )
st = r = w = nil
File.open(src, 'rb') {|r|
File.open(dest, 'wb') {|w|
st = r.stat
begin
while true
w.write r.sysread(st.blksize)
end
rescue EOFError
end
} }
end
end
module MailFlags
def seen=( b )
set_status 'S', b
end
def seen?
get_status 'S'
end
def replied=( b )
set_status 'R', b
end
def replied?
get_status 'R'
end
def flagged=( b )
set_status 'F', b
end
def flagged?
get_status 'F'
end
private
def procinfostr( str, tag, true_p )
a = str.upcase.split(//)
a.push true_p ? tag : nil
a.delete tag unless true_p
a.compact.sort.join('').squeeze
end
end
class MhPort < FilePort
include MailFlags
private
def set_status( tag, flag )
begin
tmpfile = @filename + '.tmailtmp.' + $$.to_s
File.open(tmpfile, 'w') {|f|
write_status f, tag, flag
}
File.unlink @filename
File.link tmpfile, @filename
ensure
File.unlink tmpfile
end
end
def write_status( f, tag, flag )
stat = ''
File.open(@filename) {|r|
while line = r.gets
if line.strip.empty?
break
elsif m = /\AX-TMail-Status:/i.match(line)
stat = m.post_match.strip
else
f.print line
end
end
s = procinfostr(stat, tag, flag)
f.puts 'X-TMail-Status: ' + s unless s.empty?
f.puts
while s = r.read(2048)
f.write s
end
}
end
def get_status( tag )
File.foreach(@filename) {|line|
return false if line.strip.empty?
if m = /\AX-TMail-Status:/i.match(line)
return m.post_match.strip.include?(tag[0])
end
}
false
end
end
class MaildirPort < FilePort
def move_to_new
new = replace_dir(@filename, 'new')
File.rename @filename, new
@filename = new
end
def move_to_cur
new = replace_dir(@filename, 'cur')
File.rename @filename, new
@filename = new
end
def replace_dir( path, dir )
"#{File.dirname File.dirname(path)}/#{dir}/#{File.basename path}"
end
private :replace_dir
include MailFlags
private
MAIL_FILE = /\A(\d+\.[\d_]+\.[^:]+)(?:\:(\d),(\w+)?)?\z/
def set_status( tag, flag )
if m = MAIL_FILE.match(File.basename(@filename))
s, uniq, type, info, = m.to_a
return if type and type != '2' # do not change anything
newname = File.dirname(@filename) + '/' +
uniq + ':2,' + procinfostr(info.to_s, tag, flag)
else
newname = @filename + ':2,' + tag
end
File.link @filename, newname
File.unlink @filename
@filename = newname
end
def get_status( tag )
m = MAIL_FILE.match(File.basename(@filename)) or return false
m[2] == '2' and m[3].to_s.include?(tag[0])
end
end
###
### StringPort
###
class StringPort < Port
def initialize( str = '' )
@buffer = str
super()
end
def string
@buffer
end
def to_s
@buffer.dup
end
alias read_all to_s
def size
@buffer.size
end
def ==( other )
StringPort === other and @buffer.equal? other.string
end
alias eql? ==
def hash
@buffer.object_id.hash
end
def inspect
"#<#{self.class}:id=#{sprintf '0x%x', @buffer.object_id}>"
end
def reproducible?
true
end
def ropen( &block )
@buffer or raise Errno::ENOENT, "#{inspect} is already removed"
StringInput.open(@buffer, &block)
end
def wopen( &block )
@buffer = ''
StringOutput.new(@buffer, &block)
end
def aopen( &block )
@buffer ||= ''
StringOutput.new(@buffer, &block)
end
def remove
@buffer = nil
end
alias rm remove
def copy_to( port )
port.wopen {|f|
f.write @buffer
}
end
alias cp copy_to
def move_to( port )
if StringPort === port
str = @buffer
port.instance_eval { @buffer = str }
else
copy_to port
end
remove
end
end
end # module TMail

View file

@ -0,0 +1,125 @@
module TMail
class Mail
def subject(to_charset = 'utf-8')
Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
end
def unquoted_body(to_charset = 'utf-8')
from_charset = sub_header("content-type", "charset")
case (content_transfer_encoding || "7bit").downcase
when "quoted-printable"
Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
to_charset, from_charset, true)
when "base64"
Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
from_charset)
when "7bit", "8bit"
Unquoter.convert_to(quoted_body, to_charset, from_charset)
when "binary"
quoted_body
else
quoted_body
end
end
def body(to_charset = 'utf-8', &block)
attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
if multipart?
parts.collect { |part|
header = part["content-type"]
if part.multipart?
part.body(to_charset, &attachment_presenter)
elsif header.nil?
""
elsif header.main_type == "text"
part.unquoted_body(to_charset)
else
attachment_presenter.call(header["name"] || "(unnamed)")
end
}.join
else
unquoted_body(to_charset)
end
end
end
class Unquoter
class << self
def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
return "" if text.nil?
if text =~ /^=\?(.*?)\?(.)\?(.*)\?=$/
from_charset = $1
quoting_method = $2
text = $3
case quoting_method.upcase
when "Q" then
unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
when "B" then
unquote_base64_and_convert_to(text, to_charset, from_charset)
else
raise "unknown quoting method #{quoting_method.inspect}"
end
else
convert_to(text, to_charset, from_charset)
end
end
def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
text = text.gsub(/_/, " ") unless preserve_underscores
convert_to(text.unpack("M*").first, to, from)
end
def unquote_base64_and_convert_to(text, to, from)
convert_to(Base64.decode(text).first, to, from)
end
begin
require 'iconv'
def convert_to(text, to, from)
return text unless to && from
text ? Iconv.iconv(to, from, text).first : ""
rescue Iconv::IllegalSequence, Errno::EINVAL
# the 'from' parameter specifies a charset other than what the text
# actually is...not much we can do in this case but just return the
# unconverted text.
#
# Ditto if either parameter represents an unknown charset, like
# X-UNKNOWN.
text
end
rescue LoadError
# Not providing quoting support
def convert_to(text, to, from)
warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
text
end
end
end
end
end
if __FILE__ == $0
require 'test/unit'
class TC_Unquoter < Test::Unit::TestCase
def test_unquote_quoted_printable
a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
end
def test_unquote_base64
a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
end
def test_unquote_without_charset
a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
end
end
end

View file

@ -0,0 +1,41 @@
#
# scanner.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/utils'
module TMail
require 'tmail/scanner_r.rb'
begin
raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
require 'tmail/scanner_c.so'
Scanner = Scanner_C
rescue LoadError
Scanner = Scanner_R
end
end

View file

@ -0,0 +1,263 @@
#
# scanner_r.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
require 'tmail/config'
module TMail
class Scanner_R
Version = '0.10.7'
Version.freeze
MIME_HEADERS = {
:CTYPE => true,
:CENCODING => true,
:CDISPOSITION => true
}
alnum = 'a-zA-Z0-9'
atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
atomchars = alnum + Regexp.quote(atomsyms)
tokenchars = alnum + Regexp.quote(tokensyms)
iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
quoted_without_iso2022 = /\A[^\\"]+/n
domlit_without_iso2022 = /\A[^\\\]]+/n
comment_without_iso2022 = /\A[^\\()]+/n
PATTERN_TABLE = {}
PATTERN_TABLE['EUC'] =
[
/\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
quoted_with_iso2022,
domlit_with_iso2022,
comment_with_iso2022
]
PATTERN_TABLE['SJIS'] =
[
/\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
/\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
quoted_with_iso2022,
domlit_with_iso2022,
comment_with_iso2022
]
PATTERN_TABLE['UTF8'] =
[
/\A(?:[#{atomchars}]+|#{utf8str})+/n,
/\A(?:[#{tokenchars}]+|#{utf8str})+/n,
quoted_without_iso2022,
domlit_without_iso2022,
comment_without_iso2022
]
PATTERN_TABLE['NONE'] =
[
/\A[#{atomchars}]+/n,
/\A[#{tokenchars}]+/n,
quoted_without_iso2022,
domlit_without_iso2022,
comment_without_iso2022
]
def initialize( str, scantype, comments )
init_scanner str
@comments = comments || []
@debug = false
# fix scanner mode
@received = (scantype == :RECEIVED)
@is_mime_header = MIME_HEADERS[scantype]
atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
@word_re = (MIME_HEADERS[scantype] ? token : atom)
end
attr_accessor :debug
def scan( &block )
if @debug
scan_main do |arr|
s, v = arr
printf "%7d %-10s %s\n",
rest_size(),
s.respond_to?(:id2name) ? s.id2name : s.inspect,
v.inspect
yield arr
end
else
scan_main(&block)
end
end
private
RECV_TOKEN = {
'from' => :FROM,
'by' => :BY,
'via' => :VIA,
'with' => :WITH,
'id' => :ID,
'for' => :FOR
}
def scan_main
until eof?
if skip(/\A[\n\r\t ]+/n) # LWSP
break if eof?
end
if s = readstr(@word_re)
if @is_mime_header
yield :TOKEN, s
else
# atom
if /\A\d+\z/ === s
yield :DIGIT, s
elsif @received
yield RECV_TOKEN[s.downcase] || :ATOM, s
else
yield :ATOM, s
end
end
elsif skip(/\A"/)
yield :QUOTED, scan_quoted_word()
elsif skip(/\A\[/)
yield :DOMLIT, scan_domain_literal()
elsif skip(/\A\(/)
@comments.push scan_comment()
else
c = readchar()
yield c, c
end
end
yield false, '$'
end
def scan_quoted_word
scan_qstr(@quoted_re, /\A"/, 'quoted-word')
end
def scan_domain_literal
'[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
end
def scan_qstr( pattern, terminal, type )
result = ''
until eof?
if s = readstr(pattern) then result << s
elsif skip(terminal) then return result
elsif skip(/\A\\/) then result << readchar()
else
raise "TMail FATAL: not match in #{type}"
end
end
scan_error! "found unterminated #{type}"
end
def scan_comment
result = ''
nest = 1
content = @comment_re
until eof?
if s = readstr(content) then result << s
elsif skip(/\A\)/) then nest -= 1
return result if nest == 0
result << ')'
elsif skip(/\A\(/) then nest += 1
result << '('
elsif skip(/\A\\/) then result << readchar()
else
raise 'TMail FATAL: not match in comment'
end
end
scan_error! 'found unterminated comment'
end
# string scanner
def init_scanner( str )
@src = str
end
def eof?
@src.empty?
end
def rest_size
@src.size
end
def readstr( re )
if m = re.match(@src)
@src = m.post_match
m[0]
else
nil
end
end
def readchar
readstr(/\A./)
end
def skip( re )
if m = re.match(@src)
@src = m.post_match
true
else
false
end
end
def scan_error!( msg )
raise SyntaxError, msg
end
end
end # module TMail

View file

@ -0,0 +1,277 @@
#
# stringio.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
class StringInput#:nodoc:
include Enumerable
class << self
def new( str )
if block_given?
begin
f = super
yield f
ensure
f.close if f
end
else
super
end
end
alias open new
end
def initialize( str )
@src = str
@pos = 0
@closed = false
@lineno = 0
end
attr_reader :lineno
def string
@src
end
def inspect
"#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
end
def close
stream_check!
@pos = nil
@closed = true
end
def closed?
@closed
end
def pos
stream_check!
[@pos, @src.size].min
end
alias tell pos
def seek( offset, whence = IO::SEEK_SET )
stream_check!
case whence
when IO::SEEK_SET
@pos = offset
when IO::SEEK_CUR
@pos += offset
when IO::SEEK_END
@pos = @src.size - offset
else
raise ArgumentError, "unknown seek flag: #{whence}"
end
@pos = 0 if @pos < 0
@pos = [@pos, @src.size + 1].min
offset
end
def rewind
stream_check!
@pos = 0
end
def eof?
stream_check!
@pos > @src.size
end
def each( &block )
stream_check!
begin
@src.each(&block)
ensure
@pos = 0
end
end
def gets
stream_check!
if idx = @src.index(?\n, @pos)
idx += 1 # "\n".size
line = @src[ @pos ... idx ]
@pos = idx
@pos += 1 if @pos == @src.size
else
line = @src[ @pos .. -1 ]
@pos = @src.size + 1
end
@lineno += 1
line
end
def getc
stream_check!
ch = @src[@pos]
@pos += 1
@pos += 1 if @pos == @src.size
ch
end
def read( len = nil )
stream_check!
return read_all unless len
str = @src[@pos, len]
@pos += len
@pos += 1 if @pos == @src.size
str
end
alias sysread read
def read_all
stream_check!
return nil if eof?
rest = @src[@pos ... @src.size]
@pos = @src.size + 1
rest
end
def stream_check!
@closed and raise IOError, 'closed stream'
end
end
class StringOutput#:nodoc:
class << self
def new( str = '' )
if block_given?
begin
f = super
yield f
ensure
f.close if f
end
else
super
end
end
alias open new
end
def initialize( str = '' )
@dest = str
@closed = false
end
def close
@closed = true
end
def closed?
@closed
end
def string
@dest
end
alias value string
alias to_str string
def size
@dest.size
end
alias pos size
def inspect
"#<#{self.class}:#{@dest ? 'open' : 'closed'},#{id}>"
end
def print( *args )
stream_check!
raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
args.each do |s|
raise ArgumentError, 'nil not allowed' if s.nil?
@dest << s.to_s
end
nil
end
def puts( *args )
stream_check!
args.each do |str|
@dest << (s = str.to_s)
@dest << "\n" unless s[-1] == ?\n
end
@dest << "\n" if args.empty?
nil
end
def putc( ch )
stream_check!
@dest << ch.chr
nil
end
def printf( *args )
stream_check!
@dest << sprintf(*args)
nil
end
def write( str )
stream_check!
s = str.to_s
@dest << s
s.size
end
alias syswrite write
def <<( str )
stream_check!
@dest << str.to_s
self
end
private
def stream_check!
@closed and raise IOError, 'closed stream'
end
end

View file

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

View file

@ -0,0 +1,238 @@
#
# utils.rb
#
#--
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
# with permission of Minero Aoki.
#++
module TMail
class SyntaxError < StandardError; end
def TMail.new_boundary
'mimepart_' + random_tag
end
def TMail.new_message_id( fqdn = nil )
fqdn ||= ::Socket.gethostname
"<#{random_tag()}@#{fqdn}.tmail>"
end
def TMail.random_tag
@uniq += 1
t = Time.now
sprintf('%x%x_%x%x%d%x',
t.to_i, t.tv_usec,
$$, Thread.current.object_id, @uniq, rand(255))
end
private_class_method :random_tag
@uniq = 0
module TextUtils
aspecial = '()<>[]:;.\\,"'
tspecial = '()<>[];:\\,"/?='
lwsp = " \t\r\n"
control = '\x00-\x1f\x7f-\xff'
ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
CONTROL_CHAR = /[#{control}]/n
def atom_safe?( str )
not ATOM_UNSAFE === str
end
def quote_atom( str )
(ATOM_UNSAFE === str) ? dquote(str) : str
end
def quote_phrase( str )
(PHRASE_UNSAFE === str) ? dquote(str) : str
end
def token_safe?( str )
not TOKEN_UNSAFE === str
end
def quote_token( str )
(TOKEN_UNSAFE === str) ? dquote(str) : str
end
def dquote( str )
'"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
end
private :dquote
def join_domain( arr )
arr.map {|i|
if /\A\[.*\]\z/ === i
i
else
quote_atom(i)
end
}.join('.')
end
ZONESTR_TABLE = {
'jst' => 9 * 60,
'eet' => 2 * 60,
'bst' => 1 * 60,
'met' => 1 * 60,
'gmt' => 0,
'utc' => 0,
'ut' => 0,
'nst' => -(3 * 60 + 30),
'ast' => -4 * 60,
'edt' => -4 * 60,
'est' => -5 * 60,
'cdt' => -5 * 60,
'cst' => -6 * 60,
'mdt' => -6 * 60,
'mst' => -7 * 60,
'pdt' => -7 * 60,
'pst' => -8 * 60,
'a' => -1 * 60,
'b' => -2 * 60,
'c' => -3 * 60,
'd' => -4 * 60,
'e' => -5 * 60,
'f' => -6 * 60,
'g' => -7 * 60,
'h' => -8 * 60,
'i' => -9 * 60,
# j not use
'k' => -10 * 60,
'l' => -11 * 60,
'm' => -12 * 60,
'n' => 1 * 60,
'o' => 2 * 60,
'p' => 3 * 60,
'q' => 4 * 60,
'r' => 5 * 60,
's' => 6 * 60,
't' => 7 * 60,
'u' => 8 * 60,
'v' => 9 * 60,
'w' => 10 * 60,
'x' => 11 * 60,
'y' => 12 * 60,
'z' => 0 * 60
}
def timezone_string_to_unixtime( str )
if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
sec = (m[2].to_i * 60 + m[3].to_i) * 60
m[1] == '-' ? -sec : sec
else
min = ZONESTR_TABLE[str.downcase] or
raise SyntaxError, "wrong timezone format '#{str}'"
min * 60
end
end
WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
Jul Aug Sep Oct Nov Dec TMailBUG )
def time2str( tm )
# [ruby-list:7928]
gmt = Time.at(tm.to_i)
gmt.gmtime
offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
# DO NOT USE strftime: setlocale() breaks it
sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
WDAY[tm.wday], tm.mday, MONTH[tm.month],
tm.year, tm.hour, tm.min, tm.sec,
*(offset / 60).divmod(60)
end
MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
def message_id?( str )
MESSAGE_ID === str
end
MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
def mime_encoded?( str )
MIME_ENCODED === str
end
def decode_params( hash )
new = Hash.new
encoded = nil
hash.each do |key, value|
if m = /\*(?:(\d+)\*)?\z/.match(key)
((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
else
new[key] = to_kcode(value)
end
end
if encoded
encoded.each do |key, strings|
new[key] = decode_RFC2231(strings.join(''))
end
end
new
end
NKF_FLAGS = {
'EUC' => '-e -m',
'SJIS' => '-s -m'
}
def to_kcode( str )
flag = NKF_FLAGS[$KCODE] or return str
NKF.nkf(flag, str)
end
RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
def decode_RFC2231( str )
m = RFC2231_ENCODED.match(str) or return str
begin
NKF.nkf(NKF_FLAGS[$KCODE],
m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
rescue
m.post_match.gsub(/%[\da-f]{2}/in, "")
end
end
end
end

View file

@ -0,0 +1,9 @@
module ActionMailer
module VERSION #:nodoc:
MAJOR = 1
MINOR = 1
TINY = 5
STRING = [MAJOR, MINOR, TINY].join('.')
end
end

View file

@ -0,0 +1,198 @@
require 'rubygems'
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/contrib/rubyforgepublisher'
require File.join(File.dirname(__FILE__), 'lib', 'action_mailer', 'version')
PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
PKG_NAME = 'actionmailer'
PKG_VERSION = ActionMailer::VERSION::STRING + PKG_BUILD
PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
RELEASE_NAME = "REL #{PKG_VERSION}"
RUBY_FORGE_PROJECT = "actionmailer"
RUBY_FORGE_USER = "webster132"
desc "Default Task"
task :default => [ :test ]
# Run the unit tests
Rake::TestTask.new { |t|
t.libs << "test"
t.pattern = 'test/*_test.rb'
t.verbose = true
}
# Genereate the RDoc documentation
Rake::RDocTask.new { |rdoc|
rdoc.rdoc_dir = 'doc'
rdoc.title = "Action Mailer -- Easy email delivery and testing"
rdoc.options << '--line-numbers --inline-source --main README --accessor adv_attr_accessor=M'
rdoc.template = "#{ENV['template']}.rb" if ENV['template']
rdoc.rdoc_files.include('README', 'CHANGELOG')
rdoc.rdoc_files.include('lib/action_mailer.rb')
rdoc.rdoc_files.include('lib/action_mailer/*.rb')
}
# Create compressed packages
spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
s.name = PKG_NAME
s.summary = "Service layer for easy email delivery and testing."
s.description = %q{Makes it trivial to test and deliver emails sent from a single service layer.}
s.version = PKG_VERSION
s.author = "David Heinemeier Hansson"
s.email = "david@loudthinking.com"
s.rubyforge_project = "actionmailer"
s.homepage = "http://www.rubyonrails.org"
s.add_dependency('actionpack', '= 1.11.2' + PKG_BUILD)
s.has_rdoc = true
s.requirements << 'none'
s.require_path = 'lib'
s.autorequire = 'action_mailer'
s.files = [ "rakefile", "install.rb", "README", "CHANGELOG", "MIT-LICENSE" ]
s.files = s.files + Dir.glob( "lib/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
s.files = s.files + Dir.glob( "test/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
end
Rake::GemPackageTask.new(spec) do |p|
p.gem_spec = spec
p.need_tar = true
p.need_zip = true
end
desc "Publish the API documentation"
task :pgem => [:package] do
Rake::SshFilePublisher.new("davidhh@wrath.rubyonrails.org", "public_html/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
end
desc "Publish the API documentation"
task :pdoc => [:rdoc] do
Rake::SshDirPublisher.new("davidhh@wrath.rubyonrails.org", "public_html/am", "doc").upload
end
desc "Publish the release files to RubyForge."
task :release => [:package] do
files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
if RUBY_FORGE_PROJECT then
require 'net/http'
require 'open-uri'
project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
project_data = open(project_uri) { |data| data.read }
group_id = project_data[/[?&]group_id=(\d+)/, 1]
raise "Couldn't get group id" unless group_id
# This echos password to shell which is a bit sucky
if ENV["RUBY_FORGE_PASSWORD"]
password = ENV["RUBY_FORGE_PASSWORD"]
else
print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
password = STDIN.gets.chomp
end
login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
data = [
"login=1",
"form_loginname=#{RUBY_FORGE_USER}",
"form_pw=#{password}"
].join("&")
http.post("/account/login.php", data)
end
cookie = login_response["set-cookie"]
raise "Login failed" unless cookie
headers = { "Cookie" => cookie }
release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
release_data = open(release_uri, headers) { |data| data.read }
package_id = release_data[/[?&]package_id=(\d+)/, 1]
raise "Couldn't get package id" unless package_id
first_file = true
release_id = ""
files.each do |filename|
basename = File.basename(filename)
file_ext = File.extname(filename)
file_data = File.open(filename, "rb") { |file| file.read }
puts "Releasing #{basename}..."
release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
release_date = Time.now.strftime("%Y-%m-%d %H:%M")
type_map = {
".zip" => "3000",
".tgz" => "3110",
".gz" => "3110",
".gem" => "1400"
}; type_map.default = "9999"
type = type_map[file_ext]
boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
query_hash = if first_file then
{
"group_id" => group_id,
"package_id" => package_id,
"release_name" => RELEASE_NAME,
"release_date" => release_date,
"type_id" => type,
"processor_id" => "8000", # Any
"release_notes" => "",
"release_changes" => "",
"preformatted" => "1",
"submit" => "1"
}
else
{
"group_id" => group_id,
"release_id" => release_id,
"package_id" => package_id,
"step2" => "1",
"type_id" => type,
"processor_id" => "8000", # Any
"submit" => "Add This File"
}
end
query = "?" + query_hash.map do |(name, value)|
[name, URI.encode(value)].join("=")
end.join("&")
data = [
"--" + boundary,
"Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
"Content-Type: application/octet-stream",
"Content-Transfer-Encoding: binary",
"", file_data, ""
].join("\x0D\x0A")
release_headers = headers.merge(
"Content-Type" => "multipart/form-data; boundary=#{boundary}"
)
target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
http.post(target + query, data, release_headers)
end
if first_file then
release_id = release_response.body[/release_id=(\d+)/, 1]
raise("Couldn't get release id") unless release_id
end
first_file = false
end
end
end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,14 @@
From jamis_buck@byu.edu Mon May 2 16:07:05 2005
Mime-Version: 1.0 (Apple Message framework v622)
Content-Transfer-Encoding: base64
Message-Id: <d3b8cf8e49f04480850c28713a1f473e@37signals.com>
Content-Type: text/plain;
charset=EUC-KR;
format=flowed
To: willard15georgina@jamis.backpackit.com
From: Jamis Buck <jamis@37signals.com>
Subject: =?EUC-KR?Q?NOTE:_=C7=D1=B1=B9=B8=BB=B7=CE_=C7=CF=B4=C2_=B0=CD?=
Date: Mon, 2 May 2005 16:07:05 -0600
tOu6zrrQwMcguLbC+bChwfa3ziwgv+y4rrTCIMfPs6q01MC7ILnPvcC0z7TZLg0KDQrBpiDAzLin
wLogSmFtaXPA1LTPtNku

View file

@ -0,0 +1,20 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Content-Type: text/plain; charset=X-UNKNOWN
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

View file

@ -0,0 +1,34 @@
From xxx@xxxx.com Wed Apr 27 14:15:31 2005
Mime-Version: 1.0 (Apple Message framework v619.2)
To: xxxxx@xxxxx <matmail>
Message-Id: <416eaebec6d333ec6939eaf8a7d80724@xxxxx>
Content-Type: multipart/alternative;
boundary=Apple-Mail-5-1037861608
From: xxxxx@xxxxx <xxxxx@xxxxx>
Subject: worse when you use them.
Date: Wed, 27 Apr 2005 14:15:31 -0700
--Apple-Mail-5-1037861608
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
XXXXX Xxxxx
--Apple-Mail-5-1037861608
Content-Transfer-Encoding: 7bit
Content-Type: text/enriched;
charset=US-ASCII
<bold>XXXXX Xxxxx</bold>
--Apple-Mail-5-1037861608--

View file

@ -0,0 +1,32 @@
Mime-Version: 1.0 (Apple Message framework v730)
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
--Apple-Mail-13-196941151
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed
This is the first part.
--Apple-Mail-13-196941151
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-Location: Photo25.jpg
Content-ID: <qbFGyPQAS8>
Content-Disposition: inline
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
--Apple-Mail-13-196941151--

View file

@ -0,0 +1,114 @@
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
X-Original-To: xxxxx@xxxxx.xxxxxxxxx.com
Delivered-To: xxxxx@xxxxx.xxxxxxxxx.com
Received: from localhost (localhost [127.0.0.1])
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 06C9DA98D
for <xxxxx@xxxxx.xxxxxxxxx.com>; Sun, 8 May 2005 19:09:13 +0000 (GMT)
Received: from xxxxx.xxxxxxxxx.com ([127.0.0.1])
by localhost (xxxxx.xxxxxxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
with LMTP id 88783-08 for <xxxxx@xxxxx.xxxxxxxxx.com>;
Sun, 8 May 2005 19:09:12 +0000 (GMT)
Received: from xxxxxxx.xxxxxxxxx.com (xxxxxxx.xxxxxxxxx.com [69.36.39.150])
by xxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 10D8BA960
for <xxxxx@xxxxxxxxx.org>; Sun, 8 May 2005 19:09:12 +0000 (GMT)
Received: from zproxy.gmail.com (zproxy.gmail.com [64.233.162.199])
by xxxxxxx.xxxxxxxxx.com (Postfix) with ESMTP id 9EBC4148EAB
for <xxxxx@xxxxxxxxx.com>; Sun, 8 May 2005 14:09:11 -0500 (CDT)
Received: by zproxy.gmail.com with SMTP id 13so1233405nzp
for <xxxxx@xxxxxxxxx.com>; Sun, 08 May 2005 12:09:11 -0700 (PDT)
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
s=beta; d=gmail.com;
h=received:message-id:date:from:reply-to:to:subject:in-reply-to:mime-version:content-type:references;
b=cid1mzGEFa3gtRa06oSrrEYfKca2CTKu9sLMkWxjbvCsWMtp9RGEILjUz0L5RySdH5iO661LyNUoHRFQIa57bylAbXM3g2DTEIIKmuASDG3x3rIQ4sHAKpNxP7Pul+mgTaOKBv+spcH7af++QEJ36gHFXD2O/kx9RePs3JNf/K8=
Received: by 10.36.10.16 with SMTP id 16mr1012493nzj;
Sun, 08 May 2005 12:09:11 -0700 (PDT)
Received: by 10.36.5.10 with HTTP; Sun, 8 May 2005 12:09:11 -0700 (PDT)
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
Date: Sun, 8 May 2005 14:09:11 -0500
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
Subject: Fwd: Signed email causes file attachments
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_5028_7368284.1115579351471"
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
------=_Part_5028_7368284.1115579351471
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
We should not include these files or vcards as attachments.
---------- Forwarded message ----------
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
Date: May 8, 2005 1:17 PM
Subject: Signed email causes file attachments
To: xxxxxxx@xxxxxxxxxx.com
Hi,
Just started to use my xxxxxxxx account (to set-up a GTD system,
natch) and noticed that when I send content via email the signature/
certificate from my email account gets added as a file (e.g.
"smime.p7s").
Obviously I can uncheck the signature option in the Mail compose
window but how often will I remember to do that?
Is there any way these kind of files could be ignored, e.g. via some
sort of exclusions list?
------=_Part_5028_7368284.1115579351471
Content-Type: application/pkcs7-signature; name=smime.p7s
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="smime.p7s"
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
o8xS3A0a1LXealcmlEbJibmKkEaoXci3MhryLgpaa+Kk/sH02SNatDO1vS28bPsibZpcc6deFrla
hSYnL+PW54mDTGHIcCN2fbx/Y6qspzqmtKaXrv75NBtuy9cB6KzU4j2xXbTkAwz3pRSghJJaAwdp
+yIivAD3vr0kJE3p+Ez34HMh33EXEpFoWcN+MCEQZD9WnmFViMrvfvMXLGVFQfAAcC060eGFSRJ1
ZQ9UVQIDAQABoy0wKzAbBgNVHREEFDASgRBzbWhhdW5jaEBtYWMuY29tMAwGA1UdEwEB/wQCMAAw
DQYJKoZIhvcNAQEEBQADgYEAQMrg1n2pXVWteP7BBj+Pk3UfYtbuHb42uHcLJjfjnRlH7AxnSwrd
L3HED205w3Cq8T7tzVxIjRRLO/ljq0GedSCFBky7eYo1PrXhztGHCTSBhsiWdiyLWxKlOxGAwJc/
lMMnwqLOdrQcoF/YgbjeaUFOQbUh94w9VDNpWZYCZwcwggM/MIICqKADAgECAgENMA0GCSqGSIb3
DQEBBQUAMIHRMQswCQYDVQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRIwEAYDVQQHEwlD
YXBlIFRvd24xGjAYBgNVBAoTEVRoYXd0ZSBDb25zdWx0aW5nMSgwJgYDVQQLEx9DZXJ0aWZpY2F0
aW9uIFNlcnZpY2VzIERpdmlzaW9uMSQwIgYDVQQDExtUaGF3dGUgUGVyc29uYWwgRnJlZW1haWwg
Q0ExKzApBgkqhkiG9w0BCQEWHHBlcnNvbmFsLWZyZWVtYWlsQHRoYXd0ZS5jb20wHhcNMDMwNzE3
MDAwMDAwWhcNMTMwNzE2MjM1OTU5WjBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3RlIENv
bnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWlsIElz
c3VpbmcgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMSmPFVzVftOucqZWh5owHUEcJ3f
6f+jHuy9zfVb8hp2vX8MOmHyv1HOAdTlUAow1wJjWiyJFXCO3cnwK4Vaqj9xVsuvPAsH5/EfkTYk
KhPPK9Xzgnc9A74r/rsYPge/QIACZNenprufZdHFKlSFD0gEf6e20TxhBEAeZBlyYLf7AgMBAAGj
gZQwgZEwEgYDVR0TAQH/BAgwBgEB/wIBADBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsLnRo
YXd0ZS5jb20vVGhhd3RlUGVyc29uYWxGcmVlbWFpbENBLmNybDALBgNVHQ8EBAMCAQYwKQYDVR0R
BCIwIKQeMBwxGjAYBgNVBAMTEVByaXZhdGVMYWJlbDItMTM4MA0GCSqGSIb3DQEBBQUAA4GBAEiM
0VCD6gsuzA2jZqxnD3+vrL7CF6FDlpSdf0whuPg2H6otnzYvwPQcUCCTcDz9reFhYsPZOhl+hLGZ
GwDFGguCdJ4lUJRix9sncVcljd2pnDmOjCBPZV+V2vf3h9bGCE6u9uo05RAaWzVNd+NWIXiC3CEZ
Nd4ksdMdRv9dX2VPMYIC5zCCAuMCAQEwaTBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhhd3Rl
IENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVtYWls
IElzc3VpbmcgQ0ECAw5c+TAJBgUrDgMCGgUAoIIBUzAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB
MBwGCSqGSIb3DQEJBTEPFw0wNTA1MDgxODE3NDZaMCMGCSqGSIb3DQEJBDEWBBQSkG9j6+hB0pKp
fV9tCi/iP59sNTB4BgkrBgEEAYI3EAQxazBpMGIxCzAJBgNVBAYTAlpBMSUwIwYDVQQKExxUaGF3
dGUgQ29uc3VsdGluZyAoUHR5KSBMdGQuMSwwKgYDVQQDEyNUaGF3dGUgUGVyc29uYWwgRnJlZW1h
aWwgSXNzdWluZyBDQQIDDlz5MHoGCyqGSIb3DQEJEAILMWugaTBiMQswCQYDVQQGEwJaQTElMCMG
A1UEChMcVGhhd3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNv
bmFsIEZyZWVtYWlsIElzc3VpbmcgQ0ECAw5c+TANBgkqhkiG9w0BAQEFAASCAQAm1GeF7dWfMvrW
8yMPjkhE+R8D1DsiCoWSCp+5gAQm7lcK7V3KrZh5howfpI3TmCZUbbaMxOH+7aKRKpFemxoBY5Q8
rnCkbpg/++/+MI01T69hF/rgMmrGcrv2fIYy8EaARLG0xUVFSZHSP+NQSYz0TTmh4cAESHMzY3JA
nHOoUkuPyl8RXrimY1zn0lceMXlweZRouiPGuPNl1hQKw8P+GhOC5oLlM71UtStnrlk3P9gqX5v7
Tj7Hx057oVfY8FMevjxGwU3EK5TczHezHbWWgTyum9l2ZQbUQsDJxSniD3BM46C1VcbDLPaotAZ0
fTYLZizQfm5hcWEbfYVzkSzLAAAAAAAA
------=_Part_5028_7368284.1115579351471--

View file

@ -0,0 +1,70 @@
From xxxx@xxxx.com Tue May 10 11:28:07 2005
Return-Path: <xxxx@xxxx.com>
X-Original-To: xxxx@xxxx.com
Delivered-To: xxxx@xxxx.com
Received: from localhost (localhost [127.0.0.1])
by xxx.xxxxx.com (Postfix) with ESMTP id 50FD3A96F
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:50 +0000 (GMT)
Received: from xxx.xxxxx.com ([127.0.0.1])
by localhost (xxx.xxxxx.com [127.0.0.1]) (amavisd-new, port 10024)
with LMTP id 70060-03 for <xxxx@xxxx.com>;
Tue, 10 May 2005 17:26:49 +0000 (GMT)
Received: from xxx.xxxxx.com (xxx.xxxxx.com [69.36.39.150])
by xxx.xxxxx.com (Postfix) with ESMTP id 8B957A94B
for <xxxx@xxxx.com>; Tue, 10 May 2005 17:26:48 +0000 (GMT)
Received: from xxx.xxxxx.com (xxx.xxxxx.com [64.233.184.203])
by xxx.xxxxx.com (Postfix) with ESMTP id 9972514824C
for <xxxx@xxxx.com>; Tue, 10 May 2005 12:26:40 -0500 (CDT)
Received: by xxx.xxxxx.com with SMTP id 68so1694448wri
for <xxxx@xxxx.com>; Tue, 10 May 2005 10:26:40 -0700 (PDT)
DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws;
s=beta; d=xxxxx.com;
h=received:message-id:date:from:reply-to:to:subject:mime-version:content-type;
b=g8ZO5ttS6GPEMAz9WxrRk9+9IXBUfQIYsZLL6T88+ECbsXqGIgfGtzJJFn6o9CE3/HMrrIGkN5AisxVFTGXWxWci5YA/7PTVWwPOhJff5BRYQDVNgRKqMl/SMttNrrRElsGJjnD1UyQ/5kQmcBxq2PuZI5Zc47u6CILcuoBcM+A=
Received: by 10.54.96.19 with SMTP id t19mr621017wrb;
Tue, 10 May 2005 10:26:39 -0700 (PDT)
Received: by 10.54.110.5 with HTTP; Tue, 10 May 2005 10:26:39 -0700 (PDT)
Message-ID: <xxxx@xxxx.com>
Date: Tue, 10 May 2005 11:26:39 -0600
From: Test Tester <xxxx@xxxx.com>
Reply-To: Test Tester <xxxx@xxxx.com>
To: xxxx@xxxx.com, xxxx@xxxx.com
Subject: Another PDF
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_2192_32400445.1115745999735"
X-Virus-Scanned: amavisd-new at textdrive.com
------=_Part_2192_32400445.1115745999735
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
Just attaching another PDF, here, to see what the message looks like,
and to see if I can figure out what is going wrong here.
------=_Part_2192_32400445.1115745999735
Content-Type: application/pdf; name="broken.pdf"
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="broken.pdf"
JVBERi0xLjQNCiXk9tzfDQoxIDAgb2JqDQo8PCAvTGVuZ3RoIDIgMCBSDQogICAvRmlsdGVyIC9G
bGF0ZURlY29kZQ0KPj4NCnN0cmVhbQ0KeJy9Wt2KJbkNvm/od6jrhZxYln9hWEh2p+8HBvICySaE
ycLuTV4/1ifJ9qnq09NpSBimu76yLUuy/qzqcPz7+em3Ixx/CDc6CsXxs3b5+fvfjr/8cPz6/BRu
rbfAx/n3739/fuJylJ5u5fjX81OuDr4deK4Bz3z/aDP+8fz0yw8g0Ofq7ktr1Mn+u28rvhy/jVeD
QSa+9YNKHP/pxjvDNfVAx/m3MFz54FhvTbaseaxiDoN2LeMVMw+yA7RbHSCDzxZuaYB2E1Yay7QU
x89vz0+tyFDKMlAHK5yqLmnjF+c4RjEiQIUeKwblXMe+AsZjN1J5yGQL5DHpDHksurM81rF6PKab
gK6zAarIDzIiUY23rJsN9iorAE816aIu6lsgAdQFsuhhkHOUFgVjp2GjMqSewITXNQ27jrMeamkg
1rPI3iLWG2CIaSBB+V1245YVRICGbbpYKHc2USFDl6M09acQVQYhlwIrkBNLISvXhGlF1wi5FHCw
wxZkoGNJlVeJCEsqKA+3YAV5AMb6KkeaqEJQmFKKQU8T1pRi2ihE1Y4CDrqoYFFXYjJJOatsyzuI
8SIlykuxKTMibWK8H1PgEvqYgs4GmQSrEjJAalgGirIhik+p4ZQN9E3ETFPAHE1b8pp1l/0Rc1gl
fQs0ABWvyoZZzU8VnPXwVVcO9BEsyjEJaO6eBoZRyKGlrKoYoOygA8BGIzgwN3RQ15ouigG5idZQ
fx2U4Db2CqiLO0WHAZoylGiCAqhniNQjFjQPSkmjwfNTgQ6M1Ih+eWo36wFmjIxDJZiGUBiWsAyR
xX3EekGOizkGI96Ol9zVZTAivikURhRsHh2E3JhWMpSTZCnnonrLhMCodgrNcgo4uyJUJc6qnVss
nrGd1Ptr0YwisCOYyIbUwVjV4xBUNLbguSO2YHujonAMJkMdSI7bIw91Akq2AUlMUWGFTMAOamjU
OvZQCxIkY2pCpMFo/IwLdVLHs6nddwTRrgoVbvLU9eB0G4EMndV0TNoxHbt3JBWwK6hhv3iHfDtF
yokB302IpEBTnWICde4uYc/1khDbSIkQopO6lcqamGBu1OSE3N5IPSsZX00CkSHRiiyx6HQIShsS
HSVNswdVsaOUSAWq9aYhDtGDaoG5a3lBGkYt/lFlBFt1UqrYnzVtUpUQnLiZeouKgf1KhRBViRRk
ExepJCzTwEmFDalIRbLEGtw0gfpESOpIAF/NnpPzcVCG86s0g2DuSyd41uhNGbEgaSrWEXORErbw
------=_Part_2192_32400445.1115745999735--

View file

@ -0,0 +1,59 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id 6AAEE3B4D23 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:23 -0500
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id j48HUC213279 for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:13 -0500
Received: from conversion-xxx.xxxx.xxx.net by xxx.xxxx.xxx id <0IG600901LQ64I@xxx.xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
Received: from agw1 by xxx.xxxx.xxx with ESMTP id <0IG600JFYLYCAxxx@xxxx.xxx> for <xxx@xxxx.xxx>; Sun, 8 May 2005 12:30:12 -0500
Date: Sun, 8 May 2005 12:30:08 -0500
From: xxx@xxxx.xxx
To: xxx@xxxx.xxx
Message-Id: <7864245.1115573412626.JavaMxxx@xxxx.xxx>
Subject: Filth
Mime-Version: 1.0
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4ca329_133ae40413c81ef
X-Mms-Priority: 1
X-Mms-Transaction-Id: 3198421808-0
X-Mms-Message-Type: 0
X-Mms-Sender-Visibility: 1
X-Mms-Read-Reply: 1
X-Original-To: xxx@xxxx.xxx
X-Mms-Message-Class: 0
X-Mms-Delivery-Report: 0
X-Mms-Mms-Version: 16
Delivered-To: xxx@xxxx.xxx
X-Nokia-Ag-Version: 2.0
This is a multi-part message in MIME format.
--mimepart_427e4cb4ca329_133ae40413c81ef
Content-Type: multipart/mixed; boundary=mimepart_427e4cb4cbd97_133ae40413c8217
--mimepart_427e4cb4cbd97_133ae40413c8217
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
Content-Location: text.txt
Some text
--mimepart_427e4cb4cbd97_133ae40413c8217--
--mimepart_427e4cb4ca329_133ae40413c81ef
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
--
This Orange Multi Media Message was sent wirefree from an Orange
MMS phone. If you would like to reply, please text or phone the
sender directly by using the phone number listed in the sender's
address. To learn more about Orange's Multi Media Messaging
Service, find us on the Web at xxx.xxxx.xxx.uk/mms
--mimepart_427e4cb4ca329_133ae40413c81ef
--mimepart_427e4cb4ca329_133ae40413c81ef-

View file

@ -0,0 +1,19 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

View file

@ -0,0 +1,20 @@
Return-Path: <xxx@xxxx.xxx>
Received: from xxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id C1B953B4CB6 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:05 -0500
Received: from SMS-GTYxxx.xxxx.xxx by xxx.xxxx.xxx with ESMTP id ca for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:04 -0500
Received: from xxx.xxxx.xxx by SMS-GTYxxx.xxxx.xxx with ESMTP id j4AKR3r23323 for <xxxxx@Exxx.xxxx.xxx>; Tue, 10 May 2005 15:27:03 -0500
Date: Tue, 10 May 2005 15:27:03 -0500
From: xxx@xxxx.xxx
Sender: xxx@xxxx.xxx
To: xxxxxxxxxxx@xxxx.xxxx.xxx
Message-Id: <xxx@xxxx.xxx>
X-Original-To: xxxxxxxxxxx@xxxx.xxxx.xxx
Delivered-To: xxx@xxxx.xxx
Importance: normal
Content-Type: text/plain; charset=us-ascii
Test test. Hi. Waving. m
----------------------------------------------------------------
Sent via Bell Mobility's Text Messaging service.
Envoyé par le service de messagerie texte de Bell Mobilité.
----------------------------------------------------------------

View file

@ -0,0 +1,56 @@
Mime-Version: 1.0 (Apple Message framework v730)
Content-Type: multipart/mixed; boundary=Apple-Mail-13-196941151
Message-Id: <9169D984-4E0B-45EF-82D4-8F5E53AD7012@example.com>
From: foo@example.com
Subject: testing
Date: Mon, 6 Jun 2005 22:21:22 +0200
To: blah@example.com
--Apple-Mail-13-196941151
Content-Type: multipart/mixed;
boundary=Apple-Mail-12-196940926
--Apple-Mail-12-196940926
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed
This is the first part.
--Apple-Mail-12-196940926
Content-Transfer-Encoding: base64
Content-Type: application/pdf;
x-unix-mode=0666;
name="test.pdf"
Content-Disposition: inline;
filename=test.pdf
YmxhaCBibGFoIGJsYWg=
--Apple-Mail-12-196940926
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
format=flowed
--Apple-Mail-12-196940926--
--Apple-Mail-13-196941151
Content-Transfer-Encoding: base64
Content-Type: application/pkcs7-signature;
name=smime.p7s
Content-Disposition: attachment;
filename=smime.p7s
jamisSqGSIb3DQEHAqCAMIjamisxCzAJBgUrDgMCGgUAMIAGCSqGSjamisEHAQAAoIIFSjCCBUYw
ggQujamisQICBD++ukQwDQYJKojamisNAQEFBQAwMTELMAkGA1UEBhMCRjamisAKBgNVBAoTA1RE
QzEUMBIGjamisxMLVERDIE9DRVMgQ0jamisNMDQwMjI5MTE1OTAxWhcNMDYwMjamisIyOTAxWjCB
gDELMAkGA1UEjamisEsxKTAnBgNVBAoTIEjamisuIG9yZ2FuaXNhdG9yaXNrIHRpbjamisRuaW5=
--Apple-Mail-13-196941151--

View file

@ -0,0 +1,47 @@
From xxxxxxxxx.xxxxxxx@gmail.com Sun May 8 19:07:09 2005
Return-Path: <xxxxxxxxx.xxxxxxx@gmail.com>
Message-ID: <e85734b90505081209eaaa17b@mail.gmail.com>
Date: Sun, 8 May 2005 14:09:11 -0500
From: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
Reply-To: xxxxxxxxx xxxxxxx <xxxxxxxxx.xxxxxxx@gmail.com>
To: xxxxx xxxx <xxxxx@xxxxxxxxx.com>
Subject: Fwd: Signed email causes file attachments
In-Reply-To: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
Mime-Version: 1.0
Content-Type: multipart/mixed;
boundary="----=_Part_5028_7368284.1115579351471"
References: <F6E2D0B4-CC35-4A91-BA4C-C7C712B10C13@mac.com>
------=_Part_5028_7368284.1115579351471
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline
We should not include these files or vcards as attachments.
---------- Forwarded message ----------
From: xxxxx xxxxxx <xxxxxxxx@xxx.com>
Date: May 8, 2005 1:17 PM
Subject: Signed email causes file attachments
To: xxxxxxx@xxxxxxxxxx.com
Hi,
Test attachments oddly encoded with japanese charset.
------=_Part_5028_7368284.1115579351471
Content-Type: application/octet-stream; name*=iso-2022-jp'ja'01%20Quien%20Te%20Dij%8aat.%20Pitbull.mp3
Content-Transfer-Encoding: base64
Content-Disposition: attachment
MIAGCSqGSIb3DQEHAqCAMIACAQExCzAJBgUrDgMCGgUAMIAGCSqGSIb3DQEHAQAAoIIGFDCCAs0w
ggI2oAMCAQICAw5c+TANBgkqhkiG9w0BAQQFADBiMQswCQYDVQQGEwJaQTElMCMGA1UEChMcVGhh
d3RlIENvbnN1bHRpbmcgKFB0eSkgTHRkLjEsMCoGA1UEAxMjVGhhd3RlIFBlcnNvbmFsIEZyZWVt
YWlsIElzc3VpbmcgQ0EwHhcNMDUwMzI5MDkzOTEwWhcNMDYwMzI5MDkzOTEwWjBCMR8wHQYDVQQD
ExZUaGF3dGUgRnJlZW1haWwgTWVtYmVyMR8wHQYJKoZIhvcNAQkBFhBzbWhhdW5jaEBtYWMuY29t
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn90dPsYS3LjfMY211OSYrDQLzwNYPlAL
7+/0XA+kdy8/rRnyEHFGwhNCDmg0B6pxC7z3xxJD/8GfCd+IYUUNUQV5m9MkxfP9pTVXZVIYLaBw
------=_Part_5028_7368284.1115579351471--

View file

@ -0,0 +1,28 @@
Received: from xxx.xxx.xxx ([xxx.xxx.xxx.xxx] verified)
by xxx.com (CommuniGate Pro SMTP 4.2.8)
with SMTP id 2532598 for xxx@xxx.com; Wed, 23 Feb 2005 17:51:49 -0500
Received-SPF: softfail
receiver=xxx.com; client-ip=xxx.xxx.xxx.xxx; envelope-from=xxx@xxx.xxx
quite Delivered-To: xxx@xxx.xxx
Received: by xxx.xxx.xxx (Wostfix, from userid xxx)
id 0F87F333; Wed, 23 Feb 2005 16:16:17 -0600
Date: Wed, 23 Feb 2005 18:20:17 -0400
From: "xxx xxx" <xxx@xxx.xxx>
Message-ID: <4D6AA7EB.6490534@xxx.xxx>
To: xxx@xxx.com
Subject: Stop adware/spyware once and for all.
X-Scanned-By: MIMEDefang 2.11 (www dot roaringpenguin dot com slash mimedefang)
You are infected with:
Ad Ware and Spy Ware
Get your free scan and removal download now,
before it gets any worse.
http://xxx.xxx.info?aid=3D13&?stat=3D4327kdzt
no more? (you will still be infected)
http://xxx.xxx.info/discon/?xxx@xxx.com

View file

@ -0,0 +1,3 @@
Hello there,
Mr. <%= @recipient %>

View file

@ -0,0 +1,10 @@
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>
<html>
<body>
HTML formatted message to <strong><%= @recipient %></strong>.
</body>
</html>

View file

@ -0,0 +1,2 @@
Plain text to <%= @recipient %>.
Plain text to <%= @recipient %>.

View file

@ -0,0 +1 @@
yaml to: <%= @recipient %>

View file

@ -0,0 +1,3 @@
Hello there,
Mr. <%= @recipient %>

View file

@ -0,0 +1,97 @@
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
require 'test/unit'
require 'action_mailer'
module MailerHelper
def person_name
"Mr. Joe Person"
end
end
class HelperMailer < ActionMailer::Base
helper MailerHelper
helper :test
def use_helper(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
end
def use_test_helper(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
self.body = { :text => "emphasize me!" }
end
def use_mail_helper(recipient)
recipients recipient
subject "using mailing helpers"
from "tester@example.com"
self.body = { :text =>
"But soft! What light through yonder window breaks? It is the east, " +
"and Juliet is the sun. Arise, fair sun, and kill the envious moon, " +
"which is sick and pale with grief that thou, her maid, art far more " +
"fair than she. Be not her maid, for she is envious! Her vestal " +
"livery is but sick and green, and none but fools do wear it. Cast " +
"it off!"
}
end
def use_helper_method(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
self.body = { :text => "emphasize me!" }
end
private
def name_of_the_mailer_class
self.class.name
end
helper_method :name_of_the_mailer_class
end
HelperMailer.template_root = File.dirname(__FILE__) + "/fixtures"
class MailerHelperTest < Test::Unit::TestCase
def new_mail( charset="utf-8" )
mail = TMail::Mail.new
mail.set_content_type "text", "plain", { "charset" => charset } if charset
mail
end
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def test_use_helper
mail = HelperMailer.create_use_helper(@recipient)
assert_match %r{Mr. Joe Person}, mail.encoded
end
def test_use_test_helper
mail = HelperMailer.create_use_test_helper(@recipient)
assert_match %r{<em><strong><small>emphasize me!}, mail.encoded
end
def test_use_helper_method
mail = HelperMailer.create_use_helper_method(@recipient)
assert_match %r{HelperMailer}, mail.encoded
end
def test_use_mail_helper
mail = HelperMailer.create_use_mail_helper(@recipient)
assert_match %r{ But soft!}, mail.encoded
assert_match %r{east, and\n Juliet}, mail.encoded
end
end

View file

@ -0,0 +1,48 @@
$:.unshift(File.dirname(__FILE__) + "/../lib/")
require 'test/unit'
require 'action_mailer'
class RenderMailer < ActionMailer::Base
def inline_template(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
body render(:inline => "Hello, <%= @world %>", :body => { :world => "Earth" })
end
def file_template(recipient)
recipients recipient
subject "using helpers"
from "tester@example.com"
body render(:file => "signed_up", :body => { :recipient => recipient })
end
def initialize_defaults(method_name)
super
mailer_name "test_mailer"
end
end
RenderMailer.template_root = File.dirname(__FILE__) + "/fixtures"
class RenderHelperTest < Test::Unit::TestCase
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def test_inline_template
mail = RenderMailer.create_inline_template(@recipient)
assert_equal "Hello, Earth", mail.body.strip
end
def test_file_template
mail = RenderMailer.create_file_template(@recipient)
assert_equal "Hello there, \n\nMr. test@localhost", mail.body.strip
end
end

View file

@ -0,0 +1,749 @@
$:.unshift(File.dirname(__FILE__) + "/../lib/")
require 'test/unit'
require 'action_mailer'
class MockSMTP
def self.deliveries
@@deliveries
end
def initialize
@@deliveries = []
end
def sendmail(mail, from, to)
@@deliveries << [mail, from, to]
end
end
class Net::SMTP
def self.start(*args)
yield MockSMTP.new
end
end
class TestMailer < ActionMailer::Base
def signed_up(recipient)
@recipients = recipient
@subject = "[Signed up] Welcome #{recipient}"
@from = "system@loudthinking.com"
@sent_on = Time.local(2004, 12, 12)
@body["recipient"] = recipient
end
def cancelled_account(recipient)
self.recipients = recipient
self.subject = "[Cancelled] Goodbye #{recipient}"
self.from = "system@loudthinking.com"
self.sent_on = Time.local(2004, 12, 12)
self.body = "Goodbye, Mr. #{recipient}"
end
def cc_bcc(recipient)
recipients recipient
subject "testing bcc/cc"
from "system@loudthinking.com"
sent_on Time.local(2004, 12, 12)
cc "nobody@loudthinking.com"
bcc "root@loudthinking.com"
body "Nothing to see here."
end
def iso_charset(recipient)
@recipients = recipient
@subject = "testing isø charsets"
@from = "system@loudthinking.com"
@sent_on = Time.local 2004, 12, 12
@cc = "nobody@loudthinking.com"
@bcc = "root@loudthinking.com"
@body = "Nothing to see here."
@charset = "iso-8859-1"
end
def unencoded_subject(recipient)
@recipients = recipient
@subject = "testing unencoded subject"
@from = "system@loudthinking.com"
@sent_on = Time.local 2004, 12, 12
@cc = "nobody@loudthinking.com"
@bcc = "root@loudthinking.com"
@body = "Nothing to see here."
end
def extended_headers(recipient)
@recipients = recipient
@subject = "testing extended headers"
@from = "Grytøyr <stian1@example.net>"
@sent_on = Time.local 2004, 12, 12
@cc = "Grytøyr <stian2@example.net>"
@bcc = "Grytøyr <stian3@example.net>"
@body = "Nothing to see here."
@charset = "iso-8859-1"
end
def utf8_body(recipient)
@recipients = recipient
@subject = "testing utf-8 body"
@from = "Foo áëô îü <extended@example.net>"
@sent_on = Time.local 2004, 12, 12
@cc = "Foo áëô îü <extended@example.net>"
@bcc = "Foo áëô îü <extended@example.net>"
@body = "åœö blah"
@charset = "utf-8"
end
def multipart_with_mime_version(recipient)
recipients recipient
subject "multipart with mime_version"
from "test@example.com"
sent_on Time.local(2004, 12, 12)
mime_version "1.1"
content_type "multipart/alternative"
part "text/plain" do |p|
p.body = "blah"
end
part "text/html" do |p|
p.body = "<b>blah</b>"
end
end
def multipart_with_utf8_subject(recipient)
recipients recipient
subject "Foo áëô îü"
from "test@example.com"
charset "utf-8"
part "text/plain" do |p|
p.body = "blah"
end
part "text/html" do |p|
p.body = "<b>blah</b>"
end
end
def explicitly_multipart_example(recipient, ct=nil)
recipients recipient
subject "multipart example"
from "test@example.com"
sent_on Time.local(2004, 12, 12)
body "plain text default"
content_type ct if ct
part "text/html" do |p|
p.charset = "iso-8859-1"
p.body = "blah"
end
attachment :content_type => "image/jpeg", :filename => "foo.jpg",
:body => "123456789"
end
def implicitly_multipart_example(recipient, cs = nil, order = nil)
@recipients = recipient
@subject = "multipart example"
@from = "test@example.com"
@sent_on = Time.local 2004, 12, 12
@body = { "recipient" => recipient }
@charset = cs if cs
@implicit_parts_order = order if order
end
def html_mail(recipient)
recipients recipient
subject "html mail"
from "test@example.com"
body "<em>Emphasize</em> <strong>this</strong>"
content_type "text/html"
end
def html_mail_with_underscores(recipient)
subject "html mail with underscores"
body %{<a href="http://google.com" target="_blank">_Google</a>}
end
def custom_template(recipient)
recipients recipient
subject "[Signed up] Welcome #{recipient}"
from "system@loudthinking.com"
sent_on Time.local(2004, 12, 12)
template "signed_up"
body["recipient"] = recipient
end
def various_newlines(recipient)
recipients recipient
subject "various newlines"
from "test@example.com"
body "line #1\nline #2\rline #3\r\nline #4\r\r" +
"line #5\n\nline#6\r\n\r\nline #7"
end
def various_newlines_multipart(recipient)
recipients recipient
subject "various newlines multipart"
from "test@example.com"
content_type "multipart/alternative"
part :content_type => "text/plain", :body => "line #1\nline #2\rline #3\r\nline #4\r\r"
part :content_type => "text/html", :body => "<p>line #1</p>\n<p>line #2</p>\r<p>line #3</p>\r\n<p>line #4</p>\r\r"
end
def nested_multipart(recipient)
recipients recipient
subject "nested multipart"
from "test@example.com"
content_type "multipart/mixed"
part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
p.part :content_type => "text/plain", :body => "test text\nline #2"
p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
end
attachment :content_type => "application/octet-stream",:filename => "test.txt", :body => "test abcdefghijklmnopqstuvwxyz"
end
def unnamed_attachment(recipient)
recipients recipient
subject "nested multipart"
from "test@example.com"
content_type "multipart/mixed"
part :content_type => "text/plain", :body => "hullo"
attachment :content_type => "application/octet-stream", :body => "test abcdefghijklmnopqstuvwxyz"
end
def headers_with_nonalpha_chars(recipient)
recipients recipient
subject "nonalpha chars"
from "One: Two <test@example.com>"
cc "Three: Four <test@example.com>"
bcc "Five: Six <test@example.com>"
body "testing"
end
class <<self
attr_accessor :received_body
end
def receive(mail)
self.class.received_body = mail.body
end
end
TestMailer.template_root = File.dirname(__FILE__) + "/fixtures"
class ActionMailerTest < Test::Unit::TestCase
include ActionMailer::Quoting
def encode( text, charset="utf-8" )
quoted_printable( text, charset )
end
def new_mail( charset="utf-8" )
mail = TMail::Mail.new
if charset
mail.set_content_type "text", "plain", { "charset" => charset }
end
mail
end
def setup
ActionMailer::Base.delivery_method = :test
ActionMailer::Base.perform_deliveries = true
ActionMailer::Base.deliveries = []
@recipient = 'test@localhost'
end
def test_nested_parts
created = nil
assert_nothing_raised { created = TestMailer.create_nested_multipart(@recipient)}
assert_equal 2,created.parts.size
assert_equal 2,created.parts.first.parts.size
assert_equal "multipart/mixed", created.content_type
assert_equal "multipart/alternative", created.parts.first.content_type
assert_equal "text/plain", created.parts.first.parts.first.content_type
assert_equal "text/html", created.parts.first.parts[1].content_type
assert_equal "application/octet-stream", created.parts[1].content_type
end
def test_signed_up
expected = new_mail
expected.to = @recipient
expected.subject = "[Signed up] Welcome #{@recipient}"
expected.body = "Hello there, \n\nMr. #{@recipient}"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
expected.mime_version = nil
created = nil
assert_nothing_raised { created = TestMailer.create_signed_up(@recipient) }
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised { TestMailer.deliver_signed_up(@recipient) }
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_custom_template
expected = new_mail
expected.to = @recipient
expected.subject = "[Signed up] Welcome #{@recipient}"
expected.body = "Hello there, \n\nMr. #{@recipient}"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
created = nil
assert_nothing_raised { created = TestMailer.create_custom_template(@recipient) }
assert_not_nil created
assert_equal expected.encoded, created.encoded
end
def test_cancelled_account
expected = new_mail
expected.to = @recipient
expected.subject = "[Cancelled] Goodbye #{@recipient}"
expected.body = "Goodbye, Mr. #{@recipient}"
expected.from = "system@loudthinking.com"
expected.date = Time.local(2004, 12, 12)
created = nil
assert_nothing_raised { created = TestMailer.create_cancelled_account(@recipient) }
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised { TestMailer.deliver_cancelled_account(@recipient) }
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_cc_bcc
expected = new_mail
expected.to = @recipient
expected.subject = "testing bcc/cc"
expected.body = "Nothing to see here."
expected.from = "system@loudthinking.com"
expected.cc = "nobody@loudthinking.com"
expected.bcc = "root@loudthinking.com"
expected.date = Time.local 2004, 12, 12
created = nil
assert_nothing_raised do
created = TestMailer.create_cc_bcc @recipient
end
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised do
TestMailer.deliver_cc_bcc @recipient
end
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_iso_charset
expected = new_mail( "iso-8859-1" )
expected.to = @recipient
expected.subject = encode "testing isø charsets", "iso-8859-1"
expected.body = "Nothing to see here."
expected.from = "system@loudthinking.com"
expected.cc = "nobody@loudthinking.com"
expected.bcc = "root@loudthinking.com"
expected.date = Time.local 2004, 12, 12
created = nil
assert_nothing_raised do
created = TestMailer.create_iso_charset @recipient
end
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised do
TestMailer.deliver_iso_charset @recipient
end
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_unencoded_subject
expected = new_mail
expected.to = @recipient
expected.subject = "testing unencoded subject"
expected.body = "Nothing to see here."
expected.from = "system@loudthinking.com"
expected.cc = "nobody@loudthinking.com"
expected.bcc = "root@loudthinking.com"
expected.date = Time.local 2004, 12, 12
created = nil
assert_nothing_raised do
created = TestMailer.create_unencoded_subject @recipient
end
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised do
TestMailer.deliver_unencoded_subject @recipient
end
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_instances_are_nil
assert_nil ActionMailer::Base.new
assert_nil TestMailer.new
end
def test_deliveries_array
assert_not_nil ActionMailer::Base.deliveries
assert_equal 0, ActionMailer::Base.deliveries.size
TestMailer.deliver_signed_up(@recipient)
assert_equal 1, ActionMailer::Base.deliveries.size
assert_not_nil ActionMailer::Base.deliveries.first
end
def test_perform_deliveries_flag
ActionMailer::Base.perform_deliveries = false
TestMailer.deliver_signed_up(@recipient)
assert_equal 0, ActionMailer::Base.deliveries.size
ActionMailer::Base.perform_deliveries = true
TestMailer.deliver_signed_up(@recipient)
assert_equal 1, ActionMailer::Base.deliveries.size
end
def test_unquote_quoted_printable_subject
msg = <<EOF
From: me@example.com
Subject: =?utf-8?Q?testing_testing_=D6=A4?=
Content-Type: text/plain; charset=iso-8859-1
The body
EOF
mail = TMail::Mail.parse(msg)
assert_equal "testing testing \326\244", mail.subject
assert_equal "=?utf-8?Q?testing_testing_=D6=A4?=", mail.quoted_subject
end
def test_unquote_7bit_subject
msg = <<EOF
From: me@example.com
Subject: this == working?
Content-Type: text/plain; charset=iso-8859-1
The body
EOF
mail = TMail::Mail.parse(msg)
assert_equal "this == working?", mail.subject
assert_equal "this == working?", mail.quoted_subject
end
def test_unquote_7bit_body
msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: 7bit
The=3Dbody
EOF
mail = TMail::Mail.parse(msg)
assert_equal "The=3Dbody", mail.body.strip
assert_equal "The=3Dbody", mail.quoted_body.strip
end
def test_unquote_quoted_printable_body
msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: quoted-printable
The=3Dbody
EOF
mail = TMail::Mail.parse(msg)
assert_equal "The=body", mail.body.strip
assert_equal "The=3Dbody", mail.quoted_body.strip
end
def test_unquote_base64_body
msg = <<EOF
From: me@example.com
Subject: subject
Content-Type: text/plain; charset=iso-8859-1
Content-Transfer-Encoding: base64
VGhlIGJvZHk=
EOF
mail = TMail::Mail.parse(msg)
assert_equal "The body", mail.body.strip
assert_equal "VGhlIGJvZHk=", mail.quoted_body.strip
end
def test_extended_headers
@recipient = "Grytøyr <test@localhost>"
expected = new_mail "iso-8859-1"
expected.to = quote_address_if_necessary @recipient, "iso-8859-1"
expected.subject = "testing extended headers"
expected.body = "Nothing to see here."
expected.from = quote_address_if_necessary "Grytøyr <stian1@example.net>", "iso-8859-1"
expected.cc = quote_address_if_necessary "Grytøyr <stian2@example.net>", "iso-8859-1"
expected.bcc = quote_address_if_necessary "Grytøyr <stian3@example.net>", "iso-8859-1"
expected.date = Time.local 2004, 12, 12
created = nil
assert_nothing_raised do
created = TestMailer.create_extended_headers @recipient
end
assert_not_nil created
assert_equal expected.encoded, created.encoded
assert_nothing_raised do
TestMailer.deliver_extended_headers @recipient
end
assert_not_nil ActionMailer::Base.deliveries.first
assert_equal expected.encoded, ActionMailer::Base.deliveries.first.encoded
end
def test_utf8_body_is_not_quoted
@recipient = "Foo áëô îü <extended@example.net>"
expected = new_mail "utf-8"
expected.to = quote_address_if_necessary @recipient, "utf-8"
expected.subject = "testing utf-8 body"
expected.body = "åœö blah"
expected.from = quote_address_if_necessary @recipient, "utf-8"
expected.cc = quote_address_if_necessary @recipient, "utf-8"
expected.bcc = quote_address_if_necessary @recipient, "utf-8"
expected.date = Time.local 2004, 12, 12
created = TestMailer.create_utf8_body @recipient
assert_match(/åœö blah/, created.encoded)
end
def test_multiple_utf8_recipients
@recipient = ["\"Foo áëô îü\" <extended@example.net>", "\"Example Recipient\" <me@example.com>"]
expected = new_mail "utf-8"
expected.to = quote_address_if_necessary @recipient, "utf-8"
expected.subject = "testing utf-8 body"
expected.body = "åœö blah"
expected.from = quote_address_if_necessary @recipient.first, "utf-8"
expected.cc = quote_address_if_necessary @recipient, "utf-8"
expected.bcc = quote_address_if_necessary @recipient, "utf-8"
expected.date = Time.local 2004, 12, 12
created = TestMailer.create_utf8_body @recipient
assert_match(/\nFrom: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>\r/, created.encoded)
assert_match(/\nTo: =\?utf-8\?Q\?Foo_.*?\?= <extended@example.net>, Example Recipient <me/, created.encoded)
end
def test_receive_decodes_base64_encoded_mail
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email")
TestMailer.receive(fixture)
assert_match(/Jamis/, TestMailer.received_body)
end
def test_receive_attachments
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email2")
mail = TMail::Mail.parse(fixture)
attachment = mail.attachments.last
assert_equal "smime.p7s", attachment.original_filename
assert_equal "application/pkcs7-signature", attachment.content_type
end
def test_decode_attachment_without_charset
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email3")
mail = TMail::Mail.parse(fixture)
attachment = mail.attachments.last
assert_equal 1026, attachment.read.length
end
def test_attachment_using_content_location
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email12")
mail = TMail::Mail.parse(fixture)
assert_equal 1, mail.attachments.length
assert_equal "Photo25.jpg", mail.attachments.first.original_filename
end
def test_decode_part_without_content_type
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email4")
mail = TMail::Mail.parse(fixture)
assert_nothing_raised { mail.body }
end
def test_decode_message_without_content_type
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email5")
mail = TMail::Mail.parse(fixture)
assert_nothing_raised { mail.body }
end
def test_decode_message_with_incorrect_charset
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email6")
mail = TMail::Mail.parse(fixture)
assert_nothing_raised { mail.body }
end
def test_multipart_with_mime_version
mail = TestMailer.create_multipart_with_mime_version(@recipient)
assert_equal "1.1", mail.mime_version
end
def test_multipart_with_utf8_subject
mail = TestMailer.create_multipart_with_utf8_subject(@recipient)
assert_match(/\nSubject: =\?utf-8\?Q\?Foo_.*?\?=/, mail.encoded)
end
def test_explicitly_multipart_messages
mail = TestMailer.create_explicitly_multipart_example(@recipient)
assert_equal 3, mail.parts.length
assert_nil mail.content_type
assert_equal "text/plain", mail.parts[0].content_type
assert_equal "text/html", mail.parts[1].content_type
assert_equal "iso-8859-1", mail.parts[1].sub_header("content-type", "charset")
assert_equal "inline", mail.parts[1].content_disposition
assert_equal "image/jpeg", mail.parts[2].content_type
assert_equal "attachment", mail.parts[2].content_disposition
assert_equal "foo.jpg", mail.parts[2].sub_header("content-disposition", "filename")
assert_equal "foo.jpg", mail.parts[2].sub_header("content-type", "name")
assert_nil mail.parts[2].sub_header("content-type", "charset")
end
def test_explicitly_multipart_with_content_type
mail = TestMailer.create_explicitly_multipart_example(@recipient, "multipart/alternative")
assert_equal 3, mail.parts.length
assert_equal "multipart/alternative", mail.content_type
end
def test_explicitly_multipart_with_invalid_content_type
mail = TestMailer.create_explicitly_multipart_example(@recipient, "text/xml")
assert_equal 3, mail.parts.length
assert_nil mail.content_type
end
def test_implicitly_multipart_messages
mail = TestMailer.create_implicitly_multipart_example(@recipient)
assert_equal 3, mail.parts.length
assert_equal "1.0", mail.mime_version
assert_equal "multipart/alternative", mail.content_type
assert_equal "text/yaml", mail.parts[0].content_type
assert_equal "utf-8", mail.parts[0].sub_header("content-type", "charset")
assert_equal "text/plain", mail.parts[1].content_type
assert_equal "utf-8", mail.parts[1].sub_header("content-type", "charset")
assert_equal "text/html", mail.parts[2].content_type
assert_equal "utf-8", mail.parts[2].sub_header("content-type", "charset")
end
def test_implicitly_multipart_messages_with_custom_order
mail = TestMailer.create_implicitly_multipart_example(@recipient, nil, ["text/yaml", "text/plain"])
assert_equal 3, mail.parts.length
assert_equal "text/html", mail.parts[0].content_type
assert_equal "text/plain", mail.parts[1].content_type
assert_equal "text/yaml", mail.parts[2].content_type
end
def test_implicitly_multipart_messages_with_charset
mail = TestMailer.create_implicitly_multipart_example(@recipient, 'iso-8859-1')
assert_equal "multipart/alternative", mail.header['content-type'].body
assert_equal 'iso-8859-1', mail.parts[0].sub_header("content-type", "charset")
assert_equal 'iso-8859-1', mail.parts[1].sub_header("content-type", "charset")
assert_equal 'iso-8859-1', mail.parts[2].sub_header("content-type", "charset")
end
def test_html_mail
mail = TestMailer.create_html_mail(@recipient)
assert_equal "text/html", mail.content_type
end
def test_html_mail_with_underscores
mail = TestMailer.create_html_mail_with_underscores(@recipient)
assert_equal %{<a href="http://google.com" target="_blank">_Google</a>}, mail.body
end
def test_various_newlines
mail = TestMailer.create_various_newlines(@recipient)
assert_equal("line #1\nline #2\nline #3\nline #4\n\n" +
"line #5\n\nline#6\n\nline #7", mail.body)
end
def test_various_newlines_multipart
mail = TestMailer.create_various_newlines_multipart(@recipient)
assert_equal "line #1\nline #2\nline #3\nline #4\n\n", mail.parts[0].body
assert_equal "<p>line #1</p>\n<p>line #2</p>\n<p>line #3</p>\n<p>line #4</p>\n\n", mail.parts[1].body
end
def test_headers_removed_on_smtp_delivery
ActionMailer::Base.delivery_method = :smtp
TestMailer.deliver_cc_bcc(@recipient)
assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
assert MockSMTP.deliveries[0][2].include?(@recipient)
assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
end
def test_recursive_multipart_processing
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
mail = TMail::Mail.parse(fixture)
assert_equal "This is the first part.\n\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
end
def test_decode_encoded_attachment_filename
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
mail = TMail::Mail.parse(fixture)
attachment = mail.attachments.last
assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
end
def test_wrong_mail_header
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
end
def test_decode_message_with_unknown_charset
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
mail = TMail::Mail.parse(fixture)
assert_nothing_raised { mail.body }
end
def test_decode_message_with_unquoted_atchar_in_header
fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
mail = TMail::Mail.parse(fixture)
assert_not_nil mail.from
end
def test_empty_header_values_omitted
result = TestMailer.create_unnamed_attachment(@recipient).encoded
assert_match %r{Content-Type: application/octet-stream[^;]}, result
assert_match %r{Content-Disposition: attachment[^;]}, result
end
def test_headers_with_nonalpha_chars
mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
assert !mail.from_addrs.empty?
assert !mail.cc_addrs.empty?
assert !mail.bcc_addrs.empty?
assert_match(/:/, mail.from_addrs.to_s)
assert_match(/:/, mail.cc_addrs.to_s)
assert_match(/:/, mail.bcc_addrs.to_s)
end
def test_deliver_with_mail_object
mail = TestMailer::create_headers_with_nonalpha_chars(@recipient)
assert_nothing_raised { TestMailer.deliver(mail) }
assert_equal 1, TestMailer.deliveries.length
end
end

View file

@ -0,0 +1,48 @@
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift(File.dirname(__FILE__) + "/../lib/action_mailer/vendor")
require 'test/unit'
require 'tmail'
require 'tempfile'
class QuotingTest < Test::Unit::TestCase
def test_quote_multibyte_chars
original = "\303\246 \303\270 and \303\245"
result = execute_in_sandbox(<<-CODE)
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$KCODE = 'u'
require 'jcode'
require 'action_mailer/quoting'
include ActionMailer::Quoting
quoted_printable(#{original.inspect}, "UTF-8")
CODE
unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
assert_equal unquoted, original
end
private
# This whole thing *could* be much simpler, but I don't think Tempfile,
# popen and others exist on all platforms (like Windows).
def execute_in_sandbox(code)
test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
File.open(test_name, "w+") do |file|
file.write(<<-CODE)
block = Proc.new do
#{code}
end
puts block.call
CODE
end
system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
File.read(res_name)
ensure
File.delete(test_name) rescue nil
File.delete(res_name) rescue nil
end
end

View file

@ -0,0 +1,17 @@
$:.unshift(File.dirname(__FILE__) + "/../lib/")
$:.unshift File.dirname(__FILE__) + "/fixtures/helpers"
require 'test/unit'
require 'action_mailer'
class TMailMailTest < Test::Unit::TestCase
def test_body
m = TMail::Mail.new
expected = 'something_with_underscores'
m.encoding = 'quoted-printable'
quoted_body = [expected].pack('*M')
m.body = quoted_body
assert_equal "something_with_underscores=\n", m.quoted_body
assert_equal expected, m.body
end
end

2120
tracks/vendor/rails/actionpack/CHANGELOG vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
Copyright (c) 2004 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

471
tracks/vendor/rails/actionpack/README vendored Normal file
View file

@ -0,0 +1,471 @@
= Action Pack -- On rails from request to response
Action Pack splits the response to a web request into a controller part
(performing the logic) and a view part (rendering a template). This two-step
approach is known as an action, which will normally create, read, update, or
delete (CRUD for short) some sort of model part (often backed by a database)
before choosing either to render a template or redirecting to another action.
Action Pack implements these actions as public methods on Action Controllers
and uses Action Views to implement the template rendering. Action Controllers
are then responsible for handling all the actions relating to a certain part
of an application. This grouping usually consists of actions for lists and for
CRUDs revolving around a single (or a few) model objects. So ContactController
would be responsible for listing contacts, creating, deleting, and updating
contacts. A WeblogController could be responsible for both posts and comments.
Action View templates are written using embedded Ruby in tags mingled in with
the HTML. To avoid cluttering the templates with code, a bunch of helper
classes provide common behavior for forms, dates, and strings. And it's easy
to add specific helpers to keep the separation as the application evolves.
Note: Some of the features, such as scaffolding and form building, are tied to
ActiveRecord[http://activerecord.rubyonrails.org] (an object-relational
mapping package), but that doesn't mean that Action Pack depends on Active
Record. Action Pack is an independent package that can be used with any sort
of backend (Instiki[http://www.instiki.org], which is based on an older version
of Action Pack, used Madeleine for example). Read more about the role Action
Pack can play when used together with Active Record on
http://www.rubyonrails.org.
A short rundown of the major features:
* Actions grouped in controller as methods instead of separate command objects
and can therefore share helper methods.
BlogController < ActionController::Base
def display
@customer = find_customer
end
def update
@customer = find_customer
@customer.attributes = params[:customer]
@customer.save ?
redirect_to(:action => "display") :
render(:action => "edit")
end
private
def find_customer() Customer.find(params[:id]) end
end
{Learn more}[link:classes/ActionController/Base.html]
* Embedded Ruby for templates (no new "easy" template language)
<% for post in @posts %>
Title: <%= post.title %>
<% end %>
All post titles: <%= @post.collect{ |p| p.title }.join ", " %>
<% unless @person.is_client? %>
Not for clients to see...
<% end %>
{Learn more}[link:classes/ActionView.html]
* Builder-based templates (great for XML content, like RSS)
xml.rss("version" => "2.0") do
xml.channel do
xml.title(@feed_title)
xml.link(@url)
xml.description "Basecamp: Recent items"
xml.language "en-us"
xml.ttl "40"
for item in @recent_items
xml.item do
xml.title(item_title(item))
xml.description(item_description(item))
xml.pubDate(item_pubDate(item))
xml.guid(@recent_items.url(item))
xml.link(@recent_items.url(item))
end
end
end
end
{Learn more}[link:classes/ActionView/Base.html]
* Filters for pre and post processing of the response (as methods, procs, and classes)
class WeblogController < ActionController::Base
before_filter :authenticate, :cache, :audit
after_filter { |c| c.response.body = GZip::compress(c.response.body) }
after_filter LocalizeFilter
def list
# Before this action is run, the user will be authenticated, the cache
# will be examined to see if a valid copy of the results already
# exists, and the action will be logged for auditing.
# After this action has run, the output will first be localized then
# compressed to minimize bandwidth usage
end
private
def authenticate
# Implement the filter with full access to both request and response
end
end
{Learn more}[link:classes/ActionController/Filters/ClassMethods.html]
* Helpers for forms, dates, action links, and text
<%= text_field "post", "title", "size" => 30 %>
<%= html_date_select(Date.today) %>
<%= link_to "New post", :controller => "post", :action => "new" %>
<%= truncate(post.title, 25) %>
{Learn more}[link:classes/ActionView/Helpers.html]
* Layout sharing for template reuse (think simple version of Struts
Tiles[http://jakarta.apache.org/struts/userGuide/dev_tiles.html])
class WeblogController < ActionController::Base
layout "weblog_layout"
def hello_world
end
end
Layout file (called weblog_layout):
<html><body><%= @content_for_layout %></body></html>
Template for hello_world action:
<h1>Hello world</h1>
Result of running hello_world action:
<html><body><h1>Hello world</h1></body></html>
{Learn more}[link:classes/ActionController/Layout/ClassMethods.html]
* Routing makes pretty urls incredibly easy
map.connect 'clients/:client_name/:project_name/:controller/:action'
Accessing /clients/37signals/basecamp/project/dash calls ProjectController#dash with
{ "client_name" => "37signals", "project_name" => "basecamp" } in @params["params"]
From that URL, you can rewrite the redirect in a number of ways:
redirect_to(:action => "edit") =>
/clients/37signals/basecamp/project/dash
redirect_to(:client_name => "nextangle", :project_name => "rails") =>
/clients/nextangle/rails/project/dash
{Learn more}[link:classes/ActionController/Base.html]
* Javascript and Ajax integration.
link_to_function "Greeting", "alert('Hello world!')"
link_to_remote "Delete this post", :update => "posts",
:url => { :action => "destroy", :id => post.id }
{Learn more}[link:classes/ActionView/Helpers/JavaScriptHelper.html]
* Pagination for navigating lists of results.
# controller
def list
@pages, @people =
paginate :people, :order => 'last_name, first_name'
end
# view
<%= link_to "Previous page", { :page => @pages.current.previous } if @pages.current.previous %>
<%= link_to "Next page", { :page => @pages.current.next } if @pages.current.next %>
{Learn more}[link:classes/ActionController/Pagination.html]
* Easy testing of both controller and template result through TestRequest/Response
class LoginControllerTest < Test::Unit::TestCase
def setup
@controller = LoginController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_failing_authenticate
process :authenticate, :user_name => "nop", :password => ""
assert flash.has_key?(:alert)
assert_redirected_to :action => "index"
end
end
{Learn more}[link:classes/ActionController/TestRequest.html]
* Automated benchmarking and integrated logging
Processing WeblogController#index (for 127.0.0.1 at Fri May 28 00:41:55)
Parameters: {"action"=>"index", "controller"=>"weblog"}
Rendering weblog/index (200 OK)
Completed in 0.029281 (34 reqs/sec)
If Active Record is used as the model, you'll have the database debugging
as well:
Processing WeblogController#create (for 127.0.0.1 at Sat Jun 19 14:04:23)
Params: {"controller"=>"weblog", "action"=>"create",
"post"=>{"title"=>"this is good"} }
SQL (0.000627) INSERT INTO posts (title) VALUES('this is good')
Redirected to http://test/weblog/display/5
Completed in 0.221764 (4 reqs/sec) | DB: 0.059920 (27%)
You specify a logger through a class method, such as:
ActionController::Base.logger = Logger.new("Application Log")
ActionController::Base.logger = Log4r::Logger.new("Application Log")
* Caching at three levels of granularity (page, action, fragment)
class WeblogController < ActionController::Base
caches_page :show
caches_action :account
def show
# the output of the method will be cached as
# ActionController::Base.page_cache_directory + "/weblog/show/n.html"
# and the web server will pick it up without even hitting Rails
end
def account
# the output of the method will be cached in the fragment store
# but Rails is hit to retrieve it, so filters are run
end
def update
List.update(params[:list][:id], params[:list])
expire_page :action => "show", :id => params[:list][:id]
expire_action :action => "account"
redirect_to :action => "show", :id => params[:list][:id]
end
end
{Learn more}[link:classes/ActionController/Caching.html]
* Component requests from one controller to another
class WeblogController < ActionController::Base
# Performs a method and then lets hello_world output its render
def delegate_action
do_other_stuff_before_hello_world
render_component :controller => "greeter", :action => "hello_world"
end
end
class GreeterController < ActionController::Base
def hello_world
render_text "Hello World!"
end
end
The same can be done in a view to do a partial rendering:
Let's see a greeting:
<%= render_component :controller => "greeter", :action => "hello_world" %>
{Learn more}[link:classes/ActionController/Components.html]
* Powerful debugging mechanism for local requests
All exceptions raised on actions performed on the request of a local user
will be presented with a tailored debugging screen that includes exception
message, stack trace, request parameters, session contents, and the
half-finished response.
{Learn more}[link:classes/ActionController/Rescue.html]
* Scaffolding for Action Record model objects
require 'account' # must be an Active Record class
class AccountController < ActionController::Base
scaffold :account
end
The AccountController now has the full CRUD range of actions and default
templates: list, show, destroy, new, create, edit, update
{Learn more}link:classes/ActionController/Scaffolding/ClassMethods.html
* Form building for Active Record model objects
The post object has a title (varchar), content (text), and
written_on (date)
<%= form "post" %>
...will generate something like (the selects will have more options, of
course):
<form action="create" method="POST">
<p>
<b>Title:</b><br/>
<input type="text" name="post[title]" value="<%= @post.title %>" />
</p>
<p>
<b>Content:</b><br/>
<textarea name="post[content]"><%= @post.title %></textarea>
</p>
<p>
<b>Written on:</b><br/>
<select name='post[written_on(3i)]'><option>18</option></select>
<select name='post[written_on(2i)]'><option value='7'>July</option></select>
<select name='post[written_on(1i)]'><option>2004</option></select>
</p>
<input type="submit" value="Create">
</form>
This form generates a @params["post"] array that can be used directly in a save action:
class WeblogController < ActionController::Base
def save
post = Post.create(params[:post])
redirect_to :action => "display", :id => post.id
end
end
{Learn more}[link:classes/ActionView/Helpers/ActiveRecordHelper.html]
* Runs on top of WEBrick, CGI, FCGI, and mod_ruby
== Simple example
This example will implement a simple weblog system using inline templates and
an Active Record model. So let's build that WeblogController with just a few
methods:
require 'action_controller'
require 'post'
class WeblogController < ActionController::Base
layout "weblog/layout"
def index
@posts = Post.find_all
end
def display
@post = Post.find(:params[:id])
end
def new
@post = Post.new
end
def create
@post = Post.create(params[:post])
redirect_to :action => "display", :id => @post.id
end
end
WeblogController::Base.template_root = File.dirname(__FILE__)
WeblogController.process_cgi if $0 == __FILE__
The last two lines are responsible for telling ActionController where the
template files are located and actually running the controller on a new
request from the web-server (like to be Apache).
And the templates look like this:
weblog/layout.rhtml:
<html><body>
<%= @content_for_layout %>
</body></html>
weblog/index.rhtml:
<% for post in @posts %>
<p><%= link_to(post.title, :action => "display", :id => post.id %></p>
<% end %>
weblog/display.rhtml:
<p>
<b><%= post.title %></b><br/>
<b><%= post.content %></b>
</p>
weblog/new.rhtml:
<%= form "post" %>
This simple setup will list all the posts in the system on the index page,
which is called by accessing /weblog/. It uses the form builder for the Active
Record model to make the new screen, which in turn hands everything over to
the create action (that's the default target for the form builder when given a
new model). After creating the post, it'll redirect to the display page using
an URL such as /weblog/display/5 (where 5 is the id of the post).
== Examples
Action Pack ships with three examples that all demonstrate an increasingly
detailed view of the possibilities. First is blog_controller that is just a
single file for the whole MVC (but still split into separate parts). Second is
the debate_controller that uses separate template files and multiple screens.
Third is the address_book_controller that uses the layout feature to separate
template casing from content.
Please note that you might need to change the "shebang" line to
#!/usr/local/env ruby, if your Ruby is not placed in /usr/local/bin/ruby
== Download
The latest version of Action Pack can be found at
* http://rubyforge.org/project/showfiles.php?group_id=249
Documentation can be found at
* http://ap.rubyonrails.com
== Installation
You can install Action Pack with the following command.
% [sudo] ruby install.rb
from its distribution directory.
== License
Action Pack is released under the MIT license.
== Support
The Action Pack homepage is http://www.rubyonrails.com. You can find
the Action Pack RubyForge page at http://rubyforge.org/projects/actionpack.
And as Jim from Rake says:
Feel free to submit commits or feature requests. If you send a patch,
remember to update the corresponding unit tests. If fact, I prefer
new feature to be submitted in the form of new unit tests.
For other information, feel free to ask on the ruby-talk mailing list (which
is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.

View file

@ -0,0 +1,25 @@
== Running with Rake
The easiest way to run the unit tests is through Rake. The default task runs
the entire test suite for all classes. For more information, checkout the
full array of rake tasks with "rake -T"
Rake can be found at http://rake.rubyforge.org
== Running by hand
If you only want to run a single test suite, or don't want to bother with Rake,
you can do so with something like:
ruby controller/base_tests.rb
== Dependency on ActiveRecord and database setup
Test cases in test/controller/active_record_assertions.rb depend on having
activerecord installed and configured in a particular way. See comment in the
test file itself for details. If ActiveRecord is not in
actionpack/../activerecord directory, these tests are skipped. If activerecord
is installed, but not configured as expected, the tests will fail.
Other tests are runnable from a fresh copy of actionpack without any configuration.

View file

@ -0,0 +1,24 @@
<IfModule mod_ruby.c>
RubyRequire apache/ruby-run
RubySafeLevel 0
<Files *.rbx>
SetHandler ruby-object
RubyHandler Apache::RubyRun.instance
</Files>
</IfModule>
RewriteEngine On
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.fcgi?action=$2&id=$3 [QSA]
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.fcgi?action=$2 [QSA]
RewriteRule ^fcgi/([-_a-zA-Z0-9]+)/$ /$1_controller.fcgi?action=index [QSA]
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.rbx?action=$2&id=$3 [QSA]
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.rbx?action=$2 [QSA]
RewriteRule ^modruby/([-_a-zA-Z0-9]+)/$ /$1_controller.rbx?action=index [QSA]
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)/([0-9]+)$ /$1_controller.cgi?action=$2&id=$3 [QSA]
RewriteRule ^([-_a-zA-Z0-9]+)/([-_a-zA-Z0-9]+)$ /$1_controller.cgi?action=$2 [QSA]
RewriteRule ^([-_a-zA-Z0-9]+)/$ /$1_controller.cgi?action=index [QSA]

View file

@ -0,0 +1,33 @@
<h1>Address Book</h1>
<% if @people.empty? %>
<p>No people in the address book yet</p>
<% else %>
<table>
<tr><th>Name</th><th>Email Address</th><th>Phone Number</th></tr>
<% for person in @people %>
<tr><td><%= person.name %></td><td><%= person.email_address %></td><td><%= person.phone_number %></td></tr>
<% end %>
</table>
<% end %>
<form action="create_person">
<p>
Name:<br />
<input type="text" name="person[name]">
</p>
<p>
Email address:<br />
<input type="text" name="person[email_address]">
</p>
<p>
Phone number:<br />
<input type="text" name="person[phone_number]">
</p>
<p>
<input type="submit" value="Create Person">
</p>
</form>

View file

@ -0,0 +1,8 @@
<html>
<head>
<title><%= @title || "Untitled" %></title>
</head>
<body>
<%= @content_for_layout %>
</body>
</html>

View file

@ -0,0 +1,9 @@
#!/usr/local/bin/ruby
require "address_book_controller"
begin
AddressBookController.process_cgi(CGI.new)
rescue => e
CGI.new.out { "#{e.class}: #{e.message}" }
end

View file

@ -0,0 +1,6 @@
#!/usr/local/bin/ruby
require "address_book_controller"
require "fcgi"
FCGI.each_cgi { |cgi| AddressBookController.process_cgi(cgi) }

View file

@ -0,0 +1,52 @@
$:.unshift(File.dirname(__FILE__) + "/../lib")
require "action_controller"
require "action_controller/test_process"
Person = Struct.new("Person", :id, :name, :email_address, :phone_number)
class AddressBookService
attr_reader :people
def initialize() @people = [] end
def create_person(data) people.unshift(Person.new(next_person_id, data["name"], data["email_address"], data["phone_number"])) end
def find_person(topic_id) people.select { |person| person.id == person.to_i }.first end
def next_person_id() people.first.id + 1 end
end
class AddressBookController < ActionController::Base
layout "address_book/layout"
before_filter :initialize_session_storage
# Could also have used a proc
# before_filter proc { |c| c.instance_variable_set("@address_book", c.session["address_book"] ||= AddressBookService.new) }
def index
@title = "Address Book"
@people = @address_book.people
end
def person
@person = @address_book.find_person(@params["id"])
end
def create_person
@address_book.create_person(@params["person"])
redirect_to :action => "index"
end
private
def initialize_session_storage
@address_book = @session["address_book"] ||= AddressBookService.new
end
end
ActionController::Base.template_root = File.dirname(__FILE__)
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
begin
AddressBookController.process_cgi(CGI.new) if $0 == __FILE__
rescue => e
CGI.new.out { "#{e.class}: #{e.message}" }
end

View file

@ -0,0 +1,4 @@
#!/usr/local/bin/ruby
require "address_book_controller"
AddressBookController.process_cgi(CGI.new)

View file

@ -0,0 +1,52 @@
$:.unshift(File.dirname(__FILE__) + "/../lib")
require "action_controller"
require 'action_controller/test_process'
Person = Struct.new("Person", :name, :address, :age)
class BenchmarkController < ActionController::Base
def message
render_text "hello world"
end
def list
@people = [ Person.new("David"), Person.new("Mary") ]
render_template "hello: <% for person in @people %>Name: <%= person.name %><% end %>"
end
def form_helper
@person = Person.new "david", "hyacintvej", 24
render_template(
"<% person = Person.new 'Mary', 'hyacintvej', 22 %> " +
"change the name <%= text_field 'person', 'name' %> and <%= text_field 'person', 'address' %> and <%= text_field 'person', 'age' %>"
)
end
end
#ActionController::Base.template_root = File.dirname(__FILE__)
require "benchmark"
RUNS = ARGV[0] ? ARGV[0].to_i : 50
require "profile" if ARGV[1]
runtime = Benchmark.measure {
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "list" })) }
}
puts "List: #{RUNS / runtime.real}"
runtime = Benchmark.measure {
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "message" })) }
}
puts "Message: #{RUNS / runtime.real}"
runtime = Benchmark.measure {
RUNS.times { BenchmarkController.process_test(ActionController::TestRequest.new({ "action" => "form_helper" })) }
}
puts "Form helper: #{RUNS / runtime.real}"

View file

@ -0,0 +1,89 @@
#!/usr/local/bin/ruby
begin
$:.unshift(File.dirname(__FILE__) + "/../lib")
$:.unshift(File.dirname(__FILE__) + "/../../../edge/activerecord/lib")
require 'fcgi'
require 'action_controller'
require 'action_controller/test_process'
require 'active_record'
class Post < ActiveRecord::Base; end
ActiveRecord::Base.establish_connection(:adapter => "mysql", :database => "basecamp")
SESSION_OPTIONS = { "database_manager" => CGI::Session::MemoryStore }
class TestController < ActionController::Base
def index
render_template <<-EOT
<% for post in Post.find_all(nil,nil,100) %>
<%= post.title %>
<% end %>
EOT
end
def show_one
render_template <<-EOT
<%= Post.find_first.title %>
EOT
end
def text
render_text "hello world"
end
def erb_text
render_template "hello <%= 'world' %>"
end
def erb_loop
render_template <<-EOT
<% for post in 1..100 %>
<%= post %>
<% end %>
EOT
end
def rescue_action(e) puts e.message + e.backtrace.join("\n") end
end
if ARGV.empty? && ENV["REQUEST_URI"]
FCGI.each_cgi do |cgi|
TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi)).out
end
else
if ARGV.empty?
cgi = CGI.new
end
require 'benchmark'
require 'profile' if ARGV[2] == "profile"
RUNS = ARGV[1] ? ARGV[1].to_i : 50
runtime = Benchmark::measure {
RUNS.times {
if ARGV.empty?
TestController.process(ActionController::CgiRequest.new(cgi, SESSION_OPTIONS), ActionController::CgiResponse.new(cgi))
else
response = TestController.process_test(
ActionController::TestRequest.new({"action" => ARGV[0]})
)
puts(response.body) if ARGV[2] == "show"
end
}
}
puts "Runs: #{RUNS}"
puts "Avg. runtime: #{runtime.real / RUNS}"
puts "Requests/second: #{RUNS / runtime.real}"
end
rescue Exception => e
# CGI.new.out { "<pre>" + e.message + e.backtrace.join("\n") + "</pre>" }
$stderr << e.message + e.backtrace.join("\n")
end

View file

@ -0,0 +1,53 @@
#!/usr/local/bin/ruby
$:.unshift(File.dirname(__FILE__) + "/../lib")
require "action_controller"
Post = Struct.new("Post", :title, :body)
class BlogController < ActionController::Base
before_filter :initialize_session_storage
def index
@posts = @session["posts"]
render_template <<-"EOF"
<html><body>
<%= @flash["alert"] %>
<h1>Posts</h1>
<% @posts.each do |post| %>
<p><b><%= post.title %></b><br /><%= post.body %></p>
<% end %>
<h1>Create post</h1>
<form action="create">
Title: <input type="text" name="post[title]"><br>
Body: <textarea name="post[body]"></textarea><br>
<input type="submit" value="save">
</form>
</body></html>
EOF
end
def create
@session["posts"].unshift(Post.new(@params["post"]["title"], @params["post"]["body"]))
flash["alert"] = "New post added!"
redirect_to :action => "index"
end
private
def initialize_session_storage
@session["posts"] = [] if @session["posts"].nil?
end
end
ActionController::Base.template_root = File.dirname(__FILE__)
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
begin
BlogController.process_cgi(CGI.new) if $0 == __FILE__
rescue => e
CGI.new.out { "#{e.class}: #{e.message}" }
end

View file

@ -0,0 +1,14 @@
<html>
<body>
<h1>Topics</h1>
<%= link_to "New topic", :action => "new_topic" %>
<ul>
<% for topic in @topics %>
<li><%= link_to "#{topic.title} (#{topic.replies.length} replies)", :action => "topic", :path_params => { "id" => topic.id } %></li>
<% end %>
</ul>
</body>
</html>

View file

@ -0,0 +1,22 @@
<html>
<body>
<h1>New topic</h1>
<form action="<%= url_for(:action => "create_topic") %>" method="post">
<p>
Title:<br>
<input type="text" name="topic[title]">
</p>
<p>
Body:<br>
<textarea name="topic[body]" style="width: 200px; height: 200px"></textarea>
</p>
<p>
<input type="submit" value="Create topic">
</p>
</form>
</body>
</html>

View file

@ -0,0 +1,32 @@
<html>
<body>
<h1><%= @topic.title %></h1>
<p><%= @topic.body %></p>
<%= link_to "Back to topics", :action => "index" %>
<% unless @topic.replies.empty? %>
<h2>Replies</h2>
<ol>
<% for reply in @topic.replies %>
<li><%= reply.body %></li>
<% end %>
</ol>
<% end %>
<h2>Reply to this topic</h2>
<form action="<%= url_for(:action => "create_reply") %>" method="post">
<input type="hidden" name="reply[topic_id]" value="<%= @topic.id %>">
<p>
<textarea name="reply[body]" style="width: 200px; height: 200px"></textarea>
</p>
<p>
<input type="submit" value="Create reply">
</p>
</form>
</body>
</html>

View file

@ -0,0 +1,57 @@
#!/usr/local/bin/ruby
$:.unshift(File.dirname(__FILE__) + "/../lib")
require "action_controller"
Topic = Struct.new("Topic", :id, :title, :body, :replies)
Reply = Struct.new("Reply", :body)
class DebateService
attr_reader :topics
def initialize() @topics = [] end
def create_topic(data) topics.unshift(Topic.new(next_topic_id, data["title"], data["body"], [])) end
def create_reply(data) find_topic(data["topic_id"]).replies << Reply.new(data["body"]) end
def find_topic(topic_id) topics.select { |topic| topic.id == topic_id.to_i }.first end
def next_topic_id() topics.first.id + 1 end
end
class DebateController < ActionController::Base
before_filter :initialize_session_storage
def index
@topics = @debate.topics
end
def topic
@topic = @debate.find_topic(@params["id"])
end
# def new_topic() end <-- This is not needed as the template doesn't require any assigns
def create_topic
@debate.create_topic(@params["topic"])
redirect_to :action => "index"
end
def create_reply
@debate.create_reply(@params["reply"])
redirect_to :action => "topic", :path_params => { "id" => @params["reply"]["topic_id"] }
end
private
def initialize_session_storage
@session["debate"] = DebateService.new if @session["debate"].nil?
@debate = @session["debate"]
end
end
ActionController::Base.template_root = File.dirname(__FILE__)
# ActionController::Base.logger = Logger.new("debug.log") # Remove first comment to turn on logging in current dir
begin
DebateController.process_cgi(CGI.new) if $0 == __FILE__
rescue => e
CGI.new.out { "#{e.class}: #{e.message}" }
end

View file

@ -0,0 +1,30 @@
require 'rbconfig'
require 'find'
require 'ftools'
include Config
# this was adapted from rdoc's install.rb by ways of Log4r
$sitedir = CONFIG["sitelibdir"]
unless $sitedir
version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
$libdir = File.join(CONFIG["libdir"], "ruby", version)
$sitedir = $:.find {|x| x =~ /site_ruby/ }
if !$sitedir
$sitedir = File.join($libdir, "site_ruby")
elsif $sitedir !~ Regexp.quote(version)
$sitedir = File.join($sitedir, version)
end
end
# the acual gruntwork
Dir.chdir("lib")
Find.find("action_controller", "action_controller.rb", "action_view", "action_view.rb") { |f|
if f[-3..-1] == ".rb"
File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
else
File::makedirs(File.join($sitedir, *f.split(/\//)))
end
}

View file

@ -0,0 +1,79 @@
#--
# Copyright (c) 2004 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
$:.unshift(File.dirname(__FILE__)) unless
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
unless defined?(ActiveSupport)
begin
$:.unshift(File.dirname(__FILE__) + "/../../activesupport/lib")
require 'active_support'
rescue LoadError
require 'rubygems'
require_gem 'activesupport'
end
end
require 'action_controller/base'
require 'action_controller/deprecated_redirects'
require 'action_controller/rescue'
require 'action_controller/benchmarking'
require 'action_controller/filters'
require 'action_controller/layout'
require 'action_controller/flash'
require 'action_controller/dependencies'
require 'action_controller/pagination'
require 'action_controller/scaffolding'
require 'action_controller/helpers'
require 'action_controller/cookies'
require 'action_controller/cgi_process'
require 'action_controller/caching'
require 'action_controller/components'
require 'action_controller/verification'
require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/macros/auto_complete'
require 'action_controller/macros/in_place_editing'
require 'action_view'
ActionController::Base.template_class = ActionView::Base
ActionController::Base.class_eval do
include ActionController::Filters
include ActionController::Layout
include ActionController::Flash
include ActionController::Benchmarking
include ActionController::Rescue
include ActionController::Dependencies
include ActionController::Pagination
include ActionController::Scaffolding
include ActionController::Helpers
include ActionController::Cookies
include ActionController::Caching
include ActionController::Components
include ActionController::Verification
include ActionController::Streaming
include ActionController::SessionManagement
include ActionController::Macros::AutoComplete
include ActionController::Macros::InPlaceEditing
end

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