mailfactory 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. data/lib/mailfactory.rb +383 -371
  2. metadata +2 -2
@@ -7,421 +7,433 @@
7
7
  # License:: Ruby License
8
8
  # ---
9
9
  # = Usage:
10
- # require 'net/smtp'
11
- # require 'rubygems'
12
- # require 'mailfactory'
10
+ # require 'net/smtp'
11
+ # require 'rubygems'
12
+ # require 'mailfactory'
13
13
  #
14
14
  #
15
- # mail = MailFactory.new()
16
- # mail.to = "test@test.com"
17
- # mail.from = "sender@sender.com"
18
- # mail.subject = "Here are some files for you!"
19
- # mail.text = "This is what people with plain text mail readers will see"
20
- # mail.html = "A little something <b>special</b> for people with HTML readers"
21
- # mail.attach("/etc/fstab")
22
- # mail.attach("/some/other/file")
15
+ # mail = MailFactory.new()
16
+ # mail.to = "test@test.com"
17
+ # mail.from = "sender@sender.com"
18
+ # mail.subject = "Here are some files for you!"
19
+ # mail.text = "This is what people with plain text mail readers will see"
20
+ # mail.html = "A little something <b>special</b> for people with HTML readers"
21
+ # mail.attach("/etc/fstab")
22
+ # mail.attach("/some/other/file")
23
23
  #
24
- # Net::SMTP.start('smtp1.testmailer.com', 25, 'mail.from.domain', fromaddress, password, :cram_md5) { |smtp|
25
- # mail.to = toaddress
26
- # smtp.send_message(mail.to_s(), fromaddress, toaddress)
27
- # }
28
-
29
-
24
+ # Net::SMTP.start('smtp1.testmailer.com', 25, 'mail.from.domain', fromaddress, password, :cram_md5) { |smtp|
25
+ # mail.to = toaddress
26
+ # smtp.send_message(mail.to_s(), fromaddress, toaddress)
27
+ # }
30
28
 
31
29
  require 'base64'
32
30
  require 'pathname'
33
31
 
34
32
  # try to bring in the mime/types module, make a dummy module if it can't be found
35
33
  begin
36
- begin
37
- require 'rubygems'
38
- rescue LoadError
39
- end
40
- require 'mime/types'
34
+ begin
35
+ require 'rubygems'
36
+ rescue LoadError
37
+ end
38
+ require 'mime/types'
41
39
  rescue LoadError
42
- module MIME
43
- class Types
44
- def Types::type_for(filename)
45
- return('')
46
- end
47
- end
48
- end
40
+ module MIME
41
+ class Types
42
+ def Types::type_for(filename)
43
+ return('')
44
+ end
45
+ end
46
+ end
49
47
  end
50
48
 
51
49
  # An easy class for creating a mail message
52
50
  class MailFactory
53
51
 
54
- def initialize()
55
- @headers = Array.new()
56
- @attachments = Array.new()
57
- @attachmentboundary = generate_boundary()
58
- @bodyboundary = generate_boundary()
59
- @html = nil
60
- @text = nil
61
- @charset = 'utf-8'
62
- end
63
-
64
-
65
- # adds a header to the bottom of the headers
66
- def add_header(header, value)
67
- value = quote_if_necessary(value, @charset) if header == 'subject'
68
- value = quote_address_if_necessary(value, @charset) if header == 'from'
69
- value = quote_address_if_necessary(value, @charset) if header == 'to'
70
- @headers << "#{header}: #{value}"
71
- end
72
-
73
-
74
- # removes the named header - case insensitive
75
- def remove_header(header)
76
- @headers.each_index() { |i|
77
- if(@headers[i] =~ /^#{Regexp.escape(header)}:/i)
78
- @headers.delete_at(i)
79
- end
80
- }
81
- end
82
-
83
-
84
- # sets a header (removing any other versions of that header)
85
- def set_header(header, value)
86
- remove_header(header)
87
- add_header(header, value)
88
- end
89
-
90
-
91
- def replyto=(newreplyto)
92
- remove_header("Reply-To")
93
- add_header("Reply-To", newreplyto)
94
- end
95
-
96
-
97
- def replyto()
98
- return(get_header("Reply-To")[0])
99
- end
100
-
101
-
102
- # sets the plain text body of the message
103
- def text=(newtext)
104
- @text = newtext
105
- end
106
-
107
-
108
- # sets the HTML body of the message. Only the body of the
109
- # html should be provided
110
- def html=(newhtml)
111
- @html = "<html>\n<head>\n<meta content=\"text/html;charset=#{@charset}\" http-equiv=\"Content-Type\">\n</head>\n<body bgcolor=\"#ffffff\" text=\"#000000\">\n#{newhtml}\n</body>\n</html>"
112
- end
113
-
114
-
115
- # sets the HTML body of the message. The entire HTML section should be provided
116
- def rawhtml=(newhtml)
117
- @html = newhtml
118
- end
119
-
120
-
121
- # implement method missing to provide helper methods for setting and getting headers.
122
- # Headers with '-' characters may be set/gotten as 'x_mailer' or 'XMailer' (splitting
123
- # will occur between capital letters or on '_' chracters)
124
- def method_missing(methId, *args)
125
- name = methId.id2name()
126
-
127
- # mangle the name if we have to
128
- if(name =~ /_/)
129
- name = name.gsub(/_/, '-')
130
- elsif(name =~ /[A-Z]/)
131
- name = name.gsub(/([a-zA-Z])([A-Z])/, '\1-\2')
132
- end
133
-
134
- # determine if it sets or gets, and do the right thing
135
- if(name =~ /=$/)
136
- if(args.length != 1)
137
- super(methId, args)
138
- end
139
- set_header(name[/^(.*)=$/, 1], args[0])
140
- else
141
- if(args.length != 0)
142
- super(methId, args)
143
- end
144
- headers = get_header(name)
145
- return(get_header(name))
146
- end
147
- end
52
+ def initialize()
53
+ @headers = Array.new()
54
+ @attachments = Array.new()
55
+ @attachmentboundary = generate_boundary()
56
+ @bodyboundary = generate_boundary()
57
+ @html = nil
58
+ @text = nil
59
+ @charset = 'utf-8'
60
+ end
61
+
62
+
63
+ # adds a header to the bottom of the headers
64
+ def add_header(header, value)
65
+ value = quoted_printable_with_instruction(value, @charset) if header == 'subject'
66
+ value = quote_address_if_necessary(value, @charset) if %w[from to cc bcc reply-to].include?(header.downcase)
67
+ @headers << "#{header}: #{value}"
68
+ end
69
+
70
+
71
+ # removes the named header - case insensitive
72
+ def remove_header(header)
73
+ @headers.each_index() { |i|
74
+ if(@headers[i] =~ /^#{Regexp.escape(header)}:/i)
75
+ @headers.delete_at(i)
76
+ end
77
+ }
78
+ end
79
+
80
+
81
+ # sets a header (removing any other versions of that header)
82
+ def set_header(header, value)
83
+ remove_header(header)
84
+ add_header(header, value)
85
+ end
86
+
87
+
88
+ def replyto=(newreplyto)
89
+ remove_header("Reply-To")
90
+ add_header("Reply-To", newreplyto)
91
+ end
92
+
93
+
94
+ def replyto()
95
+ return(get_header("Reply-To")[0])
96
+ end
97
+
98
+
99
+ # sets the plain text body of the message
100
+ def text=(newtext)
101
+ @text = newtext
102
+ end
103
+
104
+
105
+ # sets the HTML body of the message. Only the body of the
106
+ # html should be provided
107
+ def html=(newhtml)
108
+ @html = "<html>\n<head>\n<meta content=\"text/html;charset=#{@charset}\" http-equiv=\"Content-Type\">\n</head>\n<body bgcolor=\"#ffffff\" text=\"#000000\">\n#{newhtml}\n</body>\n</html>"
109
+ end
110
+
111
+
112
+ # sets the HTML body of the message. The entire HTML section should be provided
113
+ def rawhtml=(newhtml)
114
+ @html = newhtml
115
+ end
116
+
117
+
118
+ # implement method missing to provide helper methods for setting and getting headers.
119
+ # Headers with '-' characters may be set/gotten as 'x_mailer' or 'XMailer' (splitting
120
+ # will occur between capital letters or on '_' chracters)
121
+ def method_missing(methId, *args)
122
+ name = methId.id2name()
123
+
124
+ # mangle the name if we have to
125
+ if(name =~ /_/)
126
+ name = name.gsub(/_/, '-')
127
+ elsif(name =~ /[A-Z]/)
128
+ name = name.gsub(/([a-zA-Z])([A-Z])/, '\1-\2')
129
+ end
130
+
131
+ # determine if it sets or gets, and do the right thing
132
+ if(name =~ /=$/)
133
+ if(args.length != 1)
134
+ super(methId, args)
135
+ end
136
+ set_header(name[/^(.*)=$/, 1], args[0])
137
+ else
138
+ if(args.length != 0)
139
+ super(methId, args)
140
+ end
141
+ headers = get_header(name)
142
+ return(get_header(name))
143
+ end
144
+ end
148
145
 
149
-
150
- # returns the value (or values) of the named header in an array
151
- def get_header(header)
152
- headers = Array.new()
153
- headerregex = /^#{Regexp.escape(header)}:/i
154
- @headers.each() { |h|
155
- if(headerregex.match(h))
156
- headers << h[/^[^:]+:(.*)/i, 1].strip()
157
- end
158
- }
159
-
160
- return(headers)
161
- end
162
-
163
-
164
- # returns true if the email is multipart
165
- def multipart?()
166
- if(@attachments.length > 0 or @html != nil)
167
- return(true)
168
- else
169
- return(false)
170
- end
171
- end
172
-
146
+
147
+ # returns the value (or values) of the named header in an array
148
+ def get_header(header)
149
+ headers = Array.new()
150
+ headerregex = /^#{Regexp.escape(header)}:/i
151
+ @headers.each() { |h|
152
+ if(headerregex.match(h))
153
+ headers << h[/^[^:]+:(.*)/i, 1].strip()
154
+ end
155
+ }
156
+
157
+ return(headers)
158
+ end
159
+
160
+
161
+ # returns true if the email is multipart
162
+ def multipart?()
163
+ if(@attachments.length > 0 or @html != nil)
164
+ return(true)
165
+ else
166
+ return(false)
167
+ end
168
+ end
169
+
173
170
 
174
- # builds an email and returns it as a string. Takes the following options:
175
- # <tt>:messageid</tt>:: Adds a message id to the message based on the from header (defaults to false)
176
- # <tt>:date</tt>:: Adds a date to the message if one is not present (defaults to true)
177
- def construct(options = Hash.new)
178
- if(options[:date] == nil)
179
- options[:date] = true
180
- end
181
-
182
- if(options[:messageid])
183
- # add a unique message-id
184
- remove_header("Message-ID")
185
- sendingdomain = get_header('from')[0].to_s()[/@([-a-zA-Z0-9._]+)/,1].to_s()
186
- add_header("Message-ID", "<#{Time.now.to_f()}.#{Process.euid()}.#{String.new.object_id()}@#{sendingdomain}>")
187
- end
171
+ # builds an email and returns it as a string. Takes the following options:
172
+ # <tt>:messageid</tt>:: Adds a message id to the message based on the from header (defaults to false)
173
+ # <tt>:date</tt>:: Adds a date to the message if one is not present (defaults to true)
174
+ def construct(options = Hash.new)
175
+ if(options[:date] == nil)
176
+ options[:date] = true
177
+ end
178
+
179
+ if(options[:messageid])
180
+ # add a unique message-id
181
+ remove_header("Message-ID")
182
+ sendingdomain = get_header('from')[0].to_s()[/@([-a-zA-Z0-9._]+)/,1].to_s()
183
+ add_header("Message-ID", "<#{Time.now.to_f()}.#{Process.euid()}.#{String.new.object_id()}@#{sendingdomain}>")
184
+ end
188
185
 
189
- if(options[:date])
190
- if(get_header("Date").length == 0)
191
- add_header("Date", Time.now.strftime("%a, %d %b %Y %H:%M:%S %z"))
192
- end
193
- end
186
+ if(options[:date])
187
+ if(get_header("Date").length == 0)
188
+ add_header("Date", Time.now.strftime("%a, %d %b %Y %H:%M:%S %z"))
189
+ end
190
+ end
194
191
 
195
- # Add a mime header if we don't already have one and we have multiple parts
196
- if(multipart?())
197
- if(get_header("MIME-Version").length == 0)
198
- add_header("MIME-Version", "1.0")
199
- end
200
-
201
- if(get_header("Content-Type").length == 0)
202
- if(@attachments.length == 0)
203
- add_header("Content-Type", "multipart/alternative;boundary=\"#{@bodyboundary}\"")
204
- else
205
- add_header("Content-Type", "multipart/mixed; boundary=\"#{@attachmentboundary}\"")
206
- end
207
- end
208
- end
209
-
210
- return("#{headers_to_s()}#{body_to_s()}")
211
- end
192
+ # Add a mime header if we don't already have one and we have multiple parts
193
+ if(multipart?())
194
+ if(get_header("MIME-Version").length == 0)
195
+ add_header("MIME-Version", "1.0")
196
+ end
197
+
198
+ if(get_header("Content-Type").length == 0)
199
+ if(@attachments.length == 0)
200
+ add_header("Content-Type", "multipart/alternative;boundary=\"#{@bodyboundary}\"")
201
+ else
202
+ add_header("Content-Type", "multipart/mixed; boundary=\"#{@attachmentboundary}\"")
203
+ end
204
+ end
205
+ end
206
+
207
+ return("#{headers_to_s()}#{body_to_s()}")
208
+ end
212
209
 
213
-
214
- # returns a formatted email - equivalent to construct(:messageid => true)
215
- def to_s()
216
- return(construct(:messageid => true))
217
- end
218
-
219
-
220
- # generates a unique boundary string
221
- def generate_boundary()
222
- randomstring = Array.new()
223
- 1.upto(25) {
224
- whichglyph = rand(100)
225
- if(whichglyph < 40)
226
- randomstring << (rand(25) + 65).chr()
227
- elsif(whichglyph < 70)
228
- randomstring << (rand(25) + 97).chr()
229
- elsif(whichglyph < 90)
230
- randomstring << (rand(10) + 48).chr()
231
- elsif(whichglyph < 95)
232
- randomstring << '.'
233
- else
234
- randomstring << '_'
235
- end
236
- }
237
- return("----=_NextPart_#{randomstring.join()}")
238
- end
239
-
240
-
241
- # adds an attachment to the mail. Type may be given as a mime type. If it
242
- # is left off and the MIME::Types module is available it will be determined automagically.
243
- # If the optional attachemntheaders is given, then they will be added to the attachment
244
- # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
245
- # can be given as an Array or a String.
246
- def add_attachment(filename, type=nil, attachmentheaders = nil)
247
- attachment = Hash.new()
248
- attachment['filename'] = Pathname.new(filename).basename
249
- if(type == nil)
250
- attachment['mimetype'] = MIME::Types.type_for(filename).to_s
251
- else
252
- attachment['mimetype'] = type
253
- end
254
-
255
- # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
256
- File.open(filename, "rb") { |fp|
257
- attachment['attachment'] = file_encode(fp.read())
258
- }
210
+
211
+ # returns a formatted email - equivalent to construct(:messageid => true)
212
+ def to_s()
213
+ return(construct(:messageid => true))
214
+ end
215
+
216
+
217
+ # generates a unique boundary string
218
+ def generate_boundary()
219
+ randomstring = Array.new()
220
+ 1.upto(25) {
221
+ whichglyph = rand(100)
222
+ if(whichglyph < 40)
223
+ randomstring << (rand(25) + 65).chr()
224
+ elsif(whichglyph < 70)
225
+ randomstring << (rand(25) + 97).chr()
226
+ elsif(whichglyph < 90)
227
+ randomstring << (rand(10) + 48).chr()
228
+ elsif(whichglyph < 95)
229
+ randomstring << '.'
230
+ else
231
+ randomstring << '_'
232
+ end
233
+ }
234
+ return("----=_NextPart_#{randomstring.join()}")
235
+ end
236
+
237
+
238
+ # adds an attachment to the mail. Type may be given as a mime type. If it
239
+ # is left off and the MIME::Types module is available it will be determined automagically.
240
+ # If the optional attachemntheaders is given, then they will be added to the attachment
241
+ # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
242
+ # can be given as an Array or a String.
243
+ def add_attachment(filename, type=nil, attachmentheaders = nil)
244
+ attachment = Hash.new()
245
+ attachment['filename'] = Pathname.new(filename).basename
246
+ if(type == nil)
247
+ attachment['mimetype'] = MIME::Types.type_for(filename).to_s
248
+ else
249
+ attachment['mimetype'] = type
250
+ end
251
+
252
+ # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
253
+ File.open(filename, "rb") { |fp|
254
+ attachment['attachment'] = file_encode(fp.read())
255
+ }
259
256
 
260
- if(attachmentheaders != nil)
261
- if(!attachmentheaders.kind_of?(Array))
262
- attachmentheaders = attachmentheaders.split(/\r?\n/)
263
- end
264
- attachment['headers'] = attachmentheaders
265
- end
257
+ if(attachmentheaders != nil)
258
+ if(!attachmentheaders.kind_of?(Array))
259
+ attachmentheaders = attachmentheaders.split(/\r?\n/)
260
+ end
261
+ attachment['headers'] = attachmentheaders
262
+ end
266
263
 
267
- @attachments << attachment
268
- end
269
-
270
-
271
- # adds an attachment to the mail as emailfilename. Type may be given as a mime type. If it
272
- # is left off and the MIME::Types module is available it will be determined automagically.
273
- # file may be given as an IO stream (which will be read until the end) or as a filename.
274
- # If the optional attachemntheaders is given, then they will be added to the attachment
275
- # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
276
- # can be given as an Array of a String.
277
- def add_attachment_as(file, emailfilename, type=nil, attachmentheaders = nil)
278
- attachment = Hash.new()
279
- attachment['filename'] = emailfilename
264
+ @attachments << attachment
265
+ end
266
+
267
+
268
+ # adds an attachment to the mail as emailfilename. Type may be given as a mime type. If it
269
+ # is left off and the MIME::Types module is available it will be determined automagically.
270
+ # file may be given as an IO stream (which will be read until the end) or as a filename.
271
+ # If the optional attachemntheaders is given, then they will be added to the attachment
272
+ # boundary in the email, which can be used to produce Content-ID markers. attachmentheaders
273
+ # can be given as an Array of a String.
274
+ def add_attachment_as(file, emailfilename, type=nil, attachmentheaders = nil)
275
+ attachment = Hash.new()
276
+ attachment['filename'] = emailfilename
280
277
 
281
- if(type != nil)
282
- attachment['mimetype'] = type.to_s()
283
- elsif(file.kind_of?(String) or file.kind_of?(Pathname))
284
- attachment['mimetype'] = MIME::Types.type_for(file.to_s()).to_s
285
- else
286
- attachment['mimetype'] = ''
287
- end
288
-
289
- if(file.kind_of?(String) or file.kind_of?(Pathname))
290
- # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
291
- File.open(file.to_s(), "rb") { |fp|
292
- attachment['attachment'] = file_encode(fp.read())
293
- }
294
- elsif(file.respond_to?(:read))
295
- attachment['attachment'] = file_encode(file.read())
296
- else
297
- raise(Exception, "file is not a supported type (must be a String, Pathnamem, or support read method)")
298
- end
299
-
300
- if(attachmentheaders != nil)
301
- if(!attachmentheaders.kind_of?(Array))
302
- attachmentheaders = attachmentheaders.split(/\r?\n/)
303
- end
304
- attachment['headers'] = attachmentheaders
305
- end
306
-
307
- @attachments << attachment
308
- end
309
-
310
-
311
- alias attach add_attachment
312
- alias attach_as add_attachment_as
313
-
278
+ if(type != nil)
279
+ attachment['mimetype'] = type.to_s()
280
+ elsif(file.kind_of?(String) or file.kind_of?(Pathname))
281
+ attachment['mimetype'] = MIME::Types.type_for(file.to_s()).to_s
282
+ else
283
+ attachment['mimetype'] = ''
284
+ end
285
+
286
+ if(file.kind_of?(String) or file.kind_of?(Pathname))
287
+ # Open in rb mode to handle Windows, which mangles binary files opened in a text mode
288
+ File.open(file.to_s(), "rb") { |fp|
289
+ attachment['attachment'] = file_encode(fp.read())
290
+ }
291
+ elsif(file.respond_to?(:read))
292
+ attachment['attachment'] = file_encode(file.read())
293
+ else
294
+ raise(Exception, "file is not a supported type (must be a String, Pathnamem, or support read method)")
295
+ end
296
+
297
+ if(attachmentheaders != nil)
298
+ if(!attachmentheaders.kind_of?(Array))
299
+ attachmentheaders = attachmentheaders.split(/\r?\n/)
300
+ end
301
+ attachment['headers'] = attachmentheaders
302
+ end
303
+
304
+ @attachments << attachment
305
+ end
306
+
307
+
308
+ alias attach add_attachment
309
+ alias attach_as add_attachment_as
310
+
314
311
  protected
315
-
316
- # returns the @headers as a properly formatted string
317
- def headers_to_s()
318
- return("#{@headers.join("\r\n")}\r\n\r\n")
319
- end
320
-
321
-
322
- # returns the body as a properly formatted string
323
- def body_to_s()
324
- body = Array.new()
325
-
326
- # simple message with one part
327
- if(!multipart?())
328
- return(@text)
329
- else
330
- body << "This is a multi-part message in MIME format.\r\n\r\n--#{@attachmentboundary}\r\nContent-Type: multipart/alternative; boundary=\"#{@bodyboundary}\""
331
-
332
- if(@attachments.length > 0)
333
- # text part
334
- body << "#{buildbodyboundary("text/plain; charset=#{@charset}; format=flowed", 'quoted-printable')}\r\n\r\n#{quote_text_if_necessary(@text)}"
335
-
336
- # html part if one is provided
337
- if @html
338
- body << "#{buildbodyboundary("text/html; charset=#{@charset}", 'quoted-printable')}\r\n\r\n#{quote_text_if_necessary(@html)}"
339
- end
340
-
341
- body << "--#{@bodyboundary}--"
342
-
343
- # and, the attachments
344
- if(@attachments.length > 0)
345
- @attachments.each() { |attachment|
346
- body << "#{buildattachmentboundary(attachment)}\r\n\r\n#{attachment['attachment']}"
347
- }
348
- body << "\r\n--#{@attachmentboundary}--"
349
- end
350
- else
351
- # text part
352
- body << "#{buildbodyboundary("text/plain; charset=#{@charset}; format=flowed", 'quoted-printable')}\r\n\r\n#{quote_text_if_necessary(@text)}"
353
-
354
- # html part
355
- body << "#{buildbodyboundary("text/html; charset=#{@charset}", 'quoted-printable')}\r\n\r\n#{quote_text_if_necessary(@html)}"
356
-
357
- body << "--#{@bodyboundary}--"
358
- end
359
-
360
- return(body.join("\r\n\r\n"))
361
- end
362
- end
363
-
364
-
365
- # builds a boundary string for including attachments in the body, expects an attachment hash as built by
366
- # add_attachment and add_attachment_as
367
- def buildattachmentboundary(attachment)
368
- disposition = "Content-Disposition: inline; filename=\"#{attachment['filename']}\""
369
- boundary = "--#{@attachmentboundary}\r\nContent-Type: #{attachment['mimetype']}; name=\"#{attachment['filename']}\"\r\nContent-Transfer-Encoding: base64\r\n#{disposition}"
370
- if(attachment['headers'])
371
- boundary = boundary + "\r\n#{attachment['headers'].join("\r\n")}"
372
- end
373
-
374
- return(boundary)
375
- end
376
-
377
-
378
- # builds a boundary string for inclusion in the body of a message
379
- def buildbodyboundary(type, encoding)
380
- return("--#{@bodyboundary}\r\nContent-Type: #{type}\r\nContent-Transfer-Encoding: #{encoding}")
381
- end
312
+
313
+ # returns the @headers as a properly formatted string
314
+ def headers_to_s()
315
+ return("#{@headers.join("\r\n")}\r\n\r\n")
316
+ end
317
+
318
+
319
+ # returns the body as a properly formatted string
320
+ def body_to_s()
321
+ body = Array.new()
322
+
323
+ # simple message with one part
324
+ if(!multipart?())
325
+ return(@text)
326
+ else
327
+ body << "This is a multi-part message in MIME format.\r\n\r\n--#{@attachmentboundary}\r\nContent-Type: multipart/alternative; boundary=\"#{@bodyboundary}\""
328
+
329
+ if(@attachments.length > 0)
330
+ # text part
331
+ body << "#{buildbodyboundary("text/plain; charset=#{@charset}; format=flowed", 'quoted-printable')}\r\n\r\n#{quote_if_necessary(@text, @charset)}"
332
+
333
+ # html part if one is provided
334
+ if @html
335
+ body << "#{buildbodyboundary("text/html; charset=#{@charset}", 'quoted-printable')}\r\n\r\n#{quote_if_necessary(@html, @charset)}"
336
+ end
337
+
338
+ body << "--#{@bodyboundary}--"
339
+
340
+ # and, the attachments
341
+ if(@attachments.length > 0)
342
+ @attachments.each() { |attachment|
343
+ body << "#{buildattachmentboundary(attachment)}\r\n\r\n#{attachment['attachment']}"
344
+ }
345
+ body << "\r\n--#{@attachmentboundary}--"
346
+ end
347
+ else
348
+ # text part
349
+ body << "#{buildbodyboundary("text/plain; charset=#{@charset}; format=flowed", 'quoted-printable')}\r\n\r\n#{quote_if_necessary(@text, @charset)}"
350
+
351
+ # html part
352
+ body << "#{buildbodyboundary("text/html; charset=#{@charset}", 'quoted-printable')}\r\n\r\n#{quote_if_necessary(@html, @charset)}"
353
+
354
+ body << "--#{@bodyboundary}--"
355
+ end
356
+
357
+ return(body.join("\r\n\r\n"))
358
+ end
359
+ end
360
+
361
+
362
+ # builds a boundary string for including attachments in the body, expects an attachment hash as built by
363
+ # add_attachment and add_attachment_as
364
+ def buildattachmentboundary(attachment)
365
+ disposition = "Content-Disposition: inline; filename=\"#{attachment['filename']}\""
366
+ boundary = "--#{@attachmentboundary}\r\nContent-Type: #{attachment['mimetype']}; name=\"#{attachment['filename']}\"\r\nContent-Transfer-Encoding: base64\r\n#{disposition}"
367
+ if(attachment['headers'])
368
+ boundary = boundary + "\r\n#{attachment['headers'].join("\r\n")}"
369
+ end
370
+
371
+ return(boundary)
372
+ end
373
+
374
+
375
+ # builds a boundary string for inclusion in the body of a message
376
+ def buildbodyboundary(type, encoding)
377
+ return("--#{@bodyboundary}\r\nContent-Type: #{type}\r\nContent-Transfer-Encoding: #{encoding}")
378
+ end
382
379
 
383
380
 
384
381
  # returns a base64 encoded version of the contents of str
385
382
  def file_encode(str)
386
383
  collection = Array.new()
387
384
  enc = Base64.encode64(str)
388
- # while(enc.length > 60)
389
- # collection << enc.slice!(0..59)
390
- # end
391
- # collection << enc
392
- # return(collection.join("\n"))
385
+ # while(enc.length > 60)
386
+ # collection << enc.slice!(0..59)
387
+ # end
388
+ # collection << enc
389
+ # return(collection.join("\n"))
393
390
  return(enc)
394
391
  end
395
392
 
396
- def quote_text_if_necessary(text)
397
- text = text.gsub( /[^a-z ]/i ) { quote_char($&) }.
398
- gsub( / /, "_" )
399
- text
400
- end
401
393
 
402
- def quote_if_necessary(text, charset)
403
- text = text.gsub( /[^a-z ]/i ) { quote_char($&) }.
404
- gsub( / /, "_" )
394
+ # Convert the given text into quoted printable format, with an instruction
395
+ # that the text be eventually interpreted in the given charset.
396
+ def quoted_printable_with_instruction(text, charset)
397
+ text = [text].pack("M").gsub(/\n/, "\r\n").chomp.gsub(/ /, "_")
405
398
  "=?#{charset}?Q?#{text}?="
406
399
  end
407
-
400
+
401
+
402
+ # Convert the given character to quoted printable format
403
+ def quoted_printable_encode(text)
404
+ [text].pack("M").gsub(/\n/, "\r\n").chomp
405
+ end
406
+
407
+
408
+ # A quick-and-dirty regexp for determining whether a string contains any
409
+ # characters that need escaping.
410
+ if !defined?(CHARS_NEEDING_QUOTING)
411
+ CHARS_NEEDING_QUOTING = /[\000-\011\013\014\016-\037\177-\377]/
412
+ end
413
+
414
+
415
+ # Quote the given text if it contains any "illegal" characters
416
+ def quote_if_necessary(text, charset, instruction = false)
417
+ text = text.dup.force_encoding(Encoding::ASCII_8BIT) if text.respond_to?(:force_encoding)
418
+ (text =~ CHARS_NEEDING_QUOTING) ? (instruction ? quoted_printable_with_instruction(text, charset) : quoted_printable_encode(text)) : text
419
+ end
420
+
421
+
422
+ # Quote the given address if it needs to be. The address may be a
423
+ # regular email address, or it can be a phrase followed by an address in
424
+ # brackets. The phrase is the only part that will be quoted, and only if
425
+ # it needs to be. This allows extended characters to be used in the
426
+ # "to", "from", "cc", and "bcc" headers.
408
427
  def quote_address_if_necessary(address, charset)
409
428
  if Array === address
410
429
  address.map { |a| quote_address_if_necessary(a, charset) }
411
430
  elsif address =~ /^(\S.*)\s+(<.*>)$/
412
431
  address = $2
413
- phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset)
432
+ phrase = quote_if_necessary($1.gsub(/^['"](.*)['"]$/, '\1'), charset, true)
414
433
  "\"#{phrase}\" #{address}"
415
434
  else
416
435
  address
417
436
  end
418
437
  end
419
438
 
420
- def quote_char(character)
421
- result = ""
422
- character.each_byte { |b| result << "=%02x" % b }
423
- result
424
- end
425
-
426
- end
427
-
439
+ end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: mailfactory
5
5
  version: !ruby/object:Gem::Version
6
- version: 1.3.0
7
- date: 2008-04-13 00:00:00 +01:00
6
+ version: 1.3.1
7
+ date: 2008-04-28 00:00:00 +01:00
8
8
  summary: MailFactory is a pure-ruby MIME mail generator
9
9
  require_paths:
10
10
  - lib