mailcatcher 0.7.1 → 0.8.0

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: 5dad3d6d6d69ec716d04e4c45f68005d1e0653b0184c2dcdd07f781d7bcfee28
4
- data.tar.gz: df15e7ec89932cb7e017e51fbb840460b65ef32870c4e42d9e5e743e060ba41d
3
+ metadata.gz: c7bd063a1a2ccd3cd8e3cf6a8ee495acafdca0d90ab2c3a16e4b62f6180bb9aa
4
+ data.tar.gz: 62cec0bcce04b0f74e200c9f47d8ab157ab889060377137f840f2717923c3f3f
5
5
  SHA512:
6
- metadata.gz: c96abaf84b5bf1f681b40293c19d00c078c85627b3350afbcd74ca94dce273b0240526773a777228693dd841111b238943448e2c99a41c625b0bd673259daf27
7
- data.tar.gz: ebddd9615c1dbf7a80932042dde6b0fef1a4e4d58b19b9cf851b3fda9ed6d51b214f1dd34fcf370d5587b1064e96e0a19995793fe013c31c23b19b175faa426b
6
+ metadata.gz: f532803f11699a109ce7be2ec7c35251dfc15e448cb6d90c65fc5a1db7757a5924883dff6259fec7dbf3e3139ad41815a3a29e4c545575e252c40c9c29b50ba3
7
+ data.tar.gz: 9cfadf47007c3fe0362ce6c7111e688539caaff2dd32da84ae5af78840d2c67860aceb54f46cf7c511d4700f21a056e8218a0cc90f174ba4a8cacb9130e775f0
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
@@ -1,2 +1 @@
1
- �7����x&�^j�[4C�No�@��'*��y�<�3RE�װ���4D��ӂ{1�htN��oV�\4ֳ��^�����?�'��
2
-
1
+ Ox�A�.�`^',��JA��t#9�P�/�9H<ݚ����|��C ���>���w{���/@ӈx:�i��L��c��l�p����"|BqЕ߿�dah�$�-�ƶ�zsgW~WOۯ���V�bdv�*UO����%�@��r{�Cl!��v}��JĪ\�+,���gϏ�z=c�ً�6��j �–�Z�?���t���\ӎ~(.#M����%��� �j�@d��Ʃ�H��?a���;�jl��-?�l���
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
 
@@ -87,12 +121,6 @@ A fairly RESTful URL schema means you can download a list of messages in JSON fr
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
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.
89
123
 
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?
95
-
96
124
  ## Thanks
97
125
 
98
126
  MailCatcher is just a mishmash of other people's hard work. Thank you so much to the people who have built the wonderful guts on which this project relies.
@@ -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.1"
4
+ VERSION = "0.8.0"
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