gurgitate-mail 1.8.4 → 1.8.5

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.
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