gurgitate-mail 1.7.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.
@@ -0,0 +1,47 @@
1
+ #!/opt/bin/ruby -w
2
+
3
+ #------------------------------------------------------------------------
4
+ # Delivers a message to an mbox (also includes mbox detector)
5
+ #------------------------------------------------------------------------
6
+
7
+ module Gurgitate
8
+ module Deliver
9
+ module MBox
10
+ # Checks to see if +mailbox+ is an mbox mailbox
11
+ # mailbox::
12
+ # A string containing the path of the mailbox to save
13
+ # the message to. If it is of the form "=mailbox", it
14
+ # saves the message to +Maildir+/+mailbox+. Otherwise,
15
+ # it simply saves the message to the file +mailbox+.
16
+ def self::check_mailbox(mailbox)
17
+
18
+ begin
19
+ if File.stat(mailbox).file? then
20
+ return MBox
21
+ else
22
+ return nil
23
+ end
24
+ rescue Errno::ENOENT
25
+ return nil
26
+ end
27
+ end
28
+
29
+ # Delivers the message to +mailbox+
30
+ # mailbox::
31
+ # A string containing the path of the mailbox to save
32
+ # the message to. If it is of the form "=mailbox", it
33
+ # saves the message to +Maildir+/+mailbox+. Otherwise,
34
+ # it simply saves the message to the file +mailbox+.
35
+ def deliver_message(mailbox)
36
+ File.open(mailbox,File::WRONLY |
37
+ File::APPEND |
38
+ File::CREAT) do |f|
39
+ f.flock(File::LOCK_EX)
40
+ message=(if f.stat.size > 0 then "\n" else "" end) + to_mbox
41
+ f.print message
42
+ f.flock(File::LOCK_UN)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,264 @@
1
+ #!/opt/bin/ruby -w
2
+
3
+ #------------------------------------------------------------------------
4
+ #
5
+ #------------------------------------------------------------------------
6
+
7
+ module Gurgitate
8
+ class IllegalHeader < RuntimeError ; end
9
+
10
+ # A little class for a single header
11
+
12
+ class Header
13
+ # The name of the header
14
+ attr_accessor :name
15
+ # The contents of the header
16
+ attr_accessor :contents
17
+
18
+ alias_method :value, :contents
19
+
20
+ # A recent rash of viruses has forced me to canonicalize
21
+ # the capitalization of headers. Sigh.
22
+ def capitalize_words(s)
23
+ return s.split(/-/).map { |w| w.capitalize }.join("-")
24
+ rescue
25
+ return s
26
+ end
27
+
28
+ private :capitalize_words
29
+
30
+ # Creates a Header object.
31
+ # header::
32
+ # The text of the email-message header
33
+ def initialize(*header)
34
+ name,contents=nil,nil
35
+ if header.length == 1 then
36
+ # RFC822 says that a header consists of some (printable,
37
+ # non-whitespace) crap, followed by a colon, followed by
38
+ # some more (printable, but can include whitespaces)
39
+ # crap.
40
+ if(header[0] =~ /^[\x21-\x39\x3b-\x7e]+:/) then
41
+ (name,contents)=header[0].split(/:\s+/,2)
42
+ if(name =~ /:$/ and contents == nil) then
43
+ # It looks like someone is using Becky!
44
+ name=header[0].gsub(/:$/,"")
45
+ contents = ""
46
+ end
47
+
48
+ raise IllegalHeader, "Empty name" \
49
+ if (name == "" or name == nil)
50
+ contents="" if contents == nil
51
+
52
+ @@lastname=name
53
+ else
54
+ raise IllegalHeader, "Bad header syntax: no colon in #{header}"
55
+ end
56
+ elsif header.length == 2 then
57
+ name,contents = *header
58
+ end
59
+
60
+ @name=capitalize_words(name)
61
+ @contents=contents
62
+ end
63
+
64
+ # Extended header
65
+ def << (text)
66
+ @contents += "\n" + text
67
+ end
68
+
69
+ # Matches a header's contents.
70
+ # regex::
71
+ # The regular expression to match against the header's contents
72
+ def matches (regex)
73
+ @contents =~ regex
74
+ end
75
+
76
+ alias :=~ :matches
77
+
78
+ # Returns the header, ready to put into an email message
79
+ def to_s
80
+ @name+": "+@contents
81
+ end
82
+ end
83
+
84
+ # ========================================================================
85
+
86
+ class HeaderBag < Array
87
+ def =~(regex)
88
+ inject(false) do |y,x|
89
+ y or ( ( x =~ regex ) != nil )
90
+ end
91
+ end
92
+ end
93
+
94
+ # A slightly bigger class for all of a message's headers
95
+ class Headers
96
+
97
+ # Creates a Headers object.
98
+ # headertext::
99
+ # The text of the message headers.
100
+ def initialize(headertext)
101
+ @headers=Hash.new(nil)
102
+ @headertext=headertext
103
+
104
+ (unix_from,normal_headers)=headertext.split(/\n/,2);
105
+
106
+ # If you run "fetchmail" with the -m option to feed the
107
+ # mail message straight to gurgitate, skipping the "local
108
+ # MTA" step, then it doesn't have a "From " line. So I
109
+ # have to deal with that by hand. First, check to see if
110
+ # there's a "From " line present in the first place.
111
+ if unix_from =~ /^From / then
112
+ @headertext=normal_headers
113
+ @unix_from=unix_from
114
+ else
115
+ # If there isn't, then deal with it after we've
116
+ # worried about the rest of the headers, 'cos we'll
117
+ # have to make our own.
118
+ unix_from=""
119
+ end
120
+
121
+ @headertext.each do |h|
122
+ h.chomp!
123
+ if(h=~/^\s+/) then
124
+ @lastheader << h
125
+ else
126
+ header=Header.new(h)
127
+ @headers[header.name] ||= HeaderBag.new;
128
+ @headers[header.name].push(header)
129
+ @lastheader=header
130
+ end
131
+ end
132
+
133
+ @headers_changed=false
134
+
135
+ # Okay, now worry about the "From foo@bar" line. If it's
136
+ # not there, then make one up from the Return-Path:
137
+ # header. If there isn't a "Return-Path:" header (then I
138
+ # suspect we have bigger problems, but still) then use
139
+ # From:
140
+ if unix_from == "" then
141
+ fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+)/;
142
+ if self["Return-Path"] != nil then
143
+ fromregex.match(self["Return-Path"][0].contents);
144
+ else
145
+ if self["From"] != nil then
146
+ fromregex.match(self["From"][0].contents);
147
+ end
148
+ end
149
+ address_candidate=$+
150
+
151
+ # If there STILL isn't a match, then it's probably safe to
152
+ # assume that it's local mail, and doesn't have an @ in its
153
+ # address.
154
+ if address_candidate == nil then
155
+ if self["Return-Path"] != nil then
156
+ self["Return-Path"][0].contents =~ /(\S+)/
157
+ address_candidate=$+
158
+ else
159
+ self["From"][0].contents =~ /(\S+)/
160
+ address_candidate=$+
161
+ end
162
+ end
163
+
164
+ @from=address_candidate
165
+
166
+ @unix_from="From "+self.from+" "+Time.new.to_s;
167
+ else
168
+ # If it is there, then grab the email address in it and
169
+ # use that as our official "from".
170
+ fromregex=/^From ([^ ]+@[^ ]+) /;
171
+ fromregex.match(unix_from);
172
+ @from=$+
173
+
174
+ # or maybe it's local
175
+ if @from == nil then
176
+ unix_from =~ /^From (\S+) /;
177
+ @from=$+
178
+ end
179
+ end
180
+ end
181
+
182
+ # Grab the headers with names +names+
183
+ # names:: The names of the header.
184
+ def [](*names)
185
+ if names.inject(false) do |accum,name|
186
+ accum or @headers.has_key? name
187
+ end then
188
+ return HeaderBag.new(names.collect { |name|
189
+ @headers[name]
190
+ }.flatten.delete_if { |e| e == nil } )
191
+ else
192
+ return nil
193
+ end
194
+ end
195
+
196
+ # Set the header named +name+ to +value+
197
+ # name:: The name of the header.
198
+ # value:: The new value of the header.
199
+ def []=(name,value)
200
+ @headers_changed = true
201
+ @headers[name]=HeaderBag.new([Header.new(name,value)])
202
+ end
203
+
204
+ # Who the message is from (the envelope from)
205
+ def from
206
+ return @from || ""
207
+ end
208
+
209
+ # Change the envelope from line to whatever you want. This might
210
+ # not be particularly neighborly, but oh well.
211
+ # newfrom:: An email address
212
+ def from=(newfrom)
213
+ @from=newfrom
214
+ @unix_from="From "+self.from+" "+Time.new.to_s;
215
+ end
216
+
217
+ # Match header +name+ against +regex+
218
+ # name::
219
+ # A string containing the name of the header to match (for example,
220
+ # "From")
221
+ # regex:: The regex to match it against (for example, /@aol.com/)
222
+ def match(name,regex)
223
+ ret=false
224
+ if(@headers[name]) then
225
+ @headers[name].each do |h|
226
+ ret |= h.matches(regex)
227
+ end
228
+ end
229
+ return ret
230
+ end
231
+
232
+ # Return true if headers +names+ match +regex+
233
+ # names:: An array of header names (for example, %w{From Reply-To})
234
+ # regex:: The regex to match the headers against.
235
+ def matches(names,regex)
236
+ ret=false
237
+ if names.class == "String" then
238
+ names=[names];
239
+ end
240
+ names.each do |n|
241
+ ret |= match(n,regex)
242
+ end
243
+ return ret
244
+ end
245
+
246
+ # Returns the headers properly formatted for an email
247
+ # message.
248
+ def to_mbox
249
+ return @unix_from+"\n"+to_s
250
+ end
251
+
252
+ # Returns the headers formatted for an email message (without
253
+ # the "From " line
254
+ def to_s
255
+ if @headers_changed then
256
+ return @headers.collect { |n,h|
257
+ h.collect { |h| h.to_s }.join("\n")
258
+ }.join("\n")
259
+ else
260
+ return @headertext
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,45 @@
1
+ #!/opt/bin/ruby -w
2
+
3
+ #------------------------------------------------------------------------
4
+ # Handles a complete mail message
5
+ #------------------------------------------------------------------------
6
+
7
+ require 'gurgitate/headers'
8
+
9
+ module Gurgitate
10
+ # A complete mail message.
11
+
12
+ class Mailmessage
13
+ # The headers of the message
14
+ attr_reader :headers
15
+ # The body of the message
16
+ attr_accessor :body
17
+
18
+ def initialize(text)
19
+ (@headertext,@body)=text.split(/^$/,2)
20
+ fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<][<](.*@.*)[>]|([^ ]+@[^ ]+)/;
21
+ @headers=Headers.new(@headertext);
22
+ fromregex.match(@headers["From"][0].contents);
23
+ @from=$+
24
+ end
25
+
26
+ # Returns the header +name+
27
+ def header(name)
28
+ @headers[name].each { |h| h.contents }.join(", ")
29
+ end
30
+
31
+ # custom accessors
32
+
33
+ # Returns the UNIX "from" line
34
+ def from; @headers.from; end
35
+
36
+ # Returns all the candidates for a "to" line
37
+ def to; @headers["To","Cc"]; end
38
+
39
+ # Returns the formatted mail message
40
+ def to_s; @headers.to_s + @body; end
41
+
42
+ # Returns the mail message formatted for mbox
43
+ def to_mbox; @headers.to_mbox + @body; end
44
+ end
45
+ end
data/test.rb ADDED
@@ -0,0 +1,527 @@
1
+ #!/opt/bin/ruby -w
2
+
3
+ #------------------------------------------------------------------------
4
+ # Unit tests for gurgitate-mail
5
+ #------------------------------------------------------------------------
6
+
7
+ require 'test/unit'
8
+ require 'test/unit/ui/console/testrunner'
9
+ require 'stringio'
10
+
11
+ class TC_Header < Test::Unit::TestCase
12
+
13
+ def setup
14
+ require './gurgitate-mail'
15
+ end
16
+
17
+ # def teardown
18
+ # end
19
+
20
+ def test_simple_header
21
+ h=Gurgitate::Header.new("From: fromheader@example.com")
22
+ assert_equal(h.name,"From", "Simple header name is From")
23
+ assert_equal(h.contents,"fromheader@example.com",
24
+ "Contents is fromheader@example.com")
25
+ assert_equal(h.value,"fromheader@example.com",
26
+ "Contents is fromheader@example.com")
27
+ end
28
+
29
+ # This is an illegal header that turns up in spam sometimes.
30
+ # Crashing when you get spam is bad.
31
+ def test_malcapitalized_header
32
+ h=Gurgitate::Header.new("FROM: fromheader@example.com")
33
+ assert_equal(h.name,"From", "Badly-capitalized header is From")
34
+ assert_equal(h.contents,"fromheader@example.com",
35
+ "Badly-capitalized header")
36
+ assert_equal(h.value,"fromheader@example.com",
37
+ "Badly-capitalized header")
38
+ end
39
+
40
+ # I got a message with "X-Qmail-Scanner-1.19" once. I hate whoever did
41
+ # that.
42
+ def test_dot_in_header
43
+ h=Gurgitate::Header.new("From.Header: fromheader@example.com")
44
+ assert_equal(h.name, "From.header",
45
+ "header with dot in it is From.header")
46
+ assert_equal(h.contents, "fromheader@example.com",
47
+ "header with dot in it")
48
+ assert_equal(h.value, "fromheader@example.com",
49
+ "header with dot in it")
50
+ end
51
+
52
+ # Dammit! My new "anything goes" header parser was parsing too much
53
+ def test_delivered_to
54
+ h=Gurgitate::Header.new("Delivered-To: dagbrown@example.com")
55
+ assert_equal("Delivered-To", h.name)
56
+ assert_equal "dagbrown@example.com", h.contents
57
+ assert_equal "dagbrown@example.com", h.value
58
+ end
59
+
60
+ # This is another particularly horrible spamware-generated not-a-header.
61
+ def test_header_that_starts_with_hyphen
62
+ h=Gurgitate::Header.new("-From: -fromheader@example.com")
63
+ assert_equal(h.name, "-From",
64
+ "header with leading hyphen is -From")
65
+ assert_equal(h.contents, "-fromheader@example.com",
66
+ "header with leading hyphen")
67
+ assert_equal(h.value, "-fromheader@example.com",
68
+ "header with leading hyphen")
69
+ end
70
+
71
+ # This is another illegal header that turns up in spam sometimes.
72
+ # Crashing when you get spam is bad.
73
+ def test_nonalphabetic_initial_char_header
74
+ h=Gurgitate::Header.new("2From: fromheader@example.com")
75
+ assert_equal(h.name,"2from",
76
+ "Header that starts with illegal char is 2From")
77
+ assert_equal(h.contents, "fromheader@example.com",
78
+ "Header that starts with illegal char")
79
+ assert_equal(h.value, "fromheader@example.com",
80
+ "Header that starts with illegal char")
81
+ end
82
+
83
+ def test_bad_headers
84
+ assert_raises(Gurgitate::IllegalHeader,"Empty name") {
85
+ h=Gurgitate::Header.new(": Hi")
86
+ }
87
+ # assert_raises(Gurgitate::IllegalHeader,"Empty header contents") {
88
+ # h=Gurgitate::Header.new("From: ")
89
+ # }
90
+ assert_raises(Gurgitate::IllegalHeader,"Bad header syntax") {
91
+ h=Gurgitate::Header.new("This is completely wrong")
92
+ }
93
+ end
94
+ def test_extending_header
95
+ h=Gurgitate::Header.new("From: fromheader@example.com")
96
+ h << " (Dave Brown)"
97
+ assert_equal(h.name,"From","Extended header is From")
98
+ assert_equal(h.contents,"fromheader@example.com\n (Dave Brown)",
99
+ "Extended header contains all data")
100
+ assert_equal(h.contents,h.value,"Contents same as value")
101
+ end
102
+
103
+ def test_empty_header_with_extension
104
+ h=Gurgitate::Header.new("From:")
105
+ h << " fromheader@example.com"
106
+ assert_equal("From",h.name,"Empty extended header is From")
107
+ assert_equal("\n fromheader@example.com", h.contents,
108
+ "Empty extended header contains all data")
109
+ assert_equal(h.contents, h.value, "Contents same as value")
110
+ end
111
+
112
+ def test_changing_header
113
+ h=Gurgitate::Header.new("From: fromheader@example.com")
114
+ h.contents="anotherfromheader@example.com"
115
+ assert_equal(h.contents,"anotherfromheader@example.com",
116
+ "header contents contains new data")
117
+ end
118
+
119
+ def test_tabseparated_header
120
+ h=Gurgitate::Header.new("From:\tfromheader@example.com")
121
+ assert_equal(h.name,"From","Tabseparated header is From (bug#154)")
122
+ assert_equal(h.contents,"fromheader@example.com",
123
+ "Tabseparated header's contents are correct (bug#154)")
124
+ assert_equal(h.contents,h.value,"Contents same as value (bug#154)")
125
+ end
126
+
127
+ def test_conversion_to_String
128
+ h=Gurgitate::Header.new("From: fromheader@example.com")
129
+ assert_equal(h.to_s,"From: fromheader@example.com", "Conversion to string returns input")
130
+ end
131
+
132
+ def test_regex_match
133
+ h=Gurgitate::Header.new("From: fromheader@example.com")
134
+ assert_equal(0,h.matches(/fromheader/),"Matches regex that would match input")
135
+ assert_equal(nil,h.matches(/notininput/),"Does not match regex that would not match input")
136
+ end
137
+ end
138
+
139
+ class TC_Headers < Test::Unit::TestCase
140
+ def setup
141
+ require './gurgitate-mail'
142
+ end
143
+
144
+ def test_single_header
145
+ h=Gurgitate::Headers.new(<<'EOF'
146
+ From fromline@example.com Sat Sep 27 12:20:25 PDT 2003
147
+ From: fromheader@example.com
148
+ EOF
149
+ )
150
+ assert_equal("fromline@example.com",h.from)
151
+ assert_equal(1,h["From"].length)
152
+ assert_equal("From",h["From"][0].name)
153
+ assert_equal("fromheader@example.com",h["From"][0].contents)
154
+ end
155
+
156
+ def test_fromline_simple_username
157
+ h=Gurgitate::Headers.new(<<'EOF'
158
+ From fromline Sat Sep 27 12:20:25 PDT 2003
159
+ From: fromheader@example.com
160
+ EOF
161
+ )
162
+ assert_equal("fromline",h.from)
163
+ assert_equal(1,h["From"].length)
164
+ assert_equal("From",h["From"][0].name)
165
+ assert_equal("fromheader@example.com",h["From"][0].contents)
166
+ end
167
+
168
+ def test_fromline_no_username
169
+ h=Gurgitate::Headers.new(<<'EOF'
170
+ From Sat Sep 27 12:20:25 PDT 2003
171
+ From: fromheader@example.com
172
+ EOF
173
+ )
174
+ assert_equal("",h.from)
175
+ assert_equal(1,h["From"].length)
176
+ assert_equal("From",h["From"][0].name)
177
+ assert_equal("fromheader@example.com",h["From"][0].contents)
178
+ end
179
+
180
+ def test_missing_toline
181
+ h=Gurgitate::Headers.new(<<'EOF'
182
+ From: fromheader@example.com
183
+ EOF
184
+ )
185
+ assert_equal('fromheader@example.com',h.from)
186
+ assert_equal(1,h["From"].length)
187
+ assert_equal("From",h["From"][0].name)
188
+ assert_equal("fromheader@example.com",h["From"][0].contents)
189
+ end
190
+
191
+ def test_multiple_headers
192
+ h=Gurgitate::Headers.new(<<'EOF'
193
+ From fromline@example.com Sat Sep 27 12:20:25 PDT 2003
194
+ From: fromheader@example.com
195
+ To: toheader@example.com
196
+ Subject: Subject line
197
+ EOF
198
+ )
199
+ assert_equal(h.from,"fromline@example.com")
200
+ assert_equal(1,h["From"].length)
201
+ assert_equal("From",h["From"][0].name)
202
+ assert_equal("fromheader@example.com",h["From"][0].contents)
203
+ assert_equal(1,h["To"].length)
204
+ assert_equal("To",h["To"][0].name)
205
+ assert_equal("toheader@example.com",h["To"][0].contents)
206
+ assert_equal(1,h["Subject"].length)
207
+ assert_equal("Subject",h["Subject"][0].name)
208
+ assert_equal("Subject line",h["Subject"][0].contents)
209
+ end
210
+
211
+ def test_missing_fromline
212
+ h=Gurgitate::Headers.new(<<'EOF'
213
+ From: fromheader@example.com
214
+ To: toheader@example.com
215
+ Subject: Subject line
216
+ EOF
217
+ )
218
+ assert_equal('fromheader@example.com',h.from)
219
+ assert_equal(1,h["From"].length)
220
+ assert_equal("From",h["From"][0].name)
221
+ assert_equal("fromheader@example.com",h["From"][0].contents)
222
+ assert_equal(1,h["To"].length)
223
+ assert_equal("To",h["To"][0].name)
224
+ assert_equal("toheader@example.com",h["To"][0].contents)
225
+ assert_equal(1,h["Subject"].length)
226
+ assert_equal("Subject",h["Subject"][0].name)
227
+ assert_equal("Subject line",h["Subject"][0].contents)
228
+ end
229
+
230
+ def test_multiline_headers
231
+ h=Gurgitate::Headers.new(<<'EOF'
232
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
233
+ From: fromheader@example.com
234
+ To: toheader@example.com,
235
+ nexttoheader@example.com
236
+ Subject: Subject line
237
+ EOF
238
+ )
239
+ assert_equal(h.from,"fromline@example.com")
240
+ assert_equal(1,h["From"].length)
241
+ assert_equal("From",h["From"][0].name)
242
+ assert_equal("fromheader@example.com",h["From"][0].contents)
243
+ assert_equal(1,h["To"].length)
244
+ assert_equal("To",h["To"][0].name)
245
+ assert_equal("toheader@example.com,\n nexttoheader@example.com",h["To"][0].contents)
246
+ assert_equal(1,h["Subject"].length)
247
+ assert_equal("Subject",h["Subject"][0].name)
248
+ assert_equal("Subject line",h["Subject"][0].contents)
249
+ end
250
+
251
+ def test_multiline_headers_with_extra_colons
252
+ h=Gurgitate::Headers.new(<<'EOF'
253
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
254
+ From: fromheader@example.com
255
+ To: toheader@example.com,
256
+ nexttoheader@example.com (The test: header)
257
+ Subject: Subject line
258
+ EOF
259
+ )
260
+ assert_equal(h.from,"fromline@example.com")
261
+ assert_equal(1,h["From"].length)
262
+ assert_equal("From",h["From"][0].name)
263
+ assert_equal("fromheader@example.com",h["From"][0].contents)
264
+ assert_equal(1,h["To"].length)
265
+ assert_equal("To",h["To"][0].name)
266
+ assert_equal("toheader@example.com,\n nexttoheader@example.com (The test: header)",h["To"][0].contents)
267
+ assert_equal(1,h["Subject"].length)
268
+ assert_equal("Subject",h["Subject"][0].name)
269
+ assert_equal("Subject line",h["Subject"][0].contents)
270
+ end
271
+
272
+ def test_multiline_headers_with_various_levels_of_indentation
273
+ h=Gurgitate::Headers.new(<<'EOF'
274
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
275
+ From: fromheader@example.com
276
+ To: toheader@example.com,
277
+ nexttoheader@example.com,
278
+ thirdtoheader@example.com,
279
+ fourthtoheader@example.com
280
+ Subject: Subject line
281
+ EOF
282
+ )
283
+ assert_equal(h.from,"fromline@example.com")
284
+ assert_equal(1,h["From"].length)
285
+ assert_equal("From",h["From"][0].name)
286
+ assert_equal("fromheader@example.com",h["From"][0].contents)
287
+ assert_equal(1,h["To"].length)
288
+ assert_equal("To",h["To"][0].name)
289
+ assert_equal("toheader@example.com,\n nexttoheader@example.com,\n thirdtoheader@example.com,\n fourthtoheader@example.com",h["To"][0].contents)
290
+ assert_equal(1,h["Subject"].length)
291
+ assert_equal("Subject",h["Subject"][0].name)
292
+ assert_equal("Subject line",h["Subject"][0].contents)
293
+ end
294
+
295
+ def test_a_header_that_actually_crashed_gurgitate
296
+ h=Gurgitate::Headers.new(<<'EOF'
297
+ Return-path: <nifty-bounces@neurotica.com>
298
+ Received: from pd8mr3no.prod.shaw.ca
299
+ (pd8mr3no-qfe2.prod.shaw.ca [10.0.144.160]) by l-daemon
300
+ (iPlanet Messaging Server 5.2 HotFix 1.16 (built May 14 2003))
301
+ with ESMTP id <0HO6002FDGPREL@l-daemon> for dagbrown@shaw.ca; Tue,
302
+ 11 Nov 2003 00:56:15 -0700 (MST)
303
+ Received: from pd7mi4no.prod.shaw.ca ([10.0.149.117])
304
+ by l-daemon (iPlanet Messaging Server 5.2 HotFix 1.18 (built Jul 28 2003))
305
+ with ESMTP id <0HO60055LGPR40@l-daemon> for dagbrown@shaw.ca
306
+ (ORCPT dagbrown@shaw.ca); Tue, 11 Nov 2003 00:56:15 -0700 (MST)
307
+ Received: from venom.easydns.com (smtp.easyDNS.com [216.220.40.247])
308
+ by l-daemon (iPlanet Messaging Server 5.2 HotFix 1.18 (built Jul 28 2003))
309
+ with ESMTP id <0HO60079HGPR79@l-daemon> for dagbrown@shaw.ca; Tue,
310
+ 11 Nov 2003 00:56:15 -0700 (MST)
311
+ Received: from ohno.mrbill.net (ohno.mrbill.net [207.200.6.75])
312
+ by venom.easydns.com (Postfix) with ESMTP id D6493722BB for
313
+ <dagbrown@lart.ca>; Tue, 11 Nov 2003 02:53:50 -0500 (EST)
314
+ Received: by ohno.mrbill.net (Postfix) id ED0AD53380; Tue,
315
+ 11 Nov 2003 01:56:13 -0600 (CST)
316
+ Received: from mail.neurotica.com (neurotica.com [207.100.203.161])
317
+ by ohno.mrbill.net (Postfix) with ESMTP id 5CD465337F for
318
+ <dagbrown@dagbrown.com>; Tue, 11 Nov 2003 01:56:13 -0600 (CST)
319
+ Received: from mail.neurotica.com (localhost [127.0.0.1])
320
+ by mail.neurotica.com (Postfix) with ESMTP id CDAA2364C; Tue,
321
+ 11 Nov 2003 02:56:03 -0500 (EST)
322
+ Received: from smtpzilla5.xs4all.nl (smtpzilla5.xs4all.nl [194.109.127.141])
323
+ by mail.neurotica.com (Postfix) with ESMTP id B6A22361E for
324
+ <nifty@neurotica.com>; Tue, 11 Nov 2003 02:56:00 -0500 (EST)
325
+ Received: from xs1.xs4all.nl (xs1.xs4all.nl [194.109.21.2])
326
+ by smtpzilla5.xs4all.nl (8.12.9/8.12.9) with ESMTP id hAB7u5ZZ042116 for
327
+ <nifty@neurotica.com>; Tue, 11 Nov 2003 08:56:05 +0100 (CET)
328
+ Received: from xs1.xs4all.nl (wstan@localhost.xs4all.nl [127.0.0.1])
329
+ by xs1.xs4all.nl (8.12.10/8.12.9) with ESMTP id hAB7u5xE048677 for
330
+ <nifty@neurotica.com>; Tue,
331
+ 11 Nov 2003 08:56:05 +0100 (CET envelope-from wstan@xs4all.nl)
332
+ Received: (from wstan@localhost) by xs1.xs4all.nl (8.12.10/8.12.9/Submit)
333
+ id hAB7u4sZ048676 for nifty@neurotica.com; Tue,
334
+ 11 Nov 2003 08:56:04 +0100 (CET envelope-from wstan)
335
+ Date: Tue, 11 Nov 2003 08:56:04 +0100
336
+ From: William Staniewicz <wstan@xs4all.nl>
337
+ Subject: Re: [nifty] Ping...
338
+ In-reply-to: <9636B78C-140B-11D8-9EE6-003065D0C184@nimitzbrood.com>
339
+ Sender: nifty-bounces@neurotica.com
340
+ To: Nifty <nifty@neurotica.com>
341
+ Cc:
342
+ Errors-to: nifty-bounces@neurotica.com
343
+ Reply-to: Nifty <nifty@neurotica.com>
344
+ Message-id: <20031111075604.GE79497@xs4all.nl>
345
+ MIME-version: 1.0
346
+ Content-type: text/plain; charset=us-ascii
347
+ Content-disposition: inline
348
+ Precedence: list
349
+ X-BeenThere: nifty@mail.neurotica.com
350
+ Delivered-to: dagbrown@mrbill.net
351
+ Delivered-to: nifty@neurotica.com
352
+ User-Agent: Mutt/1.4.1i
353
+ X-Original-To: nifty@neurotica.com
354
+ References: <9636B78C-140B-11D8-9EE6-003065D0C184@nimitzbrood.com>
355
+ X-Mailman-Version: 2.1.2
356
+ List-Post: <mailto:nifty@mail.neurotica.com>
357
+ List-Subscribe: <http://mail.neurotica.com:8080/mailman/listinfo/nifty>,
358
+ <mailto:nifty-request@mail.neurotica.com?subject=subscribe>
359
+ List-Unsubscribe: <http://mail.neurotica.com:8080/mailman/listinfo/nifty>,
360
+ <mailto:nifty-request@mail.neurotica.com?subject=unsubscribe>
361
+ List-Help: <mailto:nifty-request@mail.neurotica.com?subject=help>
362
+ List-Id: Nifty <nifty.mail.neurotica.com>
363
+ Original-recipient: rfc822;dagbrown@shaw.ca
364
+ EOF
365
+ )
366
+ assert_equal(h.from,"nifty-bounces@neurotica.com")
367
+ assert_equal(1,h["From"].length)
368
+ assert_equal("From",h["From"][0].name)
369
+ assert_equal("William Staniewicz <wstan@xs4all.nl>",h["From"][0].contents)
370
+ assert_equal(1,h["To"].length)
371
+ assert_equal("To",h["To"][0].name)
372
+ assert_equal('Nifty <nifty@neurotica.com>',h["To"][0].contents)
373
+
374
+ assert_equal(1,h["Subject"].length)
375
+ assert_equal("Subject",h["Subject"][0].name)
376
+ assert_equal("Re: [nifty] Ping...",h["Subject"][0].contents)
377
+ end
378
+
379
+ def another_crashy_test
380
+ h=Gurgitate::Headers.new(<<'EOF'
381
+ From HEYITBLEWUP Fri Nov 21 14:41:08 PST 2003
382
+ Received: from unknown (harley.radius [192.168.0.123]) by yoda.radius with SMTP (Microsoft Exchange Internet Mail Service Version 5.5.2653.13)
383
+ id LYN7YZKG; Wed, 9 Jul 2003 14:36:40 -0700
384
+ Subject: IAP password
385
+ EOF
386
+ )
387
+ assert_equal(h.from,"HEYITBLEWUP")
388
+ assert_equal(1,h["From"].length)
389
+ assert_equal("From",h["From"][0].name)
390
+ assert_equal("IAP password",h["Subject"][0].name)
391
+ end
392
+
393
+ def test_fromline_no_hostname # illegal from line
394
+ m=<<'EOF'
395
+ From HEYITBLEWUP Sat Mar 27 16:02:12 PST 2004
396
+ Received: from ohno.mrbill.net (ohno.mrbill.net [207.200.6.75])
397
+ by lart.ca (Postfix) with ESMTP id A485F104CA9
398
+ for <dagbrown@lart.ca>; Sat, 27 Mar 2004 15:58:06 -0800 (PST)
399
+ Received: by ohno.mrbill.net (Postfix)
400
+ id 0D3423A289; Sat, 27 Mar 2004 17:58:42 -0600 (CST)
401
+ Delivered-To: dagbrown@mrbill.net
402
+ Received: from 66-168-59-126.jvl.wi.charter.com (66-168-59-126.jvl.wi.charter.com [66.168.59.126])
403
+ by ohno.mrbill.net (Postfix) with SMTP id 948BD3A288
404
+ for <dagbrown@dagbrown.com>; Sat, 27 Mar 2004 17:58:41 -0600 (CST)
405
+ X-Message-Info: HOCBSQX
406
+ Message-Id: <20040327235841.948BD3A288@ohno.mrbill.net>
407
+ Date: Sat, 27 Mar 2004 17:58:41 -0600 (CST)
408
+ From: ""@
409
+ To: undisclosed-recipients: ;
410
+ EOF
411
+ h=Gurgitate::Headers.new(m)
412
+ assert_equal(%{""@},h["From"][0].contents)
413
+ assert_equal(1,h["From"].length)
414
+ end
415
+
416
+ def test_editing_header
417
+ m = <<'EOF'
418
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
419
+ From: fromline@example.com
420
+ To: toline@example.com
421
+ Subject: Subject line
422
+ EOF
423
+ h=Gurgitate::Headers.new(m)
424
+ h["From"]="anotherfromline@example.com"
425
+ assert_equal("anotherfromline@example.com",h["From"][0].contents,
426
+ "From line correctly changed")
427
+ assert_match(/^From: anotherfromline@example.com$/,h.to_s,
428
+ "From line correctly turns up in finished product")
429
+ end
430
+
431
+ def test_editing_from
432
+ m = <<'EOF'
433
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
434
+ From: fromline@example.com
435
+ To: toline@example.com
436
+ Subject: Subject line
437
+ EOF
438
+ h=Gurgitate::Headers.new(m)
439
+ t=Time.new.to_s
440
+ h.from="anotherfromline@example.com"
441
+ assert_equal("anotherfromline@example.com",h.from,
442
+ "Envelope from correctly changed")
443
+ assert_match(/^From anotherfromline@example.com #{t}/,
444
+ h.to_mbox, "Envelope from changed in finished product")
445
+ end
446
+
447
+ def test_match_multiple_headers
448
+ m = <<'EOF'
449
+ From fromline@example.com Sat Oct 25 12:58:31 PDT 2003
450
+ From: fromline@example.com
451
+ To: toline@example.com
452
+ Subject: Subject line
453
+ EOF
454
+ h=Gurgitate::Headers.new(m)
455
+ assert_equal(true,h["From","To"] =~ /fromline@example.com/,
456
+ "headers contains fromline")
457
+ assert_equal(true,h["From","To"] =~ /toline@example.com/,
458
+ "headers contains toline")
459
+ assert_equal(false,h["From","To"] =~ /nonexistent@example.com/,
460
+ "headers do not contain nonexistent value")
461
+ assert_equal(false,h["Rabbit"] =~ /nonexistent/,
462
+ "Asking for a nonexistent header")
463
+ end
464
+
465
+ def test_broken_spam
466
+ m=<<'EOF'
467
+ Return-Path: kirstenparsonsry@yahoo.com
468
+ Delivery-Date: Fri May 21 19:42:02 PDT
469
+ Return-Path: kirstenparsonsry@yahoo.com
470
+ Delivery-Date: Fri May 21 17:39:51 2004
471
+ Return-Path: <kirstenparsonsry@yahoo.com>
472
+ X-Original-To: dagbrown@lart.ca
473
+ Delivered-To: dagbrown@lart.ca
474
+ Received: from anest.co.jp (c-24-1-221-189.client.comcast.net [24.1.221.189])
475
+ by lart.ca (Postfix) with ESMTP id 05B7F5704
476
+ for <dagbrown@lart.ca>; Fri, 21 May 2004 17:39:51 -0700 (PDT)
477
+ Message-ID: <NKELFLPJDPLDHJCMGFHDFEKLLNAA.kirstenparsonsry@yahoo.com>
478
+ From: "Kirsten Parsons" <kirstenparsonsry@yahoo.com>
479
+ To: dagbrown@lart.ca
480
+ Subject: Congrats!
481
+ Date: Fri, 21 May 2004 20:56:27 +0000
482
+ MIME-Version: 1.0
483
+ Content-Type: text/plain
484
+ Content-Transfer-Encoding: base64
485
+ EOF
486
+ h=Gurgitate::Headers.new(m)
487
+
488
+ assert_equal(Gurgitate::Header.new("To","dagbrown@lart.ca").contents,
489
+ h["To"][0].contents,"To header is as expected")
490
+
491
+ assert_equal(false,h["To","Cc"] =~ /\blunar@lunar-linux.org\b/i,
492
+ "There should be no Lunar Linux mailing list here")
493
+
494
+ assert_equal(false,h["To"] =~ /\blunar@lunar-linux.org\b/i,
495
+ "There should be no Lunar Linux mailing list in To line")
496
+ assert_equal(false,h["Cc"] =~ /\blunar@lunar-linux.org\b/i,
497
+ "There should be no Lunar Linux mailing list in Cc line")
498
+ end
499
+ end
500
+
501
+ class TC_Process < Test::Unit::TestCase
502
+ def setup
503
+ m = StringIO.new("From: me\nTo: you\nSubject: test\n\nHi.")
504
+ @gurgitate = Gurgitate::Gurgitate.new(m)
505
+ end
506
+
507
+ def test_1
508
+ assert_nothing_raised do
509
+ @gurgitate.process { break }
510
+ @gurgitate.process { pipe('cat > /dev/null') }
511
+ @gurgitate.process { return }
512
+ end
513
+ end
514
+ end
515
+
516
+ def runtests
517
+ Test::Unit::UI::Console::TestRunner.run(TC_Header)
518
+ Test::Unit::UI::Console::TestRunner.run(TC_Headers)
519
+ Test::Unit::UI::Console::TestRunner.run(TC_Process)
520
+ end
521
+
522
+ if __FILE__ == $0 then
523
+ if(ARGV[0] == '-c')
524
+ require 'coverage'
525
+ end
526
+ runtests
527
+ end