mailcatcher-jruby 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|