458dbbfe6a77feb0314e50291c21395640b4c964
4 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
26 # with permission of Minero Aoki.
30 require 'tmail/base64'
31 require 'tmail/stringio'
44 module StrategyInterface
46 def create_dest( obj
)
55 raise TypeError
, 'cannot handle this type of object for dest'
58 module_function
:create_dest
61 # Returns the TMail object encoded and ready to be sent via SMTP etc.
62 # You should call this before you are packaging up your email to
63 # correctly escape all the values that need escaping in the email, line
66 # It is also a good idea to call this before you marshal or serialize
71 # email = TMail::Load(my_email_file)
72 # email_to_send = email.encoded
73 def encoded( eol
= "\r\n", charset
= 'j', dest
= nil )
74 accept_strategy Encoder
, eol
, charset
, dest
77 # Returns the TMail object decoded and ready to be used by you, your
80 # You should call this before you are packaging up your email to
81 # correctly escape all the values that need escaping in the email, line
86 # email = TMail::Load(my_email_file)
87 # email_to_send = email.encoded
88 def decoded( eol
= "\n", charset
= 'e', dest
= nil )
89 # Turn the E-Mail into a string and return it with all
90 # encoded characters decoded. alias for to_s
91 accept_strategy Decoder
, eol
, charset
, dest
96 def accept_strategy( klass
, eol
, charset
, dest
= nil ) #:nodoc:
98 accept klass
.new( create_dest(dest
), charset
, eol
)
107 ### MIME B encoding decoder
114 encoded
= '=\?(?:iso-2022-jp|euc-jp|shift_jis)\?[QB]\?[a-z0-9+/=]+\?='
115 ENCODED_WORDS
= /#{encoded}(?:\s+#{encoded})*/i
122 def self.decode( str
, encoding
= nil )
123 encoding
||= (OUTPUT_ENCODING
[TMail
.KCODE
] || 'j')
124 opt
= '-mS' + encoding
125 str
.gsub(ENCODED_WORDS
) {|s
| NKF
.nkf(opt
, s
) }
128 def initialize( dest
, encoding
= nil, eol
= "\n" )
129 @f = StrategyInterface
.create_dest(dest
)
130 @encoding = (/\A[ejs]/ === encoding
) ? encoding
[0,1] : nil
135 self.class.decode(str
, @encoding)
142 def header_line( str
)
146 def header_name( nm
)
150 def header_body( str
)
173 @f << quote_phrase(decode(str
))
177 v
= dquote(v
) unless token_safe
?(v
)
181 def puts( str
= nil )
194 ### MIME B-encoding encoder
198 # FIXME: This class can handle only (euc-jp/shift_jis -> iso-2022-jp).
204 BENCODE_DEBUG
= false unless defined?(BENCODE_DEBUG
)
206 def Encoder
.encode( str
)
215 RFC_2822_MAX_LENGTH
= 998
220 'UTF8' => nil, # FIXME
224 def initialize( dest
= nil, encoding
= nil, eol
= "\r\n", limit
= nil )
225 @f = StrategyInterface
.create_dest(dest
)
226 @opt = OPTIONS
[TMail
.KCODE
]
229 @preserve_quotes = true
233 def preserve_quotes
=( bool
)
241 def normalize_encoding( str
)
243 then NKF
.nkf(@opt, str
)
263 def puts( str
= nil )
276 def header_line( line
)
280 def header_name( name
)
281 add_text name
.split(/-/).map
{|i
| i
.capitalize
}.join('-')
286 def header_body( str
)
287 scanadd
normalize_encoding(str
)
297 add_lwsp str
.sub(/[\r\n]+[^\r\n]*\z/, '')
305 scanadd
normalize_encoding(str
)
309 str
= normalize_encoding(str
)
310 if CONTROL_CHAR
=== str
313 add_text
quote_phrase(str
)
317 # FIXME: implement line folding
321 v
= normalize_encoding(v
)
324 elsif not CONTROL_CHAR
=== v
325 add_text k
+ '=' + quote_token(v
)
327 # apply RFC2231 encoding
328 kv
= k
+ '*=' + "iso-2022-jp'ja'" + encode_value(v
)
333 def encode_value( str
)
334 str
.gsub(TOKEN_UNSAFE
) {|s
| '%%%02x' % s
[0] }
339 def scanadd( str
, force
= false )
342 if str
.respond_to
?(:encoding)
344 str
.force_encoding(Encoding
::ASCII_8BIT)
347 if m
= /\A[^\e\t\r\n ]+/.match(str
)
348 types
<< (force
? 'j' : 'a')
349 if str
.respond_to
?(:encoding)
350 strs
.push m
[0].force_encoding(enc
)
354 elsif m
= /\A[\t\r\n ]+/.match(str
)
356 if str
.respond_to
?(:encoding)
357 strs
.push m
[0].force_encoding(enc
)
362 elsif m
= /\A\e../.match(str
)
365 if esc
!= "\e(B" and m
= /\A[^\e]+/.match(str
)
367 if str
.respond_to
?(:encoding)
368 strs
.push m
[0].force_encoding(enc
)
375 raise 'TMail FATAL: encoder scan fail'
377 (str
= m
.post_match
) unless m
.nil?
380 do_encode types
, strs
383 def do_encode( types
, strs
)
385 # result : (A|E)(S(A|E))*
387 # W : (J|A)+ but must contain J # (J|A)*J(J|A)*
388 # A : <<A character string not to be encoded>>
389 # J : <<A character string to be encoded>>
392 # An encoding unit is `E'.
393 # Input (parameter `types') is (J|A)(J|A|S)*(J|A)
397 puts
'-- do_encode ------------'
398 puts types
.split(//).join(' ')
402 e
= /[ja]*j[ja]*(?:s[ja]*j[ja]*)*/
404 while m
= e
.match(types
)
406 concat_A_S pre
, strs
[0, pre
.size
] unless pre
.empty
?
407 concat_E m
[0], strs
[m
.begin(0) ... m
.end(0)]
409 strs
.slice
! 0, m
.end(0)
411 concat_A_S types
, strs
414 def concat_A_S( types
, strs
)
415 if RUBY_VERSION < '1.9'
418 a
= 'a'.ord
; s
= 's'.ord
421 types
.each_byte
do |t
|
423 when a
then add_text strs
[i
]
424 when s
then add_lwsp strs
[i
]
426 raise "TMail FATAL: unknown flag: #{t.chr}"
439 def concat_E( types
, strs
)
442 puts
"types=#{types.split(//).join(' ')}"
443 puts
"strs =#{strs.inspect}"
446 flush() unless @text.empty
?
449 strs
.each_with_index
do |s
,i
|
450 mid
= METHOD_ID
[types
[i
]]
452 unless c
= __send__(mid
, chunk
.size
, s
)
453 add_with_encode chunk
unless chunk
.empty
?
457 c
= __send__(mid
, 0, s
)
458 raise 'TMail FATAL: extract fail' unless c
463 add_with_encode chunk
unless chunk
.empty
?
466 def extract_J( chunksize
, str
)
467 size
= max_bytes(chunksize
, str
.size
) - 6
468 size
= (size
% 2 == 0) ? (size
) : (size
- 1)
469 return nil if size
<= 0
470 if str
.respond_to
?(:encoding)
472 str
.force_encoding(Encoding
::ASCII_8BIT)
473 "\e$B#{str.slice!(0, size)}\e(B".force_encoding(enc
)
475 "\e$B#{str.slice!(0, size)}\e(B"
479 def extract_A( chunksize
, str
)
480 size
= max_bytes(chunksize
, str
.size
)
481 return nil if size
<= 0
485 alias extract_S extract_A
487 def max_bytes( chunksize
, ssize
)
488 (restsize() - '=?iso-2022-jp?B??='.size
) / 4 * 3 - chunksize
497 # puts '---- text -------------------------------------'
498 # puts "+ #{str.inspect}"
499 # puts "txt >>>#{@text.inspect}<<<"
502 def add_with_encode( str
)
503 @text << "=?iso-2022-jp?B?#{Base64.encode(str)}?="
507 # puts '---- lwsp -------------------------------------'
508 # puts "+ #{lwsp.inspect}"
509 fold
if restsize() <= 0
514 def flush(folded
= false)
515 # puts '---- flush ----'
516 # puts "spc >>>#{@lwsp.inspect}<<<"
517 # puts "txt >>>#{@text.inspect}<<<"
522 @curlen += (@lwsp.size
+ @text.size
)
529 # puts '---- fold ----'
530 unless @f.string
=~
/^.*?:$/
541 # Called because line is too long - so we need to wrap.
542 # First look for whitespace in the text
543 # if it has text, fold there
544 # check the remaining text, if too long, fold again
545 # if it doesn't, then don't fold unless the line goes beyond 998 chars
547 # Check the text to see if there is whitespace, or if not
552 @text = @wrapped_text.join("#{@eol}#{SPACER}")
556 whitespace_location
= @text =~
/\s/ || @text.length
557 # Is the location of the whitespace shorter than the RCF_2822_MAX_LENGTH?
558 # if there is no whitespace in the string, then this
559 unless mazsize(whitespace_location
) <= 0
561 @wrapped_text << @text.slice
!(0...whitespace_location
)
562 # If it is not less, we have to wrap it destructively
564 slice_point
= RFC_2822_MAX_LENGTH
- @curlen - @lwsp.length
566 @wrapped_text << @text.slice
!(0...slice_point
)
571 MAX_LINE_LEN
- (@curlen + @lwsp.size
+ @text.size
)
574 def mazsize(whitespace_location
)
575 # Per RFC2822, the maximum length of a line is 998 chars
576 RFC_2822_MAX_LENGTH
- (@curlen + @lwsp.size
+ whitespace_location
)