mail 1.2.6 → 1.2.8

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of mail might be problematic. Click here for more details.

data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,24 @@
1
+ == Sun Nov 22 12:19:44 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
2
+
3
+ * Added check on add_part to make sure if there is already a body, and if so, make a text_part of the body
4
+ * Fixing up attachment adding and making sure multipart emails always have boundaries
5
+ * Change Message#attachments to now recursively return all attachments in the email in an ordered flattened array
6
+ * Added ability for Mail::Message to accept {:headers => {'custom-header' => 'value', 'another-custom-header' => 'value'}} as a param on init
7
+ * Adding ability to Mail::Message to add a part via :part(params) with optional block
8
+ * Fixed up QP encoding forcing underscores into everything with a space
9
+ * Added ReturnPathField#address
10
+ * Updating gem loads and active support loads
11
+
12
+ == Sat Nov 21 12:52:46 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
13
+
14
+ * Changed Mail::Encodings to clean it up, added in unquote_and_convert_to as well as refactor in this area
15
+
16
+ == Thu Nov 19 04:16:10 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
17
+
18
+ * Added sendmail support from (Simon Rozet)
19
+ * Changed to bundler for gem dependancies and moved gem generation into rakefile (Simon Rozet)
20
+ * Bumped to 1.2.6 for sendmail support
21
+
1
22
  == Wed Nov 18 04:26:21 UTC 2009 Mikel Lindsaar <raasdnil@gmail.com>
2
23
 
3
24
  * Changed Encodings.param_encode(string) so it intelligently encodes and quotes needed
data/Rakefile CHANGED
@@ -7,7 +7,7 @@ require 'bundler'
7
7
 
8
8
  spec = Gem::Specification.new do |s|
9
9
  s.name = "mail"
10
- s.version = "1.2.6"
10
+ s.version = "1.2.8"
11
11
  s.author = "Mike Lindsaar"
12
12
  s.email = "raasdnil@gmail.com"
13
13
  s.homepage = "http://github.com/mikel/mail"
data/lib/mail.rb CHANGED
@@ -3,10 +3,16 @@ module Mail # :doc:
3
3
 
4
4
  require 'date'
5
5
 
6
- gem "treetop", ">= 1.4"
7
6
  require 'treetop'
8
- gem 'activesupport', ">= 2.3"
9
- require 'activesupport'
7
+ require 'active_support'
8
+ require 'active_support'
9
+
10
+ # Have to handle ActiveSupport 2.3 and 3.0
11
+ # Following two lines make sure that HashWithIndifferentAccess is available
12
+ # regardless of having activesupport 3 or 2.3 loaded
13
+ require 'active_support/core_ext/hash/indifferent_access'
14
+ HashWithIndifferentAccess
15
+
10
16
  require 'uri'
11
17
  require 'net/smtp'
12
18
  require 'mime/types'
@@ -36,7 +36,7 @@ module Mail
36
36
  def initialize(options_hash)
37
37
  case
38
38
  when options_hash[:data]
39
- @filename = options_hash[:filename]
39
+ @filename = File.basename(options_hash[:filename])
40
40
  add_file(options_hash[:data], options_hash[:encoding])
41
41
  when options_hash[:filename]
42
42
  @filename = File.basename(options_hash[:filename])
@@ -64,6 +64,8 @@ module Mail
64
64
  end
65
65
  end
66
66
 
67
+ alias :read :decoded
68
+
67
69
  def mime_type
68
70
  @mime_type.to_s
69
71
  end
data/lib/mail/body.rb CHANGED
@@ -48,7 +48,7 @@ module Mail
48
48
  encoded_parts = parts.map { |p| p.to_s }
49
49
  ([preamble] + encoded_parts).join(crlf_boundary) + end_boundary + epilogue.to_s
50
50
  else
51
- raw_source
51
+ raw_source.to_crlf
52
52
  end
53
53
  end
54
54
 
@@ -56,7 +56,11 @@ module Mail
56
56
 
57
57
  # Returns the raw source right now. Need to implement
58
58
  def decoded
59
- raw_source
59
+ if encoding.nil? || !Encodings.defined?(encoding)
60
+ raw_source.to_lf
61
+ else
62
+ Encodings.get_encoding(encoding).decode(raw_source)
63
+ end
60
64
  end
61
65
 
62
66
  def charset
@@ -88,18 +88,49 @@ module Mail
88
88
  # Decodes a given string as Base64 or Quoted Printable, depending on what
89
89
  # type it is.
90
90
  #
91
- #
91
+ # String has to be of the format =?<encoding>?[QB]?<string>?=
92
92
  def Encodings.value_decode(str)
93
- case
94
- when str =~ /\=\?.+?\?B\?.+?\?\=/
95
- Encodings.b_value_decode(str)
96
- when str =~ /\=\?.+?\?Q\?.+?\?\=/
97
- Encodings.q_value_decode(str)
98
- else
99
- str
93
+ str.gsub!(/\?=(\s*)=\?/, '?==?') # Remove whitespaces between 'encoded-word's
94
+ str.gsub(/(.*?)(=\?.*?\?.\?.*?\?=)|$/) do
95
+ before = $1.to_s
96
+ text = $2.to_s
97
+
98
+ case
99
+ when text =~ /=\?.+\?[Bb]\?/
100
+ before + b_value_decode(text)
101
+ when text =~ /=\?.+\?[Qq]\?/
102
+ before + q_value_decode(text)
103
+ else
104
+ before + text
105
+ end
100
106
  end
101
107
  end
108
+
109
+ # Takes an encoded string of the format =?<encoding>?[QB]?<string>?=
110
+ def Encodings.unquote_and_convert_to(str, to_encoding)
111
+ original_encoding, string = split_encoding_from_string( str )
112
+
113
+ output = value_decode( str ).to_s
102
114
 
115
+ if original_encoding.to_s.downcase.gsub("-", "") == to_encoding.to_s.downcase.gsub("-", "")
116
+ output
117
+ elsif original_encoding && to_encoding
118
+ begin
119
+ Iconv.iconv(to_encoding, original_encoding, output).first
120
+ rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
121
+ # the 'from' parameter specifies a charset other than what the text
122
+ # actually is...not much we can do in this case but just return the
123
+ # unconverted text.
124
+ #
125
+ # Ditto if either parameter represents an unknown charset, like
126
+ # X-UNKNOWN.
127
+ output
128
+ end
129
+ else
130
+ output
131
+ end
132
+ end
133
+
103
134
  # Encode a string with Base64 Encoding and returns it ready to be inserted
104
135
  # as a value for a field, that is, in the =?<charset>?B?<string>?= format
105
136
  #
@@ -114,19 +145,6 @@ module Mail
114
145
  end.join(" ")
115
146
  end
116
147
 
117
- # Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
118
- #
119
- # Example:
120
- #
121
- # Encodings.b_value_encode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
122
- # #=> 'This is あ string'
123
- def Encodings.b_value_decode(str)
124
- string = str.split(Mail::Patterns::WSP)
125
- string.flatten.map do |s|
126
- RubyVer.b_value_decode(s)
127
- end.join('')
128
- end
129
-
130
148
  # Encode a string with Quoted-Printable Encoding and returns it ready to be inserted
131
149
  # as a value for a field, that is, in the =?<charset>?Q?<string>?= format
132
150
  #
@@ -136,7 +154,19 @@ module Mail
136
154
  # #=> "=?UTF-8?Q?This_is_=E3=81=82_string?="
137
155
  def Encodings.q_value_encode(str, encoding = nil)
138
156
  string, encoding = RubyVer.q_value_encode(str, encoding)
139
- "=?#{encoding}?Q?#{string.chomp}?="
157
+ "=?#{encoding}?Q?#{string.chomp.gsub(/ /, '_')}?="
158
+ end
159
+
160
+ private
161
+
162
+ # Decodes a Base64 string from the "=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=" format
163
+ #
164
+ # Example:
165
+ #
166
+ # Encodings.b_value_encode("=?UTF-8?B?VGhpcyBpcyDjgYIgc3RyaW5n?=")
167
+ # #=> 'This is あ string'
168
+ def Encodings.b_value_decode(str)
169
+ RubyVer.b_value_decode(str)
140
170
  end
141
171
 
142
172
  # Decodes a Quoted-Printable string from the "=?UTF-8?Q?This_is_=E3=81=82_string?=" format
@@ -146,13 +176,17 @@ module Mail
146
176
  # Encodings.b_value_encode("=?UTF-8?Q?This_is_=E3=81=82_string?=")
147
177
  # #=> 'This is あ string'
148
178
  def Encodings.q_value_decode(str)
149
- string = str.split(Mail::Patterns::WSP)
150
- string.flatten.map do |s|
151
- RubyVer.q_value_decode(s)
152
- end.join('')
179
+ RubyVer.q_value_decode(str).gsub(/_/, ' ')
153
180
  end
154
181
 
155
- private
182
+ def Encodings.split_encoding_from_string( str )
183
+ match = str.match(/\=\?(.+)?\?[QB]\?(.+)?\?\=/i)
184
+ if match
185
+ [match[1], match[2]]
186
+ else
187
+ nil
188
+ end
189
+ end
156
190
 
157
191
  def Encodings.find_encoding(str)
158
192
  RUBY_VERSION >= '1.9' ? str.encoding : $KCODE
@@ -5,11 +5,11 @@ module Mail
5
5
 
6
6
  # Decode the string from Quoted-Printable
7
7
  def self.decode(str)
8
- str.unpack("M*").first.gsub('_', ' ')
8
+ str.unpack("M*").first
9
9
  end
10
10
 
11
11
  def self.encode(str)
12
- str.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }.gsub( / /, "_" )
12
+ str.gsub( /[^a-z ]/i ) { quoted_printable_encode($&) }
13
13
  end
14
14
 
15
15
  private
@@ -37,8 +37,9 @@ module Mail
37
37
  super(CAPITALIZED_FIELD, strip_field(FIELD_NAME, args.last))
38
38
  end
39
39
 
40
+ # Bcc field should never be :encoded
40
41
  def encoded
41
- do_encode(CAPITALIZED_FIELD)
42
+ ''
42
43
  end
43
44
 
44
45
  def decoded
@@ -6,9 +6,7 @@ module Mail
6
6
  # have the name*0="blah", name*1="bleh" keys, and will just return
7
7
  # a single key called name="blahbleh" and do any required un-encoding
8
8
  # to make that happen
9
- class ParameterHash < Hash
10
-
11
- include Enumerable
9
+ class ParameterHash < HashWithIndifferentAccess
12
10
 
13
11
  def [](key_name)
14
12
  pairs = select { |k,v| k =~ /^#{key_name}\*/ }
@@ -41,10 +41,12 @@ module Mail
41
41
  @sub_type ||= element.sub_type
42
42
  end
43
43
 
44
- def content_type
44
+ def string
45
45
  "#{main_type}/#{sub_type}"
46
46
  end
47
47
 
48
+ alias :content_type :string
49
+
48
50
  def parameters
49
51
  unless @parameters
50
52
  @parameters = ParameterHash.new
@@ -22,7 +22,7 @@
22
22
  module Mail
23
23
  class ReturnPathField < StructuredField
24
24
 
25
- include CommonAddress
25
+ include Mail::CommonAddress
26
26
 
27
27
  FIELD_NAME = 'return-path'
28
28
  CAPITALIZED_FIELD = 'Return-Path'
@@ -39,5 +39,9 @@ module Mail
39
39
  do_decode
40
40
  end
41
41
 
42
+ def address
43
+ addresses.first
44
+ end
45
+
42
46
  end
43
47
  end
data/lib/mail/message.rb CHANGED
@@ -149,6 +149,13 @@ module Mail
149
149
  value ? self.header = value : @header
150
150
  end
151
151
 
152
+ # Provides a way to set custom headers, by passing in a hash
153
+ def headers(hash = {})
154
+ hash.each_pair do |k,v|
155
+ header[k] = v
156
+ end
157
+ end
158
+
152
159
  # Sets the body object of the message object.
153
160
  #
154
161
  # Example:
@@ -183,6 +190,7 @@ module Mail
183
190
  else
184
191
  @body = Mail::Body.new(value)
185
192
  end
193
+ add_encoding_to_body
186
194
  end
187
195
 
188
196
  # Returns the body of the message object. Or, if passed
@@ -196,7 +204,12 @@ module Mail
196
204
  # mail.body 'This is another body'
197
205
  # mail.body #=> #<Mail::Body:0x13919c @raw_source="This is anothe...
198
206
  def body(value = nil)
199
- value ? self.body = value : @body
207
+ if value
208
+ self.body = value
209
+ add_encoding_to_body
210
+ else
211
+ @body
212
+ end
200
213
  end
201
214
 
202
215
  # Sets the to filed of the message header.
@@ -261,6 +274,30 @@ module Mail
261
274
  def destinations
262
275
  [to, cc, bcc].map { |f| f.addresses if f }.compact.flatten
263
276
  end
277
+
278
+ # Returns an array of addresses (the encoded value) in the From field,
279
+ # if no From field, returns an empty array
280
+ def from_addrs
281
+ from ? from.formatted.compact.flatten : []
282
+ end
283
+
284
+ # Returns an array of addresses (the encoded value) in the To field,
285
+ # if no To field, returns an empty array
286
+ def to_addrs
287
+ to ? to.formatted.compact.flatten : []
288
+ end
289
+
290
+ # Returns an array of addresses (the encoded value) in the Cc field,
291
+ # if no Cc field, returns an empty array
292
+ def cc_addrs
293
+ cc ? cc.formatted.compact.flatten : []
294
+ end
295
+
296
+ # Returns an array of addresses (the encoded value) in the Bcc field,
297
+ # if no Bcc field, returns an empty array
298
+ def bcc_addrs
299
+ bcc ? bcc.formatted.compact.flatten : []
300
+ end
264
301
 
265
302
  # Sets the subject field in the message header.
266
303
  #
@@ -295,6 +332,8 @@ module Mail
295
332
  def []=(name, value)
296
333
  if name.to_s == 'body'
297
334
  self.body = value
335
+ elsif name.to_s =~ /content[-_]type/i
336
+ header[underscoreize(name)] = value
298
337
  else
299
338
  header[underscoreize(name)] = value
300
339
  end
@@ -477,6 +516,16 @@ module Mail
477
516
  def charset
478
517
  content_type ? content_type.parameters['charset'] : nil
479
518
  end
519
+
520
+ # Sets the charset to the supplied value. Will set the content type to text/plain if
521
+ # it does not already exist
522
+ def charset=(value)
523
+ if content_type
524
+ content_type.parameters['charset'] = value
525
+ else
526
+ self.content_type ['text', 'plain', {'charset' => value}]
527
+ end
528
+ end
480
529
 
481
530
  # Returns the main content type
482
531
  def main_type
@@ -551,10 +600,17 @@ module Mail
551
600
  body.parts
552
601
  end
553
602
 
603
+ # Returns an array of attachments in the email recursively
554
604
  def attachments
555
- body.parts.select { |p| p.attachment? }.map { |p| p.attachment }
605
+ body.parts.map do |p|
606
+ if p.parts.empty?
607
+ p.attachment if p.attachment?
608
+ else
609
+ p.attachments
610
+ end
611
+ end.compact.flatten
556
612
  end
557
-
613
+
558
614
  def has_attachments?
559
615
  !attachments.empty?
560
616
  end
@@ -563,6 +619,7 @@ module Mail
563
619
  def html_part(&block)
564
620
  if block_given?
565
621
  @html_part = Mail::Part.new(&block)
622
+ add_multipart_alternate_header
566
623
  add_part(@html_part)
567
624
  else
568
625
  @html_part
@@ -573,6 +630,7 @@ module Mail
573
630
  def text_part(&block)
574
631
  if block_given?
575
632
  @text_part = Mail::Part.new(&block)
633
+ add_multipart_alternate_header
576
634
  add_part(@text_part)
577
635
  else
578
636
  @text_part
@@ -588,6 +646,7 @@ module Mail
588
646
  else
589
647
  @html_part = Mail::Part.new('Content-Type: text/html;')
590
648
  end
649
+ add_multipart_alternate_header
591
650
  add_part(@html_part)
592
651
  end
593
652
 
@@ -600,14 +659,37 @@ module Mail
600
659
  else
601
660
  @text_part = Mail::Part.new('Content-Type: text/plain;')
602
661
  end
662
+ add_multipart_alternate_header
603
663
  add_part(@text_part)
604
664
  end
605
665
 
606
666
  # Adds a part to the parts list or creates the part list
607
667
  def add_part(part)
608
- add_multipart_alternate_header
668
+ if body.parts.empty? && !self.body.decoded.blank?
669
+ @text_part = Mail::Part.new('Content-Type: text/plain;')
670
+ @text_part.body = body.decoded
671
+ self.body << @text_part
672
+ add_multipart_alternate_header
673
+ end
674
+ add_boundary
609
675
  self.body << part
610
676
  end
677
+
678
+ # Allows you to add a part in block form to an existing mail message object
679
+ #
680
+ # Example:
681
+ #
682
+ # mail = Mail.new do
683
+ # part :content_type => "multipart/alternative", :content_disposition => "inline" do |p|
684
+ # p.part :content_type => "text/plain", :body => "test text\nline #2"
685
+ # p.part :content_type => "text/html", :body => "<b>test</b> HTML<br/>\nline #2"
686
+ # end
687
+ # end
688
+ def part(params = {})
689
+ new_part = Part.new(params)
690
+ yield new_part if block_given?
691
+ add_part(new_part)
692
+ end
611
693
 
612
694
  # Adds a file to the message. You have two options with this method, you can
613
695
  # just pass in the absolute path to the file you want and Mail will read the file,
@@ -735,6 +817,12 @@ module Mail
735
817
  body.split!(boundary)
736
818
  end
737
819
 
820
+ def add_encoding_to_body
821
+ unless content_transfer_encoding.blank?
822
+ body.encoding = content_transfer_encoding.decoded
823
+ end
824
+ end
825
+
738
826
  def add_required_fields
739
827
  @body = Mail::Body.new('') if body.nil?
740
828
  add_message_id unless (has_message_id? || self.class == Mail::Part)
@@ -746,30 +834,37 @@ module Mail
746
834
  end
747
835
 
748
836
  def add_multipart_alternate_header
749
- if html_part && text_part
750
- unless header['content-type']
751
- header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
752
- body.boundary = boundary
753
- end
837
+ header['content-type'] = ContentTypeField.with_boundary('multipart/alternative').value
838
+ body.boundary = boundary
839
+ end
840
+
841
+ def add_boundary
842
+ unless body.boundary && boundary
843
+ header['content-type'] = 'multipart/mixed' unless header['content-type']
844
+ header['content-type'].parameters[:boundary] = ContentTypeField.generate_boundary
845
+ body.boundary = boundary
754
846
  end
755
847
  end
756
848
 
757
849
  def add_multipart_mixed_header
758
850
  unless header['content-type']
759
851
  header['content-type'] = ContentTypeField.with_boundary('multipart/mixed').value
760
-
761
852
  body.boundary = boundary
762
853
  end
763
854
  end
764
855
 
765
856
  def init_with_hash(hash)
857
+ passed_in_options = hash
766
858
  self.raw_source = ''
767
859
  @header = Mail::Header.new
768
860
  @body = Mail::Body.new
769
861
  hash.each_pair do |k,v|
770
862
  next if k.to_sym == :data
771
863
  if k.to_sym == :filename
772
- add_attachment(hash)
864
+ add_attachment(passed_in_options)
865
+ break
866
+ elsif k == :headers
867
+ self.headers(v)
773
868
  else
774
869
  self[k] = v
775
870
  end
@@ -778,9 +873,13 @@ module Mail
778
873
 
779
874
  def add_attachment(options_hash)
780
875
  @attachment = Mail::Attachment.new(options_hash)
781
- self.content_type = "#{attachment.mime_type}; filename=\"#{attachment.filename}\""
876
+ mime_type = options_hash[:content_type] || attachment.mime_type
877
+ self.content_type = "#{mime_type}; filename=\"#{attachment.filename}\""
782
878
  self.content_transfer_encoding = "Base64"
783
- self.content_disposition = "attachment; filename=\"#{attachment.filename}\""
879
+
880
+ disposition = options_hash[:content_disposition] || "attachment"
881
+ self.content_disposition = "#{disposition}; filename=\"#{attachment.filename}\""
882
+ add_boundary
784
883
  self.body = attachment.encoded
785
884
  end
786
885
 
data/lib/mail/version.rb CHANGED
@@ -3,7 +3,7 @@ module Mail
3
3
  module VERSION
4
4
  MAJOR = 1
5
5
  MINOR = 2
6
- TINY = 6
6
+ TINY = 8
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY].join('.')
9
9
  end
@@ -45,7 +45,7 @@ module Mail
45
45
  end
46
46
 
47
47
  def Ruby18.b_value_decode(str)
48
- match = str.match(/\=\?(.+)?\?B\?(.+)?\?\=/)
48
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/)
49
49
  if match
50
50
  encoding = match[1]
51
51
  str = Ruby18.decode_base64(match[2])
@@ -61,7 +61,7 @@ module Mail
61
61
  end
62
62
 
63
63
  def Ruby18.q_value_decode(str)
64
- match = str.match(/\=\?(.+)?\?Q\?(.+)?\?\=/)
64
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/)
65
65
  if match
66
66
  encoding = match[1]
67
67
  str = Encodings::QuotedPrintable.decode(match[2])
@@ -37,7 +37,7 @@ module Mail
37
37
  end
38
38
 
39
39
  def Ruby19.b_value_decode(str)
40
- match = str.match(/\=\?(.+)?\?B\?(.+)?\?\=/)
40
+ match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/)
41
41
  if match
42
42
  encoding = match[1]
43
43
  str = Ruby19.decode_base64(match[2])
@@ -52,7 +52,7 @@ module Mail
52
52
  end
53
53
 
54
54
  def Ruby19.q_value_decode(str)
55
- match = str.match(/\=\?(.+)?\?Q\?(.+)?\?\=/)
55
+ match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/)
56
56
  if match
57
57
  encoding = match[1]
58
58
  str = Encodings::QuotedPrintable.decode(match[2])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mail
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.6
4
+ version: 1.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Lindsaar
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-11-19 00:00:00 +11:00
12
+ date: 2009-11-22 00:00:00 +11:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: "0"
33
+ version: "1.4"
34
34
  version:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: activesupport
@@ -40,7 +40,7 @@ dependencies:
40
40
  requirements:
41
41
  - - ">="
42
42
  - !ruby/object:Gem::Version
43
- version: "0"
43
+ version: "2.3"
44
44
  version:
45
45
  - !ruby/object:Gem::Dependency
46
46
  name: mime-types