gurgitate-mail 1.10.0 → 1.10.1
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/gurgitate-mail +0 -0
- data/lib/gurgitate-mail.rb +107 -15
- data/lib/gurgitate/deliver/maildir.rb +4 -4
- data/lib/gurgitate/deliver/mbox.rb +5 -5
- data/lib/gurgitate/deliver/mh.rb +15 -15
- data/lib/gurgitate/headers.rb +10 -7
- data/lib/gurgitate/mail_headers.rb +166 -0
- data/lib/gurgitate/mailmessage.rb +56 -27
- data/lib/gurgitate/message.rb +113 -0
- data/test/gurgitate-test.rb +7 -1
- data/test/runtests.rb +16 -7
- data/test/test_configuration.rb +6 -0
- data/test/test_deliver.rb +6 -0
- data/test/test_delivery.rb +6 -0
- data/test/test_execute_rules.rb +43 -0
- data/test/test_gurgitate_delivery.rb +52 -57
- data/test/test_header.rb +19 -7
- data/test/test_headers.rb +60 -84
- data/test/test_headers_creating_from_hash.rb +18 -0
- data/test/test_headers_meddling_with_headers.rb +57 -0
- data/test/test_mail_headers.rb +429 -0
- data/test/test_mail_headers_meddling_with_headers.rb +60 -0
- data/test/test_process.rb +2 -1
- data/test/test_rules.rb +9 -1
- data/test/test_writing.rb +1 -2
- metadata +60 -44
data/bin/gurgitate-mail
CHANGED
File without changes
|
data/lib/gurgitate-mail.rb
CHANGED
@@ -9,10 +9,61 @@ require 'gurgitate/mailmessage'
|
|
9
9
|
require 'gurgitate/deliver'
|
10
10
|
|
11
11
|
module Gurgitate
|
12
|
-
|
13
|
-
#
|
14
|
-
# other stuff with it, like save to a mailbox or forward
|
12
|
+
# This is the actual gurgitator; it reads a message and then it can
|
13
|
+
# do other stuff with it, like saving it to a mailbox or forwarding
|
15
14
|
# it somewhere else.
|
15
|
+
#
|
16
|
+
# To set configuration parameters for gurgitate-mail, use a keyword-
|
17
|
+
# based system. It's almost like an attribute, only if you give the
|
18
|
+
# accessor a parameter, it will set the configuration parameter to
|
19
|
+
# the parameter's value. For instance:
|
20
|
+
#
|
21
|
+
# maildir "#{homedir}/Mail"
|
22
|
+
# sendmail "/usr/sbin/sendmail"
|
23
|
+
# spoolfile "Maildir"
|
24
|
+
# spooldir homedir
|
25
|
+
#
|
26
|
+
# (This is because of an oddity in Ruby where, even if an
|
27
|
+
# accessor exists in the current object, if you say:
|
28
|
+
# name = value
|
29
|
+
# it'll always create a local variable. Not quite what you
|
30
|
+
# want when you're trying to set a config parameter. You have
|
31
|
+
# to say <code>self.name = value</code>, which [I think] is ugly.
|
32
|
+
#
|
33
|
+
# In the interests of promoting harmony, of course,
|
34
|
+
# <code>self.name = value</code> still works.)
|
35
|
+
#
|
36
|
+
# The attributes you can define are:
|
37
|
+
#
|
38
|
+
# homedir :: Your home directory. This defaults to what your
|
39
|
+
# actual home directory is.
|
40
|
+
#
|
41
|
+
# maildir :: The directory you store your mail in. This defaults
|
42
|
+
# to the "Mail" directory in your home dir.
|
43
|
+
#
|
44
|
+
# logfile :: The path to your gurgitate-mail log file. If you
|
45
|
+
# set this to +nil+, gurgitate-mail won't log anything.
|
46
|
+
# The default value is ".gurgitate.log" in your home
|
47
|
+
# directory.
|
48
|
+
#
|
49
|
+
# The following parameters are more likely to be interesting to the
|
50
|
+
# system administrator than the everyday user.
|
51
|
+
#
|
52
|
+
# sendmail :: The full path of your "sendmail" program, or at least
|
53
|
+
# a program that provides functionality equivalent to
|
54
|
+
# sendmail.
|
55
|
+
#
|
56
|
+
# spoolfile :: The default location to store mail messages, for the
|
57
|
+
# messages that have been unaffected by your gurgitate
|
58
|
+
# rules. If an exception is raised by your rules, the
|
59
|
+
# message will be delivered to the spoolfile.
|
60
|
+
#
|
61
|
+
# spooldir :: The location where users' system mail boxes live.
|
62
|
+
#
|
63
|
+
# folderstyle :: The style of mailbox to create (and to expect,
|
64
|
+
# although gurgitate-mail automatically detects the
|
65
|
+
# type of existing mailboxes). See the separate
|
66
|
+
# documentation for folderstyle for more details.
|
16
67
|
class Gurgitate < Mailmessage
|
17
68
|
include Deliver
|
18
69
|
|
@@ -36,7 +87,7 @@ module Gurgitate
|
|
36
87
|
# syntax will continue to work.
|
37
88
|
def self.attr_configparam(*syms)
|
38
89
|
syms.each do |sym|
|
39
|
-
class_eval
|
90
|
+
class_eval %{
|
40
91
|
def #{sym} (*vals)
|
41
92
|
if vals.length == 1
|
42
93
|
@#{sym} = vals[0]
|
@@ -53,7 +104,7 @@ module Gurgitate
|
|
53
104
|
# old-style accessors though. Breaking people's
|
54
105
|
# .gurgitate-rules is a bad idea.
|
55
106
|
attr_writer :#{sym}
|
56
|
-
|
107
|
+
}
|
57
108
|
end
|
58
109
|
end
|
59
110
|
|
@@ -78,7 +129,36 @@ module Gurgitate
|
|
78
129
|
# What kind of mailboxes you prefer
|
79
130
|
# attr_configparam :folderstyle
|
80
131
|
|
81
|
-
# What kind of mailboxes you prefer
|
132
|
+
# What kind of mailboxes you prefer. Treat this like a
|
133
|
+
# configuration parameter. If no argument is given, then
|
134
|
+
# return the current default type.
|
135
|
+
#
|
136
|
+
# Depending on what you set this to, some other configuration
|
137
|
+
# parameters change. You can set this to the following things:
|
138
|
+
#
|
139
|
+
# <code>Maildir</code> :: Create Maildir mailboxes.
|
140
|
+
#
|
141
|
+
# This sets +spooldir+ to your home
|
142
|
+
# directory, +spoolfile+ to
|
143
|
+
# $HOME/Maildir and creates
|
144
|
+
# mail folders underneath that.
|
145
|
+
#
|
146
|
+
# <code>MH</code> :: Create MH mail boxes.
|
147
|
+
#
|
148
|
+
# This reads your <code>.mh_profile</code>
|
149
|
+
# file to find out where you've told MH to
|
150
|
+
# find its mail folders, and uses that value.
|
151
|
+
# If it can't find that in your .mh_profile,
|
152
|
+
# it will assume you want mailboxes in
|
153
|
+
# $HOME/Mail. It sets +spoolfile+ to
|
154
|
+
# "inbox" in your mail directory.
|
155
|
+
#
|
156
|
+
# <code>Mbox</code> :: Create +mbox+ mailboxes.
|
157
|
+
#
|
158
|
+
# This sets +spooldir+ to
|
159
|
+
# <code>/var/spool/mail</code> and
|
160
|
+
# +spoolfile+ to a file with your username
|
161
|
+
# in <code>/var/spool/mail</code>.
|
82
162
|
def folderstyle(*style)
|
83
163
|
if style.length == 0 then
|
84
164
|
@folderstyle
|
@@ -141,7 +221,7 @@ module Gurgitate
|
|
141
221
|
instance_eval(&block) if block_given?
|
142
222
|
end
|
143
223
|
|
144
|
-
def add_rules(filename, options = {})
|
224
|
+
def add_rules(filename, options = {}) #:nodoc:
|
145
225
|
if not Hash === options
|
146
226
|
raise ArgumentError.new("Expected hash of options")
|
147
227
|
end
|
@@ -171,15 +251,16 @@ module Gurgitate
|
|
171
251
|
end
|
172
252
|
end
|
173
253
|
|
174
|
-
# Deletes the current message.
|
254
|
+
# Deletes (discards) the current message.
|
175
255
|
def delete
|
176
256
|
# Well, nothing here, really.
|
177
257
|
end
|
178
258
|
|
179
|
-
# This is
|
180
|
-
#
|
181
|
-
# x_face and that gets it for you. It
|
182
|
-
# that header isn't found.
|
259
|
+
# This is kind of neat. You can get a header by calling its
|
260
|
+
# name as a method. For example, if you want the header
|
261
|
+
# "X-Face", then you call x_face and that gets it for you. It
|
262
|
+
# raises NameError if that header isn't found.
|
263
|
+
#
|
183
264
|
# meth::
|
184
265
|
# The method that the caller tried to call which isn't
|
185
266
|
# handled any other way.
|
@@ -193,6 +274,7 @@ module Gurgitate
|
|
193
274
|
end
|
194
275
|
|
195
276
|
# Forwards the message to +address+.
|
277
|
+
#
|
196
278
|
# address::
|
197
279
|
# A valid email address to forward the message to.
|
198
280
|
def forward(address)
|
@@ -204,7 +286,7 @@ module Gurgitate
|
|
204
286
|
|
205
287
|
# Writes +message+ to the log file.
|
206
288
|
def log(message)
|
207
|
-
if
|
289
|
+
if @logfile then
|
208
290
|
File.open(@logfile,"a") do |f|
|
209
291
|
f.flock(File::LOCK_EX)
|
210
292
|
f.print(Time.new.to_s+" "+message+"\n")
|
@@ -225,6 +307,17 @@ module Gurgitate
|
|
225
307
|
|
226
308
|
# Pipes the message through +program+, and returns another
|
227
309
|
# +Gurgitate+ object containing the output of the filter
|
310
|
+
#
|
311
|
+
# Use it like this:
|
312
|
+
#
|
313
|
+
# filter "bogofilter -p" do
|
314
|
+
# if x_bogosity =~ /Spam/ then
|
315
|
+
# log "Found spam"
|
316
|
+
# delete
|
317
|
+
# return
|
318
|
+
# end
|
319
|
+
# end
|
320
|
+
#
|
228
321
|
def filter(program,&block)
|
229
322
|
self.log "Filtering with "+program
|
230
323
|
IO.popen("-","w+") do |filter|
|
@@ -255,8 +348,7 @@ module Gurgitate
|
|
255
348
|
end
|
256
349
|
end
|
257
350
|
|
258
|
-
|
259
|
-
def process(&block)
|
351
|
+
def process(&block) #:nodoc:
|
260
352
|
begin
|
261
353
|
if @rules.size > 0 or block
|
262
354
|
@rules.each do |configfilespec|
|
@@ -15,7 +15,7 @@ module Gurgitate
|
|
15
15
|
# message to. If it is of the form "=mailbox", it saves
|
16
16
|
# the message to +Maildir+/+mailbox+. Otherwise, it
|
17
17
|
# simply saves the message to the file +mailbox+.
|
18
|
-
def self::check_mailbox
|
18
|
+
def self::check_mailbox mailbox
|
19
19
|
begin
|
20
20
|
if File.stat(mailbox).directory? then
|
21
21
|
if File.stat(File.join(mailbox,"cur")).directory? then
|
@@ -33,7 +33,7 @@ module Gurgitate
|
|
33
33
|
# One of "+mailbox+/tmp" or "+mailbox+/new", but that's
|
34
34
|
# only because that's what the maildir spec
|
35
35
|
# (http://cr.yp.to/proto/maildir.html) says.
|
36
|
-
def maildir_getfilename
|
36
|
+
def maildir_getfilename dir
|
37
37
|
time=Time.now.to_f
|
38
38
|
counter=0
|
39
39
|
hostname=Socket::gethostname
|
@@ -50,7 +50,7 @@ module Gurgitate
|
|
50
50
|
# Creates a new Maildir folder +mailbox+
|
51
51
|
# mailbox::
|
52
52
|
# The full path of the new folder to be created
|
53
|
-
def make_mailbox
|
53
|
+
def make_mailbox mailbox
|
54
54
|
Dir.mkdir(mailbox)
|
55
55
|
%w{cur tmp new}.each do |dir|
|
56
56
|
Dir.mkdir(File.join(mailbox,dir))
|
@@ -63,7 +63,7 @@ module Gurgitate
|
|
63
63
|
# message to. If it is of the form "=mailbox", it saves
|
64
64
|
# the message to +Maildir+/+mailbox+. Otherwise, it
|
65
65
|
# simply saves the message to the file +mailbox+.
|
66
|
-
def deliver_message
|
66
|
+
def deliver_message mailbox
|
67
67
|
begin
|
68
68
|
File.stat(mailbox)
|
69
69
|
rescue Errno::ENOENT
|
@@ -13,10 +13,10 @@ module Gurgitate
|
|
13
13
|
# the message to. If it is of the form "=mailbox", it
|
14
14
|
# saves the message to +Maildir+/+mailbox+. Otherwise,
|
15
15
|
# it simply saves the message to the file +mailbox+.
|
16
|
-
def self::check_mailbox
|
16
|
+
def self::check_mailbox mailbox
|
17
17
|
|
18
18
|
begin
|
19
|
-
if File.stat(mailbox).file? then
|
19
|
+
if File.stat(mailbox).file? then
|
20
20
|
return MBox
|
21
21
|
else
|
22
22
|
return nil
|
@@ -32,9 +32,9 @@ module Gurgitate
|
|
32
32
|
# the message to. If it is of the form "=mailbox", it
|
33
33
|
# saves the message to +Maildir+/+mailbox+. Otherwise,
|
34
34
|
# it simply saves the message to the file +mailbox+.
|
35
|
-
def deliver_message
|
36
|
-
File.open(mailbox,File::WRONLY |
|
37
|
-
File::APPEND |
|
35
|
+
def deliver_message mailbox
|
36
|
+
File.open(mailbox,File::WRONLY |
|
37
|
+
File::APPEND |
|
38
38
|
File::CREAT) do |f|
|
39
39
|
f.flock(File::LOCK_EX)
|
40
40
|
message=(if f.stat.size > 0 then "\n" else "" end) + to_mbox
|
data/lib/gurgitate/deliver/mh.rb
CHANGED
@@ -15,7 +15,7 @@ module Gurgitate
|
|
15
15
|
# the message to. If it is of the form "=mailbox", it
|
16
16
|
# saves the message to +Maildir+/+mailbox+. Otherwise,
|
17
17
|
# it simply saves the message to the file +mailbox+.
|
18
|
-
def self::check_mailbox
|
18
|
+
def self::check_mailbox mailbox
|
19
19
|
begin
|
20
20
|
# Rather annoyingly, pretty well any directory can
|
21
21
|
# be a MH mailbox, but this just checks to make sure
|
@@ -24,10 +24,10 @@ module Gurgitate
|
|
24
24
|
# I could put in a check for the path given in
|
25
25
|
# $HOME/.mh_profile, but Claws-Mail uses MH mailboxes and
|
26
26
|
# disregards $HOME/.mh_profile.
|
27
|
-
if File.stat(mailbox).directory? and
|
28
|
-
not ( File.exists?(File.join(mailbox, "cur")) or
|
29
|
-
File.exists?(File.join(mailbox, "tmp")) or
|
30
|
-
File.exists?(File.join(mailbox, "new"))) then
|
27
|
+
if File.stat(mailbox).directory? and
|
28
|
+
not ( File.exists?(File.join(mailbox, "cur")) or
|
29
|
+
File.exists?(File.join(mailbox, "tmp")) or
|
30
|
+
File.exists?(File.join(mailbox, "new"))) then
|
31
31
|
return MH
|
32
32
|
end
|
33
33
|
rescue Errno::ENOENT
|
@@ -41,7 +41,7 @@ module Gurgitate
|
|
41
41
|
# the message to. If it is of the form "=mailbox", it
|
42
42
|
# saves the message to +Maildir+/+mailbox+. Otherwise,
|
43
43
|
# it simply saves the message to the file +mailbox+.
|
44
|
-
def deliver_message
|
44
|
+
def deliver_message mailbox
|
45
45
|
if ! File.exists? mailbox then
|
46
46
|
Dir.mkdir(mailbox)
|
47
47
|
end
|
@@ -59,16 +59,16 @@ module Gurgitate
|
|
59
59
|
|
60
60
|
private
|
61
61
|
|
62
|
-
def update_sequences
|
62
|
+
def update_sequences mailbox, msgnum
|
63
63
|
sequences = File.join(mailbox, ".mh_sequences")
|
64
64
|
lockfile = sequences + ".lock" # how quaint
|
65
65
|
loop do
|
66
66
|
begin
|
67
|
-
File.open(lockfile,
|
68
|
-
File::WRONLY |
|
69
|
-
File::CREAT |
|
67
|
+
File.open(lockfile,
|
68
|
+
File::WRONLY |
|
69
|
+
File::CREAT |
|
70
70
|
File::EXCL ) do |lock|
|
71
|
-
File.open(sequences,
|
71
|
+
File.open(sequences,
|
72
72
|
File::RDWR | File::CREAT) do |seq|
|
73
73
|
|
74
74
|
seq.flock(File::LOCK_EX)
|
@@ -91,7 +91,7 @@ module Gurgitate
|
|
91
91
|
rescue Errno::EEXIST
|
92
92
|
# some other process is doing something, so wait a few
|
93
93
|
# milliseconds until it's done
|
94
|
-
sleep(0.01)
|
94
|
+
sleep(0.01)
|
95
95
|
end
|
96
96
|
end
|
97
97
|
end
|
@@ -115,13 +115,13 @@ module Gurgitate
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
-
def next_message
|
118
|
+
def next_message mailbox
|
119
119
|
next_msgnum = Dir.open(mailbox).map { |ent| ent.to_i }.max + 1
|
120
120
|
loop do
|
121
121
|
begin
|
122
122
|
File.open(File.join(mailbox, next_msgnum.to_s),
|
123
|
-
File::WRONLY |
|
124
|
-
File::CREAT |
|
123
|
+
File::WRONLY |
|
124
|
+
File::CREAT |
|
125
125
|
File::EXCL ) do |filehandle|
|
126
126
|
yield filehandle
|
127
127
|
end
|
data/lib/gurgitate/headers.rb
CHANGED
@@ -22,7 +22,7 @@ module Gurgitate
|
|
22
22
|
|
23
23
|
def sub(regex, replacement)
|
24
24
|
::Gurgitate::HeaderBag.new(
|
25
|
-
|
25
|
+
clone.map do |header|
|
26
26
|
::Gurgitate::Header.new(
|
27
27
|
"#{header.name}: " + header.contents.sub(regex,
|
28
28
|
replacement)
|
@@ -47,7 +47,7 @@ module Gurgitate
|
|
47
47
|
# Figures out whether the first line of a mail message is an
|
48
48
|
# mbox-style "From " line (say, if you get this from sendmail),
|
49
49
|
# or whether it's just a normal header.
|
50
|
-
# --
|
50
|
+
# --
|
51
51
|
# If you run "fetchmail" with the -m option to feed the
|
52
52
|
# mail message straight to gurgitate, skipping the "local
|
53
53
|
# MTA" step, then it doesn't have a "From " line. So I
|
@@ -121,7 +121,8 @@ module Gurgitate
|
|
121
121
|
@from=$+
|
122
122
|
end
|
123
123
|
else
|
124
|
-
fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+
|
124
|
+
fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+
|
125
|
+
)/
|
125
126
|
if self["Return-Path"] != nil then
|
126
127
|
fromregex.match(self["Return-Path"][0].contents)
|
127
128
|
else
|
@@ -135,12 +136,14 @@ module Gurgitate
|
|
135
136
|
# assume that it's local mail, and doesn't have an @ in its
|
136
137
|
# address.
|
137
138
|
unless address_candidate
|
138
|
-
if self["Return-Path"]
|
139
|
+
if self["Return-Path"] then
|
139
140
|
self["Return-Path"][0].contents =~ /(\S+)/
|
140
141
|
address_candidate=$+
|
141
142
|
else
|
142
|
-
self["From"]
|
143
|
-
|
143
|
+
if self["From"] then
|
144
|
+
self["From"][0].contents =~ /(\S+)/
|
145
|
+
address_candidate=$+
|
146
|
+
end
|
144
147
|
end
|
145
148
|
end
|
146
149
|
|
@@ -222,7 +225,7 @@ module Gurgitate
|
|
222
225
|
# Change the envelope from line to whatever you want. This might
|
223
226
|
# not be particularly neighborly, but oh well.
|
224
227
|
# newfrom:: An email address
|
225
|
-
def from=(newfrom)
|
228
|
+
def from=(newfrom)
|
226
229
|
@from=newfrom
|
227
230
|
@unix_from="From "+self.from+" "+Time.new.to_s
|
228
231
|
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
#!/opt/bin/ruby -w
|
2
|
+
|
3
|
+
require "gurgitate/headers"
|
4
|
+
|
5
|
+
module Gurgitate
|
6
|
+
class IllegalHeader < RuntimeError ; end
|
7
|
+
|
8
|
+
# ========================================================================
|
9
|
+
|
10
|
+
# A slightly bigger class for all of a message's headers
|
11
|
+
class MailHeaders < Headers
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Figures out whether the first line of a mail message is an
|
16
|
+
# mbox-style "From " line (say, if you get this from sendmail),
|
17
|
+
# or whether it's just a normal header.
|
18
|
+
# --
|
19
|
+
# If you run "fetchmail" with the -m option to feed the
|
20
|
+
# mail message straight to gurgitate, skipping the "local
|
21
|
+
# MTA" step, then it doesn't have a "From " line. So I
|
22
|
+
# have to deal with that by hand. First, check to see if
|
23
|
+
# there's a "From " line present in the first place.
|
24
|
+
def figure_out_from_line(headertext)
|
25
|
+
(unix_from,normal_headers) = headertext.split(/\n/,2)
|
26
|
+
|
27
|
+
if unix_from =~ /^From / then
|
28
|
+
headertext=normal_headers
|
29
|
+
unix_from=unix_from
|
30
|
+
else
|
31
|
+
# If there isn't, then deal with it after we've
|
32
|
+
# worried about the rest of the headers, 'cos we'll
|
33
|
+
# have to make our own.
|
34
|
+
unix_from=nil
|
35
|
+
end
|
36
|
+
return unix_from, headertext
|
37
|
+
end
|
38
|
+
|
39
|
+
# Get the envelope From information. This comes with a
|
40
|
+
# whole category of rants: this information is absurdly hard
|
41
|
+
# to get your hands on. The best you can manage is a sort
|
42
|
+
# of educated guess. Thus, this horrible glob of hackiness.
|
43
|
+
# I don't recommend looking too closely at this code if you
|
44
|
+
# can avoid it, and further I recommend making sure to
|
45
|
+
# configure your MTA so that it sends proper sender and
|
46
|
+
# recipient information to gurgitate so that this code never
|
47
|
+
# has to be run at all.
|
48
|
+
def guess_sender
|
49
|
+
# Start by worrying about the "From foo@bar" line. If it's
|
50
|
+
# not there, then make one up from the Return-Path: header.
|
51
|
+
# If there isn't a "Return-Path:" header (then I suspect we
|
52
|
+
# have bigger problems, but still) then use From: as a wild
|
53
|
+
# guess. If I hope that this entire lot of code doesn't get
|
54
|
+
# used, then I _particularly_ hope that things never get so
|
55
|
+
# bad that poor gurgitate has to use the From: header as a
|
56
|
+
# source of authoritative information on anything.
|
57
|
+
#
|
58
|
+
# And then after all that fuss, if we're delivering to a
|
59
|
+
# Maildir, I have to get rid of it. And sometimes the MTA
|
60
|
+
# gives me a mbox-style From line and sometimes it doesn't.
|
61
|
+
# It's annoying, but I have no choice but to Just Deal With
|
62
|
+
# It.
|
63
|
+
if @unix_from then
|
64
|
+
# If it is there, then grab the email address in it and
|
65
|
+
# use that as our official "from".
|
66
|
+
fromregex=/^From ([^ ]+@[^ ]+) /
|
67
|
+
fromregex.match(@unix_from)
|
68
|
+
@from=$+
|
69
|
+
|
70
|
+
# or maybe it's local
|
71
|
+
if @from == nil then
|
72
|
+
@unix_from =~ /^From (\S+) /
|
73
|
+
@from=$+
|
74
|
+
end
|
75
|
+
else
|
76
|
+
fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+)/
|
77
|
+
if self["Return-Path"] != nil then
|
78
|
+
fromregex.match(self["Return-Path"][0].contents)
|
79
|
+
else
|
80
|
+
if self["From"] != nil then
|
81
|
+
fromregex.match(self["From"][0].contents)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
address_candidate=$+
|
85
|
+
|
86
|
+
# If there STILL isn't a match, then it's probably safe to
|
87
|
+
# assume that it's local mail, and doesn't have an @ in its
|
88
|
+
# address.
|
89
|
+
unless address_candidate
|
90
|
+
if self["Return-Path"] != nil then
|
91
|
+
self["Return-Path"][0].contents =~ /(\S+)/
|
92
|
+
address_candidate=$+
|
93
|
+
else
|
94
|
+
self["From"][0].contents =~ /(\S+)/
|
95
|
+
address_candidate=$+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
@from=address_candidate
|
100
|
+
|
101
|
+
@unix_from="From "+self.from+" "+Time.new.to_s
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
public
|
106
|
+
|
107
|
+
# Creates a MailHeaders object.
|
108
|
+
# headertext::
|
109
|
+
# The text of the message headers.
|
110
|
+
def initialize(headertext=nil, sender=nil, recipient=nil)
|
111
|
+
@from = sender
|
112
|
+
@to = recipient
|
113
|
+
@headers = Hash.new(nil)
|
114
|
+
|
115
|
+
if Hash === headertext
|
116
|
+
@headers_changed = true
|
117
|
+
headertext.each_key do |key|
|
118
|
+
|
119
|
+
headername = key.to_s.gsub("_","-")
|
120
|
+
|
121
|
+
header=Header.new(headername, headertext[key])
|
122
|
+
@headers[header.name] ||= HeaderBag.new
|
123
|
+
@headers[header.name].push(header)
|
124
|
+
end
|
125
|
+
else
|
126
|
+
if headertext
|
127
|
+
@unix_from, @headertext = figure_out_from_line headertext
|
128
|
+
parse_headers if @headertext
|
129
|
+
|
130
|
+
if sender # then don't believe the mbox separator
|
131
|
+
@from = sender
|
132
|
+
@unix_from="From "+self.from+" "+Time.new.to_s
|
133
|
+
else
|
134
|
+
guess_sender
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Who the message is to (the envelope to)
|
141
|
+
#
|
142
|
+
# Yet another bucket of rants. Unix mail sucks.
|
143
|
+
def to
|
144
|
+
return @to || @headers["X-Original-To"] || nil
|
145
|
+
end
|
146
|
+
|
147
|
+
# Who the message is from (the envelope from)
|
148
|
+
def from
|
149
|
+
return @from || ""
|
150
|
+
end
|
151
|
+
|
152
|
+
# Change the envelope from line to whatever you want. This might
|
153
|
+
# not be particularly neighborly, but oh well.
|
154
|
+
# newfrom:: An email address
|
155
|
+
def from=(newfrom)
|
156
|
+
@from=newfrom
|
157
|
+
@unix_from="From "+self.from+" "+Time.new.to_s
|
158
|
+
end
|
159
|
+
|
160
|
+
# Returns the headers properly formatted for an mbox-format
|
161
|
+
# email message.
|
162
|
+
def to_mbox
|
163
|
+
return @unix_from+"\n"+to_s
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|