mailcatcher-jruby 1.1.4 → 1.1.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +20 -20
- data/README.md +165 -165
- data/bin/catchmail +62 -62
- data/bin/mailcatcher +4 -4
- data/bin/mailweb +7 -7
- data/config.ru +7 -0
- data/lib/mail_catcher.rb +219 -219
- data/lib/mail_catcher/events.rb +5 -5
- data/lib/mail_catcher/growl.rb +16 -16
- data/lib/mail_catcher/mail.rb +149 -149
- data/lib/mail_catcher/smtp.rb +57 -57
- data/lib/mail_catcher/version.rb +3 -3
- data/lib/mail_catcher/web.rb +152 -152
- data/public/javascripts/application.js +441 -474
- data/public/javascripts/flexie.min.js +36 -36
- data/public/javascripts/jquery.js +6883 -6883
- data/public/javascripts/keymaster.min.js +3 -3
- data/public/javascripts/modernizr.js +3 -3
- data/public/javascripts/xslt-3.2.js +1 -1
- data/public/stylesheets/analysis.xsl +33 -33
- data/public/stylesheets/application.css +365 -365
- data/views/index.haml +64 -64
- metadata +93 -92
data/lib/mail_catcher/events.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
|
3
|
-
module MailCatcher::Events
|
4
|
-
MessageAdded = EventMachine::Channel.new
|
5
|
-
end
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
module MailCatcher::Events
|
4
|
+
MessageAdded = EventMachine::Channel.new
|
5
|
+
end
|
data/lib/mail_catcher/growl.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
module MailCatcher
|
2
|
-
module Growl extend self
|
3
|
-
def start
|
4
|
-
MailCatcher::Events::MessageAdded.subscribe MailCatcher::Growl.method :notify
|
5
|
-
end
|
6
|
-
|
7
|
-
def notify message
|
8
|
-
image_path = File.expand_path(File.join(__FILE__, '..', '..', '..', 'public', 'images', 'logo_large.png'))
|
9
|
-
system "growlnotify", "--image", image_path, "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
|
10
|
-
end
|
11
|
-
|
12
|
-
# TODO: Native support on MacRuby with click backs
|
13
|
-
#def click
|
14
|
-
#end
|
15
|
-
end
|
16
|
-
end
|
1
|
+
module MailCatcher
|
2
|
+
module Growl extend self
|
3
|
+
def start
|
4
|
+
MailCatcher::Events::MessageAdded.subscribe MailCatcher::Growl.method :notify
|
5
|
+
end
|
6
|
+
|
7
|
+
def notify message
|
8
|
+
image_path = File.expand_path(File.join(__FILE__, '..', '..', '..', 'public', 'images', 'logo_large.png'))
|
9
|
+
system "growlnotify", "--image", image_path, "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# TODO: Native support on MacRuby with click backs
|
13
|
+
#def click
|
14
|
+
#end
|
15
|
+
end
|
16
|
+
end
|
data/lib/mail_catcher/mail.rb
CHANGED
@@ -1,149 +1,149 @@
|
|
1
|
-
require 'active_support/json'
|
2
|
-
require 'active_record'
|
3
|
-
require 'mail'
|
4
|
-
require 'eventmachine'
|
5
|
-
require 'tmpdir'
|
6
|
-
|
7
|
-
db_path = File.join(Dir::tmpdir, 'mailcatcherdb')
|
8
|
-
|
9
|
-
#ActiveRecord::Base.configurations["db"] = { adapter: 'jdbcsqlite3', database: db_path, pool: 5 }
|
10
|
-
ActiveRecord::Base.establish_connection({ adapter: 'jdbcsqlite3', database: db_path, pool: 5 })
|
11
|
-
|
12
|
-
class Message < ActiveRecord::Base
|
13
|
-
#establish_connection :db
|
14
|
-
has_many :message_parts, dependent: :destroy
|
15
|
-
self.inheritance_column = nil
|
16
|
-
|
17
|
-
def recipients
|
18
|
-
self[:recipients] &&= [ActiveSupport::JSON.decode(self[:recipients])].flatten!
|
19
|
-
end
|
20
|
-
end
|
21
|
-
class MessagePart < ActiveRecord::Base
|
22
|
-
#establish_connection :db
|
23
|
-
belongs_to :message
|
24
|
-
self.inheritance_column = nil
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
Message.connection.create_table :messages do |t|
|
29
|
-
t.text :sender
|
30
|
-
t.text :recipients
|
31
|
-
t.text :subject
|
32
|
-
t.binary :source
|
33
|
-
t.text :size
|
34
|
-
t.text :type
|
35
|
-
|
36
|
-
t.timestamps
|
37
|
-
end unless Message.table_exists?
|
38
|
-
|
39
|
-
MessagePart.connection.create_table :message_parts do |t|
|
40
|
-
t.integer :message_id
|
41
|
-
t.text :cid
|
42
|
-
t.text :type
|
43
|
-
t.integer :is_attachment
|
44
|
-
t.text :filename
|
45
|
-
t.text :charset
|
46
|
-
t.binary :body
|
47
|
-
t.integer :size
|
48
|
-
end unless MessagePart.table_exists?
|
49
|
-
|
50
|
-
module MailCatcher::Mail extend self
|
51
|
-
|
52
|
-
def add_message(message)
|
53
|
-
|
54
|
-
mail = Mail.new(message[:source])
|
55
|
-
|
56
|
-
m = Message.create(
|
57
|
-
sender: message[:sender],
|
58
|
-
recipients: message[:recipients].to_json,
|
59
|
-
subject: mail.subject,
|
60
|
-
source: message[:source],
|
61
|
-
type: mail.mime_type || 'text/plain',
|
62
|
-
size: message[:source].length
|
63
|
-
)
|
64
|
-
|
65
|
-
#message_id = db.last_insert_row_id
|
66
|
-
message_id = m.id
|
67
|
-
parts = mail.all_parts
|
68
|
-
parts = [mail] if parts.empty?
|
69
|
-
parts.each do |part|
|
70
|
-
body = part.body.to_s
|
71
|
-
# Only parts have CIDs, not mail
|
72
|
-
cid = part.cid if part.respond_to? :cid
|
73
|
-
MessagePart.create(
|
74
|
-
message_id: message_id,
|
75
|
-
cid: cid,
|
76
|
-
type: part.mime_type || 'text/plain',
|
77
|
-
is_attachment: part.attachment? ? 1 : 0,
|
78
|
-
filename: part.filename,
|
79
|
-
charset: part.charset,
|
80
|
-
body: body,
|
81
|
-
size: body.length
|
82
|
-
)
|
83
|
-
end
|
84
|
-
|
85
|
-
EventMachine.next_tick do
|
86
|
-
message = MailCatcher::Mail.message message_id
|
87
|
-
MailCatcher::Events::MessageAdded.push message
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def messages
|
92
|
-
Message.all.map(&:attributes)
|
93
|
-
end
|
94
|
-
|
95
|
-
def message(id)
|
96
|
-
Message.find(id).attributes
|
97
|
-
end
|
98
|
-
|
99
|
-
def message_has_html?(id)
|
100
|
-
part = MessagePart.where(message_id: id, is_attachment: 0).where("type IN ('application/xhtml+xml', 'text/html')").first
|
101
|
-
part.present? || ['text/html', 'application/xhtml+xml'].include?(Message.find(id).type)
|
102
|
-
end
|
103
|
-
|
104
|
-
def message_has_plain?(id)
|
105
|
-
part = MessagePart.where(message_id: id, is_attachment: 0, type: 'text/plain').first
|
106
|
-
part.present? || Message.find(id).type == 'text/plain'
|
107
|
-
end
|
108
|
-
|
109
|
-
def message_parts(id)
|
110
|
-
MessagePart.where(:message_id => id).order('filename ASC').map(&:attributes)
|
111
|
-
end
|
112
|
-
|
113
|
-
def message_attachments(id)
|
114
|
-
MessagePart.where(message_id: id, is_attachment: 1).order('filename ASC').map(&:attributes)
|
115
|
-
end
|
116
|
-
|
117
|
-
def message_part(message_id, part_id)
|
118
|
-
MessagePart.where(message_id: message_id, id: part_id).first.try(:attributes)
|
119
|
-
end
|
120
|
-
|
121
|
-
def message_part_type(message_id, part_type)
|
122
|
-
MessagePart.where(message_id: message_id, type: part_type, is_attachment: 0).first.try(:attributes)
|
123
|
-
end
|
124
|
-
|
125
|
-
def message_part_html(message_id)
|
126
|
-
part = message_part_type(message_id, "text/html")
|
127
|
-
part ||= message_part_type(message_id, "application/xhtml+xml")
|
128
|
-
part ||= begin
|
129
|
-
message = message(message_id)
|
130
|
-
message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def message_part_plain(message_id)
|
135
|
-
message_part_type message_id, "text/plain"
|
136
|
-
end
|
137
|
-
|
138
|
-
def message_part_cid(message_id, cid)
|
139
|
-
MessagePart.where(message_id: message_id, cid: cid).first.try(:attributes)
|
140
|
-
end
|
141
|
-
|
142
|
-
def delete!
|
143
|
-
Message.destroy_all
|
144
|
-
end
|
145
|
-
|
146
|
-
def delete_message!(message_id)
|
147
|
-
Message.where(id: message_id).destroy_all
|
148
|
-
end
|
149
|
-
end
|
1
|
+
require 'active_support/json'
|
2
|
+
require 'active_record'
|
3
|
+
require 'mail'
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'tmpdir'
|
6
|
+
|
7
|
+
db_path = File.join(Dir::tmpdir, 'mailcatcherdb')
|
8
|
+
|
9
|
+
#ActiveRecord::Base.configurations["db"] = { adapter: 'jdbcsqlite3', database: db_path, pool: 5 }
|
10
|
+
ActiveRecord::Base.establish_connection({ adapter: 'jdbcsqlite3', database: db_path, pool: 5 })
|
11
|
+
|
12
|
+
class Message < ActiveRecord::Base
|
13
|
+
#establish_connection :db
|
14
|
+
has_many :message_parts, dependent: :destroy
|
15
|
+
self.inheritance_column = nil
|
16
|
+
|
17
|
+
def recipients
|
18
|
+
self[:recipients] &&= [ActiveSupport::JSON.decode(self[:recipients])].flatten!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
class MessagePart < ActiveRecord::Base
|
22
|
+
#establish_connection :db
|
23
|
+
belongs_to :message
|
24
|
+
self.inheritance_column = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
Message.connection.create_table :messages do |t|
|
29
|
+
t.text :sender
|
30
|
+
t.text :recipients
|
31
|
+
t.text :subject
|
32
|
+
t.binary :source
|
33
|
+
t.text :size
|
34
|
+
t.text :type
|
35
|
+
|
36
|
+
t.timestamps
|
37
|
+
end unless Message.table_exists?
|
38
|
+
|
39
|
+
MessagePart.connection.create_table :message_parts do |t|
|
40
|
+
t.integer :message_id
|
41
|
+
t.text :cid
|
42
|
+
t.text :type
|
43
|
+
t.integer :is_attachment
|
44
|
+
t.text :filename
|
45
|
+
t.text :charset
|
46
|
+
t.binary :body
|
47
|
+
t.integer :size
|
48
|
+
end unless MessagePart.table_exists?
|
49
|
+
|
50
|
+
module MailCatcher::Mail extend self
|
51
|
+
|
52
|
+
def add_message(message)
|
53
|
+
|
54
|
+
mail = Mail.new(message[:source])
|
55
|
+
|
56
|
+
m = Message.create(
|
57
|
+
sender: message[:sender],
|
58
|
+
recipients: message[:recipients].to_json,
|
59
|
+
subject: mail.subject,
|
60
|
+
source: message[:source],
|
61
|
+
type: mail.mime_type || 'text/plain',
|
62
|
+
size: message[:source].length
|
63
|
+
)
|
64
|
+
|
65
|
+
#message_id = db.last_insert_row_id
|
66
|
+
message_id = m.id
|
67
|
+
parts = mail.all_parts
|
68
|
+
parts = [mail] if parts.empty?
|
69
|
+
parts.each do |part|
|
70
|
+
body = part.body.to_s
|
71
|
+
# Only parts have CIDs, not mail
|
72
|
+
cid = part.cid if part.respond_to? :cid
|
73
|
+
MessagePart.create(
|
74
|
+
message_id: message_id,
|
75
|
+
cid: cid,
|
76
|
+
type: part.mime_type || 'text/plain',
|
77
|
+
is_attachment: part.attachment? ? 1 : 0,
|
78
|
+
filename: part.filename,
|
79
|
+
charset: part.charset,
|
80
|
+
body: body,
|
81
|
+
size: body.length
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
EventMachine.next_tick do
|
86
|
+
message = MailCatcher::Mail.message message_id
|
87
|
+
MailCatcher::Events::MessageAdded.push message
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def messages
|
92
|
+
Message.all.map(&:attributes)
|
93
|
+
end
|
94
|
+
|
95
|
+
def message(id)
|
96
|
+
Message.find(id).attributes
|
97
|
+
end
|
98
|
+
|
99
|
+
def message_has_html?(id)
|
100
|
+
part = MessagePart.where(message_id: id, is_attachment: 0).where("type IN ('application/xhtml+xml', 'text/html')").first
|
101
|
+
part.present? || ['text/html', 'application/xhtml+xml'].include?(Message.find(id).type)
|
102
|
+
end
|
103
|
+
|
104
|
+
def message_has_plain?(id)
|
105
|
+
part = MessagePart.where(message_id: id, is_attachment: 0, type: 'text/plain').first
|
106
|
+
part.present? || Message.find(id).type == 'text/plain'
|
107
|
+
end
|
108
|
+
|
109
|
+
def message_parts(id)
|
110
|
+
MessagePart.where(:message_id => id).order('filename ASC').map(&:attributes)
|
111
|
+
end
|
112
|
+
|
113
|
+
def message_attachments(id)
|
114
|
+
MessagePart.where(message_id: id, is_attachment: 1).order('filename ASC').map(&:attributes)
|
115
|
+
end
|
116
|
+
|
117
|
+
def message_part(message_id, part_id)
|
118
|
+
MessagePart.where(message_id: message_id, id: part_id).first.try(:attributes)
|
119
|
+
end
|
120
|
+
|
121
|
+
def message_part_type(message_id, part_type)
|
122
|
+
MessagePart.where(message_id: message_id, type: part_type, is_attachment: 0).first.try(:attributes)
|
123
|
+
end
|
124
|
+
|
125
|
+
def message_part_html(message_id)
|
126
|
+
part = message_part_type(message_id, "text/html")
|
127
|
+
part ||= message_part_type(message_id, "application/xhtml+xml")
|
128
|
+
part ||= begin
|
129
|
+
message = message(message_id)
|
130
|
+
message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def message_part_plain(message_id)
|
135
|
+
message_part_type message_id, "text/plain"
|
136
|
+
end
|
137
|
+
|
138
|
+
def message_part_cid(message_id, cid)
|
139
|
+
MessagePart.where(message_id: message_id, cid: cid).first.try(:attributes)
|
140
|
+
end
|
141
|
+
|
142
|
+
def delete!
|
143
|
+
Message.destroy_all
|
144
|
+
end
|
145
|
+
|
146
|
+
def delete_message!(message_id)
|
147
|
+
Message.where(id: message_id).destroy_all
|
148
|
+
end
|
149
|
+
end
|
data/lib/mail_catcher/smtp.rb
CHANGED
@@ -1,57 +1,57 @@
|
|
1
|
-
require 'eventmachine'
|
2
|
-
|
3
|
-
class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
|
4
|
-
# We override EM's mail from processing to allow multiple mail-from commands
|
5
|
-
# per [RFC 2821](http://tools.ietf.org/html/rfc2821#section-4.1.1.2)
|
6
|
-
def process_mail_from sender
|
7
|
-
if @state.include? :mail_from
|
8
|
-
@state -= [:mail_from, :rcpt, :data]
|
9
|
-
receive_reset
|
10
|
-
end
|
11
|
-
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
def current_message
|
16
|
-
@current_message ||= {}
|
17
|
-
end
|
18
|
-
|
19
|
-
def receive_reset
|
20
|
-
@current_message = nil
|
21
|
-
true
|
22
|
-
end
|
23
|
-
|
24
|
-
def receive_sender(sender)
|
25
|
-
current_message[:sender] = sender
|
26
|
-
true
|
27
|
-
end
|
28
|
-
|
29
|
-
def receive_recipient(recipient)
|
30
|
-
current_message[:recipients] ||= []
|
31
|
-
current_message[:recipients] << recipient
|
32
|
-
true
|
33
|
-
end
|
34
|
-
|
35
|
-
def receive_data_chunk(lines)
|
36
|
-
current_message[:source] ||= ""
|
37
|
-
current_message[:source] << lines.join("\n")
|
38
|
-
true
|
39
|
-
end
|
40
|
-
|
41
|
-
def receive_message
|
42
|
-
MailCatcher::Mail.add_message current_message
|
43
|
-
puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
|
44
|
-
true
|
45
|
-
rescue
|
46
|
-
puts "*** Error receiving message: #{current_message.inspect}"
|
47
|
-
puts " Exception: #{$!}"
|
48
|
-
puts " Backtrace:"
|
49
|
-
$!.backtrace.each do |line|
|
50
|
-
puts " #{line}"
|
51
|
-
end
|
52
|
-
puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
|
53
|
-
false
|
54
|
-
ensure
|
55
|
-
@current_message = nil
|
56
|
-
end
|
57
|
-
end
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
|
4
|
+
# We override EM's mail from processing to allow multiple mail-from commands
|
5
|
+
# per [RFC 2821](http://tools.ietf.org/html/rfc2821#section-4.1.1.2)
|
6
|
+
def process_mail_from sender
|
7
|
+
if @state.include? :mail_from
|
8
|
+
@state -= [:mail_from, :rcpt, :data]
|
9
|
+
receive_reset
|
10
|
+
end
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_message
|
16
|
+
@current_message ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def receive_reset
|
20
|
+
@current_message = nil
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def receive_sender(sender)
|
25
|
+
current_message[:sender] = sender
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def receive_recipient(recipient)
|
30
|
+
current_message[:recipients] ||= []
|
31
|
+
current_message[:recipients] << recipient
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def receive_data_chunk(lines)
|
36
|
+
current_message[:source] ||= ""
|
37
|
+
current_message[:source] << lines.join("\n")
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def receive_message
|
42
|
+
MailCatcher::Mail.add_message current_message
|
43
|
+
puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
|
44
|
+
true
|
45
|
+
rescue
|
46
|
+
puts "*** Error receiving message: #{current_message.inspect}"
|
47
|
+
puts " Exception: #{$!}"
|
48
|
+
puts " Backtrace:"
|
49
|
+
$!.backtrace.each do |line|
|
50
|
+
puts " #{line}"
|
51
|
+
end
|
52
|
+
puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
|
53
|
+
false
|
54
|
+
ensure
|
55
|
+
@current_message = nil
|
56
|
+
end
|
57
|
+
end
|