gurgitate-mail 1.8.4 → 1.8.5

Sign up to get free protection for your applications and to get access to all the features.
data/bin/gurgitate-mail CHANGED
@@ -47,4 +47,8 @@ else
47
47
  gurgitate.add_rules(GLOBAL_RULES_POST, :system => true)
48
48
  end
49
49
 
50
- gurgitate.process
50
+ begin
51
+ gurgitate.process
52
+ rescue
53
+ exit 75
54
+ end
@@ -36,7 +36,7 @@ module Gurgitate
36
36
  # syntax will continue to work.
37
37
  def self.attr_configparam(*syms)
38
38
  syms.each do |sym|
39
- class_eval %{
39
+ class_eval %/
40
40
  def #{sym} (*vals)
41
41
  if vals.length == 1
42
42
  @#{sym} = vals[0]
@@ -53,7 +53,7 @@ module Gurgitate
53
53
  # old-style accessors though. Breaking people's
54
54
  # .gurgitate-rules is a bad idea.
55
55
  attr_writer :#{sym}
56
- }
56
+ /
57
57
  end
58
58
  end
59
59
 
@@ -175,7 +175,7 @@ module Gurgitate
175
175
  # handled any other way.
176
176
  def method_missing(meth)
177
177
  headername=meth.to_s.split(/_/).map {|x| x.capitalize}.join("-")
178
- if defined?(headers[headername]) then
178
+ if headers[headername] then
179
179
  return headers[headername]
180
180
  else
181
181
  raise NameError,"undefined local variable or method, or header not found `#{meth}' for #{self}:#{self.class}"
@@ -211,9 +211,6 @@ module Gurgitate
211
211
  f.print(self.to_s)
212
212
  end
213
213
  return $?>>8
214
- rescue SystemCallError
215
- save(spoolfile())
216
- return -1
217
214
  end
218
215
 
219
216
  # Pipes the message through +program+, and returns another
@@ -237,34 +234,39 @@ module Gurgitate
237
234
  end
238
235
  end
239
236
  end
240
- rescue SystemCallError
241
- save(Spoolfile)
242
- return nil
243
237
  end
244
238
 
245
239
  # Processes your .gurgitate-rules.rb.
246
240
  def process(&block)
247
- if @rules.size > 0 or block
248
- @rules.each do |configfilespec|
249
- begin
250
- eval File.new(configfilespec).read, nil,
251
- configfilespec
252
- rescue ScriptError
253
- log "Couldn't load #{configfilespec}: "+$!
254
- save(spoolfile)
255
- rescue Exception
256
- log "Error while executing #{configfilespec}: #{$!}"
257
- $@.each { |tr| log "Backtrace: #{tr}" }
258
- folderstyle = MBox
259
- save(spoolfile)
241
+ begin
242
+ if @rules.size > 0 or block
243
+ @rules.each do |configfilespec|
244
+ begin
245
+ eval File.new(configfilespec).read, nil,
246
+ configfilespec
247
+ rescue ScriptError
248
+ log "Couldn't load #{configfilespec}: "+$!
249
+ save(spoolfile)
250
+ rescue Exception
251
+ log "Error while executing #{configfilespec}: #{$!}"
252
+ $@.each { |tr| log "Backtrace: #{tr}" }
253
+ folderstyle = MBox
254
+ save(spoolfile)
255
+ end
260
256
  end
257
+ if block
258
+ instance_eval(&block)
259
+ end
260
+ log "Mail not covered by rules, saving to default spool"
261
+ save(spoolfile)
262
+ else
263
+ save(spoolfile)
261
264
  end
262
- if block
263
- instance_eval(&block)
264
- end
265
- log "Mail not covered by rules, saving to default spool"
266
- save(spoolfile)
267
- else
265
+ rescue Exception
266
+ log "Error while executing rules: #{$!}"
267
+ $@.each { |tr| log "Backtrace: #{tr}" }
268
+ log "Attempting to save to spoolfile after error"
269
+ folderstyle = MBox
268
270
  save(spoolfile)
269
271
  end
270
272
  end
@@ -40,10 +40,8 @@ module Gurgitate
40
40
  begin
41
41
  [MBox,Maildir].each do |mod|
42
42
  if mod::check_mailbox(mailbox) then
43
- self.class.instance_eval do
44
- include mod
45
- raise MailboxFound
46
- end
43
+ extend mod
44
+ raise MailboxFound
47
45
  end
48
46
  end
49
47
 
@@ -51,23 +49,21 @@ module Gurgitate
51
49
  # let's default to whatever's in @folderstyle. (I
52
50
  # guess we'll be making a new mailbox, eh?)
53
51
 
54
- if defined? @folderstyle then
52
+ if Module === @folderstyle then
55
53
  #
56
54
  # Careful we don't get the wrong instance variable
57
55
  folderstyle=@folderstyle
58
56
 
59
- self.class.instance_eval do
60
- include folderstyle
61
- end
57
+ extend folderstyle
62
58
  else
63
59
  # No hints from the user either. Let's guess!
64
60
  # I'll use the same heuristic that Postfix uses--if the
65
61
  # mailbox name ends with a /, then make it a Maildir,
66
62
  # otherwise make it a mail file
67
63
  if mailbox =~ /\/$/ then
68
- include Maildir
64
+ extend Maildir
69
65
  else
70
- include MBox
66
+ extend MBox
71
67
  end
72
68
  end
73
69
 
@@ -81,7 +77,8 @@ module Gurgitate
81
77
  deliver_message(mailbox)
82
78
  rescue SystemCallError
83
79
  self.log "Gack! Something went wrong: "+$!
84
- exit 75
80
+ raise
81
+ # exit 75
85
82
  end
86
83
  end
87
84
  end
@@ -70,6 +70,10 @@ module Gurgitate
70
70
  make_mailbox(mailbox)
71
71
  end
72
72
 
73
+ unless File.stat(mailbox).directory?
74
+ raise SystemError, 'not a directory'
75
+ end
76
+
73
77
  tmpfilename=maildir_getfilename(File.join(mailbox,"tmp"))
74
78
  File.open(tmpfilename,File::CREAT|File::WRONLY) do |fh|
75
79
  fh.write(self.to_s)
@@ -91,7 +95,7 @@ module Gurgitate
91
95
  File.link(tmpfilename,newfilename)
92
96
  rescue SystemCallError
93
97
  log("Couldn't create maildir link to \"new\"!")
94
- exit 75 # Argh, I tried, it didn't work out
98
+ raise
95
99
  end
96
100
  File.delete(tmpfilename)
97
101
  end
@@ -65,6 +65,9 @@ module Gurgitate
65
65
  # regex::
66
66
  # The regular expression to match against the header's contents
67
67
  def matches (regex)
68
+ if String === regex
69
+ regex = Regexp.new(Regexp.escape(regex))
70
+ end
68
71
  @contents =~ regex
69
72
  end
70
73
 
@@ -89,7 +92,6 @@ module Gurgitate
89
92
  each do |header|
90
93
  header.contents = header.contents.sub regex, replacement
91
94
  end
92
- p self
93
95
  end
94
96
 
95
97
  def sub(regex, replacement)
@@ -108,61 +110,91 @@ module Gurgitate
108
110
  # A slightly bigger class for all of a message's headers
109
111
  class Headers
110
112
 
111
- # Creates a Headers object.
112
- # headertext::
113
- # The text of the message headers.
114
- def initialize(headertext)
115
- @headers=Hash.new(nil)
116
- @headertext=headertext
113
+ private
117
114
 
118
- (unix_from,normal_headers)=headertext.split(/\n/,2);
115
+ # Figures out whether the first line of a mail message is an
116
+ # mbox-style "From " line (say, if you get this from sendmail),
117
+ # or whether it's just a normal header.
118
+ # --
119
+ # If you run "fetchmail" with the -m option to feed the
120
+ # mail message straight to gurgitate, skipping the "local
121
+ # MTA" step, then it doesn't have a "From " line. So I
122
+ # have to deal with that by hand. First, check to see if
123
+ # there's a "From " line present in the first place.
124
+ def figure_out_from_line(headertext)
125
+ (unix_from,normal_headers) = headertext.split(/\n/,2)
119
126
 
120
- # If you run "fetchmail" with the -m option to feed the
121
- # mail message straight to gurgitate, skipping the "local
122
- # MTA" step, then it doesn't have a "From " line. So I
123
- # have to deal with that by hand. First, check to see if
124
- # there's a "From " line present in the first place.
125
127
  if unix_from =~ /^From / then
126
- @headertext=normal_headers
127
- @unix_from=unix_from
128
+ headertext=normal_headers
129
+ unix_from=unix_from
128
130
  else
129
131
  # If there isn't, then deal with it after we've
130
132
  # worried about the rest of the headers, 'cos we'll
131
133
  # have to make our own.
132
- unix_from=""
134
+ unix_from=nil
133
135
  end
136
+ return unix_from, headertext
137
+ end
134
138
 
139
+ def parse_headers
135
140
  @headertext.each do |h|
136
141
  h.chomp!
137
142
  if(h=~/^\s+/) then
138
143
  @lastheader << h
139
144
  else
140
145
  header=Header.new(h)
141
- @headers[header.name] ||= HeaderBag.new;
146
+ @headers[header.name] ||= HeaderBag.new
142
147
  @headers[header.name].push(header)
143
148
  @lastheader=header
144
149
  end
145
150
  end
146
151
 
147
152
  @headers_changed=false
153
+ end
148
154
 
149
- # Get the envelope From information. This comes with a
150
- # whole category of rants: this information is absurdly hard
151
- # to get your hands on. The best you can manage is a sort
152
- # of educated guess.
155
+ # Get the envelope From information. This comes with a
156
+ # whole category of rants: this information is absurdly hard
157
+ # to get your hands on. The best you can manage is a sort
158
+ # of educated guess. Thus, this horrible glob of hackiness.
159
+ # I don't recommend looking too closely at this code if you
160
+ # can avoid it, and further I recommend making sure to
161
+ # configure your MTA so that it sends proper sender and
162
+ # recipient information to gurgitate so that this code never
163
+ # has to be run at all.
164
+ def guess_sender
165
+ # Start by worrying about the "From foo@bar" line. If it's
166
+ # not there, then make one up from the Return-Path: header.
167
+ # If there isn't a "Return-Path:" header (then I suspect we
168
+ # have bigger problems, but still) then use From: as a wild
169
+ # guess. If I hope that this entire lot of code doesn't get
170
+ # used, then I _particularly_ hope that things never get so
171
+ # bad that poor gurgitate has to use the From: header as a
172
+ # source of authoritative information on anything.
153
173
  #
154
- # Okay, now worry about the "From foo@bar" line. If it's
155
- # not there, then make one up from the Return-Path:
156
- # header. If there isn't a "Return-Path:" header (then I
157
- # suspect we have bigger problems, but still) then use
158
- # From:
159
- if unix_from == "" then
160
- fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+)/;
174
+ # And then after all that fuss, if we're delivering to a
175
+ # Maildir, I have to get rid of it. And sometimes the MTA
176
+ # gives me a mbox-style From line and sometimes it doesn't.
177
+ # It's annoying, but I have no choice but to Just Deal With
178
+ # It.
179
+ if @unix_from then
180
+ # If it is there, then grab the email address in it and
181
+ # use that as our official "from".
182
+ fromregex=/^From ([^ ]+@[^ ]+) /
183
+ fromregex.match(@unix_from)
184
+ @from=$+
185
+
186
+ # or maybe it's local
187
+ if @from == nil then
188
+ @unix_from =~ /^From (\S+) /
189
+ @from=$+
190
+ end
191
+ else
192
+ fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<]*[<](.*@.*)[>]|([^ ]+@[^ ]+)/
161
193
  if self["Return-Path"] != nil then
162
- fromregex.match(self["Return-Path"][0].contents);
194
+ fromregex.match(self["Return-Path"][0].contents)
163
195
  else
164
196
  if self["From"] != nil then
165
- fromregex.match(self["From"][0].contents);
197
+ fromregex.match(self["From"][0].contents)
166
198
  end
167
199
  end
168
200
  address_candidate=$+
@@ -170,32 +202,53 @@ module Gurgitate
170
202
  # If there STILL isn't a match, then it's probably safe to
171
203
  # assume that it's local mail, and doesn't have an @ in its
172
204
  # address.
173
- if address_candidate == nil then
205
+ unless address_candidate
174
206
  if self["Return-Path"] != nil then
175
207
  self["Return-Path"][0].contents =~ /(\S+)/
176
208
  address_candidate=$+
177
209
  else
178
- if self["From"] then
179
- self["From"][0].contents =~ /(\S+)/
180
- end
210
+ self["From"][0].contents =~ /(\S+)/
181
211
  address_candidate=$+
182
212
  end
183
213
  end
184
214
 
185
215
  @from=address_candidate
186
216
 
187
- @unix_from="From "+self.from+" "+Time.new.to_s;
217
+ @unix_from="From "+self.from+" "+Time.new.to_s
218
+ end
219
+ end
220
+
221
+ public
222
+
223
+ # Creates a Headers object.
224
+ # headertext::
225
+ # The text of the message headers.
226
+ def initialize(headertext=nil,sender=nil, recipient=nil)
227
+ @from = sender
228
+ @to = recipient
229
+ @headers = Hash.new(nil)
230
+
231
+ if Hash === headertext
232
+ @headers_changed = true
233
+ headertext.each_key do |key|
234
+
235
+ headername = key.to_s.gsub("_","-")
236
+
237
+ header=Header.new(headername, headertext[key])
238
+ @headers[header.name] ||= HeaderBag.new
239
+ @headers[header.name].push(header)
240
+ end
188
241
  else
189
- # If it is there, then grab the email address in it and
190
- # use that as our official "from".
191
- fromregex=/^From ([^ ]+@[^ ]+) /;
192
- fromregex.match(unix_from);
193
- @from=$+
242
+ if headertext
243
+ @unix_from, @headertext = figure_out_from_line headertext
244
+ parse_headers if @headertext
194
245
 
195
- # or maybe it's local
196
- if @from == nil then
197
- unix_from =~ /^From (\S+) /;
198
- @from=$+
246
+ if sender # then don't believe the mbox separator
247
+ @from = sender
248
+ @unix_from="From "+self.from+" "+Time.new.to_s
249
+ else
250
+ guess_sender
251
+ end
199
252
  end
200
253
  end
201
254
  end
@@ -226,7 +279,7 @@ module Gurgitate
226
279
  #
227
280
  # Yet another bucket of rants. Unix mail sucks.
228
281
  def to
229
- return @headers["X-Original-To"] || nil
282
+ return @to || @headers["X-Original-To"] || nil
230
283
  end
231
284
 
232
285
  # Who the message is from (the envelope from)
@@ -239,7 +292,7 @@ module Gurgitate
239
292
  # newfrom:: An email address
240
293
  def from=(newfrom)
241
294
  @from=newfrom
242
- @unix_from="From "+self.from+" "+Time.new.to_s;
295
+ @unix_from="From "+self.from+" "+Time.new.to_s
243
296
  end
244
297
 
245
298
  # Match header +name+ against +regex+
@@ -262,9 +315,11 @@ module Gurgitate
262
315
  # regex:: The regex to match the headers against.
263
316
  def matches(names,regex)
264
317
  ret=false
265
- if names.class == "String" then
266
- names=[names];
318
+
319
+ if names.class == String then
320
+ names=[names]
267
321
  end
322
+
268
323
  names.each do |n|
269
324
  ret |= match(n,regex)
270
325
  end
@@ -281,9 +336,9 @@ module Gurgitate
281
336
  # the "From " line
282
337
  def to_s
283
338
  if @headers_changed then
284
- return @headers.collect { |n,h|
285
- h.collect { |h| h.to_s }.join("\n")
286
- }.join("\n")
339
+ return @headers.map do |n,h|
340
+ h.map do |h| h.to_s end.join("\n")
341
+ end.join("\n")
287
342
  else
288
343
  return @headertext
289
344
  end
@@ -7,22 +7,67 @@
7
7
  require 'gurgitate/headers'
8
8
 
9
9
  module Gurgitate
10
- # A complete mail message.
11
10
 
11
+ # A complete mail message.
12
12
  class Mailmessage
13
+
14
+ Fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<][<](.*@.*)[>]|([^ ]+@[^ ]+)/;
15
+
13
16
  # The headers of the message
14
17
  attr_reader :headers
15
18
  # The body of the message
16
19
  attr_accessor :body
17
20
 
18
- def initialize(text, recipient=nil, sender=nil)
19
- (@headertext,@body)=text.split(/^$/,2)
20
- fromregex=/([^ ]+@[^ ]+) \(.*\)|[^<][<](.*@.*)[>]|([^ ]+@[^ ]+)/;
21
- @headers=Headers.new(@headertext);
22
- fromregex.match(@headers["From"][0].contents);
23
- @from=$+
24
- @recipient = recipient
25
- @sender = sender
21
+ # The envelope sender and recipient, if anyone thought to
22
+ # mention them to us.
23
+ attr_accessor :sender
24
+ attr_accessor :recipient
25
+
26
+ # Creates a new mail message with headers built from the options hash,
27
+ # and the body of the message in a string.
28
+ def self.create(*args)
29
+ options = body = nil
30
+
31
+ if String === args[0]
32
+ options = args[1]
33
+ body = args[0]
34
+ elsif Hash === args[0]
35
+ options = args[0]
36
+ end
37
+
38
+ options = options.clone # just in case, since I'm meddling with it
39
+ message = self.new
40
+
41
+ message.instance_eval do
42
+ if body
43
+ @body=body
44
+ end
45
+
46
+ %w/sender recipient body/.each do |key|
47
+ if options.has_key? key.to_sym
48
+ instance_variable_set("@#{key}", options[key.to_sym])
49
+ options.delete key
50
+ end
51
+ end
52
+
53
+ @headers = Headers.new(options)
54
+ end
55
+
56
+ message
57
+ end
58
+
59
+ def initialize(text=nil, recipient=nil, sender=nil)
60
+ if text
61
+ (@headertext,@body)=text.split(/\n\n/,2)
62
+ @headers=Headers.new(@headertext);
63
+ Fromregex.match(@headers["From"][0].contents);
64
+ @from=$+
65
+ @recipient = recipient
66
+ @sender = sender
67
+ else
68
+ @headers = Headers.new
69
+ @body = ""
70
+ end
26
71
  end
27
72
 
28
73
  # Returns the header +name+
@@ -40,9 +85,9 @@ module Gurgitate
40
85
  # def to; @headers["To","Cc"]; end
41
86
 
42
87
  # Returns the formatted mail message
43
- def to_s; @headers.to_s + ( @body || ""); end
88
+ def to_s; @headers.to_s + "\n\n" + ( @body || ""); end
44
89
 
45
90
  # Returns the mail message formatted for mbox
46
- def to_mbox; @headers.to_mbox + @body; end
91
+ def to_mbox; @headers.to_mbox + "\n\n" + @body; end
47
92
  end
48
93
  end