mailcatcher 0.7.0 → 0.8.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55ed5f9b9d7ae94f7fc71297726c6ba3d800eb6ff95f3cb15ae33dfbb278a9e
4
- data.tar.gz: cfb1fb234b49b9b7e0bd98a6513406494d36b179f80ac3d92b09cf2f6dc29420
3
+ metadata.gz: ccfb98c689953785fe9c132ac154e90f2fcf5ead0c433de7611b64c1f7f89b98
4
+ data.tar.gz: 0c87d50eece53958f858966c9f607bef6bb885dbf0a4df9938bf4b5f5616900d
5
5
  SHA512:
6
- metadata.gz: 2c8fd76e50fe31dfb042fb8abe59b50485df2e52f06813f2651d2acd41aeca3a4ede8ffc375a02d4cad1ba50af89028f022dc38ed78a6cdaddd2bd0ad2f9cee7
7
- data.tar.gz: 218dfdc17255c5a86a4674742831b134ca9fdb5147e19db3ae1b6dac6a251dcfb8a8abbe9b8475bbab20b95bef248cfe40b06dc233218a1169e8873a83451338
6
+ metadata.gz: a1b0fae5875ae31b39700d0df1fd08c04b87ea2cd20cd7c45799c456af7cc2e4c9f4a2fc5e366b1f9ff24e0e35355752b7349fc8c991cebd8f1a664b159f2ddc
7
+ data.tar.gz: 4f051455ddc9718c69450318b8420bd74f7d7acea1d20fb42be18751b3c537f73b38550f807a6a0ead01544b9d84b288a7a7a77e2856ad0e782337dfa21706fc
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/README.md CHANGED
@@ -26,7 +26,41 @@ MailCatcher runs a super simple SMTP server which catches any message sent to it
26
26
  3. Go to http://127.0.0.1:1080/
27
27
  4. Send mail through smtp://127.0.0.1:1025
28
28
 
29
- Use `mailcatcher --help` to see the command line options. The brave can get the source from [the GitHub repository][mailcatcher-github].
29
+ ### Command Line Options
30
+
31
+ Use `mailcatcher --help` to see the command line options.
32
+
33
+ ```
34
+ Usage: mailcatcher [options]
35
+ --ip IP Set the ip address of both servers
36
+ --smtp-ip IP Set the ip address of the smtp server
37
+ --smtp-port PORT Set the port of the smtp server
38
+ --http-ip IP Set the ip address of the http server
39
+ --http-port PORT Set the port address of the http server
40
+ --http-path PATH Add a prefix to all HTTP paths
41
+ --no-quit Don't allow quitting the process
42
+ -f, --foreground Run in the foreground
43
+ -b, --browse Open web browser
44
+ -v, --verbose Be more verbose
45
+ -h, --help Display this help information
46
+ ```
47
+
48
+ ### Ruby
49
+
50
+ If you have trouble with the setup commands, make sure you have [Ruby installed](https://www.ruby-lang.org/en/documentation/installation/):
51
+
52
+ ```
53
+ ruby -v
54
+ gem environment
55
+ ```
56
+
57
+ You might need to install build tools for some of the gem dependencies. On Debian or Ubuntu, `apt install build-essential`. On macOS, `xcode-select --install`.
58
+
59
+ If you encounter issues installing [thin](https://rubygems.org/gems/thin), try:
60
+
61
+ ```
62
+ gem install thin -v 1.5.1 -- --with-cflags="-Wno-error=implicit-function-declaration"
63
+ ```
30
64
 
31
65
  ### Bundler
32
66
 
@@ -51,11 +85,11 @@ To set up your rails app, I recommend adding this to your `environments/developm
51
85
 
52
86
  ### PHP
53
87
 
54
- For projects using PHP, or PHP frameworks and application platforms like Drupal, you can set [PHP's mail configuration](http://www.php.net/manual/en/mail.configuration.php) in your [php.ini](http://www.php.net/manual/en/configuration.file.php) to send via MailCatcher with:
88
+ For projects using PHP, or PHP frameworks and application platforms like Drupal, you can set [PHP's mail configuration](https://www.php.net/manual/en/mail.configuration.php) in your [php.ini](https://www.php.net/manual/en/configuration.file.php) to send via MailCatcher with:
55
89
 
56
90
  sendmail_path = /usr/bin/env catchmail -f some@from.address
57
91
 
58
- You can do this in your [Apache configuration](http://php.net/manual/en/configuration.changes.php) like so:
92
+ You can do this in your [Apache configuration](https://www.php.net/manual/en/configuration.changes.php) like so:
59
93
 
60
94
  php_admin_value sendmail_path "/usr/bin/env catchmail -f some@from.address"
61
95
 
@@ -85,13 +119,7 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
85
119
  ## Caveats
86
120
 
87
121
  * Mail processing is fairly basic but easily modified. If something doesn't work for you, fork and fix it or [file an issue][mailcatcher-issues] and let me know. Include the whole message you're having problems with.
88
- * The interface is very basic and has not been tested on many browsers yet.
89
-
90
- ## TODO
91
-
92
- * Add mail delivery on request, optionally multiple times.
93
- * Compatibility testing against CampaignMonitor's [design guidelines](http://www.campaignmonitor.com/design-guidelines/) and [CSS support matrix](http://www.campaignmonitor.com/css/).
94
- * Forward mail to rendering service, maybe CampaignMonitor?
122
+ * Encodings are difficult. MailCatcher does not completely support utf-8 straight over the wire, you must use a mail library which encodes things properly based on SMTP server capabilities.
95
123
 
96
124
  ## Thanks
97
125
 
@@ -103,10 +131,10 @@ I work on MailCatcher mostly in my own spare time. If you've found Mailcatcher u
103
131
 
104
132
  ## License
105
133
 
106
- Copyright © 2010-2018 Samuel Cochran (sj26@sj26.com). Released under the MIT License, see [LICENSE][license] for details.
134
+ Copyright © 2010-2019 Samuel Cochran (sj26@sj26.com). Released under the MIT License, see [LICENSE][license] for details.
107
135
 
108
136
  [donate]: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=522WUPLRWUSKE
109
137
  [license]: https://github.com/sj26/mailcatcher/blob/master/LICENSE
110
138
  [mailcatcher-github]: https://github.com/sj26/mailcatcher
111
139
  [mailcatcher-issues]: https://github.com/sj26/mailcatcher/issues
112
- [websockets]: http://www.whatwg.org/specs/web-socket-protocol/
140
+ [websockets]: https://tools.ietf.org/html/rfc6455
data/bin/catchmail CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  begin
4
5
  require 'mail'
data/bin/mailcatcher CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'mail_catcher'
4
5
 
data/lib/mail_catcher.rb CHANGED
@@ -1,12 +1,4 @@
1
- # Apparently rubygems won't activate these on its own, so here we go. Let's
2
- # repeat the invention of Bundler all over again.
3
- gem "eventmachine", "1.0.9.1"
4
- gem "mail", "~> 2.3"
5
- gem "rack", "~> 1.5"
6
- gem "sinatra", "~> 1.2"
7
- gem "sqlite3", "~> 1.3"
8
- gem "thin", "~> 1.5.0"
9
- gem "skinny", "~> 0.2.3"
1
+ # frozen_string_literal: true
10
2
 
11
3
  require "open3"
12
4
  require "optparse"
@@ -26,7 +18,7 @@ end
26
18
  require "mail_catcher/version"
27
19
 
28
20
  module MailCatcher extend self
29
- autoload :Events, "mail_catcher/events"
21
+ autoload :Bus, "mail_catcher/bus"
30
22
  autoload :Mail, "mail_catcher/mail"
31
23
  autoload :Smtp, "mail_catcher/smtp"
32
24
  autoload :Web, "mail_catcher/web"
@@ -45,18 +37,10 @@ module MailCatcher extend self
45
37
  end
46
38
  end
47
39
 
48
- def mac?
49
- RbConfig::CONFIG["host_os"] =~ /darwin/
50
- end
51
-
52
40
  def windows?
53
41
  RbConfig::CONFIG["host_os"] =~ /mswin|mingw/
54
42
  end
55
43
 
56
- def macruby?
57
- mac? and const_defined? :MACRUBY_VERSION
58
- end
59
-
60
44
  def browseable?
61
45
  windows? or which? "open"
62
46
  end
@@ -77,7 +61,7 @@ module MailCatcher extend self
77
61
  puts "*** #{message}: #{context.inspect}"
78
62
  puts " Exception: #{exception}"
79
63
  puts " Backtrace:", *exception.backtrace.map { |line| " #{line.sub(gems_regexp, gems_replace)}" }
80
- puts " Please submit this as an issue at http://github.com/sj26/mailcatcher/issues"
64
+ puts " Please submit this as an issue at https://github.com/sj26/mailcatcher/issues"
81
65
  end
82
66
 
83
67
  @@defaults = {
@@ -86,6 +70,7 @@ module MailCatcher extend self
86
70
  :http_ip => "127.0.0.1",
87
71
  :http_port => "1080",
88
72
  :http_path => "/",
73
+ :messages_limit => nil,
89
74
  :verbose => false,
90
75
  :daemon => !windows?,
91
76
  :browse => false,
@@ -105,6 +90,9 @@ module MailCatcher extend self
105
90
  OptionParser.new do |parser|
106
91
  parser.banner = "Usage: mailcatcher [options]"
107
92
  parser.version = VERSION
93
+ parser.separator ""
94
+ parser.separator "MailCatcher v#{VERSION}"
95
+ parser.separator ""
108
96
 
109
97
  parser.on("--ip IP", "Set the ip address of both servers") do |ip|
110
98
  options[:smtp_ip] = options[:http_ip] = ip
@@ -126,6 +114,10 @@ module MailCatcher extend self
126
114
  options[:http_port] = port
127
115
  end
128
116
 
117
+ parser.on("--messages-limit COUNT", Integer, "Only keep up to COUNT most recent messages") do |count|
118
+ options[:messages_limit] = count
119
+ end
120
+
129
121
  parser.on("--http-path PATH", String, "Add a prefix to all HTTP paths") do |path|
130
122
  clean_path = Rack::Utils.clean_path_info("/#{path}")
131
123
 
@@ -136,13 +128,6 @@ module MailCatcher extend self
136
128
  options[:quit] = false
137
129
  end
138
130
 
139
- if mac?
140
- parser.on("--[no-]growl") do |growl|
141
- puts "Growl is no longer supported"
142
- exit -2
143
- end
144
- end
145
-
146
131
  unless windows?
147
132
  parser.on("-f", "--foreground", "Run in the foreground") do
148
133
  options[:daemon] = false
@@ -159,10 +144,15 @@ module MailCatcher extend self
159
144
  options[:verbose] = true
160
145
  end
161
146
 
162
- parser.on("-h", "--help", "Display this help information") do
147
+ parser.on_tail("-h", "--help", "Display this help information") do
163
148
  puts parser
164
149
  exit
165
150
  end
151
+
152
+ parser.on_tail("--version", "Display the current version") do
153
+ puts "MailCatcher v#{VERSION}"
154
+ exit
155
+ end
166
156
  end.parse!
167
157
  end
168
158
  end
@@ -181,7 +171,7 @@ module MailCatcher extend self
181
171
  $stdout.sync = $stderr.sync = true
182
172
  end
183
173
 
184
- puts "Starting MailCatcher"
174
+ puts "Starting MailCatcher v#{VERSION}"
185
175
 
186
176
  Thin::Logging.debug = development?
187
177
  Thin::Logging.silent = !development?
@@ -223,6 +213,8 @@ module MailCatcher extend self
223
213
  end
224
214
 
225
215
  def quit!
216
+ MailCatcher::Bus.push(type: "quit")
217
+
226
218
  EventMachine.next_tick { EventMachine.stop_event_loop }
227
219
  end
228
220
 
@@ -233,7 +225,7 @@ protected
233
225
  end
234
226
 
235
227
  def http_url
236
- "http://#{@@options[:http_ip]}:#{@@options[:http_port]}#{@@options[:http_path]}"
228
+ "http://#{@@options[:http_ip]}:#{@@options[:http_port]}#{@@options[:http_path]}".chomp("/")
237
229
  end
238
230
 
239
231
  def rescue_port port
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "eventmachine"
4
+
5
+ module MailCatcher
6
+ Bus = EventMachine::Channel.new
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "eventmachine"
2
4
  require "json"
3
5
  require "mail"
@@ -30,9 +32,11 @@ module MailCatcher::Mail extend self
30
32
  charset TEXT,
31
33
  body BLOB,
32
34
  size INTEGER,
33
- created_at DATETIME DEFAULT CURRENT_DATETIME
35
+ created_at DATETIME DEFAULT CURRENT_DATETIME,
36
+ FOREIGN KEY (message_id) REFERENCES message (id) ON DELETE CASCADE
34
37
  )
35
38
  SQL
39
+ db.foreign_keys = true
36
40
  end
37
41
  end
38
42
  end
@@ -54,7 +58,7 @@ module MailCatcher::Mail extend self
54
58
 
55
59
  EventMachine.next_tick do
56
60
  message = MailCatcher::Mail.message message_id
57
- MailCatcher::Events::MessageAdded.push message
61
+ MailCatcher::Bus.push(type: "add", message: message)
58
62
  end
59
63
  end
60
64
 
@@ -151,16 +155,29 @@ module MailCatcher::Mail extend self
151
155
 
152
156
  def delete!
153
157
  @delete_all_messages_query ||= db.prepare "DELETE FROM message"
154
- @delete_all_message_parts_query ||= db.prepare "DELETE FROM message_part"
158
+ @delete_all_messages_query.execute
155
159
 
156
- @delete_all_messages_query.execute and
157
- @delete_all_message_parts_query.execute
160
+ EventMachine.next_tick do
161
+ MailCatcher::Bus.push(type: "clear")
162
+ end
158
163
  end
159
164
 
160
165
  def delete_message!(message_id)
161
166
  @delete_messages_query ||= db.prepare "DELETE FROM message WHERE id = ?"
162
- @delete_message_parts_query ||= db.prepare "DELETE FROM message_part WHERE message_id = ?"
163
- @delete_messages_query.execute(message_id) and
164
- @delete_message_parts_query.execute(message_id)
167
+ @delete_messages_query.execute(message_id)
168
+
169
+ EventMachine.next_tick do
170
+ MailCatcher::Bus.push(type: "remove", id: message_id)
171
+ end
172
+ end
173
+
174
+ def delete_older_messages!(count = MailCatcher.options[:messages_limit])
175
+ return if count.nil?
176
+ @older_messages_query ||= db.prepare "SELECT id FROM message WHERE id NOT IN (SELECT id FROM message ORDER BY created_at DESC LIMIT ?)"
177
+ @older_messages_query.execute(count).map do |row|
178
+ Hash[row.fields.zip(row)]
179
+ end.each do |message|
180
+ delete_message!(message["id"])
181
+ end
165
182
  end
166
183
  end
@@ -1,13 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "eventmachine"
2
4
 
3
5
  require "mail_catcher/mail"
4
6
 
5
7
  class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
6
8
  # We override EM's mail from processing to allow multiple mail-from commands
7
- # per [RFC 2821](http://tools.ietf.org/html/rfc2821#section-4.1.1.2)
9
+ # per [RFC 2821](https://tools.ietf.org/html/rfc2821#section-4.1.1.2)
8
10
  def process_mail_from sender
9
11
  if @state.include? :mail_from
10
12
  @state -= [:mail_from, :rcpt, :data]
13
+
11
14
  receive_reset
12
15
  end
13
16
 
@@ -20,30 +23,40 @@ class MailCatcher::Smtp < EventMachine::Protocols::SmtpServer
20
23
 
21
24
  def receive_reset
22
25
  @current_message = nil
26
+
23
27
  true
24
28
  end
25
29
 
26
30
  def receive_sender(sender)
31
+ # EventMachine SMTP advertises size extensions [https://tools.ietf.org/html/rfc1870]
32
+ # so strip potential " SIZE=..." suffixes from senders
33
+ sender = $` if sender =~ / SIZE=\d+\z/
34
+
27
35
  current_message[:sender] = sender
36
+
28
37
  true
29
38
  end
30
39
 
31
40
  def receive_recipient(recipient)
32
41
  current_message[:recipients] ||= []
33
42
  current_message[:recipients] << recipient
43
+
34
44
  true
35
45
  end
36
46
 
37
47
  def receive_data_chunk(lines)
38
- current_message[:source] ||= ""
48
+ current_message[:source] ||= +""
49
+
39
50
  lines.each do |line|
40
51
  current_message[:source] << line << "\r\n"
41
52
  end
53
+
42
54
  true
43
55
  end
44
56
 
45
57
  def receive_message
46
58
  MailCatcher::Mail.add_message current_message
59
+ MailCatcher::Mail.delete_older_messages!
47
60
  puts "==> SMTP: Received message from '#{current_message[:sender]}' (#{current_message[:source].length} bytes)"
48
61
  true
49
62
  rescue => exception
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module MailCatcher
2
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0.beta4"
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rack/builder"
2
4
 
3
5
  require "mail_catcher/web/application"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pathname"
2
4
  require "net/http"
3
5
  require "uri"
@@ -5,7 +7,7 @@ require "uri"
5
7
  require "sinatra"
6
8
  require "skinny"
7
9
 
8
- require "mail_catcher/events"
10
+ require "mail_catcher/bus"
9
11
  require "mail_catcher/mail"
10
12
 
11
13
  class Sinatra::Request
@@ -62,7 +64,7 @@ module MailCatcher
62
64
  if request.websocket?
63
65
  request.websocket!(
64
66
  :on_start => proc do |websocket|
65
- subscription = Events::MessageAdded.subscribe do |message|
67
+ bus_subscription = MailCatcher::Bus.subscribe do |message|
66
68
  begin
67
69
  websocket.send_message(JSON.generate(message))
68
70
  rescue => exception
@@ -71,7 +73,7 @@ module MailCatcher
71
73
  end
72
74
 
73
75
  websocket.on_close do |*|
74
- Events::MessageAdded.unsubscribe subscription
76
+ MailCatcher::Bus.unsubscribe bus_subscription
75
77
  end
76
78
  end)
77
79
  else
@@ -95,9 +97,7 @@ module MailCatcher
95
97
  ("html" if Mail.message_has_html? id),
96
98
  ("plain" if Mail.message_has_plain? id)
97
99
  ].compact,
98
- "attachments" => Mail.message_attachments(id).map do |attachment|
99
- attachment.merge({"href" => "/messages/#{escape(id)}/parts/#{escape(attachment["cid"])}"})
100
- end,
100
+ "attachments" => Mail.message_attachments(id),
101
101
  }))
102
102
  else
103
103
  not_found
data/lib/mailcatcher.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "mail_catcher"
2
4
 
3
5
  Mailcatcher = MailCatcher
Binary file