mailcatcher 0.7.0 → 0.8.0.beta4

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 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