mailfactory 1.3.0 → 1.3.1

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