tangofoxtrot-tmail_bouncer 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.rdoc +9 -0
- data/Rakefile +56 -0
- data/lib/bouncers/base.rb +69 -0
- data/lib/bouncers/no_bouncer.rb +12 -0
- data/lib/bouncers/standard_bouncer.rb +48 -0
- data/lib/tmail_bouncer.rb +5 -0
- data/test/fixtures/yahoo.eml +72 -0
- data/test/fixtures/yahoo_legit.eml +42 -0
- data/test/test_helper.rb +10 -0
- data/test/unit/tmail_bouncer_test.rb +70 -0
- metadata +65 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 tangofoxtrot
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "tmail_bouncer"
|
8
|
+
gem.summary = %Q{TODO}
|
9
|
+
gem.email = "richard.luther@gmail.com"
|
10
|
+
gem.homepage = "http://github.com/tangofoxtrot/tmail_bouncer"
|
11
|
+
gem.authors = ["tangofoxtrot"]
|
12
|
+
|
13
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
14
|
+
end
|
15
|
+
rescue LoadError
|
16
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
Rake::TestTask.new(:test) do |test|
|
21
|
+
test.libs << 'lib' << 'test'
|
22
|
+
test.pattern = 'test/**/*_test.rb'
|
23
|
+
test.verbose = true
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
require 'rcov/rcovtask'
|
28
|
+
Rcov::RcovTask.new do |test|
|
29
|
+
test.libs << 'test'
|
30
|
+
test.pattern = 'test/**/*_test.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
rescue LoadError
|
34
|
+
task :rcov do
|
35
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
task :default => :test
|
41
|
+
|
42
|
+
require 'rake/rdoctask'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
if File.exist?('VERSION.yml')
|
45
|
+
config = YAML.load(File.read('VERSION.yml'))
|
46
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
47
|
+
else
|
48
|
+
version = ""
|
49
|
+
end
|
50
|
+
|
51
|
+
rdoc.rdoc_dir = 'rdoc'
|
52
|
+
rdoc.title = "tmail_bouncer #{version}"
|
53
|
+
rdoc.rdoc_files.include('README*')
|
54
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module TmailBouncer
|
2
|
+
class Base
|
3
|
+
attr_accessor :status_info, :original_message_id, :original_sender, :original_recipient, :original_subject, :handling_server, :status_part, :original_message
|
4
|
+
|
5
|
+
|
6
|
+
def initialize(email)
|
7
|
+
@email = email
|
8
|
+
determine_status_part
|
9
|
+
determine_original_message_part
|
10
|
+
parse_status_part
|
11
|
+
parse_original_message_part
|
12
|
+
parse_original_recipient
|
13
|
+
parse_original_sender
|
14
|
+
parse_original_subject
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.available_bouncers
|
18
|
+
@@bouncers ||= [TmailBouncer::StandardBouncer]
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def self.determine_bouncer(email)
|
23
|
+
if klass = available_bouncers.find {|bouncer| bouncer.recognize? email}
|
24
|
+
klass.new(email)
|
25
|
+
else
|
26
|
+
TmailBouncer::NoBouncer.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.from_email(email)
|
31
|
+
bounce = determine_bouncer(email)
|
32
|
+
# lets start by trying to extract the message/delivery-status part
|
33
|
+
|
34
|
+
bounce
|
35
|
+
end
|
36
|
+
|
37
|
+
def status
|
38
|
+
case status_info['Status']
|
39
|
+
when /^5/
|
40
|
+
'Failure'
|
41
|
+
when /^4/
|
42
|
+
'Temporary Failure'
|
43
|
+
when /^2/
|
44
|
+
'Success'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
# def determine_status_part
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# def determine_original_message_part
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def parse_status_part
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# def parse_original_message_part
|
59
|
+
#
|
60
|
+
# end
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
def undeliverable_info
|
66
|
+
TmailBouncer::Base.from_email(self)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module TmailBouncer
|
2
|
+
class StandardBouncer < Base
|
3
|
+
|
4
|
+
def self.recognize?(email)
|
5
|
+
email.parts.detect do |part|
|
6
|
+
part.content_type == "message/delivery-status"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def determine_status_part
|
11
|
+
self.status_part = @email.parts.detect { |part| part.content_type == "message/delivery-status" }
|
12
|
+
end
|
13
|
+
|
14
|
+
def determine_original_message_part
|
15
|
+
self.original_message = @email.parts.detect do |part|
|
16
|
+
part.content_type == "message/rfc822"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def parse_status_part
|
22
|
+
statuses = self.status_part.body.gsub("\n ","").split(/\n/)
|
23
|
+
self.status_info = statuses.inject({}) do |hash,line|
|
24
|
+
key,value = line.split(/:/,2)
|
25
|
+
hash[key] = value.strip rescue nil
|
26
|
+
hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_original_message_part
|
31
|
+
self.original_message = TMail::Mail.parse(self.original_message.body)
|
32
|
+
self.original_message_id = self.original_message.message_id
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_original_recipient
|
36
|
+
self.original_recipient = self.status_info["Final-Recipient"].to_s.gsub("Final-Recipient: ","").gsub("rfc822;","").strip
|
37
|
+
end
|
38
|
+
|
39
|
+
def parse_original_sender
|
40
|
+
self.original_sender = self.original_message.from.to_a.first
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_original_subject
|
44
|
+
self.original_subject = self.original_message.subject
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
Return-Path: <>
|
2
|
+
X-Original-To: joe@example.com
|
3
|
+
Delivered-To: joe@example.com
|
4
|
+
Received: by mail.example.com (Postfix)
|
5
|
+
id CD6DE1780F5; Thu, 28 May 2009 14:03:11 -0700 (PDT)
|
6
|
+
Date: Thu, 28 May 2009 14:03:11 -0700 (PDT)
|
7
|
+
From: MAILER-DAEMON@example.com (Mail Delivery System)
|
8
|
+
Subject: Undelivered Mail Returned to Sender
|
9
|
+
To: joe@example.com
|
10
|
+
MIME-Version: 1.0
|
11
|
+
Content-Type: multipart/report; report-type=delivery-status;
|
12
|
+
boundary="A6CFB1785AB.1243544591/mail.example.com"
|
13
|
+
Message-Id: <20090528210311.CD6DE1780F5@mail.example.com>
|
14
|
+
|
15
|
+
This is a MIME-encapsulated message.
|
16
|
+
|
17
|
+
--A6CFB1785AB.1243544591/mail.example.com
|
18
|
+
Content-Description: Notification
|
19
|
+
Content-Type: text/plain
|
20
|
+
|
21
|
+
This is the Postfix program at host mail.example.com.
|
22
|
+
|
23
|
+
I'm sorry to have to inform you that your message could not
|
24
|
+
be delivered to one or more recipients. It's attached below.
|
25
|
+
|
26
|
+
For further assistance, please send mail to <postmaster>
|
27
|
+
|
28
|
+
If you do so, please include this problem report. You can
|
29
|
+
delete your own text from the attached returned message.
|
30
|
+
|
31
|
+
The Postfix program
|
32
|
+
|
33
|
+
<fred@somewhere.com>: host mail.somewhere.com[1.1.1.1] said: 554
|
34
|
+
delivery error: dd Sorry your message to fred@somewhere.com cannot be
|
35
|
+
delivered. This account has been disabled or discontinued [#102]. -
|
36
|
+
mail.somewhere.com (in reply to end of DATA command)
|
37
|
+
|
38
|
+
--A6CFB1785AB.1243544591/mail.example.com
|
39
|
+
Content-Description: Delivery report
|
40
|
+
Content-Type: message/delivery-status
|
41
|
+
|
42
|
+
Reporting-MTA: dns; mail.example.com
|
43
|
+
X-Postfix-Queue-ID: A6CFB1785AB
|
44
|
+
X-Postfix-Sender: rfc822; joe@example.com
|
45
|
+
Arrival-Date: Thu, 28 May 2009 13:26:56 -0700 (PDT)
|
46
|
+
|
47
|
+
Final-Recipient: rfc822; fred@somewhere.com
|
48
|
+
Action: failed
|
49
|
+
Status: 5.0.0
|
50
|
+
Diagnostic-Code: X-Postfix; host mail.somewhere.com[1.1.1.1] said: 554
|
51
|
+
delivery error: dd Sorry your message to fred@somewhere.com cannot be
|
52
|
+
delivered. This account has been disabled or discontinued [#102]. -
|
53
|
+
mail.somewhere.com (in reply to end of DATA command)
|
54
|
+
|
55
|
+
--A6CFB1785AB.1243544591/mail.example.com
|
56
|
+
Content-Description: Undelivered Message
|
57
|
+
Content-Type: message/rfc822
|
58
|
+
|
59
|
+
Received: from mail.example.com (example.com [2.2.2.2])
|
60
|
+
by mail.example.com (Postfix) with ESMTP id A6CFB1785AB
|
61
|
+
for <fred@somewhere.com>; Thu, 28 May 2009 13:26:56 -0700 (PDT)
|
62
|
+
Date: Thu, 28 May 2009 13:26:06 -0700
|
63
|
+
From: joe@example.com
|
64
|
+
To: fred@somewhere.com
|
65
|
+
Subject: I like turtles
|
66
|
+
Mime-Version: 1.0
|
67
|
+
Content-Type: text/html; charset=utf-8
|
68
|
+
Message-Id: <1234.1212@example.com>
|
69
|
+
|
70
|
+
Hi
|
71
|
+
|
72
|
+
--A6CFB1785AB.1243544591/mail.example.com--
|
@@ -0,0 +1,42 @@
|
|
1
|
+
Return-Path: <joe@example.com>
|
2
|
+
X-Original-To: fred@somewhere.com
|
3
|
+
Delivered-To: fred@somewhere.com
|
4
|
+
Received: from mail.mud.yahoo.com (mail.pnn.com [8.12.160.195])
|
5
|
+
by mail.somewhere.com (Postfix) with SMTP id 618E317804A
|
6
|
+
for <fred@somewhere.com>; Thu, 28 May 2009 23:13:11 -0700 (PDT)
|
7
|
+
Received: from mail.mud.yahoo.com ([68.142.200.144]
|
8
|
+
helo=mail.mud.yahoo.com) by ASSP.nospam; 28 May 2009
|
9
|
+
23:13:11 -0700
|
10
|
+
Received: (qmail 3737 invoked by uid 60001); 29 May 2009 06:06:40 -0000
|
11
|
+
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=yahoo.com; s=s1024; t=1243577200; bh=jq9T7juj3eU1mLVzXoF9iug+sOAeLdokzpK7l6424R0=; h=Message-ID:X-YMail-OSG:Received:X-Mailer:Date:From:Subject:To:MIME-Version:Content-Type; b=DFynmvLugBnhvJRnSgOKYtXDUQHVYBQbu4ZIaw8HrtBcfVXdunZUAXfalf2wL22pQpBI4jd70OVucAWhAi7g8IqpdDYCZ5EMApRQP8yOlelrCULXnvv8hqbPO8IL/nenNq1W+2gOY9zTgHNubWBvOd8pAxF3odwlPJqqx7XgQb4=
|
12
|
+
DomainKey-Signature:a=rsa-sha1; q=dns; c=nofws;
|
13
|
+
s=s1024; d=yahoo.com;
|
14
|
+
h=Message-ID:X-YMail-OSG:Received:X-Mailer:Date:From:Subject:To:MIME-Version:Content-Type;
|
15
|
+
b=0PXdCXj2w/uWB5Cvl1Aau1YBGeITW4Ep333GHD8NkGFt+87rTzn8Kz2Zxup//fqCjfUv5rU3mLt35ALNmzJ1pwX9odVsz2lpGQxZS0ExDizP+WBLlI51/1q11ufv6EJ7dlTo0E8Ia9x2pc9MdrOImKkEbUTzdzLTVzdGR34cXrs=;
|
16
|
+
Message-ID: <381499.2516.qm@web30801.mail.mud.yahoo.com>
|
17
|
+
X-YMail-OSG: hjm_1hIVM1k3pRgaY.b.u55HvX..XUf4Kr8T.rVbhY8FeitCmjki3l2l70Zr4q3KCc0MXf0.2Oo6Mg80Tj.gwa.yZ_sS79Ecp_JSaummtL54K.3FM4vtPI.bBy6O9pqXU5mesTKuK8tUMU5LQFkIK1XLhcCwpu96BXag9EMFXW0Ttvt_2KojLGAv14ul0_28Xy883D8s_AYimofrN..ywdsTSir3_3W1fKiyFIo.EXgo16Qqt8OcwEqE0qIBpkSaEmC4a6qxg8JuqPiIAKNvp_hkn8pmoOEa4yW.614Y
|
18
|
+
Received: from [75.62.232.23] by web30801.mail.mud.yahoo.com via HTTP; Thu, 28 May 2009 23:06:40 PDT
|
19
|
+
X-Mailer: YahooMailClassic/5.3.9 YahooMailWebService/0.7.289.10
|
20
|
+
Date: Thu, 28 May 2009 23:06:40 -0700 (PDT)
|
21
|
+
From: Richard L <joe@example.com>
|
22
|
+
Subject: Hi
|
23
|
+
To: Richard <fred@somewhere.com>
|
24
|
+
MIME-Version: 1.0
|
25
|
+
Content-Type: multipart/alternative; boundary="0-1875523946-1243577200=:2516"
|
26
|
+
X-Assp-Received-RBL: pass
|
27
|
+
|
28
|
+
|
29
|
+
--0-1875523946-1243577200=:2516
|
30
|
+
Content-Type: text/plain; charset=us-ascii
|
31
|
+
|
32
|
+
Hello
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
--0-1875523946-1243577200=:2516
|
37
|
+
Content-Type: text/html; charset=us-ascii
|
38
|
+
|
39
|
+
<table cellspacing="0" cellpadding="0" border="0" ><tr><td valign="top" style="font: inherit;">Hello</td></tr></table><br>
|
40
|
+
|
41
|
+
|
42
|
+
--0-1875523946-1243577200=:2516--
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TmailBouncerTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Yahoo Bounce" do
|
6
|
+
setup do
|
7
|
+
@email = read_email('yahoo.eml')
|
8
|
+
end
|
9
|
+
|
10
|
+
should "be a tmail object" do
|
11
|
+
assert @email.is_a?(TMail::Mail), "Whoops Im a #{@email.class}"
|
12
|
+
end
|
13
|
+
|
14
|
+
should "return StandardBouncer" do
|
15
|
+
assert @email.undeliverable_info.is_a?(TmailBouncer::StandardBouncer) , "Whoops Im a #{@email.class}"
|
16
|
+
end
|
17
|
+
|
18
|
+
should "detect bounced" do
|
19
|
+
assert_equal @email.undeliverable_info.status, "Failure"
|
20
|
+
end
|
21
|
+
|
22
|
+
should "detect original sender" do
|
23
|
+
assert_equal @email.undeliverable_info.original_sender, "joe@example.com"
|
24
|
+
end
|
25
|
+
|
26
|
+
should "detect original recipient" do
|
27
|
+
assert_equal @email.undeliverable_info.original_recipient, "fred@somewhere.com"
|
28
|
+
end
|
29
|
+
|
30
|
+
should "detect original subject" do
|
31
|
+
assert_equal @email.undeliverable_info.original_subject, "I like turtles"
|
32
|
+
end
|
33
|
+
|
34
|
+
should "detect original_message_id" do
|
35
|
+
assert_equal @email.undeliverable_info.original_message_id, "<1234.1212@example.com>"
|
36
|
+
end
|
37
|
+
|
38
|
+
should "return status of failure" do
|
39
|
+
assert_equal "Failure", @email.undeliverable_info.status
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "Yahoo Legit Email" do
|
44
|
+
|
45
|
+
setup do
|
46
|
+
@email = read_email('yahoo_legit.eml')
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
should "be a tmail object" do
|
51
|
+
assert @email.is_a?(TMail::Mail), "Whoops Im a #{@email.class}"
|
52
|
+
end
|
53
|
+
|
54
|
+
should "return NoBouce" do
|
55
|
+
assert @email.undeliverable_info.is_a?(TmailBouncer::NoBouncer) , "Whoops Im a #{@email.class}"
|
56
|
+
end
|
57
|
+
|
58
|
+
should "return status of Success" do
|
59
|
+
assert_equal "Success", @email.undeliverable_info.status
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def read_email(name)
|
68
|
+
TMail::Mail.parse(File.read('test/fixtures/' + name))
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tangofoxtrot-tmail_bouncer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- tangofoxtrot
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-05-29 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: richard.luther@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- LICENSE
|
27
|
+
- README.rdoc
|
28
|
+
- Rakefile
|
29
|
+
- lib/bouncers/base.rb
|
30
|
+
- lib/bouncers/no_bouncer.rb
|
31
|
+
- lib/bouncers/standard_bouncer.rb
|
32
|
+
- lib/tmail_bouncer.rb
|
33
|
+
- test/fixtures/yahoo.eml
|
34
|
+
- test/fixtures/yahoo_legit.eml
|
35
|
+
- test/test_helper.rb
|
36
|
+
- test/unit/tmail_bouncer_test.rb
|
37
|
+
has_rdoc: true
|
38
|
+
homepage: http://github.com/tangofoxtrot/tmail_bouncer
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --charset=UTF-8
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project:
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: TODO
|
63
|
+
test_files:
|
64
|
+
- test/test_helper.rb
|
65
|
+
- test/unit/tmail_bouncer_test.rb
|