mms2r 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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