mlist 0.1.11 → 0.1.12
Sign up to get free protection for your applications and to get access to all the features.
- data/README +2 -1
- data/VERSION.yml +1 -1
- data/lib/mlist/mail_list.rb +64 -46
- data/lib/mlist/util/email_helpers.rb +56 -20
- data/lib/mlist/util/quoting.rb +1 -3
- data/lib/mlist/util/tmail_methods.rb +36 -24
- data/spec/models/mail_list_spec.rb +98 -71
- metadata +12 -5
data/README
CHANGED
@@ -141,7 +141,7 @@ environment.rb after the initialize block (our gem needs to have been loaded):
|
|
141
141
|
# Please do specify a version, and check for the latest!
|
142
142
|
config.gem "mlist", :version => '0.1.0'
|
143
143
|
end
|
144
|
-
|
144
|
+
|
145
145
|
MLIST_SERVER = MList::Server.new(
|
146
146
|
:list_manager => MList::Manager::Database.new,
|
147
147
|
:email_server => MList::EmailServer::Default.new(
|
@@ -185,6 +185,7 @@ list something like this:
|
|
185
185
|
(The MIT License)
|
186
186
|
|
187
187
|
Copyright (c) 2008-2009 Adam Williams (aiwilliams)
|
188
|
+
Portions from Rails are Copyright (c) 2004-2008 David Heinemeier Hansson
|
188
189
|
|
189
190
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
190
191
|
this software and associated documentation files (the 'Software'), to deal in the
|
data/VERSION.yml
CHANGED
data/lib/mlist/mail_list.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module MList
|
2
2
|
class MailList < ActiveRecord::Base
|
3
3
|
set_table_name 'mlist_mail_lists'
|
4
|
-
|
4
|
+
|
5
5
|
# Provides the MailList for a given implementation of MList::List,
|
6
6
|
# connecting it to the provided email server for delivering posts.
|
7
7
|
#
|
@@ -17,19 +17,19 @@ module MList
|
|
17
17
|
mail_list.outgoing_server = outgoing_server
|
18
18
|
mail_list
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
include MList::Util::EmailHelpers
|
22
|
-
|
22
|
+
|
23
23
|
belongs_to :manager_list, :polymorphic => true
|
24
|
-
|
24
|
+
|
25
25
|
before_destroy :delete_unreferenced_email
|
26
26
|
has_many :messages, :class_name => 'MList::Message', :dependent => :delete_all
|
27
27
|
has_many :threads, :class_name => 'MList::Thread', :dependent => :delete_all
|
28
|
-
|
28
|
+
|
29
29
|
delegate :address, :label, :post_url, :to => :list
|
30
|
-
|
30
|
+
|
31
31
|
attr_accessor :outgoing_server
|
32
|
-
|
32
|
+
|
33
33
|
# Creates a new MList::Message and delivers it to the subscribers of this
|
34
34
|
# list.
|
35
35
|
#
|
@@ -45,7 +45,7 @@ module MList
|
|
45
45
|
:email => MList::Email.new(:source => email.to_s)
|
46
46
|
), :search_parent => false, :copy_sender => email.copy_sender
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
# Processes the email received by the MList::Server.
|
50
50
|
#
|
51
51
|
def process_email(email, subscriber)
|
@@ -57,7 +57,7 @@ module MList
|
|
57
57
|
:email => email
|
58
58
|
), :copy_sender => list.copy_sender?(subscriber)
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
# Answers the provided subject with superfluous 're:' and this list's
|
62
62
|
# labels removed.
|
63
63
|
#
|
@@ -72,14 +72,14 @@ module MList
|
|
72
72
|
without_label
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
def find_parent_message(email)
|
77
77
|
if in_reply_to = email.header_string('in-reply-to')
|
78
78
|
message = messages.find(:first,
|
79
79
|
:conditions => ['identifier = ?', remove_brackets(in_reply_to)])
|
80
80
|
return message if message
|
81
81
|
end
|
82
|
-
|
82
|
+
|
83
83
|
if email.references
|
84
84
|
reference_identifiers = email.references.collect {|rid| remove_brackets(rid)}
|
85
85
|
message = messages.find(:first,
|
@@ -87,7 +87,7 @@ module MList
|
|
87
87
|
:order => 'created_at desc')
|
88
88
|
return message if message
|
89
89
|
end
|
90
|
-
|
90
|
+
|
91
91
|
if email.subject =~ REGARD_RE
|
92
92
|
message = messages.find(:first,
|
93
93
|
:conditions => ['subject = ?', remove_regard(clean_subject(email.subject))],
|
@@ -95,13 +95,13 @@ module MList
|
|
95
95
|
return message if message
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
98
|
+
|
99
99
|
# The MList::List instance of the list manager.
|
100
100
|
#
|
101
101
|
def list
|
102
102
|
@list ||= manager_list
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def manager_list_with_dual_type=(list)
|
106
106
|
if list.is_a?(ActiveRecord::Base)
|
107
107
|
self.manager_list_without_dual_type = list
|
@@ -112,27 +112,27 @@ module MList
|
|
112
112
|
end
|
113
113
|
end
|
114
114
|
alias_method_chain :manager_list=, :dual_type
|
115
|
-
|
115
|
+
|
116
116
|
# Distinct footer start marker. It is important to realize that changing
|
117
117
|
# this could be problematic.
|
118
118
|
#
|
119
119
|
FOOTER_BLOCK_START = "-~----~~----~----~----~----~---~~-~----~------~--~-~-"
|
120
|
-
|
120
|
+
|
121
121
|
# Distinct footer end marker. It is important to realize that changing
|
122
122
|
# this could be problematic.
|
123
123
|
#
|
124
124
|
FOOTER_BLOCK_END = "--~--~---~-----~--~----~-----~~~----~---~---~--~----~"
|
125
|
-
|
125
|
+
|
126
126
|
private
|
127
127
|
FOOTER_BLOCK_START_RE = %r[#{FOOTER_BLOCK_START}]
|
128
128
|
FOOTER_BLOCK_END_RE = %r[#{FOOTER_BLOCK_END}]
|
129
|
-
|
129
|
+
|
130
130
|
# http://mail.python.org/pipermail/mailman-developers/2006-April/018718.html
|
131
131
|
def bounce_headers
|
132
132
|
# tmail would not correctly quote the label in the sender header, which would break smtp delivery
|
133
133
|
{'sender' => "<mlist-#{address}>", 'errors-to' => "#{label} <mlist-#{address}>"}
|
134
134
|
end
|
135
|
-
|
135
|
+
|
136
136
|
def delete_unreferenced_email
|
137
137
|
conditions = %Q{
|
138
138
|
mlist_emails.id in (
|
@@ -144,7 +144,7 @@ module MList
|
|
144
144
|
)}
|
145
145
|
MList::Email.delete_all(conditions)
|
146
146
|
end
|
147
|
-
|
147
|
+
|
148
148
|
def strip_list_footers(content)
|
149
149
|
if content =~ FOOTER_BLOCK_START_RE
|
150
150
|
in_footer_block = false
|
@@ -160,7 +160,7 @@ module MList
|
|
160
160
|
end
|
161
161
|
content
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
# http://www.jamesshuggins.com/h/web1/list-email-headers.htm
|
165
165
|
def list_headers
|
166
166
|
headers = list.list_headers.dup
|
@@ -169,43 +169,43 @@ module MList
|
|
169
169
|
headers.update(bounce_headers)
|
170
170
|
headers.delete_if {|k,v| v.nil?}
|
171
171
|
end
|
172
|
-
|
172
|
+
|
173
173
|
# Answer headers values which should be stripped from outgoing email.
|
174
174
|
#
|
175
175
|
def strip_headers
|
176
176
|
%w(return-receipt-to domainkey-signature dkim-signature)
|
177
177
|
end
|
178
|
-
|
178
|
+
|
179
179
|
def process_message(message, options = {})
|
180
180
|
raise MList::DoubleDeliveryError.new(message) unless message.new_record?
|
181
|
-
|
181
|
+
|
182
182
|
options = {
|
183
183
|
:search_parent => true,
|
184
184
|
:delivery_time => Time.now,
|
185
185
|
:copy_sender => false
|
186
186
|
}.merge(options)
|
187
|
-
|
187
|
+
|
188
188
|
transaction do
|
189
189
|
thread = find_thread(message, options)
|
190
190
|
thread.updated_at = options[:delivery_time]
|
191
|
-
|
191
|
+
|
192
192
|
delivery = prepare_delivery(message, options)
|
193
193
|
thread.messages << message
|
194
|
-
|
194
|
+
|
195
195
|
self.updated_at = options[:delivery_time]
|
196
196
|
thread.save! && save!
|
197
|
-
|
197
|
+
|
198
198
|
outgoing_server.deliver(delivery.tmail)
|
199
199
|
end
|
200
|
-
|
200
|
+
|
201
201
|
message
|
202
202
|
end
|
203
|
-
|
203
|
+
|
204
204
|
def prepare_delivery(message, options)
|
205
205
|
message.identifier = outgoing_server.generate_message_id
|
206
206
|
message.created_at = options[:delivery_time]
|
207
207
|
message.subject = clean_subject(message.subject)
|
208
|
-
|
208
|
+
|
209
209
|
recipient_addresses = message.recipient_addresses
|
210
210
|
sender_address = message.subscriber.email_address
|
211
211
|
if options[:copy_sender]
|
@@ -213,7 +213,7 @@ module MList
|
|
213
213
|
else
|
214
214
|
recipient_addresses.delete(sender_address)
|
215
215
|
end
|
216
|
-
|
216
|
+
|
217
217
|
returning(message.delivery) do |delivery|
|
218
218
|
delivery.date ||= options[:delivery_time]
|
219
219
|
delivery.message_id = message.identifier
|
@@ -228,22 +228,40 @@ module MList
|
|
228
228
|
prepare_list_footer(delivery, message)
|
229
229
|
end
|
230
230
|
end
|
231
|
-
|
231
|
+
|
232
232
|
def prepare_list_footer(delivery, message)
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
233
|
+
footer = list_footer(message)
|
234
|
+
prepare_html_footer(delivery.text_html_part, footer)
|
235
|
+
prepare_text_footer(delivery.text_plain_part, footer)
|
236
|
+
end
|
237
|
+
|
238
|
+
def prepare_html_footer(part, footer)
|
239
|
+
return unless part
|
240
|
+
content = part.body
|
241
|
+
content.gsub!(%r(<p>\s*#{FOOTER_BLOCK_START_RE}.*?#{FOOTER_BLOCK_END_RE}\s*<\/p>)im, '')
|
242
|
+
content.strip!
|
243
|
+
html_footer = "<p>#{auto_link_urls(text_to_html(footer))}</p>"
|
244
|
+
|
245
|
+
unless content.sub!(/<\/body>/, "#{html_footer}</body>")
|
246
|
+
# no body was there, substitution failed
|
247
|
+
content << html_footer
|
248
|
+
end
|
249
|
+
part.body = content
|
250
|
+
end
|
251
|
+
|
252
|
+
def prepare_text_footer(part, footer)
|
253
|
+
return unless part
|
254
|
+
content = strip_list_footers(part.body)
|
237
255
|
content << "\n\n" unless content.end_with?("\n\n")
|
238
|
-
content <<
|
239
|
-
|
256
|
+
content << footer
|
257
|
+
part.body = content
|
240
258
|
end
|
241
|
-
|
259
|
+
|
242
260
|
def list_footer(message)
|
243
261
|
content = list.footer_content(message)
|
244
262
|
"#{FOOTER_BLOCK_START}\n#{content}\n#{FOOTER_BLOCK_END}"
|
245
263
|
end
|
246
|
-
|
264
|
+
|
247
265
|
def list_subject(string)
|
248
266
|
list_subject = string.dup
|
249
267
|
if list_subject =~ REGARD_RE
|
@@ -252,12 +270,12 @@ module MList
|
|
252
270
|
"#{subject_prefix} #{list_subject}"
|
253
271
|
end
|
254
272
|
end
|
255
|
-
|
273
|
+
|
256
274
|
def find_thread(message, options)
|
257
275
|
message.parent = find_parent_message(message.email) if message.email && options[:search_parent]
|
258
276
|
message.parent ? message.parent.thread : threads.build
|
259
277
|
end
|
260
|
-
|
278
|
+
|
261
279
|
def reply_to_header(message)
|
262
280
|
if list.reply_to_list?
|
263
281
|
"#{label} #{bracket(address)}"
|
@@ -265,13 +283,13 @@ module MList
|
|
265
283
|
subscriber_name_and_address(message.subscriber)
|
266
284
|
end
|
267
285
|
end
|
268
|
-
|
286
|
+
|
269
287
|
def subject_prefix_regex
|
270
288
|
@subject_prefix_regex ||= Regexp.new(Regexp.escape(subject_prefix) + ' ')
|
271
289
|
end
|
272
|
-
|
290
|
+
|
273
291
|
def subject_prefix
|
274
292
|
@subject_prefix ||= "[#{label}]"
|
275
293
|
end
|
276
294
|
end
|
277
|
-
end
|
295
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
module MList
|
2
2
|
module Util
|
3
|
-
|
3
|
+
|
4
4
|
class HtmlTextExtraction
|
5
|
-
|
5
|
+
|
6
6
|
# We need a way to maintain non-breaking spaces. Hpricot will replace
|
7
7
|
# them with ??.chr. We can easily teach it to convert it to a space, but
|
8
8
|
# then we lose the information in the Text node that we need to keep the
|
9
9
|
# space around, since that is what they would see in a view of the HTML.
|
10
10
|
NBSP = '!!!NBSP!!!'
|
11
|
-
|
11
|
+
|
12
12
|
def initialize(html)
|
13
13
|
@doc = Hpricot(html.gsub(' ', NBSP))
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
def execute
|
17
17
|
@text, @anchors = '', []
|
18
18
|
@doc.each_child do |node|
|
@@ -28,7 +28,7 @@ module MList
|
|
28
28
|
end
|
29
29
|
@text.gsub(NBSP, ' ')
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def extract_text_from_node(node)
|
33
33
|
case node.name
|
34
34
|
when 'head'
|
@@ -67,7 +67,7 @@ module MList
|
|
67
67
|
extract_text_from_children(node)
|
68
68
|
end
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
def extract_text_from_children(elem)
|
72
72
|
elem.each_child do |node|
|
73
73
|
case node
|
@@ -78,45 +78,81 @@ module MList
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
|
-
|
81
|
+
|
82
82
|
def extract_text_from_text_node(node)
|
83
83
|
text = @text.end_with?("\n") ? node.inner_text.lstrip : node.inner_text
|
84
84
|
@text << text.gsub(/\s{2,}/, ' ').sub(/\n/, '')
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
module EmailHelpers
|
89
89
|
def sanitize_header(charset, name, *values)
|
90
90
|
header_sanitizer(name).call(charset, *values)
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
def header_sanitizer(name)
|
94
94
|
Util.default_header_sanitizers[name]
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
def html_to_text(html)
|
98
98
|
HtmlTextExtraction.new(html).execute
|
99
99
|
end
|
100
|
-
|
100
|
+
|
101
101
|
def normalize_new_lines(text)
|
102
102
|
text.to_s.gsub(/\r\n?/, "\n")
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
def subscriber_name_and_address(subscriber)
|
106
106
|
a = subscriber.email_address
|
107
107
|
a = "#{subscriber.display_name} #{bracket(a)}" if subscriber.respond_to?(:display_name)
|
108
108
|
a
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
|
+
AUTO_LINK_RE = %r{
|
112
|
+
( https?:// | www\. )
|
113
|
+
[^\s<]+
|
114
|
+
}x unless const_defined?(:AUTO_LINK_RE)
|
115
|
+
|
116
|
+
BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
|
117
|
+
|
118
|
+
# Turns all urls into clickable links. If a block is given, each url
|
119
|
+
# is yielded and the result is used as the link text.
|
120
|
+
def auto_link_urls(text)
|
121
|
+
text.gsub(AUTO_LINK_RE) do
|
122
|
+
href = $&
|
123
|
+
punctuation = ''
|
124
|
+
left, right = $`, $'
|
125
|
+
# detect already linked URLs and URLs in the middle of a tag
|
126
|
+
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
|
127
|
+
# do not change string; URL is alreay linked
|
128
|
+
href
|
129
|
+
else
|
130
|
+
# don't include trailing punctuation character as part of the URL
|
131
|
+
if href.sub!(/[^\w\/-]$/, '') and punctuation = $& and opening = BRACKETS[punctuation]
|
132
|
+
if href.scan(opening).size > href.scan(punctuation).size
|
133
|
+
href << punctuation
|
134
|
+
punctuation = ''
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
link_text = block_given?? yield(href) : href
|
139
|
+
href = 'http://' + href unless href.index('http') == 0
|
140
|
+
|
141
|
+
%Q(<a href="#{href}">#{link_text}</a>)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
111
147
|
BRACKETS_RE = /\A<(.*?)>\Z/
|
112
148
|
def bracket(string)
|
113
149
|
string.blank? || string =~ BRACKETS_RE ? string : "<#{string}>"
|
114
150
|
end
|
115
|
-
|
151
|
+
|
116
152
|
def remove_brackets(string)
|
117
153
|
string =~ BRACKETS_RE ? $1 : string
|
118
154
|
end
|
119
|
-
|
155
|
+
|
120
156
|
REGARD_RE = /(^|[^\w])re: /i
|
121
157
|
def remove_regard(string)
|
122
158
|
while string =~ REGARD_RE
|
@@ -124,7 +160,7 @@ module MList
|
|
124
160
|
end
|
125
161
|
string.strip
|
126
162
|
end
|
127
|
-
|
163
|
+
|
128
164
|
def text_to_html(text)
|
129
165
|
lines = normalize_new_lines(text).split("\n")
|
130
166
|
lines.collect! do |line|
|
@@ -136,7 +172,7 @@ module MList
|
|
136
172
|
end
|
137
173
|
lines.join("<br />\n")
|
138
174
|
end
|
139
|
-
|
175
|
+
|
140
176
|
def text_to_quoted(text)
|
141
177
|
lines = normalize_new_lines(text).split("\n")
|
142
178
|
lines.collect! do |line|
|
@@ -144,12 +180,12 @@ module MList
|
|
144
180
|
end
|
145
181
|
lines.join("\n")
|
146
182
|
end
|
147
|
-
|
183
|
+
|
148
184
|
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
149
185
|
def escape_once(text)
|
150
186
|
text.gsub(/[\"><]|&(?!([a-zA-Z]+|(#\d+));)/) { |special| HTML_ESCAPE[special] }
|
151
187
|
end
|
152
188
|
end
|
153
|
-
|
189
|
+
|
154
190
|
end
|
155
|
-
end
|
191
|
+
end
|
data/lib/mlist/util/quoting.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module MList
|
2
2
|
module Util
|
3
|
-
|
3
|
+
|
4
4
|
module TMailReaders
|
5
5
|
def date
|
6
6
|
if date = tmail.header_string('date')
|
7
7
|
Time.parse(date)
|
8
8
|
end
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def from_address
|
12
12
|
tmail.from.first.downcase
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def html
|
16
16
|
case tmail.content_type
|
17
17
|
when 'text/html'
|
@@ -21,27 +21,39 @@ module MList
|
|
21
21
|
html_part.body.strip if html_part
|
22
22
|
end
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def identifier
|
26
26
|
remove_brackets(tmail.header_string('message-id'))
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
def mailer
|
30
30
|
tmail.header_string('x-mailer')
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
def text
|
34
34
|
text_content = ''
|
35
35
|
extract_text_content(tmail, text_content)
|
36
36
|
return text_content unless text_content.blank?
|
37
|
-
|
37
|
+
|
38
38
|
html_content = ''
|
39
39
|
extract_html_content(tmail, html_content)
|
40
40
|
return html_to_text(html_content) unless html_content.blank?
|
41
|
-
|
41
|
+
|
42
42
|
return nil
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
|
+
# Answers the first text/html part it can find, the tmail itself if
|
46
|
+
# it's content type is text/html.
|
47
|
+
#
|
48
|
+
def text_html_part(part = tmail)
|
49
|
+
case part.content_type
|
50
|
+
when 'text/html'
|
51
|
+
part
|
52
|
+
when 'multipart/alternative'
|
53
|
+
part.parts.detect {|part| text_html_part(part)}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
45
57
|
# Answers the first text/plain part it can find, the tmail itself if
|
46
58
|
# it's content type is text/plain.
|
47
59
|
#
|
@@ -53,7 +65,7 @@ module MList
|
|
53
65
|
part.parts.detect {|part| text_plain_part(part)}
|
54
66
|
end
|
55
67
|
end
|
56
|
-
|
68
|
+
|
57
69
|
private
|
58
70
|
def extract_html_content(part, collector)
|
59
71
|
case part.content_type
|
@@ -63,7 +75,7 @@ module MList
|
|
63
75
|
part.parts.each {|part| extract_html_content(part, collector)}
|
64
76
|
end
|
65
77
|
end
|
66
|
-
|
78
|
+
|
67
79
|
def extract_text_content(part, collector)
|
68
80
|
case part.content_type
|
69
81
|
when 'text/plain'
|
@@ -73,16 +85,16 @@ module MList
|
|
73
85
|
end
|
74
86
|
end
|
75
87
|
end
|
76
|
-
|
88
|
+
|
77
89
|
module TMailWriters
|
78
90
|
def charset
|
79
91
|
'utf-8'
|
80
92
|
end
|
81
|
-
|
93
|
+
|
82
94
|
def delete_header(name)
|
83
95
|
tmail[name] = nil
|
84
96
|
end
|
85
|
-
|
97
|
+
|
86
98
|
def headers=(updates)
|
87
99
|
updates.each do |k,v|
|
88
100
|
if TMail::Mail::ALLOW_MULTIPLE.include?(k.downcase)
|
@@ -92,7 +104,7 @@ module MList
|
|
92
104
|
end
|
93
105
|
end
|
94
106
|
end
|
95
|
-
|
107
|
+
|
96
108
|
# Add another value for the named header, it's position being earlier in
|
97
109
|
# the email than those that are already present. This will raise an error
|
98
110
|
# if the header does not allow multiple values according to
|
@@ -104,35 +116,35 @@ module MList
|
|
104
116
|
tmail[name] = sanitize_header(charset, name, value)
|
105
117
|
tmail[name] = tmail[name] + original
|
106
118
|
end
|
107
|
-
|
119
|
+
|
108
120
|
def write_header(name, value)
|
109
121
|
tmail[name] = sanitize_header(charset, name, value)
|
110
122
|
end
|
111
|
-
|
123
|
+
|
112
124
|
def to=(recipient_addresses)
|
113
125
|
tmail.to = sanitize_header(charset, 'to', recipient_addresses)
|
114
126
|
end
|
115
|
-
|
127
|
+
|
116
128
|
def bcc=(recipient_addresses)
|
117
129
|
tmail.bcc = sanitize_header(charset, 'bcc', recipient_addresses)
|
118
130
|
end
|
119
|
-
|
131
|
+
|
120
132
|
def from=(from_address)
|
121
133
|
tmail.from = sanitize_header(charset, 'from', from_address)
|
122
134
|
end
|
123
|
-
|
135
|
+
|
124
136
|
def in_reply_to=(*values)
|
125
137
|
tmail.in_reply_to = sanitize_header(charset, 'in-reply-to', *values)
|
126
138
|
end
|
127
|
-
|
139
|
+
|
128
140
|
def mailer=(value)
|
129
141
|
write_header('x-mailer', value)
|
130
142
|
end
|
131
|
-
|
143
|
+
|
132
144
|
def message_id=(value)
|
133
145
|
tmail.message_id = sanitize_header(charset, 'message-id', value)
|
134
146
|
end
|
135
147
|
end
|
136
|
-
|
148
|
+
|
137
149
|
end
|
138
|
-
end
|
150
|
+
end
|
@@ -4,38 +4,38 @@ describe MList::MailList do
|
|
4
4
|
class ManagerList
|
5
5
|
include MList::List
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
before do
|
9
9
|
@manager_list = ManagerList.new
|
10
10
|
stub(@manager_list).label {'Discussions'}
|
11
11
|
stub(@manager_list).address {'list_one@example.com'}
|
12
12
|
stub(@manager_list).list_id {'list_one@example.com'}
|
13
|
-
|
13
|
+
|
14
14
|
@subscriber_one = MList::EmailSubscriber.new('adam@nomail.net')
|
15
15
|
@subscriber_two = MList::EmailSubscriber.new('john@example.com')
|
16
16
|
stub(@manager_list).subscribers {[@subscriber_one, @subscriber_two]}
|
17
|
-
|
17
|
+
|
18
18
|
@outgoing_server = MList::EmailServer::Fake.new
|
19
19
|
@mail_list = MList::MailList.create!(
|
20
20
|
:manager_list => @manager_list,
|
21
21
|
:outgoing_server => @outgoing_server)
|
22
22
|
end
|
23
|
-
|
23
|
+
|
24
24
|
it 'should not require the manager list be an ActiveRecord type' do
|
25
25
|
@mail_list.list.should == @manager_list
|
26
26
|
@mail_list.manager_list.should be_nil
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
it 'should have messages counted' do
|
30
30
|
MList::Message.reflect_on_association(:mail_list).counter_cache_column.should == :messages_count
|
31
31
|
MList::MailList.column_names.should include('messages_count')
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
it 'should have threads counted' do
|
35
35
|
MList::Thread.reflect_on_association(:mail_list).counter_cache_column.should == :threads_count
|
36
36
|
MList::MailList.column_names.should include('threads_count')
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
it 'should delete email not referenced by other lists' do
|
40
40
|
email_in_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
|
41
41
|
email_not_other = MList::Email.create!(:tmail => tmail_fixture('single_list'))
|
@@ -48,7 +48,7 @@ describe MList::MailList do
|
|
48
48
|
MList::Email.exists?(email_in_other).should be_true
|
49
49
|
MList::Email.exists?(email_not_other).should be_false
|
50
50
|
end
|
51
|
-
|
51
|
+
|
52
52
|
describe 'post' do
|
53
53
|
it 'should allow posting a new message to the list' do
|
54
54
|
lambda do
|
@@ -60,12 +60,12 @@ describe MList::MailList do
|
|
60
60
|
)
|
61
61
|
end.should change(MList::Message, :count).by(1)
|
62
62
|
end.should change(MList::Thread, :count).by(1)
|
63
|
-
|
63
|
+
|
64
64
|
tmail = @outgoing_server.deliveries.last
|
65
65
|
tmail.subject.should =~ /I'm a Program!/
|
66
66
|
tmail.from.should == ['adam@nomail.net']
|
67
67
|
end
|
68
|
-
|
68
|
+
|
69
69
|
it 'should answer the message for use by the application' do
|
70
70
|
@mail_list.post(
|
71
71
|
:subscriber => @subscriber_one,
|
@@ -73,7 +73,7 @@ describe MList::MailList do
|
|
73
73
|
:text => 'Are you a programmer or what?'
|
74
74
|
).should be_instance_of(MList::Message)
|
75
75
|
end
|
76
|
-
|
76
|
+
|
77
77
|
it 'should allow posting a reply to an existing message' do
|
78
78
|
@mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
|
79
79
|
existing_message = @mail_list.messages.last
|
@@ -89,7 +89,7 @@ describe MList::MailList do
|
|
89
89
|
new_message = MList::Message.last
|
90
90
|
new_message.subject.should == "Re: Test"
|
91
91
|
end
|
92
|
-
|
92
|
+
|
93
93
|
it 'should not associate a posting to a parent if not reply' do
|
94
94
|
@mail_list.process_email(MList::Email.new(:tmail => tmail_fixture('single_list')), @subscriber_one)
|
95
95
|
lambda do
|
@@ -105,7 +105,7 @@ describe MList::MailList do
|
|
105
105
|
message.parent.should be_nil
|
106
106
|
message.parent_identifier.should be_nil
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
it 'should capture the message-id of delivered email' do
|
110
110
|
message = @mail_list.post(
|
111
111
|
:subscriber => @subscriber_one,
|
@@ -113,91 +113,91 @@ describe MList::MailList do
|
|
113
113
|
:text => 'Email must have a message id for threading')
|
114
114
|
message.reload.identifier.should_not be_nil
|
115
115
|
end
|
116
|
-
|
116
|
+
|
117
117
|
it 'should copy the subscriber if desired' do
|
118
118
|
@mail_list.post(
|
119
119
|
:subscriber => @subscriber_one,
|
120
120
|
:subject => 'Copy Me',
|
121
121
|
:text => 'Email should be sent to subscriber if desired',
|
122
122
|
:copy_sender => true)
|
123
|
-
|
123
|
+
|
124
124
|
tmail = @outgoing_server.deliveries.last
|
125
125
|
tmail.bcc.should include(@subscriber_one.email_address)
|
126
126
|
end
|
127
|
-
|
127
|
+
|
128
128
|
it 'should not copy the subscriber if undesired and list includes the subscriber' do
|
129
129
|
# The MList::List implementor may include the sending subscriber
|
130
130
|
stub(@manager_list).recipients {[@subscriber_one, @subscriber_two]}
|
131
|
-
|
131
|
+
|
132
132
|
@mail_list.post(
|
133
133
|
:subscriber => @subscriber_one,
|
134
134
|
:subject => 'Do Not Copy Me',
|
135
135
|
:text => 'Email should not be sent to subscriber if undesired',
|
136
136
|
:copy_sender => false)
|
137
|
-
|
137
|
+
|
138
138
|
tmail = @outgoing_server.deliveries.last
|
139
139
|
tmail.bcc.should_not include(@subscriber_one.email_address)
|
140
140
|
end
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
describe 'message storage' do
|
144
144
|
def process_post
|
145
145
|
@mail_list.process_email(MList::Email.new(:tmail => @post_tmail), @subscriber)
|
146
146
|
MList::Message.last
|
147
147
|
end
|
148
|
-
|
148
|
+
|
149
149
|
before do
|
150
150
|
@post_tmail = tmail_fixture('single_list')
|
151
151
|
@subscriber = @subscriber_one
|
152
152
|
end
|
153
|
-
|
153
|
+
|
154
154
|
it 'should not include list label in subject' do
|
155
155
|
@post_tmail.subject = '[Discussions] Test'
|
156
156
|
process_post.subject.should == 'Test'
|
157
157
|
end
|
158
|
-
|
158
|
+
|
159
159
|
it 'should not include list label in reply subject' do
|
160
160
|
@post_tmail.subject = 'Re: [Discussions] Test'
|
161
161
|
process_post.subject.should == 'Re: Test'
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
it 'should not bother labels it does not understand in subject' do
|
165
165
|
@post_tmail.subject = '[Ann] Test'
|
166
166
|
process_post.subject.should == '[Ann] Test'
|
167
167
|
end
|
168
|
-
|
168
|
+
|
169
169
|
it 'should not bother labels it does not understand in reply subject' do
|
170
170
|
@post_tmail.subject = 'Re: [Ann] Test'
|
171
171
|
process_post.subject.should == 'Re: [Ann] Test'
|
172
172
|
end
|
173
|
-
|
173
|
+
|
174
174
|
it 'should be careful of multiple re:' do
|
175
175
|
@post_tmail.subject = 'Re: [Ann] RE: Test'
|
176
176
|
process_post.subject.should == 'Re: [Ann] Test'
|
177
177
|
end
|
178
178
|
end
|
179
|
-
|
179
|
+
|
180
180
|
describe 'finding parent message' do
|
181
181
|
def email(path)
|
182
182
|
MList::Email.new(:tmail => tmail_fixture(path))
|
183
183
|
end
|
184
|
-
|
184
|
+
|
185
185
|
before do
|
186
186
|
@parent_message = MList::Message.new
|
187
187
|
end
|
188
|
-
|
188
|
+
|
189
189
|
it 'should be nil if none found' do
|
190
190
|
do_not_call(@mail_list.messages).find
|
191
191
|
@mail_list.find_parent_message(email('single_list')).should be_nil
|
192
192
|
end
|
193
|
-
|
193
|
+
|
194
194
|
it 'should use in-reply-to field when present' do
|
195
195
|
mock(@mail_list.messages).find(:first, :conditions => [
|
196
196
|
'identifier = ?', 'F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative.com'
|
197
197
|
]) { @parent_message }
|
198
198
|
@mail_list.find_parent_message(email('single_list_reply')).should == @parent_message
|
199
199
|
end
|
200
|
-
|
200
|
+
|
201
201
|
it 'should be references field if present and no in-reply-to' do
|
202
202
|
tmail = tmail_fixture('single_list_reply')
|
203
203
|
tmail['in-reply-to'] = nil
|
@@ -206,7 +206,7 @@ describe MList::MailList do
|
|
206
206
|
:order => 'created_at desc') { @parent_message }
|
207
207
|
@mail_list.find_parent_message(MList::Email.new(:tmail => tmail)).should == @parent_message
|
208
208
|
end
|
209
|
-
|
209
|
+
|
210
210
|
describe 'by subject' do
|
211
211
|
def search_subject(subject = nil)
|
212
212
|
simple_matcher("search by the subject '#{subject}'") do |email|
|
@@ -223,7 +223,7 @@ describe MList::MailList do
|
|
223
223
|
!subject.nil?
|
224
224
|
end
|
225
225
|
end
|
226
|
-
|
226
|
+
|
227
227
|
before do
|
228
228
|
@parent_message = MList::Message.new
|
229
229
|
@mail_list = MList::MailList.new
|
@@ -231,16 +231,16 @@ describe MList::MailList do
|
|
231
231
|
@reply_tmail = tmail_fixture('single_list')
|
232
232
|
@reply_email = MList::Email.new(:tmail => @reply_tmail)
|
233
233
|
end
|
234
|
-
|
234
|
+
|
235
235
|
it 'should be employed if it has "re:" in it' do
|
236
236
|
@reply_tmail.subject = "Re: Test"
|
237
237
|
@reply_email.should search_subject('Test')
|
238
238
|
end
|
239
|
-
|
239
|
+
|
240
240
|
it 'should not be employed when no "re:"' do
|
241
241
|
@reply_email.should_not search_subject
|
242
242
|
end
|
243
|
-
|
243
|
+
|
244
244
|
['RE: [list name] Re: Test', 'Re: [list name] Re: [list name] Test', '[list name] Re: Test'].each do |subject|
|
245
245
|
it "should handle '#{subject}'" do
|
246
246
|
@reply_tmail.subject = subject
|
@@ -249,20 +249,20 @@ describe MList::MailList do
|
|
249
249
|
end
|
250
250
|
end
|
251
251
|
end
|
252
|
-
|
252
|
+
|
253
253
|
describe 'delivery' do
|
254
254
|
include MList::Util::EmailHelpers
|
255
|
-
|
255
|
+
|
256
256
|
def process_post
|
257
257
|
@mail_list.process_email(MList::Email.new(:source => @post_tmail.to_s), @subscriber)
|
258
258
|
@outgoing_server.deliveries.last
|
259
259
|
end
|
260
|
-
|
260
|
+
|
261
261
|
before do
|
262
262
|
@post_tmail = tmail_fixture('single_list')
|
263
263
|
@subscriber = @subscriber_one
|
264
264
|
end
|
265
|
-
|
265
|
+
|
266
266
|
it 'should be blind copied to recipients' do
|
267
267
|
mock.proxy(@mail_list.messages).build(anything) do |message|
|
268
268
|
mock(message.delivery).bcc=(%w(john@example.com))
|
@@ -270,7 +270,7 @@ describe MList::MailList do
|
|
270
270
|
end
|
271
271
|
process_post
|
272
272
|
end
|
273
|
-
|
273
|
+
|
274
274
|
it 'should not deliver to addresses found in the to header' do
|
275
275
|
@post_tmail.to = ['john@example.com', 'list_one@example.com']
|
276
276
|
mock.proxy(@mail_list.messages).build(anything) do |message|
|
@@ -279,7 +279,7 @@ describe MList::MailList do
|
|
279
279
|
end
|
280
280
|
process_post
|
281
281
|
end
|
282
|
-
|
282
|
+
|
283
283
|
it 'should not deliver to addresses found in the cc header' do
|
284
284
|
@post_tmail.cc = ['john@example.com']
|
285
285
|
mock.proxy(@mail_list.messages).build(anything) do |message|
|
@@ -288,88 +288,88 @@ describe MList::MailList do
|
|
288
288
|
end
|
289
289
|
process_post
|
290
290
|
end
|
291
|
-
|
291
|
+
|
292
292
|
it 'should use list address as reply-to by default' do
|
293
293
|
process_post.should have_header('reply-to', 'Discussions <list_one@example.com>')
|
294
294
|
end
|
295
|
-
|
295
|
+
|
296
296
|
it 'should use subscriber address as reply-to if list says to not use address' do
|
297
297
|
mock(@manager_list).reply_to_list? { false }
|
298
298
|
process_post.should have_header('reply-to', 'adam@nomail.net')
|
299
299
|
end
|
300
|
-
|
300
|
+
|
301
301
|
it 'should use the reply-to already in an email - should not override it' do
|
302
302
|
@post_tmail['reply-to'] = 'theotheradam@nomail.net'
|
303
303
|
process_post.should have_header('reply-to', 'theotheradam@nomail.net')
|
304
304
|
end
|
305
|
-
|
305
|
+
|
306
306
|
it 'should set x-beenthere on emails it delivers to keep from re-posting them' do
|
307
307
|
process_post.should have_header('x-beenthere', 'list_one@example.com')
|
308
308
|
end
|
309
|
-
|
309
|
+
|
310
310
|
it 'should not remove any existing x-beenthere headers' do
|
311
311
|
@post_tmail['x-beenthere'] = 'somewhere@nomail.net'
|
312
312
|
process_post.should have_header('x-beenthere', %w(list_one@example.com somewhere@nomail.net))
|
313
313
|
end
|
314
|
-
|
314
|
+
|
315
315
|
it 'should not modify existing headers' do
|
316
316
|
@post_tmail['x-something-custom'] = 'existing'
|
317
317
|
process_post.should have_header('x-something-custom', 'existing')
|
318
318
|
end
|
319
|
-
|
319
|
+
|
320
320
|
it 'should delete Return-Receipt-To headers since they cause clients to spam the list (the sender address)' do
|
321
321
|
@post_tmail['return-receipt-to'] = 'somewhere@nomail.net'
|
322
322
|
process_post.should_not have_header('return-receipt-to')
|
323
323
|
end
|
324
|
-
|
324
|
+
|
325
325
|
it 'should not have any cc addresses' do
|
326
326
|
@post_tmail['cc'] = 'billybob@anywhere.com'
|
327
327
|
process_post.should_not have_header('cc')
|
328
328
|
end
|
329
|
-
|
329
|
+
|
330
330
|
it 'should prefix the list label to the subject of messages' do
|
331
331
|
process_post.subject.should == '[Discussions] Test'
|
332
332
|
end
|
333
|
-
|
333
|
+
|
334
334
|
it 'should move the list label to the front of subjects that already include the label' do
|
335
335
|
@post_tmail.subject = 'Re: [Discussions] Test'
|
336
336
|
process_post.subject.should == 'Re: [Discussions] Test'
|
337
337
|
end
|
338
|
-
|
338
|
+
|
339
339
|
it 'should remove multiple occurrences of Re:' do
|
340
340
|
@post_tmail.subject = 'Re: [Discussions] Re: Test'
|
341
341
|
process_post.subject.should == 'Re: [Discussions] Test'
|
342
342
|
end
|
343
|
-
|
343
|
+
|
344
344
|
it 'should remove DomainKey-Signature headers so that we can sign the redistribution' do
|
345
345
|
@post_tmail['DomainKey-Signature'] = "a whole bunch of junk"
|
346
346
|
process_post.should_not have_header('domainkey-signature')
|
347
347
|
end
|
348
|
-
|
348
|
+
|
349
349
|
it 'should remove DKIM-Signature headers so that we can sign the redistribution' do
|
350
350
|
@post_tmail['DKIM-Signature'] = "a whole bunch of junk"
|
351
351
|
process_post.should_not have_header('dkim-signature')
|
352
352
|
end
|
353
|
-
|
353
|
+
|
354
354
|
it 'should capture the new message-ids' do
|
355
355
|
delivered = process_post
|
356
356
|
delivered.header_string('message-id').should_not be_blank
|
357
357
|
MList::Message.last.identifier.should == remove_brackets(delivered.header_string('message-id'))
|
358
358
|
delivered.header_string('message-id').should_not match(/F5F9DC55-CB54-4F2C-9B46-A05F241BCF22@recursivecreative\.com/)
|
359
359
|
end
|
360
|
-
|
360
|
+
|
361
361
|
it 'should maintain the content-id part headers (inline images, etc)' do
|
362
362
|
@post_tmail = tmail_fixture('embedded_content')
|
363
363
|
process_post.parts[1].parts[1]['content-id'].to_s.should == "<CF68EC17-F8ED-478A-A4A1-AEBF165A8830/bg_pattern.jpg>"
|
364
364
|
end
|
365
|
-
|
365
|
+
|
366
366
|
it 'should add standard list headers when they are available' do
|
367
367
|
stub(@manager_list).help_url {'http://list_manager.example.com/help'}
|
368
368
|
stub(@manager_list).subscribe_url {'http://list_manager.example.com/subscribe'}
|
369
369
|
stub(@manager_list).unsubscribe_url {'http://list_manager.example.com/unsubscribe'}
|
370
370
|
stub(@manager_list).owner_url {"<mailto:list_manager@example.com>\n(Jimmy Fish)"}
|
371
371
|
stub(@manager_list).archive_url {'http://list_manager.example.com/archive'}
|
372
|
-
|
372
|
+
|
373
373
|
tmail = process_post
|
374
374
|
tmail.should have_headers(
|
375
375
|
'list-id' => "<list_one@example.com>",
|
@@ -385,33 +385,39 @@ describe MList::MailList do
|
|
385
385
|
)
|
386
386
|
tmail.header_string('x-mlist-version').should =~ /\d+\.\d+\.\d+/
|
387
387
|
end
|
388
|
-
|
388
|
+
|
389
389
|
it 'should not add list headers that are not available or nil' do
|
390
390
|
stub(@manager_list).help_url {nil}
|
391
391
|
delivery = process_post
|
392
392
|
delivery.should_not have_header('list-help')
|
393
393
|
delivery.should_not have_header('list-subscribe')
|
394
394
|
end
|
395
|
-
|
395
|
+
|
396
396
|
it 'should append the list footer to text/plain emails' do
|
397
397
|
@post_tmail.body = "My Email\n\n\n\n\n"
|
398
398
|
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
399
399
|
process_post.body.should == "My Email\n\n\n\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
400
400
|
end
|
401
|
-
|
401
|
+
|
402
402
|
it 'should append the list footer to multipart/alternative, text/plain part of emails' do
|
403
403
|
@post_tmail = tmail_fixture('content_types/multipart_alternative_simple')
|
404
404
|
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
405
405
|
process_post.parts[0].body.should match(/#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}/)
|
406
406
|
end
|
407
|
-
|
407
|
+
|
408
|
+
it 'should append the list footer to multipart/alternative, text/html part of emails' do
|
409
|
+
@post_tmail = tmail_fixture('content_types/multipart_alternative_simple')
|
410
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { "my footer\nis here\nhttp://links/here" }
|
411
|
+
process_post.parts[1].body.should match(/<p>#{MList::MailList::FOOTER_BLOCK_START}<br \/>\nmy footer<br \/>\nis here<br \/>\n<a href="http:\/\/links\/here">http:\/\/links\/here<\/a><br \/>\n#{MList::MailList::FOOTER_BLOCK_END}<\/p>/)
|
412
|
+
end
|
413
|
+
|
408
414
|
it 'should handle whitespace well when appending footer' do
|
409
415
|
@post_tmail.body = "My Email"
|
410
416
|
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
411
417
|
process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
412
418
|
end
|
413
|
-
|
414
|
-
it 'should strip out any existing footers from the list' do
|
419
|
+
|
420
|
+
it 'should strip out any existing text footers from the list' do
|
415
421
|
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
416
422
|
@post_tmail.body = %{My Email
|
417
423
|
|
@@ -429,32 +435,53 @@ this is without any in front
|
|
429
435
|
}
|
430
436
|
process_post.body.should == "My Email\n\n#{MList::MailList::FOOTER_BLOCK_START}\nmy footer\n#{MList::MailList::FOOTER_BLOCK_END}"
|
431
437
|
end
|
432
|
-
|
438
|
+
|
439
|
+
it 'should strip out any existing html footers from the list' do
|
440
|
+
@post_tmail = tmail_fixture('content_types/multipart_alternative_simple')
|
441
|
+
mock(@manager_list).footer_content(is_a(MList::Message)) { 'my footer' }
|
442
|
+
@post_tmail.parts[1].body = %{<p>My Email</p>
|
443
|
+
<blockquote>
|
444
|
+
<p> Stuff in my email</p><p> #{MList::MailList::FOOTER_BLOCK_START}
|
445
|
+
> > content at front shouldn't matter
|
446
|
+
> > #{MList::MailList::FOOTER_BLOCK_END} </p>
|
447
|
+
|
448
|
+
>> not in our p!<p>#{MList::MailList::FOOTER_BLOCK_START}
|
449
|
+
>> this is fine to be removed
|
450
|
+
>> #{MList::MailList::FOOTER_BLOCK_END}</p>
|
451
|
+
|
452
|
+
<p>#{MList::MailList::FOOTER_BLOCK_START}
|
453
|
+
this is without any in front
|
454
|
+
#{MList::MailList::FOOTER_BLOCK_END}
|
455
|
+
</p>
|
456
|
+
}
|
457
|
+
process_post.parts[1].body.should == "<p>My Email</p>\n<blockquote>\n <p> Stuff in my email</p>\n\n>> not in our p!<p>#{MList::MailList::FOOTER_BLOCK_START}<br />\nmy footer<br />\n#{MList::MailList::FOOTER_BLOCK_END}</p>"
|
458
|
+
end
|
459
|
+
|
433
460
|
describe 'time' do
|
434
461
|
include TMail::TextUtils
|
435
|
-
|
462
|
+
|
436
463
|
before do
|
437
464
|
@old_zone_default = Time.zone_default
|
438
465
|
@system_time = Time.parse('Thu, 2 Apr 2009 15:22:04')
|
439
466
|
mock(Time).now.times(any_times) { @system_time }
|
440
467
|
end
|
441
|
-
|
468
|
+
|
442
469
|
after do
|
443
470
|
Time.zone_default = @old_zone_default
|
444
471
|
end
|
445
|
-
|
472
|
+
|
446
473
|
it 'should keep date of email post' do
|
447
474
|
@post_tmail['date'] = 'Thu, 2 Apr 2009 15:22:04 -0400'
|
448
475
|
process_post.header_string('date').should == 'Thu, 2 Apr 2009 15:22:04 -0400'
|
449
476
|
end
|
450
|
-
|
477
|
+
|
451
478
|
it 'should store the delivery time as created_at of message record' do
|
452
479
|
Time.zone_default = 'Pacific Time (US & Canada)'
|
453
480
|
@post_tmail['date'] = 'Wed, 1 Apr 2009 15:22:04 -0400'
|
454
481
|
process_post.header_string('date').should == 'Wed, 1 Apr 2009 15:22:04 -0400'
|
455
482
|
MList::Message.last.created_at.should == @system_time
|
456
483
|
end
|
457
|
-
|
484
|
+
|
458
485
|
# I think that what TMail is doing is evil, but it's reference to
|
459
486
|
# a ruby-talk discussion leads to Japanese, which I cannot read.
|
460
487
|
# I'd prefer that it leave the problem of timezones up to the client,
|
@@ -466,4 +493,4 @@ this is without any in front
|
|
466
493
|
end
|
467
494
|
end
|
468
495
|
end
|
469
|
-
end
|
496
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mlist
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 12
|
9
|
+
version: 0.1.12
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Adam Williams
|
@@ -9,7 +14,7 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-03-26 00:00:00 -04:00
|
13
18
|
default_executable:
|
14
19
|
dependencies: []
|
15
20
|
|
@@ -67,18 +72,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
72
|
requirements:
|
68
73
|
- - ">="
|
69
74
|
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
70
77
|
version: "0"
|
71
|
-
version:
|
72
78
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
79
|
requirements:
|
74
80
|
- - ">="
|
75
81
|
- !ruby/object:Gem::Version
|
82
|
+
segments:
|
83
|
+
- 0
|
76
84
|
version: "0"
|
77
|
-
version:
|
78
85
|
requirements: []
|
79
86
|
|
80
87
|
rubyforge_project:
|
81
|
-
rubygems_version: 1.3.
|
88
|
+
rubygems_version: 1.3.6
|
82
89
|
signing_key:
|
83
90
|
specification_version: 3
|
84
91
|
summary: A Ruby mailing list library designed to be integrated into other applications.
|