mailcatcher 0.5.1 → 0.5.2

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.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.5.2
@@ -55,6 +55,7 @@ module_function
55
55
  @@defaults.dup.tap do |options|
56
56
  OptionParser.new do |parser|
57
57
  parser.banner = "Usage: mailcatcher [options]"
58
+ parser.version = File.read(File.expand_path("../../VERSION", __FILE__))
58
59
 
59
60
  parser.on("--ip IP", "Set the ip address of both servers") do |ip|
60
61
  options[:smtp_ip] = options[:http_ip] = ip
@@ -1,7 +1,5 @@
1
1
  require 'eventmachine'
2
2
 
3
- module MailCatcher
4
- module Events
5
- MessageAdded = EventMachine::Channel.new
6
- end
3
+ module MailCatcher::Events
4
+ MessageAdded = EventMachine::Channel.new
7
5
  end
@@ -6,7 +6,8 @@ module MailCatcher
6
6
  end
7
7
 
8
8
  def notify message
9
- system "growlnotify", "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
9
+ image_path = File.expand_path(File.join(__FILE__, '..', '..', '..', 'public', 'images', 'logo_large.png'))
10
+ system "growlnotify", "--image", image_path, "--name", "MailCatcher", "--message", "Message received:\n#{message["subject"]}"
10
11
  end
11
12
 
12
13
  # TODO: Native support on MacRuby with click backs
@@ -3,152 +3,152 @@ require 'sqlite3'
3
3
  require 'eventmachine'
4
4
 
5
5
  module MailCatcher::Mail
6
- class << self
7
- def db
8
- @@__db ||= begin
9
- SQLite3::Database.new(':memory:', :type_translation => true).tap do |db|
10
- db.execute(<<-SQL)
11
- CREATE TABLE message (
12
- id INTEGER PRIMARY KEY ASC,
13
- sender TEXT,
14
- recipients TEXT,
15
- subject TEXT,
16
- source BLOB,
17
- size TEXT,
18
- type TEXT,
19
- created_at DATETIME DEFAULT CURRENT_DATETIME
20
- )
21
- SQL
22
- db.execute(<<-SQL)
23
- CREATE TABLE message_part (
24
- id INTEGER PRIMARY KEY ASC,
25
- message_id INTEGER NOT NULL,
26
- cid TEXT,
27
- type TEXT,
28
- is_attachment INTEGER,
29
- filename TEXT,
30
- charset TEXT,
31
- body BLOB,
32
- size INTEGER,
33
- created_at DATETIME DEFAULT CURRENT_DATETIME
34
- )
35
- SQL
36
- end
6
+ module_function
7
+
8
+ def db
9
+ @@__db ||= begin
10
+ SQLite3::Database.new(':memory:', :type_translation => true).tap do |db|
11
+ db.execute(<<-SQL)
12
+ CREATE TABLE message (
13
+ id INTEGER PRIMARY KEY ASC,
14
+ sender TEXT,
15
+ recipients TEXT,
16
+ subject TEXT,
17
+ source BLOB,
18
+ size TEXT,
19
+ type TEXT,
20
+ created_at DATETIME DEFAULT CURRENT_DATETIME
21
+ )
22
+ SQL
23
+ db.execute(<<-SQL)
24
+ CREATE TABLE message_part (
25
+ id INTEGER PRIMARY KEY ASC,
26
+ message_id INTEGER NOT NULL,
27
+ cid TEXT,
28
+ type TEXT,
29
+ is_attachment INTEGER,
30
+ filename TEXT,
31
+ charset TEXT,
32
+ body BLOB,
33
+ size INTEGER,
34
+ created_at DATETIME DEFAULT CURRENT_DATETIME
35
+ )
36
+ SQL
37
37
  end
38
38
  end
39
+ end
39
40
 
40
- def add_message(message)
41
- @@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, type, size, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))")
42
-
43
- mail = Mail.new(message[:source])
44
- result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
45
- message_id = db.last_insert_row_id
46
- parts = mail.all_parts
47
- parts = [mail] if parts.empty?
48
- parts.each do |part|
49
- body = part.body.to_s
50
- # Only parts have CIDs, not mail
51
- cid = part.cid if part.respond_to? :cid
52
- add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
53
- end
41
+ def add_message(message)
42
+ @@add_message_query ||= db.prepare("INSERT INTO message (sender, recipients, subject, source, type, size, created_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))")
54
43
 
55
- EventMachine.next_tick do
56
- message = MailCatcher::Mail.message message_id
57
- MailCatcher::Events::MessageAdded.push message
58
- end
44
+ mail = Mail.new(message[:source])
45
+ result = @@add_message_query.execute(message[:sender], message[:recipients].to_json, mail.subject, message[:source], mail.mime_type || 'text/plain', message[:source].length)
46
+ message_id = db.last_insert_row_id
47
+ parts = mail.all_parts
48
+ parts = [mail] if parts.empty?
49
+ parts.each do |part|
50
+ body = part.body.to_s
51
+ # Only parts have CIDs, not mail
52
+ cid = part.cid if part.respond_to? :cid
53
+ add_message_part(message_id, cid, part.mime_type || 'text/plain', part.attachment? ? 1 : 0, part.filename, part.charset, body, body.length)
59
54
  end
60
55
 
61
- def add_message_part(*args)
62
- @@add_message_part_query ||= db.prepare "INSERT INTO message_part (message_id, cid, type, is_attachment, filename, charset, body, size, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))"
63
- @@add_message_part_query.execute(*args)
56
+ EventMachine.next_tick do
57
+ message = MailCatcher::Mail.message message_id
58
+ MailCatcher::Events::MessageAdded.push message
64
59
  end
60
+ end
65
61
 
66
- def latest_created_at
67
- @@latest_created_at_query ||= db.prepare "SELECT created_at FROM message ORDER BY created_at DESC LIMIT 1"
68
- @@latest_created_at_query.execute.next
69
- end
62
+ def add_message_part(*args)
63
+ @@add_message_part_query ||= db.prepare "INSERT INTO message_part (message_id, cid, type, is_attachment, filename, charset, body, size, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))"
64
+ @@add_message_part_query.execute(*args)
65
+ end
70
66
 
71
- def messages
72
- @@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at ASC"
73
- @@messages_query.execute.map do |row|
74
- Hash[row.fields.zip(row)].tap do |message|
75
- message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
76
- end
77
- end
78
- end
67
+ def latest_created_at
68
+ @@latest_created_at_query ||= db.prepare "SELECT created_at FROM message ORDER BY created_at DESC LIMIT 1"
69
+ @@latest_created_at_query.execute.next
70
+ end
79
71
 
80
- def message(id)
81
- @@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
82
- row = @@message_query.execute(id).next
83
- row && Hash[row.fields.zip(row)].tap do |message|
72
+ def messages
73
+ @@messages_query ||= db.prepare "SELECT id, sender, recipients, subject, size, created_at FROM message ORDER BY created_at ASC"
74
+ @@messages_query.execute.map do |row|
75
+ Hash[row.fields.zip(row)].tap do |message|
84
76
  message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
85
77
  end
86
78
  end
79
+ end
87
80
 
88
- def message_has_html?(id)
89
- @@message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type IN ('application/xhtml+xml', 'text/html') LIMIT 1"
90
- (!!@@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
81
+ def message(id)
82
+ @@message_query ||= db.prepare "SELECT * FROM message WHERE id = ? LIMIT 1"
83
+ row = @@message_query.execute(id).next
84
+ row && Hash[row.fields.zip(row)].tap do |message|
85
+ message["recipients"] &&= ActiveSupport::JSON.decode message["recipients"]
91
86
  end
87
+ end
92
88
 
93
- def message_has_plain?(id)
94
- @@message_has_plain_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
95
- (!!@@message_has_plain_query.execute(id).next) || message(id)["type"] == "text/plain"
96
- end
89
+ def message_has_html?(id)
90
+ @@message_has_html_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type IN ('application/xhtml+xml', 'text/html') LIMIT 1"
91
+ (!!@@message_has_html_query.execute(id).next) || ['text/html', 'application/xhtml+xml'].include?(message(id)["type"])
92
+ end
97
93
 
98
- def message_parts(id)
99
- @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? ORDER BY filename ASC"
100
- @@message_parts_query.execute(id).map do |row|
101
- Hash[row.fields.zip(row)]
102
- end
103
- end
94
+ def message_has_plain?(id)
95
+ @@message_has_plain_query ||= db.prepare "SELECT 1 FROM message_part WHERE message_id = ? AND is_attachment = 0 AND type = 'text/plain' LIMIT 1"
96
+ (!!@@message_has_plain_query.execute(id).next) || message(id)["type"] == "text/plain"
97
+ end
104
98
 
105
- def message_attachments(id)
106
- @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? AND is_attachment = 1 ORDER BY filename ASC"
107
- @@message_parts_query.execute(id).map do |row|
108
- Hash[row.fields.zip(row)]
109
- end
99
+ def message_parts(id)
100
+ @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? ORDER BY filename ASC"
101
+ @@message_parts_query.execute(id).map do |row|
102
+ Hash[row.fields.zip(row)]
110
103
  end
104
+ end
111
105
 
112
- def message_part(message_id, part_id)
113
- @@message_part_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND id = ? LIMIT 1"
114
- row = @@message_part_query.execute(message_id, part_id).next
115
- row && Hash[row.fields.zip(row)]
106
+ def message_attachments(id)
107
+ @@message_parts_query ||= db.prepare "SELECT cid, type, filename, size FROM message_part WHERE message_id = ? AND is_attachment = 1 ORDER BY filename ASC"
108
+ @@message_parts_query.execute(id).map do |row|
109
+ Hash[row.fields.zip(row)]
116
110
  end
111
+ end
117
112
 
118
- def message_part_type(message_id, part_type)
119
- @@message_part_type_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND type = ? AND is_attachment = 0 LIMIT 1"
120
- row = @@message_part_type_query.execute(message_id, part_type).next
121
- row && Hash[row.fields.zip(row)]
122
- end
113
+ def message_part(message_id, part_id)
114
+ @@message_part_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND id = ? LIMIT 1"
115
+ row = @@message_part_query.execute(message_id, part_id).next
116
+ row && Hash[row.fields.zip(row)]
117
+ end
123
118
 
124
- def message_part_html(message_id)
125
- part = message_part_type(message_id, "text/html")
126
- part ||= message_part_type(message_id, "application/xhtml+xml")
127
- part ||= begin
128
- message = message(message_id)
129
- message if message.present? and ['text/html', 'application/xhtml+xml'].include? message["type"]
130
- end
131
- end
119
+ def message_part_type(message_id, part_type)
120
+ @@message_part_type_query ||= db.prepare "SELECT * FROM message_part WHERE message_id = ? AND type = ? AND is_attachment = 0 LIMIT 1"
121
+ row = @@message_part_type_query.execute(message_id, part_type).next
122
+ row && Hash[row.fields.zip(row)]
123
+ end
132
124
 
133
- def message_part_plain(message_id)
134
- message_part_type message_id, "text/plain"
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"]
135
131
  end
132
+ end
136
133
 
137
- def message_part_cid(message_id, cid)
138
- @@message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
139
- @@message_part_cid_query.execute(message_id).map do |row|
140
- part = Hash[row.fields.zip(row)]
141
- end.find do |part|
142
- part["cid"] == cid
143
- end
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
+ @@message_part_cid_query ||= db.prepare 'SELECT * FROM message_part WHERE message_id = ?'
140
+ @@message_part_cid_query.execute(message_id).map do |row|
141
+ part = Hash[row.fields.zip(row)]
142
+ end.find do |part|
143
+ part["cid"] == cid
144
144
  end
145
+ end
145
146
 
146
- def delete!
147
- @@delete_messages_query ||= db.prepare 'DELETE FROM message'
148
- @@delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
147
+ def delete!
148
+ @@delete_messages_query ||= db.prepare 'DELETE FROM message'
149
+ @@delete_message_parts_query ||= db.prepare 'DELETE FROM message_part'
149
150
 
150
- @@delete_messages_query.execute and
151
- @@delete_message_parts_query.execute
152
- end
151
+ @@delete_messages_query.execute and
152
+ @@delete_message_parts_query.execute
153
153
  end
154
154
  end
@@ -1,48 +1,46 @@
1
1
  require 'eventmachine'
2
2
 
3
- module MailCatcher
4
- class Smtp < EventMachine::Protocols::SmtpServer
5
- def current_message
6
- @current_message ||= {}
7
- end
3
+ class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
4
+ def current_message
5
+ @current_message ||= {}
6
+ end
8
7
 
9
- def receive_reset
10
- @current_message = nil
11
- true
12
- end
8
+ def receive_reset
9
+ @current_message = nil
10
+ true
11
+ end
13
12
 
14
- def receive_sender(sender)
15
- current_message[:sender] = sender
16
- true
17
- end
13
+ def receive_sender(sender)
14
+ current_message[:sender] = sender
15
+ true
16
+ end
18
17
 
19
- def receive_recipient(recipient)
20
- current_message[:recipients] ||= []
21
- current_message[:recipients] << recipient
22
- true
23
- end
18
+ def receive_recipient(recipient)
19
+ current_message[:recipients] ||= []
20
+ current_message[:recipients] << recipient
21
+ true
22
+ end
24
23
 
25
- def receive_data_chunk(lines)
26
- current_message[:source] ||= ""
27
- current_message[:source] += lines.join("\n")
28
- true
29
- end
24
+ def receive_data_chunk(lines)
25
+ current_message[:source] ||= ""
26
+ current_message[:source] += lines.join("\n")
27
+ true
28
+ end
30
29
 
31
- def receive_message
32
- MailCatcher::Mail.add_message current_message
33
- puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
34
- true
35
- rescue
36
- puts "*** Error receiving message: #{current_message.inspect}"
37
- puts " Exception: #{$!}"
38
- puts " Backtrace:"
39
- $!.backtrace.each do |line|
40
- puts " #{line}"
41
- end
42
- puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
43
- false
44
- ensure
45
- @current_message = nil
30
+ def receive_message
31
+ MailCatcher::Mail.add_message current_message
32
+ puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
33
+ true
34
+ rescue
35
+ puts "*** Error receiving message: #{current_message.inspect}"
36
+ puts " Exception: #{$!}"
37
+ puts " Backtrace:"
38
+ $!.backtrace.each do |line|
39
+ puts " #{line}"
46
40
  end
41
+ puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
42
+ false
43
+ ensure
44
+ @current_message = nil
47
45
  end
48
46
  end
@@ -1,5 +1,7 @@
1
1
  require 'sinatra'
2
2
  require 'pathname'
3
+ require 'net/http'
4
+ require 'uri'
3
5
 
4
6
  require 'skinny'
5
7
 
@@ -7,120 +9,132 @@ class Sinatra::Request
7
9
  include Skinny::Helpers
8
10
  end
9
11
 
10
- module MailCatcher
11
- class Web < Sinatra::Base
12
- set :root, Pathname.new(__FILE__).dirname.parent.parent
13
- set :haml, :format => :html5
12
+ class MailCatcher::Web < Sinatra::Base
13
+ set :root, Pathname.new(__FILE__).dirname.parent.parent
14
+ set :haml, :format => :html5
14
15
 
15
- get '/' do
16
- haml :index
17
- end
16
+ get '/' do
17
+ haml :index
18
+ end
18
19
 
19
- delete '/' do
20
- MailCatcher.quit!
21
- status 204
22
- end
20
+ delete '/' do
21
+ MailCatcher.quit!
22
+ status 204
23
+ end
23
24
 
24
- get '/messages' do
25
- if request.websocket?
26
- request.websocket!(
27
- :protocol => "MailCatcher 0.2 Message Push",
28
- :on_start => proc do |websocket|
29
- subscription = MailCatcher::Events::MessageAdded.subscribe { |message| websocket.send_message message.to_json }
30
- websocket.on_close do |websocket|
31
- MailCatcher::Events::MessageAdded.unsubscribe subscription
32
- end
33
- end)
34
- else
35
- MailCatcher::Mail.messages.to_json
36
- end
25
+ get '/messages' do
26
+ if request.websocket?
27
+ puts "Gots a websocket"
28
+ request.websocket!(
29
+ :protocol => "MailCatcher 0.2 Message Push",
30
+ :on_start => proc do |websocket|
31
+ subscription = MailCatcher::Events::MessageAdded.subscribe { |message| websocket.send_message message.to_json }
32
+ websocket.on_close do |websocket|
33
+ MailCatcher::Events::MessageAdded.unsubscribe subscription
34
+ end
35
+ end)
36
+ else
37
+ MailCatcher::Mail.messages.to_json
37
38
  end
39
+ end
38
40
 
39
- delete '/messages' do
40
- MailCatcher::Mail.delete!
41
- status 204
42
- end
41
+ delete '/messages' do
42
+ MailCatcher::Mail.delete!
43
+ status 204
44
+ end
43
45
 
44
- get '/messages/:id.json' do
45
- id = params[:id].to_i
46
- if message = MailCatcher::Mail.message(id)
47
- message.merge({
48
- "formats" => [
49
- "source",
50
- ("html" if MailCatcher::Mail.message_has_html? id),
51
- ("plain" if MailCatcher::Mail.message_has_plain? id),
52
- ].compact,
53
- "attachments" => MailCatcher::Mail.message_attachments(id).map do |attachment|
54
- attachment.merge({"href" => "/messages/#{escape(id)}/#{escape(attachment['cid'])}"})
55
- end,
56
- }).to_json
57
- else
58
- not_found
59
- end
46
+ get '/messages/:id.json' do
47
+ id = params[:id].to_i
48
+ if message = MailCatcher::Mail.message(id)
49
+ message.merge({
50
+ "formats" => [
51
+ "source",
52
+ ("html" if MailCatcher::Mail.message_has_html? id),
53
+ ("plain" if MailCatcher::Mail.message_has_plain? id),
54
+ ].compact,
55
+ "attachments" => MailCatcher::Mail.message_attachments(id).map do |attachment|
56
+ attachment.merge({"href" => "/messages/#{escape(id)}/#{escape(attachment['cid'])}"})
57
+ end,
58
+ }).to_json
59
+ else
60
+ not_found
60
61
  end
62
+ end
61
63
 
62
- get '/messages/:id.html' do
63
- id = params[:id].to_i
64
- if part = MailCatcher::Mail.message_part_html(id)
65
- content_type part["type"], :charset => (part["charset"] || "utf8")
64
+ get '/messages/:id.html' do
65
+ id = params[:id].to_i
66
+ if part = MailCatcher::Mail.message_part_html(id)
67
+ content_type part["type"], :charset => (part["charset"] || "utf8")
66
68
 
67
- body = part["body"]
69
+ body = part["body"]
68
70
 
69
- # Rewrite body to link to embedded attachments served by cid
70
- body.gsub! /cid:([^'"> ]+)/, "#{id}/\\1"
71
+ # Rewrite body to link to embedded attachments served by cid
72
+ body.gsub! /cid:([^'"> ]+)/, "#{id}/parts/\\1"
71
73
 
72
- # Rewrite body to open links in a new window
73
- body.gsub! /<a\s+/, '<a target="_blank" '
74
+ # Rewrite body to open links in a new window
75
+ body.gsub! /<a\s+/, '<a target="_blank" '
74
76
 
75
- body
76
- else
77
- not_found
78
- end
77
+ body
78
+ else
79
+ not_found
79
80
  end
81
+ end
80
82
 
81
- get "/messages/:id.plain" do
82
- id = params[:id].to_i
83
- if part = MailCatcher::Mail.message_part_plain(id)
84
- content_type part["type"], :charset => (part["charset"] || "utf8")
85
- part["body"]
86
- else
87
- not_found
88
- end
83
+ get "/messages/:id.plain" do
84
+ id = params[:id].to_i
85
+ if part = MailCatcher::Mail.message_part_plain(id)
86
+ content_type part["type"], :charset => (part["charset"] || "utf8")
87
+ part["body"]
88
+ else
89
+ not_found
89
90
  end
91
+ end
90
92
 
91
- get "/messages/:id.source" do
92
- id = params[:id].to_i
93
- if message = MailCatcher::Mail.message(id)
94
- content_type "text/plain"
95
- message["source"]
96
- else
97
- not_found
98
- end
93
+ get "/messages/:id.source" do
94
+ id = params[:id].to_i
95
+ if message = MailCatcher::Mail.message(id)
96
+ content_type "text/plain"
97
+ message["source"]
98
+ else
99
+ not_found
99
100
  end
101
+ end
100
102
 
101
- get "/messages/:id.eml" do
102
- id = params[:id].to_i
103
- if message = MailCatcher::Mail.message(id)
104
- content_type "message/rfc822"
105
- message["source"]
106
- else
107
- not_found
108
- end
103
+ get "/messages/:id.eml" do
104
+ id = params[:id].to_i
105
+ if message = MailCatcher::Mail.message(id)
106
+ content_type "message/rfc822"
107
+ message["source"]
108
+ else
109
+ not_found
109
110
  end
111
+ end
110
112
 
111
- get "/messages/:id/:cid" do
112
- id = params[:id].to_i
113
- if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
114
- content_type part["type"], :charset => (part["charset"] || "utf8")
115
- attachment part["filename"] if part["is_attachment"] == 1
116
- body part["body"].to_s
117
- else
118
- not_found
119
- end
113
+ get "/messages/:id/parts/:cid" do
114
+ id = params[:id].to_i
115
+ if part = MailCatcher::Mail.message_part_cid(id, params[:cid])
116
+ content_type part["type"], :charset => (part["charset"] || "utf8")
117
+ attachment part["filename"] if part["is_attachment"] == 1
118
+ body part["body"].to_s
119
+ else
120
+ not_found
120
121
  end
122
+ end
121
123
 
122
- not_found do
123
- "<html><body><h1>No Dice</h1><p>The message you were looking for does not exist, or doesn't have content of this type.</p></body></html>"
124
+ get "/messages/:id/analysis.?:format?" do
125
+ id = params[:id].to_i
126
+ if part = MailCatcher::Mail.message_part_html(id)
127
+ # TODO: Server-side cache? Make the browser cache based on message create time? Hmm.
128
+ uri = URI.parse("http://api.getfractal.com/api/v2/validate#{"/format/#{params[:format]}" if params[:format].present?}")
129
+ response = Net::HTTP.post_form(uri, api_key: "5c463877265251386f516f7428", html: part["body"])
130
+ content_type ".#{params[:format]}" if params[:format].present?
131
+ body response.body
132
+ else
133
+ not_found
124
134
  end
125
135
  end
136
+
137
+ not_found do
138
+ "<html><body><h1>No Dice</h1><p>The message you were looking for does not exist, or doesn't have content of this type.</p></body></html>"
139
+ end
126
140
  end
@@ -34,6 +34,7 @@
34
34
  }
35
35
  });
36
36
  $('nav.app .clear a').live('click', __bind(function(e) {
37
+ e.preventDefault();
37
38
  if (confirm("You will lose all your received messages.\n\nAre you sure you want to clear all messages?")) {
38
39
  return $.ajax({
39
40
  url: '/messages',
@@ -50,6 +51,7 @@
50
51
  }
51
52
  }, this));
52
53
  $('nav.app .quit a').live('click', __bind(function(e) {
54
+ e.preventDefault();
53
55
  if (confirm("You will lose all your received messages.\n\nAre you sure you want to quit?")) {
54
56
  return $.ajax({
55
57
  type: 'DELETE',
@@ -148,15 +150,22 @@
148
150
  }
149
151
  };
150
152
  MailCatcher.prototype.loadMessageAnalysis = function(id) {
151
- var $iframe;
153
+ var $form, $iframe;
152
154
  id || (id = this.selectedMessage());
153
155
  $("#message .views .analysis.tab:not(.selected)").addClass('selected');
154
156
  $("#message .views :not(.analysis).tab.selected").removeClass('selected');
155
157
  if (id != null) {
156
- $iframe = $('#message iframe').contents().children().html("<html class=\"mailcatcher\"><head>" + ($('link[rel="stylesheet"]')[0].outerHTML) + "</head><body><iframe></iframe></body></html>").find("head").append($('link[rel="stylesheet"]').clone()).end().find('iframe').contents().children().html("<html>\n<head>\n<title>Analysis</title>\n" + ($('link[rel="stylesheet"]')[0].outerHTML) + "\n</head>\n<body class=\"iframe\">\n<h1>Analyse your email with Fractal</h1>\n<p><a href=\"http://getfractal.com/\" target=\"_blank\">Fractal</a> is a really neat service that applies common email design and development knowledge from <a href=\"http://www.email-standards.org/\" target=\"_blank\">Email Standards Project</a> to your HTML email and tells you what you've done wrong or what you should do instead.</p>\n<p>Please note that this <strong>sends your email to the Fractal service</strong> for analysis. Read their <a href=\"http://getfractal.com/terms\" target=\"_blank\">terms of service</a> if you're paranoid.</p>\n<form action=\"http://getfractal.com/validate\" method=\"POST\">\n<input type=\"hidden\" name=\"html\" />\n<input type=\"submit\" value=\"Analyse\" disabled=\"disabled\" /><span class=\"loading\" style=\"color: #999\">Loading your email...</span>\n</form>\n</body>\n</html>");
157
- return $.get("/messages/" + id + ".html", function(html) {
158
- return $iframe.find('input[name="html"]').attr('value', html).end().find('.loading').hide().end().find('input[type="submit"]').attr('disabled', null).end().find('form').submit(function() {
159
- return $(this).find('input[type="submit"]').attr('disabled', 'disabled').end().find('.loading').text('Analysing...').show();
158
+ $iframe = $('#message iframe').contents().children().html("<html>\n<head>\n<title>Analysis</title>\n" + ($('link[rel="stylesheet"]')[0].outerHTML) + "\n</head>\n<body class=\"iframe\">\n<h1>Analyse your email with Fractal</h1>\n<p><a href=\"http://getfractal.com/\" target=\"_blank\">Fractal</a> is a really neat service that applies common email design and development knowledge from <a href=\"http://www.email-standards.org/\" target=\"_blank\">Email Standards Project</a> to your HTML email and tells you what you've done wrong or what you should do instead.</p>\n<p>Please note that this <strong>sends your email to the Fractal service</strong> for analysis. Read their <a href=\"http://getfractal.com/terms\" target=\"_blank\">terms of service</a> if you're paranoid.</p>\n<p>(This output is still just raw XML. Someone keen to transform this into something prettier would be greatly appreciated!)</p>\n<form>\n<input type=\"submit\" value=\"Analyse\" /><span class=\"loading\" style=\"color: #999; display: none\">Analysing&hellip;</span>\n</form>\n</body>\n</html>");
159
+ return $form = $iframe.find('form').submit(function(e) {
160
+ e.preventDefault();
161
+ $(this).find('input[type="submit"]').attr('disabled', 'disabled').end().find('.loading').show();
162
+ return $.ajax({
163
+ url: "/messages/" + id + "/analysis.xml",
164
+ dataType: "text",
165
+ success: function(data) {
166
+ $form.replaceWith('<h2>Results</h2><pre id="result"></pre>');
167
+ return $iframe.find("#result").text(data);
168
+ }
160
169
  });
161
170
  });
162
171
  }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mailcatcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-08 00:00:00.000000000 +08:00
13
- default_executable:
12
+ date: 2011-10-09 00:00:00.000000000Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: activesupport
17
- requirement: &70179531391440 !ruby/object:Gem::Requirement
16
+ requirement: &70205943804660 !ruby/object:Gem::Requirement
18
17
  none: false
19
18
  requirements:
20
19
  - - ~>
@@ -22,10 +21,10 @@ dependencies:
22
21
  version: '3.0'
23
22
  type: :runtime
24
23
  prerelease: false
25
- version_requirements: *70179531391440
24
+ version_requirements: *70205943804660
26
25
  - !ruby/object:Gem::Dependency
27
26
  name: eventmachine
28
- requirement: &70179531390940 !ruby/object:Gem::Requirement
27
+ requirement: &70205943804020 !ruby/object:Gem::Requirement
29
28
  none: false
30
29
  requirements:
31
30
  - - ~>
@@ -33,32 +32,32 @@ dependencies:
33
32
  version: '0.12'
34
33
  type: :runtime
35
34
  prerelease: false
36
- version_requirements: *70179531390940
35
+ version_requirements: *70205943804020
37
36
  - !ruby/object:Gem::Dependency
38
- name: mail
39
- requirement: &70179531390440 !ruby/object:Gem::Requirement
37
+ name: haml
38
+ requirement: &70205943803120 !ruby/object:Gem::Requirement
40
39
  none: false
41
40
  requirements:
42
41
  - - ~>
43
42
  - !ruby/object:Gem::Version
44
- version: '2.3'
43
+ version: '3.1'
45
44
  type: :runtime
46
45
  prerelease: false
47
- version_requirements: *70179531390440
46
+ version_requirements: *70205943803120
48
47
  - !ruby/object:Gem::Dependency
49
- name: sqlite3
50
- requirement: &70179531389940 !ruby/object:Gem::Requirement
48
+ name: mail
49
+ requirement: &70205943802440 !ruby/object:Gem::Requirement
51
50
  none: false
52
51
  requirements:
53
52
  - - ~>
54
53
  - !ruby/object:Gem::Version
55
- version: '1.3'
54
+ version: '2.3'
56
55
  type: :runtime
57
56
  prerelease: false
58
- version_requirements: *70179531389940
57
+ version_requirements: *70205943802440
59
58
  - !ruby/object:Gem::Dependency
60
- name: thin
61
- requirement: &70179531389460 !ruby/object:Gem::Requirement
59
+ name: sinatra
60
+ requirement: &70205943801860 !ruby/object:Gem::Requirement
62
61
  none: false
63
62
  requirements:
64
63
  - - ~>
@@ -66,54 +65,54 @@ dependencies:
66
65
  version: '1.2'
67
66
  type: :runtime
68
67
  prerelease: false
69
- version_requirements: *70179531389460
68
+ version_requirements: *70205943801860
70
69
  - !ruby/object:Gem::Dependency
71
70
  name: skinny
72
- requirement: &70179531384340 !ruby/object:Gem::Requirement
71
+ requirement: &70205943790820 !ruby/object:Gem::Requirement
73
72
  none: false
74
73
  requirements:
75
74
  - - ~>
76
75
  - !ruby/object:Gem::Version
77
- version: '0.1'
76
+ version: '0.2'
78
77
  type: :runtime
79
78
  prerelease: false
80
- version_requirements: *70179531384340
79
+ version_requirements: *70205943790820
81
80
  - !ruby/object:Gem::Dependency
82
- name: sinatra
83
- requirement: &70179531383760 !ruby/object:Gem::Requirement
81
+ name: sqlite3
82
+ requirement: &70205943790260 !ruby/object:Gem::Requirement
84
83
  none: false
85
84
  requirements:
86
85
  - - ~>
87
86
  - !ruby/object:Gem::Version
88
- version: '1.2'
87
+ version: '1.3'
89
88
  type: :runtime
90
89
  prerelease: false
91
- version_requirements: *70179531383760
90
+ version_requirements: *70205943790260
92
91
  - !ruby/object:Gem::Dependency
93
- name: haml
94
- requirement: &70179531383120 !ruby/object:Gem::Requirement
92
+ name: thin
93
+ requirement: &70205943789560 !ruby/object:Gem::Requirement
95
94
  none: false
96
95
  requirements:
97
96
  - - ~>
98
97
  - !ruby/object:Gem::Version
99
- version: '3.1'
98
+ version: '1.2'
100
99
  type: :runtime
101
100
  prerelease: false
102
- version_requirements: *70179531383120
101
+ version_requirements: *70205943789560
103
102
  - !ruby/object:Gem::Dependency
104
- name: sass
105
- requirement: &70179531382540 !ruby/object:Gem::Requirement
103
+ name: coffee-script
104
+ requirement: &70205943788860 !ruby/object:Gem::Requirement
106
105
  none: false
107
106
  requirements:
108
107
  - - ~>
109
108
  - !ruby/object:Gem::Version
110
- version: '3.1'
109
+ version: '2.2'
111
110
  type: :development
112
111
  prerelease: false
113
- version_requirements: *70179531382540
112
+ version_requirements: *70205943788860
114
113
  - !ruby/object:Gem::Dependency
115
114
  name: compass
116
- requirement: &70179531381980 !ruby/object:Gem::Requirement
115
+ requirement: &70205943788140 !ruby/object:Gem::Requirement
117
116
  none: false
118
117
  requirements:
119
118
  - - ~>
@@ -121,18 +120,40 @@ dependencies:
121
120
  version: 0.11.1
122
121
  type: :development
123
122
  prerelease: false
124
- version_requirements: *70179531381980
123
+ version_requirements: *70205943788140
125
124
  - !ruby/object:Gem::Dependency
126
- name: coffee-script
127
- requirement: &70179531381400 !ruby/object:Gem::Requirement
125
+ name: rake
126
+ requirement: &70205943787700 !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: *70205943787700
135
+ - !ruby/object:Gem::Dependency
136
+ name: rdoc
137
+ requirement: &70205943787160 !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ type: :development
144
+ prerelease: false
145
+ version_requirements: *70205943787160
146
+ - !ruby/object:Gem::Dependency
147
+ name: sass
148
+ requirement: &70205943786420 !ruby/object:Gem::Requirement
128
149
  none: false
129
150
  requirements:
130
151
  - - ~>
131
152
  - !ruby/object:Gem::Version
132
- version: '2.2'
153
+ version: '3.1'
133
154
  type: :development
134
155
  prerelease: false
135
- version_requirements: *70179531381400
156
+ version_requirements: *70205943786420
136
157
  description: ! " MailCatcher runs a super simple SMTP server which catches any\n
137
158
  \ message sent to it to display in a web interface. Run\n mailcatcher, set
138
159
  your favourite app to deliver to\n smtp://127.0.0.1:1025 instead of your default
@@ -148,6 +169,7 @@ extra_rdoc_files:
148
169
  files:
149
170
  - README.md
150
171
  - LICENSE
172
+ - VERSION
151
173
  - bin/catchmail
152
174
  - bin/mailcatcher
153
175
  - lib/mail_catcher/events.rb
@@ -157,14 +179,14 @@ files:
157
179
  - lib/mail_catcher/web.rb
158
180
  - lib/mail_catcher.rb
159
181
  - public/images/logo.png
182
+ - public/images/logo_large.png
160
183
  - public/javascripts/application.js
161
184
  - public/javascripts/date.js
162
185
  - public/javascripts/jquery.js
163
186
  - public/javascripts/modernizr.js
164
187
  - public/stylesheets/application.css
165
188
  - views/index.haml
166
- has_rdoc: true
167
- homepage: http://github.com/sj26/mailcatcher
189
+ homepage: http://mailcatcher.me
168
190
  licenses: []
169
191
  post_install_message:
170
192
  rdoc_options: []
@@ -184,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
206
  version: '0'
185
207
  requirements: []
186
208
  rubyforge_project:
187
- rubygems_version: 1.6.2
209
+ rubygems_version: 1.8.7
188
210
  signing_key:
189
211
  specification_version: 3
190
212
  summary: Runs an SMTP server, catches and displays email in a web interface.