mms2r 1.0.0

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 (55) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +54 -0
  3. data/README.txt +81 -0
  4. data/Rakefile +30 -0
  5. data/conf/mms2r_cingularmedia_transform.yml +6 -0
  6. data/conf/mms2r_sprintmedia_ignore.yml +10 -0
  7. data/conf/mms2r_tmobilemedia_ignore.yml +17 -0
  8. data/conf/mms2r_verizonmedia_ignore.yml +3 -0
  9. data/lib/mms2r.rb +3 -0
  10. data/lib/mms2r/cingular_media.rb +11 -0
  11. data/lib/mms2r/media.rb +345 -0
  12. data/lib/mms2r/mmode_media.rb +13 -0
  13. data/lib/mms2r/sprint_media.rb +50 -0
  14. data/lib/mms2r/tmobile_media.rb +11 -0
  15. data/lib/mms2r/verizon_media.rb +11 -0
  16. data/lib/mms2r/version.rb +12 -0
  17. data/lib/vendor/text/format.rb +1466 -0
  18. data/lib/vendor/tmail.rb +3 -0
  19. data/lib/vendor/tmail/address.rb +242 -0
  20. data/lib/vendor/tmail/attachments.rb +39 -0
  21. data/lib/vendor/tmail/base64.rb +71 -0
  22. data/lib/vendor/tmail/config.rb +69 -0
  23. data/lib/vendor/tmail/encode.rb +467 -0
  24. data/lib/vendor/tmail/facade.rb +552 -0
  25. data/lib/vendor/tmail/header.rb +914 -0
  26. data/lib/vendor/tmail/info.rb +35 -0
  27. data/lib/vendor/tmail/loader.rb +1 -0
  28. data/lib/vendor/tmail/mail.rb +447 -0
  29. data/lib/vendor/tmail/mailbox.rb +433 -0
  30. data/lib/vendor/tmail/mbox.rb +1 -0
  31. data/lib/vendor/tmail/net.rb +280 -0
  32. data/lib/vendor/tmail/obsolete.rb +135 -0
  33. data/lib/vendor/tmail/parser.rb +1522 -0
  34. data/lib/vendor/tmail/port.rb +377 -0
  35. data/lib/vendor/tmail/quoting.rb +131 -0
  36. data/lib/vendor/tmail/scanner.rb +41 -0
  37. data/lib/vendor/tmail/scanner_r.rb +263 -0
  38. data/lib/vendor/tmail/stringio.rb +277 -0
  39. data/lib/vendor/tmail/tmail.rb +1 -0
  40. data/lib/vendor/tmail/utils.rb +238 -0
  41. data/test/files/dot.jpg +0 -0
  42. data/test/files/sprint-image-01.mail +195 -0
  43. data/test/files/sprint-text-01.mail +8 -0
  44. data/test/files/sprint-video-01.mail +195 -0
  45. data/test/files/sprint.mov +0 -0
  46. data/test/files/verizon-image-01.mail +815 -0
  47. data/test/files/verizon-text-01.mail +11 -0
  48. data/test/files/verizon-video-01.mail +336 -0
  49. data/test/test_mms2r_cingular.rb +52 -0
  50. data/test/test_mms2r_media.rb +311 -0
  51. data/test/test_mms2r_mmode.rb +52 -0
  52. data/test/test_mms2r_sprint.rb +154 -0
  53. data/test/test_mms2r_tmobile.rb +169 -0
  54. data/test/test_mms2r_verizon.rb +74 -0
  55. metadata +130 -0
@@ -0,0 +1,3 @@
1
+ require 'tmail/info'
2
+ require 'tmail/mail'
3
+ require 'tmail/mailbox'
@@ -0,0 +1,242 @@
1
+ #
2
+ # address.rb
3
+ #
4
+ #--
5
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27
+ # with permission of Minero Aoki.
28
+ #++
29
+
30
+ require 'tmail/encode'
31
+ require 'tmail/parser'
32
+
33
+
34
+ module TMail
35
+
36
+ class Address
37
+
38
+ include TextUtils
39
+
40
+ def Address.parse( str )
41
+ Parser.parse :ADDRESS, str
42
+ end
43
+
44
+ def address_group?
45
+ false
46
+ end
47
+
48
+ def initialize( local, domain )
49
+ if domain
50
+ domain.each do |s|
51
+ raise SyntaxError, 'empty word in domain' if s.empty?
52
+ end
53
+ end
54
+ @local = local
55
+ @domain = domain
56
+ @name = nil
57
+ @routes = []
58
+ end
59
+
60
+ attr_reader :name
61
+
62
+ def name=( str )
63
+ @name = str
64
+ @name = nil if str and str.empty?
65
+ end
66
+
67
+ alias phrase name
68
+ alias phrase= name=
69
+
70
+ attr_reader :routes
71
+
72
+ def inspect
73
+ "#<#{self.class} #{address()}>"
74
+ end
75
+
76
+ def local
77
+ return nil unless @local
78
+ return '""' if @local.size == 1 and @local[0].empty?
79
+ @local.map {|i| quote_atom(i) }.join('.')
80
+ end
81
+
82
+ def domain
83
+ return nil unless @domain
84
+ join_domain(@domain)
85
+ end
86
+
87
+ def spec
88
+ s = self.local
89
+ d = self.domain
90
+ if s and d
91
+ s + '@' + d
92
+ else
93
+ s
94
+ end
95
+ end
96
+
97
+ alias address spec
98
+
99
+
100
+ def ==( other )
101
+ other.respond_to? :spec and self.spec == other.spec
102
+ end
103
+
104
+ alias eql? ==
105
+
106
+ def hash
107
+ @local.hash ^ @domain.hash
108
+ end
109
+
110
+ def dup
111
+ obj = self.class.new(@local.dup, @domain.dup)
112
+ obj.name = @name.dup if @name
113
+ obj.routes.replace @routes
114
+ obj
115
+ end
116
+
117
+ include StrategyInterface
118
+
119
+ def accept( strategy, dummy1 = nil, dummy2 = nil )
120
+ unless @local
121
+ strategy.meta '<>' # empty return-path
122
+ return
123
+ end
124
+
125
+ spec_p = (not @name and @routes.empty?)
126
+ if @name
127
+ strategy.phrase @name
128
+ strategy.space
129
+ end
130
+ tmp = spec_p ? '' : '<'
131
+ unless @routes.empty?
132
+ tmp << @routes.map {|i| '@' + i }.join(',') << ':'
133
+ end
134
+ tmp << self.spec
135
+ tmp << '>' unless spec_p
136
+ strategy.meta tmp
137
+ strategy.lwsp ''
138
+ end
139
+
140
+ end
141
+
142
+
143
+ class AddressGroup
144
+
145
+ include Enumerable
146
+
147
+ def address_group?
148
+ true
149
+ end
150
+
151
+ def initialize( name, addrs )
152
+ @name = name
153
+ @addresses = addrs
154
+ end
155
+
156
+ attr_reader :name
157
+
158
+ def ==( other )
159
+ other.respond_to? :to_a and @addresses == other.to_a
160
+ end
161
+
162
+ alias eql? ==
163
+
164
+ def hash
165
+ map {|i| i.hash }.hash
166
+ end
167
+
168
+ def []( idx )
169
+ @addresses[idx]
170
+ end
171
+
172
+ def size
173
+ @addresses.size
174
+ end
175
+
176
+ def empty?
177
+ @addresses.empty?
178
+ end
179
+
180
+ def each( &block )
181
+ @addresses.each(&block)
182
+ end
183
+
184
+ def to_a
185
+ @addresses.dup
186
+ end
187
+
188
+ alias to_ary to_a
189
+
190
+ def include?( a )
191
+ @addresses.include? a
192
+ end
193
+
194
+ def flatten
195
+ set = []
196
+ @addresses.each do |a|
197
+ if a.respond_to? :flatten
198
+ set.concat a.flatten
199
+ else
200
+ set.push a
201
+ end
202
+ end
203
+ set
204
+ end
205
+
206
+ def each_address( &block )
207
+ flatten.each(&block)
208
+ end
209
+
210
+ def add( a )
211
+ @addresses.push a
212
+ end
213
+
214
+ alias push add
215
+
216
+ def delete( a )
217
+ @addresses.delete a
218
+ end
219
+
220
+ include StrategyInterface
221
+
222
+ def accept( strategy, dummy1 = nil, dummy2 = nil )
223
+ strategy.phrase @name
224
+ strategy.meta ':'
225
+ strategy.space
226
+ first = true
227
+ each do |mbox|
228
+ if first
229
+ first = false
230
+ else
231
+ strategy.meta ','
232
+ end
233
+ strategy.space
234
+ mbox.accept strategy
235
+ end
236
+ strategy.meta ';'
237
+ strategy.lwsp ''
238
+ end
239
+
240
+ end
241
+
242
+ end # module TMail
@@ -0,0 +1,39 @@
1
+ require 'stringio'
2
+
3
+ module TMail
4
+ class Attachment < StringIO
5
+ attr_accessor :original_filename, :content_type
6
+ end
7
+
8
+ class Mail
9
+ def has_attachments?
10
+ multipart? && parts.any? { |part| attachment?(part) }
11
+ end
12
+
13
+ def attachment?(part)
14
+ (part['content-disposition'] && part['content-disposition'].disposition == "attachment") ||
15
+ part.header['content-type'].main_type != "text"
16
+ end
17
+
18
+ def attachments
19
+ if multipart?
20
+ parts.collect { |part|
21
+ if attachment?(part)
22
+ content = part.body # unquoted automatically by TMail#body
23
+ file_name = (part['content-location'] &&
24
+ part['content-location'].body) ||
25
+ part.sub_header("content-type", "name") ||
26
+ part.sub_header("content-disposition", "filename")
27
+
28
+ next if file_name.blank? || content.blank?
29
+
30
+ attachment = Attachment.new(content)
31
+ attachment.original_filename = file_name.strip
32
+ attachment.content_type = part.content_type
33
+ attachment
34
+ end
35
+ }.compact
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,71 @@
1
+ #
2
+ # base64.rb
3
+ #
4
+ #--
5
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27
+ # with permission of Minero Aoki.
28
+ #++
29
+
30
+ module TMail
31
+
32
+ module Base64
33
+
34
+ module_function
35
+
36
+ def rb_folding_encode( str, eol = "\n", limit = 60 )
37
+ [str].pack('m')
38
+ end
39
+
40
+ def rb_encode( str )
41
+ [str].pack('m').tr( "\r\n", '' )
42
+ end
43
+
44
+ def rb_decode( str, strict = false )
45
+ str.unpack('m')
46
+ end
47
+
48
+ begin
49
+ require 'tmail/base64.so'
50
+ alias folding_encode c_folding_encode
51
+ alias encode c_encode
52
+ alias decode c_decode
53
+ class << self
54
+ alias folding_encode c_folding_encode
55
+ alias encode c_encode
56
+ alias decode c_decode
57
+ end
58
+ rescue LoadError
59
+ alias folding_encode rb_folding_encode
60
+ alias encode rb_encode
61
+ alias decode rb_decode
62
+ class << self
63
+ alias folding_encode rb_folding_encode
64
+ alias encode rb_encode
65
+ alias decode rb_decode
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -0,0 +1,69 @@
1
+ #
2
+ # config.rb
3
+ #
4
+ #--
5
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27
+ # with permission of Minero Aoki.
28
+ #++
29
+
30
+ module TMail
31
+
32
+ class Config
33
+
34
+ def initialize( strict )
35
+ @strict_parse = strict
36
+ @strict_base64decode = strict
37
+ end
38
+
39
+ def strict_parse?
40
+ @strict_parse
41
+ end
42
+
43
+ attr_writer :strict_parse
44
+
45
+ def strict_base64decode?
46
+ @strict_base64decode
47
+ end
48
+
49
+ attr_writer :strict_base64decode
50
+
51
+ def new_body_port( mail )
52
+ StringPort.new
53
+ end
54
+
55
+ alias new_preamble_port new_body_port
56
+ alias new_part_port new_body_port
57
+
58
+ end
59
+
60
+ DEFAULT_CONFIG = Config.new(false)
61
+ DEFAULT_STRICT_CONFIG = Config.new(true)
62
+
63
+ def Config.to_config( arg )
64
+ return DEFAULT_STRICT_CONFIG if arg == true
65
+ return DEFAULT_CONFIG if arg == false
66
+ arg or DEFAULT_CONFIG
67
+ end
68
+
69
+ end
@@ -0,0 +1,467 @@
1
+ #
2
+ # encode.rb
3
+ #
4
+ #--
5
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27
+ # with permission of Minero Aoki.
28
+ #++
29
+
30
+ require 'nkf'
31
+ require 'tmail/base64.rb'
32
+ require 'tmail/stringio'
33
+ require 'tmail/utils'
34
+
35
+
36
+ module TMail
37
+
38
+ module StrategyInterface
39
+
40
+ def create_dest( obj )
41
+ case obj
42
+ when nil
43
+ StringOutput.new
44
+ when String
45
+ StringOutput.new(obj)
46
+ when IO, StringOutput
47
+ obj
48
+ else
49
+ raise TypeError, 'cannot handle this type of object for dest'
50
+ end
51
+ end
52
+ module_function :create_dest
53
+
54
+ def encoded( eol = "\r\n", charset = 'j', dest = nil )
55
+ accept_strategy Encoder, eol, charset, dest
56
+ end
57
+
58
+ def decoded( eol = "\n", charset = 'e', dest = nil )
59
+ accept_strategy Decoder, eol, charset, dest
60
+ end
61
+
62
+ alias to_s decoded
63
+
64
+ def accept_strategy( klass, eol, charset, dest = nil )
65
+ dest ||= ''
66
+ accept klass.new(create_dest(dest), charset, eol)
67
+ dest
68
+ end
69
+
70
+ end
71
+
72
+
73
+ ###
74
+ ### MIME B encoding decoder
75
+ ###
76
+
77
+ class Decoder
78
+
79
+ include TextUtils
80
+
81
+ encoded = '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
82
+ ENCODED_WORDS = /#{encoded}(?:\s+#{encoded})*/i
83
+
84
+ OUTPUT_ENCODING = {
85
+ 'EUC' => 'e',
86
+ 'SJIS' => 's',
87
+ }
88
+
89
+ def self.decode( str, encoding = nil )
90
+ encoding ||= (OUTPUT_ENCODING[$KCODE] || 'j')
91
+ opt = '-m' + encoding
92
+ str.gsub(ENCODED_WORDS) {|s| NKF.nkf(opt, s) }
93
+ end
94
+
95
+ def initialize( dest, encoding = nil, eol = "\n" )
96
+ @f = StrategyInterface.create_dest(dest)
97
+ @encoding = (/\A[ejs]/ === encoding) ? encoding[0,1] : nil
98
+ @eol = eol
99
+ end
100
+
101
+ def decode( str )
102
+ self.class.decode(str, @encoding)
103
+ end
104
+ private :decode
105
+
106
+ def terminate
107
+ end
108
+
109
+ def header_line( str )
110
+ @f << decode(str)
111
+ end
112
+
113
+ def header_name( nm )
114
+ @f << nm << ': '
115
+ end
116
+
117
+ def header_body( str )
118
+ @f << decode(str)
119
+ end
120
+
121
+ def space
122
+ @f << ' '
123
+ end
124
+
125
+ alias spc space
126
+
127
+ def lwsp( str )
128
+ @f << str
129
+ end
130
+
131
+ def meta( str )
132
+ @f << str
133
+ end
134
+
135
+ def text( str )
136
+ @f << decode(str)
137
+ end
138
+
139
+ def phrase( str )
140
+ @f << quote_phrase(decode(str))
141
+ end
142
+
143
+ def kv_pair( k, v )
144
+ @f << k << '=' << v
145
+ end
146
+
147
+ def puts( str = nil )
148
+ @f << str if str
149
+ @f << @eol
150
+ end
151
+
152
+ def write( str )
153
+ @f << str
154
+ end
155
+
156
+ end
157
+
158
+
159
+ ###
160
+ ### MIME B-encoding encoder
161
+ ###
162
+
163
+ #
164
+ # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
165
+ #
166
+ class Encoder
167
+
168
+ include TextUtils
169
+
170
+ BENCODE_DEBUG = false unless defined?(BENCODE_DEBUG)
171
+
172
+ def Encoder.encode( str )
173
+ e = new()
174
+ e.header_body str
175
+ e.terminate
176
+ e.dest.string
177
+ end
178
+
179
+ SPACER = "\t"
180
+ MAX_LINE_LEN = 70
181
+
182
+ OPTIONS = {
183
+ 'EUC' => '-Ej -m0',
184
+ 'SJIS' => '-Sj -m0',
185
+ 'UTF8' => nil, # FIXME
186
+ 'NONE' => nil
187
+ }
188
+
189
+ def initialize( dest = nil, encoding = nil, eol = "\r\n", limit = nil )
190
+ @f = StrategyInterface.create_dest(dest)
191
+ @opt = OPTIONS[$KCODE]
192
+ @eol = eol
193
+ reset
194
+ end
195
+
196
+ def normalize_encoding( str )
197
+ if @opt
198
+ then NKF.nkf(@opt, str)
199
+ else str
200
+ end
201
+ end
202
+
203
+ def reset
204
+ @text = ''
205
+ @lwsp = ''
206
+ @curlen = 0
207
+ end
208
+
209
+ def terminate
210
+ add_lwsp ''
211
+ reset
212
+ end
213
+
214
+ def dest
215
+ @f
216
+ end
217
+
218
+ def puts( str = nil )
219
+ @f << str if str
220
+ @f << @eol
221
+ end
222
+
223
+ def write( str )
224
+ @f << str
225
+ end
226
+
227
+ #
228
+ # add
229
+ #
230
+
231
+ def header_line( line )
232
+ scanadd line
233
+ end
234
+
235
+ def header_name( name )
236
+ add_text name.split(/-/).map {|i| i.capitalize }.join('-')
237
+ add_text ':'
238
+ add_lwsp ' '
239
+ end
240
+
241
+ def header_body( str )
242
+ scanadd normalize_encoding(str)
243
+ end
244
+
245
+ def space
246
+ add_lwsp ' '
247
+ end
248
+
249
+ alias spc space
250
+
251
+ def lwsp( str )
252
+ add_lwsp str.sub(/[\r\n]+[^\r\n]*\z/, '')
253
+ end
254
+
255
+ def meta( str )
256
+ add_text str
257
+ end
258
+
259
+ def text( str )
260
+ scanadd normalize_encoding(str)
261
+ end
262
+
263
+ def phrase( str )
264
+ str = normalize_encoding(str)
265
+ if CONTROL_CHAR === str
266
+ scanadd str
267
+ else
268
+ add_text quote_phrase(str)
269
+ end
270
+ end
271
+
272
+ # FIXME: implement line folding
273
+ #
274
+ def kv_pair( k, v )
275
+ return if v.nil?
276
+ v = normalize_encoding(v)
277
+ if token_safe?(v)
278
+ add_text k + '=' + v
279
+ elsif not CONTROL_CHAR === v
280
+ add_text k + '=' + quote_token(v)
281
+ else
282
+ # apply RFC2231 encoding
283
+ kv = k + '*=' + "iso-2022-jp'ja'" + encode_value(v)
284
+ add_text kv
285
+ end
286
+ end
287
+
288
+ def encode_value( str )
289
+ str.gsub(TOKEN_UNSAFE) {|s| '%%%02x' % s[0] }
290
+ end
291
+
292
+ private
293
+
294
+ def scanadd( str, force = false )
295
+ types = ''
296
+ strs = []
297
+
298
+ until str.empty?
299
+ if m = /\A[^\e\t\r\n ]+/.match(str)
300
+ types << (force ? 'j' : 'a')
301
+ strs.push m[0]
302
+
303
+ elsif m = /\A[\t\r\n ]+/.match(str)
304
+ types << 's'
305
+ strs.push m[0]
306
+
307
+ elsif m = /\A\e../.match(str)
308
+ esc = m[0]
309
+ str = m.post_match
310
+ if esc != "\e(B" and m = /\A[^\e]+/.match(str)
311
+ types << 'j'
312
+ strs.push m[0]
313
+ end
314
+
315
+ else
316
+ raise 'TMail FATAL: encoder scan fail'
317
+ end
318
+ (str = m.post_match) unless m.nil?
319
+ end
320
+
321
+ do_encode types, strs
322
+ end
323
+
324
+ def do_encode( types, strs )
325
+ #
326
+ # result : (A|E)(S(A|E))*
327
+ # E : W(SW)*
328
+ # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
329
+ # A : <<A character string not to be encoded>>
330
+ # J : <<A character string to be encoded>>
331
+ # S : <<LWSP>>
332
+ #
333
+ # An encoding unit is `E'.
334
+ # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
335
+ #
336
+ if BENCODE_DEBUG
337
+ puts
338
+ puts '-- do_encode ------------'
339
+ puts types.split(//).join(' ')
340
+ p strs
341
+ end
342
+
343
+ e = /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
344
+
345
+ while m = e.match(types)
346
+ pre = m.pre_match
347
+ concat_A_S pre, strs[0, pre.size] unless pre.empty?
348
+ concat_E m[0], strs[m.begin(0) ... m.end(0)]
349
+ types = m.post_match
350
+ strs.slice! 0, m.end(0)
351
+ end
352
+ concat_A_S types, strs
353
+ end
354
+
355
+ def concat_A_S( types, strs )
356
+ i = 0
357
+ types.each_byte do |t|
358
+ case t
359
+ when ?a then add_text strs[i]
360
+ when ?s then add_lwsp strs[i]
361
+ else
362
+ raise "TMail FATAL: unknown flag: #{t.chr}"
363
+ end
364
+ i += 1
365
+ end
366
+ end
367
+
368
+ METHOD_ID = {
369
+ ?j => :extract_J,
370
+ ?e => :extract_E,
371
+ ?a => :extract_A,
372
+ ?s => :extract_S
373
+ }
374
+
375
+ def concat_E( types, strs )
376
+ if BENCODE_DEBUG
377
+ puts '---- concat_E'
378
+ puts "types=#{types.split(//).join(' ')}"
379
+ puts "strs =#{strs.inspect}"
380
+ end
381
+
382
+ flush() unless @text.empty?
383
+
384
+ chunk = ''
385
+ strs.each_with_index do |s,i|
386
+ mid = METHOD_ID[types[i]]
387
+ until s.empty?
388
+ unless c = __send__(mid, chunk.size, s)
389
+ add_with_encode chunk unless chunk.empty?
390
+ flush
391
+ chunk = ''
392
+ fold
393
+ c = __send__(mid, 0, s)
394
+ raise 'TMail FATAL: extract fail' unless c
395
+ end
396
+ chunk << c
397
+ end
398
+ end
399
+ add_with_encode chunk unless chunk.empty?
400
+ end
401
+
402
+ def extract_J( chunksize, str )
403
+ size = max_bytes(chunksize, str.size) - 6
404
+ size = (size % 2 == 0) ? (size) : (size - 1)
405
+ return nil if size <= 0
406
+ "\e$B#{str.slice!(0, size)}\e(B"
407
+ end
408
+
409
+ def extract_A( chunksize, str )
410
+ size = max_bytes(chunksize, str.size)
411
+ return nil if size <= 0
412
+ str.slice!(0, size)
413
+ end
414
+
415
+ alias extract_S extract_A
416
+
417
+ def max_bytes( chunksize, ssize )
418
+ (restsize() - '=?iso-2022-jp?B??='.size) / 4 * 3 - chunksize
419
+ end
420
+
421
+ #
422
+ # free length buffer
423
+ #
424
+
425
+ def add_text( str )
426
+ @text << str
427
+ # puts '---- text -------------------------------------'
428
+ # puts "+ #{str.inspect}"
429
+ # puts "txt >>>#{@text.inspect}<<<"
430
+ end
431
+
432
+ def add_with_encode( str )
433
+ @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
434
+ end
435
+
436
+ def add_lwsp( lwsp )
437
+ # puts '---- lwsp -------------------------------------'
438
+ # puts "+ #{lwsp.inspect}"
439
+ fold if restsize() <= 0
440
+ flush
441
+ @lwsp = lwsp
442
+ end
443
+
444
+ def flush
445
+ # puts '---- flush ----'
446
+ # puts "spc >>>#{@lwsp.inspect}<<<"
447
+ # puts "txt >>>#{@text.inspect}<<<"
448
+ @f << @lwsp << @text
449
+ @curlen += (@lwsp.size + @text.size)
450
+ @text = ''
451
+ @lwsp = ''
452
+ end
453
+
454
+ def fold
455
+ # puts '---- fold ----'
456
+ @f << @eol
457
+ @curlen = 0
458
+ @lwsp = SPACER
459
+ end
460
+
461
+ def restsize
462
+ MAX_LINE_LEN - (@curlen + @lwsp.size + @text.size)
463
+ end
464
+
465
+ end
466
+
467
+ end # module TMail