lonbaker-tmail 1.2.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 (97) hide show
  1. data/CHANGES +74 -0
  2. data/LICENSE +21 -0
  3. data/NOTES +7 -0
  4. data/README +169 -0
  5. data/ext/Makefile +20 -0
  6. data/ext/tmailscanner/tmail/MANIFEST +4 -0
  7. data/ext/tmailscanner/tmail/depend +1 -0
  8. data/ext/tmailscanner/tmail/extconf.rb +33 -0
  9. data/ext/tmailscanner/tmail/tmailscanner.c +583 -0
  10. data/lib/tmail.rb +5 -0
  11. data/lib/tmail/Makefile +18 -0
  12. data/lib/tmail/address.rb +426 -0
  13. data/lib/tmail/attachments.rb +46 -0
  14. data/lib/tmail/base64.rb +46 -0
  15. data/lib/tmail/compat.rb +41 -0
  16. data/lib/tmail/config.rb +67 -0
  17. data/lib/tmail/core_extensions.rb +63 -0
  18. data/lib/tmail/encode.rb +590 -0
  19. data/lib/tmail/header.rb +962 -0
  20. data/lib/tmail/index.rb +9 -0
  21. data/lib/tmail/interface.rb +1130 -0
  22. data/lib/tmail/loader.rb +3 -0
  23. data/lib/tmail/mail.rb +580 -0
  24. data/lib/tmail/mailbox.rb +496 -0
  25. data/lib/tmail/main.rb +6 -0
  26. data/lib/tmail/mbox.rb +3 -0
  27. data/lib/tmail/net.rb +248 -0
  28. data/lib/tmail/obsolete.rb +132 -0
  29. data/lib/tmail/parser.rb +1476 -0
  30. data/lib/tmail/parser.y +381 -0
  31. data/lib/tmail/port.rb +379 -0
  32. data/lib/tmail/quoting.rb +118 -0
  33. data/lib/tmail/require_arch.rb +58 -0
  34. data/lib/tmail/scanner.rb +49 -0
  35. data/lib/tmail/scanner_r.rb +261 -0
  36. data/lib/tmail/stringio.rb +280 -0
  37. data/lib/tmail/utils.rb +351 -0
  38. data/lib/tmail/version.rb +39 -0
  39. data/meta/MANIFEST +128 -0
  40. data/meta/project.yaml +30 -0
  41. data/meta/unixname +1 -0
  42. data/sample/bench_base64.rb +48 -0
  43. data/sample/data/multipart +23 -0
  44. data/sample/data/normal +29 -0
  45. data/sample/data/sendtest +5 -0
  46. data/sample/data/simple +14 -0
  47. data/sample/data/test +27 -0
  48. data/sample/extract-attachements.rb +33 -0
  49. data/sample/from-check.rb +26 -0
  50. data/sample/multipart.rb +26 -0
  51. data/sample/parse-bench.rb +68 -0
  52. data/sample/parse-test.rb +19 -0
  53. data/sample/sendmail.rb +94 -0
  54. data/test/extctrl.rb +6 -0
  55. data/test/fixtures/mailbox +414 -0
  56. data/test/fixtures/mailbox_without_any_from_or_sender +10 -0
  57. data/test/fixtures/mailbox_without_from +11 -0
  58. data/test/fixtures/mailbox_without_return_path +12 -0
  59. data/test/fixtures/raw_base64_decoded_string +0 -0
  60. data/test/fixtures/raw_base64_email +83 -0
  61. data/test/fixtures/raw_base64_encoded_string +1 -0
  62. data/test/fixtures/raw_email +14 -0
  63. data/test/fixtures/raw_email10 +20 -0
  64. data/test/fixtures/raw_email11 +34 -0
  65. data/test/fixtures/raw_email12 +32 -0
  66. data/test/fixtures/raw_email13 +29 -0
  67. data/test/fixtures/raw_email2 +114 -0
  68. data/test/fixtures/raw_email3 +70 -0
  69. data/test/fixtures/raw_email4 +59 -0
  70. data/test/fixtures/raw_email5 +19 -0
  71. data/test/fixtures/raw_email6 +20 -0
  72. data/test/fixtures/raw_email7 +66 -0
  73. data/test/fixtures/raw_email8 +47 -0
  74. data/test/fixtures/raw_email9 +28 -0
  75. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  76. data/test/fixtures/raw_email_reply +32 -0
  77. data/test/fixtures/raw_email_simple +11 -0
  78. data/test/fixtures/raw_email_with_bad_date +48 -0
  79. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  80. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  81. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  82. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  83. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  84. data/test/kcode.rb +14 -0
  85. data/test/test_address.rb +1211 -0
  86. data/test/test_attachments.rb +60 -0
  87. data/test/test_base64.rb +64 -0
  88. data/test/test_encode.rb +85 -0
  89. data/test/test_header.rb +969 -0
  90. data/test/test_helper.rb +9 -0
  91. data/test/test_mail.rb +753 -0
  92. data/test/test_mbox.rb +184 -0
  93. data/test/test_port.rb +436 -0
  94. data/test/test_quote.rb +98 -0
  95. data/test/test_scanner.rb +209 -0
  96. data/test/test_utils.rb +36 -0
  97. metadata +159 -0
@@ -0,0 +1,280 @@
1
+ # encoding: utf-8
2
+ =begin rdoc
3
+
4
+ = String handling class
5
+
6
+ =end
7
+ #--
8
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
9
+ #
10
+ # Permission is hereby granted, free of charge, to any person obtaining
11
+ # a copy of this software and associated documentation files (the
12
+ # "Software"), to deal in the Software without restriction, including
13
+ # without limitation the rights to use, copy, modify, merge, publish,
14
+ # distribute, sublicense, and/or sell copies of the Software, and to
15
+ # permit persons to whom the Software is furnished to do so, subject to
16
+ # the following conditions:
17
+ #
18
+ # The above copyright notice and this permission notice shall be
19
+ # included in all copies or substantial portions of the Software.
20
+ #
21
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ #
29
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
30
+ # with permission of Minero Aoki.
31
+ #++
32
+
33
+ class StringInput#:nodoc:
34
+
35
+ include Enumerable
36
+
37
+ class << self
38
+
39
+ def new( str )
40
+ if block_given?
41
+ begin
42
+ f = super
43
+ yield f
44
+ ensure
45
+ f.close if f
46
+ end
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ alias open new
53
+
54
+ end
55
+
56
+ def initialize( str )
57
+ @src = str
58
+ @pos = 0
59
+ @closed = false
60
+ @lineno = 0
61
+ end
62
+
63
+ attr_reader :lineno
64
+
65
+ def string
66
+ @src
67
+ end
68
+
69
+ def inspect
70
+ "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@src[0,30].inspect}>"
71
+ end
72
+
73
+ def close
74
+ stream_check!
75
+ @pos = nil
76
+ @closed = true
77
+ end
78
+
79
+ def closed?
80
+ @closed
81
+ end
82
+
83
+ def pos
84
+ stream_check!
85
+ [@pos, @src.size].min
86
+ end
87
+
88
+ alias tell pos
89
+
90
+ def seek( offset, whence = IO::SEEK_SET )
91
+ stream_check!
92
+ case whence
93
+ when IO::SEEK_SET
94
+ @pos = offset
95
+ when IO::SEEK_CUR
96
+ @pos += offset
97
+ when IO::SEEK_END
98
+ @pos = @src.size - offset
99
+ else
100
+ raise ArgumentError, "unknown seek flag: #{whence}"
101
+ end
102
+ @pos = 0 if @pos < 0
103
+ @pos = [@pos, @src.size + 1].min
104
+ offset
105
+ end
106
+
107
+ def rewind
108
+ stream_check!
109
+ @pos = 0
110
+ end
111
+
112
+ def eof?
113
+ stream_check!
114
+ @pos > @src.size
115
+ end
116
+
117
+ def each( &block )
118
+ stream_check!
119
+ begin
120
+ @src.each(&block)
121
+ ensure
122
+ @pos = 0
123
+ end
124
+ end
125
+
126
+ def gets
127
+ stream_check!
128
+ if idx = @src.index(?\n, @pos)
129
+ idx += 1 # "\n".size
130
+ line = @src[ @pos ... idx ]
131
+ @pos = idx
132
+ @pos += 1 if @pos == @src.size
133
+ else
134
+ line = @src[ @pos .. -1 ]
135
+ @pos = @src.size + 1
136
+ end
137
+ @lineno += 1
138
+
139
+ line
140
+ end
141
+
142
+ def getc
143
+ stream_check!
144
+ ch = @src[@pos]
145
+ @pos += 1
146
+ @pos += 1 if @pos == @src.size
147
+ ch
148
+ end
149
+
150
+ def read( len = nil )
151
+ stream_check!
152
+ return read_all unless len
153
+ str = @src[@pos, len]
154
+ @pos += len
155
+ @pos += 1 if @pos == @src.size
156
+ str
157
+ end
158
+
159
+ alias sysread read
160
+
161
+ def read_all
162
+ stream_check!
163
+ return nil if eof?
164
+ rest = @src[@pos ... @src.size]
165
+ @pos = @src.size + 1
166
+ rest
167
+ end
168
+
169
+ def stream_check!
170
+ @closed and raise IOError, 'closed stream'
171
+ end
172
+
173
+ end
174
+
175
+
176
+ class StringOutput#:nodoc:
177
+
178
+ class << self
179
+
180
+ def new( str = '' )
181
+ if block_given?
182
+ begin
183
+ f = super
184
+ yield f
185
+ ensure
186
+ f.close if f
187
+ end
188
+ else
189
+ super
190
+ end
191
+ end
192
+
193
+ alias open new
194
+
195
+ end
196
+
197
+ def initialize( str = '' )
198
+ @dest = str
199
+ @closed = false
200
+ end
201
+
202
+ def close
203
+ @closed = true
204
+ end
205
+
206
+ def closed?
207
+ @closed
208
+ end
209
+
210
+ def string
211
+ @dest
212
+ end
213
+
214
+ alias value string
215
+ alias to_str string
216
+
217
+ def size
218
+ @dest.size
219
+ end
220
+
221
+ alias pos size
222
+
223
+ def inspect
224
+ "#<#{self.class}:#{@dest ? 'open' : 'closed'},#{object_id}>"
225
+ end
226
+
227
+ def print( *args )
228
+ stream_check!
229
+ raise ArgumentError, 'wrong # of argument (0 for >1)' if args.empty?
230
+ args.each do |s|
231
+ raise ArgumentError, 'nil not allowed' if s.nil?
232
+ @dest << s.to_s
233
+ end
234
+ nil
235
+ end
236
+
237
+ def puts( *args )
238
+ stream_check!
239
+ args.each do |str|
240
+ @dest << (s = str.to_s)
241
+ @dest << "\n" unless s[-1] == ?\n
242
+ end
243
+ @dest << "\n" if args.empty?
244
+ nil
245
+ end
246
+
247
+ def putc( ch )
248
+ stream_check!
249
+ @dest << ch.chr
250
+ nil
251
+ end
252
+
253
+ def printf( *args )
254
+ stream_check!
255
+ @dest << sprintf(*args)
256
+ nil
257
+ end
258
+
259
+ def write( str )
260
+ stream_check!
261
+ s = str.to_s
262
+ @dest << s
263
+ s.size
264
+ end
265
+
266
+ alias syswrite write
267
+
268
+ def <<( str )
269
+ stream_check!
270
+ @dest << str.to_s
271
+ self
272
+ end
273
+
274
+ private
275
+
276
+ def stream_check!
277
+ @closed and raise IOError, 'closed stream'
278
+ end
279
+
280
+ end
@@ -0,0 +1,351 @@
1
+ #--
2
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
24
+ # with permission of Minero Aoki.
25
+ #++
26
+
27
+ # = TMail - The EMail Swiss Army Knife for Ruby
28
+ #
29
+ # The TMail library provides you with a very complete way to handle and manipulate EMails
30
+ # from within your Ruby programs.
31
+ #
32
+ # Used as the backbone for email handling by the Ruby on Rails and Nitro web frameworks as
33
+ # well as a bunch of other Ruby apps including the Ruby-Talk mailing list to newsgroup email
34
+ # gateway, it is a proven and reliable email handler that won't let you down.
35
+ #
36
+ # Originally created by Minero Aoki, TMail has been recently picked up by Mikel Lindsaar and
37
+ # is being actively maintained. Numerous backlogged bug fixes have been applied as well as
38
+ # Ruby 1.9 compatibility and a swath of documentation to boot.
39
+ #
40
+ # TMail allows you to treat an email totally as an object and allow you to get on with your
41
+ # own programming without having to worry about crafting the perfect email address validation
42
+ # parser, or assembling an email from all it's component parts.
43
+ #
44
+ # TMail handles the most complex part of the email - the header. It generates and parses
45
+ # headers and provides you with instant access to their innards through simple and logically
46
+ # named accessor and setter methods.
47
+ #
48
+ # TMail also provides a wrapper to Net/SMTP as well as Unix Mailbox handling methods to
49
+ # directly read emails from your unix mailbox, parse them and use them.
50
+ #
51
+ # Following is the comprehensive list of methods to access TMail::Mail objects. You can also
52
+ # check out TMail::Mail, TMail::Address and TMail::Headers for other lists.
53
+ module TMail
54
+
55
+ # Provides an exception to throw on errors in Syntax within TMail's parsers
56
+ class SyntaxError < StandardError; end
57
+
58
+ # Provides a new email boundary to separate parts of the email. This is a random
59
+ # string based off the current time, so should be fairly unique.
60
+ #
61
+ # For Example:
62
+ #
63
+ # TMail.new_boundary
64
+ # #=> "mimepart_47bf656968207_25a8fbb80114"
65
+ # TMail.new_boundary
66
+ # #=> "mimepart_47bf66051de4_25a8fbb80240"
67
+ def TMail.new_boundary
68
+ 'mimepart_' + random_tag
69
+ end
70
+
71
+ # Provides a new email message ID. You can use this to generate unique email message
72
+ # id's for your email so you can track them.
73
+ #
74
+ # Optionally takes a fully qualified domain name (default to the current hostname
75
+ # returned by Socket.gethostname) that will be appended to the message ID.
76
+ #
77
+ # For Example:
78
+ #
79
+ # email.message_id = TMail.new_message_id
80
+ # #=> "<47bf66845380e_25a8fbb80332@baci.local.tmail>"
81
+ # email.to_s
82
+ # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@baci.local.tmail>\n\n"
83
+ # email.message_id = TMail.new_message_id("lindsaar.net")
84
+ # #=> "<47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>"
85
+ # email.to_s
86
+ # #=> "Message-Id: <47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>\n\n"
87
+ def TMail.new_message_id( fqdn = nil )
88
+ fqdn ||= ::Socket.gethostname
89
+ "<#{random_tag()}@#{fqdn}.tmail>"
90
+ end
91
+
92
+ #:stopdoc:
93
+ def TMail.random_tag #:nodoc:
94
+ @uniq += 1
95
+ t = Time.now
96
+ sprintf('%x%x_%x%x%d%x',
97
+ t.to_i, t.tv_usec,
98
+ $$, Thread.current.object_id, @uniq, rand(255))
99
+ end
100
+ private_class_method :random_tag
101
+
102
+ @uniq = 0
103
+
104
+ #:startdoc:
105
+
106
+ # Text Utils provides a namespace to define TOKENs, ATOMs, PHRASEs and CONTROL characters that
107
+ # are OK per RFC 2822.
108
+ #
109
+ # It also provides methods you can call to determine if a string is safe
110
+ module TextUtils
111
+
112
+ aspecial = %Q|()<>[]:;.\\,"|
113
+ tspecial = %Q|()<>[];:\\,"/?=|
114
+ lwsp = %Q| \t\r\n|
115
+ control = %Q|\x00-\x1f\x7f-\xff|
116
+
117
+ CONTROL_CHAR = /[#{control}]/n
118
+ ATOM_UNSAFE = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
119
+ PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
120
+ TOKEN_UNSAFE = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
121
+
122
+ # Returns true if the string supplied is free from characters not allowed as an ATOM
123
+ def atom_safe?( str )
124
+ not ATOM_UNSAFE === str
125
+ end
126
+
127
+ # If the string supplied has ATOM unsafe characters in it, will return the string quoted
128
+ # in double quotes, otherwise returns the string unmodified
129
+ def quote_atom( str )
130
+ (ATOM_UNSAFE === str) ? dquote(str) : str
131
+ end
132
+
133
+ # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
134
+ # in double quotes, otherwise returns the string unmodified
135
+ def quote_phrase( str )
136
+ (PHRASE_UNSAFE === str) ? dquote(str) : str
137
+ end
138
+
139
+ # Returns true if the string supplied is free from characters not allowed as a TOKEN
140
+ def token_safe?( str )
141
+ not TOKEN_UNSAFE === str
142
+ end
143
+
144
+ # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
145
+ # in double quotes, otherwise returns the string unmodified
146
+ def quote_token( str )
147
+ (TOKEN_UNSAFE === str) ? dquote(str) : str
148
+ end
149
+
150
+ # Wraps supplied string in double quotes unless it is already wrapped
151
+ # Returns double quoted string
152
+ def dquote( str ) #:nodoc:
153
+ unless str =~ /^".*?"$/
154
+ '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
155
+ else
156
+ str
157
+ end
158
+ end
159
+ private :dquote
160
+
161
+ # Unwraps supplied string from inside double quotes
162
+ # Returns unquoted string
163
+ def unquote( str )
164
+ str =~ /^"(.*?)"$/ ? $1 : str
165
+ end
166
+
167
+ # Provides a method to join a domain name by it's parts and also makes it
168
+ # ATOM safe by quoting it as needed
169
+ def join_domain( arr )
170
+ arr.map {|i|
171
+ if /\A\[.*\]\z/ === i
172
+ i
173
+ else
174
+ quote_atom(i)
175
+ end
176
+ }.join('.')
177
+ end
178
+
179
+ #:stopdoc:
180
+ ZONESTR_TABLE = {
181
+ 'jst' => 9 * 60,
182
+ 'eet' => 2 * 60,
183
+ 'bst' => 1 * 60,
184
+ 'met' => 1 * 60,
185
+ 'gmt' => 0,
186
+ 'utc' => 0,
187
+ 'ut' => 0,
188
+ 'nst' => -(3 * 60 + 30),
189
+ 'ast' => -4 * 60,
190
+ 'edt' => -4 * 60,
191
+ 'est' => -5 * 60,
192
+ 'cdt' => -5 * 60,
193
+ 'cst' => -6 * 60,
194
+ 'mdt' => -6 * 60,
195
+ 'mst' => -7 * 60,
196
+ 'pdt' => -7 * 60,
197
+ 'pst' => -8 * 60,
198
+ 'a' => -1 * 60,
199
+ 'b' => -2 * 60,
200
+ 'c' => -3 * 60,
201
+ 'd' => -4 * 60,
202
+ 'e' => -5 * 60,
203
+ 'f' => -6 * 60,
204
+ 'g' => -7 * 60,
205
+ 'h' => -8 * 60,
206
+ 'i' => -9 * 60,
207
+ # j not use
208
+ 'k' => -10 * 60,
209
+ 'l' => -11 * 60,
210
+ 'm' => -12 * 60,
211
+ 'n' => 1 * 60,
212
+ 'o' => 2 * 60,
213
+ 'p' => 3 * 60,
214
+ 'q' => 4 * 60,
215
+ 'r' => 5 * 60,
216
+ 's' => 6 * 60,
217
+ 't' => 7 * 60,
218
+ 'u' => 8 * 60,
219
+ 'v' => 9 * 60,
220
+ 'w' => 10 * 60,
221
+ 'x' => 11 * 60,
222
+ 'y' => 12 * 60,
223
+ 'z' => 0 * 60
224
+ }
225
+ #:startdoc:
226
+
227
+ # Takes a time zone string from an EMail and converts it to Unix Time (seconds)
228
+ def timezone_string_to_unixtime( str )
229
+ if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
230
+ sec = (m[2].to_i * 60 + m[3].to_i) * 60
231
+ m[1] == '-' ? -sec : sec
232
+ else
233
+ min = ZONESTR_TABLE[str.downcase] or
234
+ raise SyntaxError, "wrong timezone format '#{str}'"
235
+ min * 60
236
+ end
237
+ end
238
+
239
+ #:stopdoc:
240
+ WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
241
+ MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
242
+ Jul Aug Sep Oct Nov Dec TMailBUG )
243
+
244
+ def time2str( tm )
245
+ # [ruby-list:7928]
246
+ gmt = Time.at(tm.to_i)
247
+ gmt.gmtime
248
+ offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
249
+
250
+ # DO NOT USE strftime: setlocale() breaks it
251
+ sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
252
+ WDAY[tm.wday], tm.mday, MONTH[tm.month],
253
+ tm.year, tm.hour, tm.min, tm.sec,
254
+ *(offset / 60).divmod(60)
255
+ end
256
+
257
+
258
+ MESSAGE_ID = /<[^\@>]+\@[^>\@]+>/
259
+
260
+ def message_id?( str )
261
+ MESSAGE_ID === str
262
+ end
263
+
264
+
265
+ MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
266
+
267
+ def mime_encoded?( str )
268
+ MIME_ENCODED === str
269
+ end
270
+
271
+
272
+ def decode_params( hash )
273
+ new = Hash.new
274
+ encoded = nil
275
+ hash.each do |key, value|
276
+ if m = /\*(?:(\d+)\*)?\z/.match(key)
277
+ ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
278
+ else
279
+ new[key] = to_kcode(value)
280
+ end
281
+ end
282
+ if encoded
283
+ encoded.each do |key, strings|
284
+ new[key] = decode_RFC2231(strings.join(''))
285
+ end
286
+ end
287
+
288
+ new
289
+ end
290
+
291
+ NKF_FLAGS = {
292
+ 'EUC' => '-e -m',
293
+ 'SJIS' => '-s -m'
294
+ }
295
+
296
+ def to_kcode( str )
297
+ flag = NKF_FLAGS[TMail.KCODE] or return str
298
+ NKF.nkf(flag, str)
299
+ end
300
+
301
+ RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
302
+
303
+ def decode_RFC2231( str )
304
+ m = RFC2231_ENCODED.match(str) or return str
305
+ begin
306
+ to_kcode(m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
307
+ rescue
308
+ m.post_match.gsub(/%[\da-f]{2}/in, "")
309
+ end
310
+ end
311
+
312
+ def quote_boundary
313
+ # Make sure the Content-Type boundary= parameter is quoted if it contains illegal characters
314
+ # (to ensure any special characters in the boundary text are escaped from the parser
315
+ # (such as = in MS Outlook's boundary text))
316
+ if @body =~ /^(.*)boundary=(.*)$/m
317
+ preamble = $1
318
+ remainder = $2
319
+ if remainder =~ /;/
320
+ remainder =~ /^(.*?)(;.*)$/m
321
+ boundary_text = $1
322
+ post = $2.chomp
323
+ else
324
+ boundary_text = remainder.chomp
325
+ end
326
+ if boundary_text =~ /[\/\?\=]/
327
+ boundary_text = "\"#{boundary_text}\"" unless boundary_text =~ /^".*?"$/
328
+ @body = "#{preamble}boundary=#{boundary_text}#{post}"
329
+ end
330
+ end
331
+ end
332
+ #:startdoc:
333
+
334
+ # if number of parentheses dont match quote them
335
+ def quote_parentheses
336
+ if @body.count("(") != @body.count(")")
337
+ @body.gsub!(/\(/, "\\(")
338
+ @body.gsub!(/\)/, "\\)")
339
+ end
340
+ end
341
+
342
+ # Remove invalid or unneeded characters appended to date header
343
+ def clean_date
344
+ if @body.index("(")
345
+ @body = @body.slice(0, @body.index("("))
346
+ end
347
+ end
348
+
349
+ end
350
+
351
+ end