From ffb7379524bfda8e80de4cdfdbd1f7baa5fbd796 Mon Sep 17 00:00:00 2001 From: kennyj Date: Sat, 3 Dec 2011 01:57:01 +0900 Subject: [PATCH 1/4] Change some spec for this fix --- spec/mail/encodings_spec.rb | 2 +- spec/mail/fields/unstructured_field_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mail/encodings_spec.rb b/spec/mail/encodings_spec.rb index 926c48c94..2f427b191 100644 --- a/spec/mail/encodings_spec.rb +++ b/spec/mail/encodings_spec.rb @@ -258,7 +258,7 @@ if RUBY_VERSION >= '1.9' original.force_encoding('UTF-8') end - result = "Subject: =?UTF8?Q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=92=D0=B0=D1=88=D0=B5=D0=B3=D0=BE=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F?=\r\n\sThis is a NUT?????Z__string that== could (break) anything\r\n" + result = "Subject: =?UTF8?Q?=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=92=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=D0=92=D0=B0=D1=88=D0=B5=D0=B3=D0=BE=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8F?=\r\n =?UTF8?Q?_This_is_a_NUT=3F=3F=3F=3F=3FZ=5F=5Fstring_that=3D=3D_could?=\r\n =?UTF8?Q?_=28break=29_anything?=\r\n" mail = Mail.new mail.subject = original mail[:subject].decoded.should eq original diff --git a/spec/mail/fields/unstructured_field_spec.rb b/spec/mail/fields/unstructured_field_spec.rb index 5bfc36aa7..e381ad428 100644 --- a/spec/mail/fields/unstructured_field_spec.rb +++ b/spec/mail/fields/unstructured_field_spec.rb @@ -136,7 +136,7 @@ else $KCODE = 'u' end - result = "Subject: =?UTF8?Q?This_is_=E3=81=82_really_long_string_This_is_=E3=81=82?=\r\n\s=?UTF8?Q?_really_long_string_This_is_=E3=81=82_really_long_string_This_is?=\r\n\s=?UTF8?Q?_=E3=81=82_really_long_string_This_is_=E3=81=82_really_long_string?=\r\n" + result = "Subject: =?UTF8?Q?This_is_=E3=81=82_really_long_string_This_is_=E3=81=82?=\r\n\s=?UTF8?Q?_really_long_string_This_is_=E3=81=82_really_long_string_This_is?=\r\n\s=?UTF8?Q?_=E3=81=82_really_long_string_This_is_=E3=81=82_really_long?=\r\n\s=?UTF8?Q?_string?=\r\n" @field.encoded.gsub("UTF-8", "UTF8").should eq result @field.decoded.should eq string $KCODE = @original if RUBY_VERSION < '1.9' @@ -151,7 +151,7 @@ else $KCODE = 'u' end - result = "X-SMTPAPI: {\"unique_args\": {\"mailing_id\":147,\"account_id\":2}, \"to\":\r\n\s[\"larspind@gmail.com\"], \"category\": \"mailing\", \"filters\": {\"domainkeys\":\r\n\s{\"settings\": {\"domain\":1,\"enable\":1}}}, \"sub\": {\"{{open_image_url}}\":\r\n\s[\"http://betaling.larspind.local/O/token/147/Mailing::FakeRecipient\"],\r\n\s\"{{name}}\": [\"[FIRST NAME]\"], \"{{signup_reminder}}\": [\"(her kommer til at\r\n\s=?UTF8?Q?st=C3=A5_hvorn=C3=A5r_folk_har_skrevet_sig_op_...=29=22],?=\r\n\s\"{{unsubscribe_url}}\":\r\n\s[\"http://betaling.larspind.local/U/token/147/Mailing::FakeRecipient\"],\r\n\s\"{{email}}\": [\"larspind@gmail.com\"], \"{{link:308}}\":\r\n\s[\"http://betaling.larspind.local/L/308/0/Mailing::FakeRecipient\"],\r\n\s\"{{confirm_url}}\": [\"\"], \"{{ref}}\": [\"[REF]\"]}}\r\n" + result = "X-SMTPAPI: =?UTF8?Q?{=22unique=5Fargs=22:_{=22mailing=5Fid=22:147,=22a?=\r\n =?UTF8?Q?ccount=5Fid=22:2},_=22to=22:_[=22larspind@gmail.com=22],_=22categ?=\r\n =?UTF8?Q?ory=22:_=22mailing=22,_=22filters=22:_{=22domainkeys=22:_{=22sett?=\r\n =?UTF8?Q?ings=22:_{=22domain=22:1,=22enable=22:1}}},_=22sub=22:_{=22{{op?=\r\n =?UTF8?Q?en=5Fimage=5Furl}}=22:_[=22http://betaling.larspind.local/O?=\r\n =?UTF8?Q?/token/147/Mailing::FakeRecipient=22],_=22{{name}}=22:_[=22[FIRST?=\r\n =?UTF8?Q?_NAME]=22],_=22{{signup=5Freminder}}=22:_[=22=28her_kommer_til_at?=\r\n =?UTF8?Q?_st=C3=A5_hvorn=C3=A5r_folk_har_skrevet_sig_op_...=29=22],?=\r\n =?UTF8?Q?_=22{{unsubscribe=5Furl}}=22:_[=22http://betaling.larspind.?=\r\n =?UTF8?Q?local/U/token/147/Mailing::FakeRecipient=22],_=22{{email}}=22:?=\r\n =?UTF8?Q?_[=22larspind@gmail.com=22],_=22{{link:308}}=22:_[=22http://beta?=\r\n =?UTF8?Q?ling.larspind.local/L/308/0/Mailing::FakeRecipient=22],_=22{{con?=\r\n =?UTF8?Q?firm=5Furl}}=22:_[=22=22],_=22{{ref}}=22:_[=22[REF]=22]}}?=\r\n" @field.encoded.gsub("UTF-8", "UTF8").should eq result @field.decoded.should eq string $KCODE = @original if RUBY_VERSION < '1.9' @@ -163,7 +163,7 @@ it "should encode an ascii string that has carriage returns if asked to" do result = "Subject: =0Aasdf=0A\r\n" @field = Mail::UnstructuredField.new("Subject", "\nasdf\n") - @field.encoded.should eq "Subject: =0Aasdf=0A\r\n" + @field.encoded.should eq result end end From a9b489a560fac223a93577d2943fb559da64f471 Mon Sep 17 00:00:00 2001 From: kennyj Date: Fri, 2 Dec 2011 13:04:13 +0900 Subject: [PATCH 2/4] Remove unnecessary instance valiables. --- lib/mail/fields/unstructured_field.rb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/mail/fields/unstructured_field.rb b/lib/mail/fields/unstructured_field.rb index 19183febe..a9b62fcbd 100644 --- a/lib/mail/fields/unstructured_field.rb +++ b/lib/mail/fields/unstructured_field.rb @@ -103,10 +103,7 @@ def do_decode # preference to other places where the field could be folded, even if # it is allowed elsewhere. def wrapped_value # :nodoc: - @folded_line = [] - @unfolded_line = decoded.to_s.split(/[ \t]/) - fold("#{name}: ".length) - wrap_lines(name, @folded_line) + wrap_lines(name, fold("#{name}: ".length)) end # 6.2. Display of 'encoded-word's @@ -126,13 +123,15 @@ def wrap_lines(name, folded_lines) end def fold(prepend = 0) # :nodoc: - encoding = @charset.to_s.upcase.gsub('_', '-') - while !@unfolded_line.empty? + encoding = charset.to_s.upcase.gsub('_', '-') + words = decoded.to_s.split(/[ \t]/) + folded_lines = [] + while !words.empty? encoded = false limit = 78 - prepend line = "" - while !@unfolded_line.empty? - break unless word = @unfolded_line.first.dup + while !words.empty? + break unless word = words.first.dup # Remember whether it was non-ascii before we encode it ('cause then we can't tell anymore) non_ascii = word.not_ascii_only? encoded_word = encode(word) @@ -149,20 +148,21 @@ def fold(prepend = 0) # :nodoc: limit = limit - 8 - encoding.length # minus the =?...?Q?...?= part, the possible leading white-space, and the name of the encoding end # Remove the word from the queue ... - @unfolded_line.shift + words.shift # ... add it in encoded form to the current line line << " " unless line.empty? encoded_word_safify!(encoded_word) if encoded line << encoded_word end # Add leading whitespace if both this and the last line were encoded, because whitespace between two encoded-words is ignored when decoding - line = " " + line if encoded && @folded_line.last && @folded_line.last.index('=?') == 0 + line = " " + line if encoded && folded_lines.last && folded_lines.last.index('=?') == 0 # Encode the line if necessary line = "=?#{encoding}?Q?#{line.gsub(/ /, '_')}?=" if encoded # Add the line to the output and reset the prepend - @folded_line << line + folded_lines << line prepend = 0 end + folded_lines end def encode(value) From 0e0066f0936ccacb1604ee282a5bac2a9005f53a Mon Sep 17 00:00:00 2001 From: kennyj Date: Fri, 2 Dec 2011 14:42:58 +0900 Subject: [PATCH 3/4] Fixing folding problem (rails issue 3824) --- lib/mail/fields/unstructured_field.rb | 75 +++++++++++++++++---------- 1 file changed, 48 insertions(+), 27 deletions(-) diff --git a/lib/mail/fields/unstructured_field.rb b/lib/mail/fields/unstructured_field.rb index a9b62fcbd..2fdf59171 100644 --- a/lib/mail/fields/unstructured_field.rb +++ b/lib/mail/fields/unstructured_field.rb @@ -123,59 +123,80 @@ def wrap_lines(name, folded_lines) end def fold(prepend = 0) # :nodoc: - encoding = charset.to_s.upcase.gsub('_', '-') - words = decoded.to_s.split(/[ \t]/) - folded_lines = [] + encoding = normalized_encoding + decoded_string = decoded.to_s + should_encode = decoded_string.not_ascii_only? + if should_encode + first = true + words = decoded_string.split(/[ \t]/).map do |word| + if first + first = !first + else + word = " " << word + end + if word.not_ascii_only? + word + else + word.scan(/.{7}|.+$/) + end + end.flatten + else + words = decoded_string.split(/[ \t]/) + end + + folded_lines = [] while !words.empty? - encoded = false limit = 78 - prepend + limit = limit - 7 - encoding.length if should_encode line = "" - while !words.empty? + while !words.empty? break unless word = words.first.dup - # Remember whether it was non-ascii before we encode it ('cause then we can't tell anymore) - non_ascii = word.not_ascii_only? - encoded_word = encode(word) + word.encode!(charset) if defined?(Encoding) && charset + word = encode(word) if should_encode + word = encode_crlf(word) # Skip to next line if we're going to go past the limit # Unless this is the first word, in which case we're going to add it anyway # Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you. # (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and # the linebreak will be ignored) - break if !line.empty? && (line.length + encoded_word.length + 1 > limit) - # If word was the first non-ascii word, we're going to make the entire line encoded and we're going to reduce the limit accordingly - if non_ascii && !encoded - encoded = true - encoded_word_safify!(line) - limit = limit - 8 - encoding.length # minus the =?...?Q?...?= part, the possible leading white-space, and the name of the encoding - end + break if !line.empty? && (line.length + word.length + 1 > limit) # Remove the word from the queue ... words.shift + # Add word separator + line << " " unless (line.empty? || should_encode) # ... add it in encoded form to the current line - line << " " unless line.empty? - encoded_word_safify!(encoded_word) if encoded - line << encoded_word + line << word end - # Add leading whitespace if both this and the last line were encoded, because whitespace between two encoded-words is ignored when decoding - line = " " + line if encoded && folded_lines.last && folded_lines.last.index('=?') == 0 # Encode the line if necessary - line = "=?#{encoding}?Q?#{line.gsub(/ /, '_')}?=" if encoded + line = "=?#{encoding}?Q?#{line}?=" if should_encode # Add the line to the output and reset the prepend folded_lines << line prepend = 0 end folded_lines end - + def encode(value) - value.encode!(charset) if charset && value.respond_to?(:encode!) - (value.not_ascii_only? ? [value].pack("M").gsub("=\n", '') : value).gsub("\r", "=0D").gsub("\n", "=0A") - end - - def encoded_word_safify!(value) + value = [value].pack("M").gsub("=\n", '') value.gsub!(/"/, '=22') value.gsub!(/\(/, '=28') value.gsub!(/\)/, '=29') value.gsub!(/\?/, '=3F') value.gsub!(/_/, '=5F') + value.gsub!(/ /, '_') + value + end + + def encode_crlf(value) + value.gsub!("\r", '=0D') + value.gsub!("\n", '=0A') + value + end + + def normalized_encoding + encoding = charset.to_s.upcase.gsub('_', '-') + encoding = 'UTF-8' if encoding == 'UTF8' # Ruby 1.8.x and $KCODE == 'u' + encoding end end From bd5e5f38ee5943955d5fe100425c879c4247b77c Mon Sep 17 00:00:00 2001 From: kennyj Date: Fri, 2 Dec 2011 12:42:31 +0900 Subject: [PATCH 4/4] Some Refactor --- lib/mail/fields/unstructured_field.rb | 28 ++++++++------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/mail/fields/unstructured_field.rb b/lib/mail/fields/unstructured_field.rb index 2fdf59171..fa64d87a8 100644 --- a/lib/mail/fields/unstructured_field.rb +++ b/lib/mail/fields/unstructured_field.rb @@ -18,9 +18,11 @@ class UnstructuredField include Mail::CommonField include Mail::Utilities - + + attr_accessor :charset + attr_reader :errors + def initialize(name, value, charset = nil) - self.charset = charset @errors = [] if charset self.charset = charset @@ -35,21 +37,9 @@ def initialize(name, value, charset = nil) self.value = value self end - - def charset - @charset - end - - def charset=(val) - @charset = val - end - - def errors - @errors - end - + def encoded - do_encode(self.name) + do_encode end def decoded @@ -66,7 +56,7 @@ def parse # An unstructured field does not parse private - def do_encode(name) + def do_encode value.nil? ? '' : "#{wrapped_value}\r\n" end @@ -115,9 +105,7 @@ def wrapped_value # :nodoc: # without having to separate 'encoded-word's where spaces occur in the # unencoded text.) def wrap_lines(name, folded_lines) - result = [] - index = 0 - result[index] = "#{name}: #{folded_lines.shift}" + result = ["#{name}: #{folded_lines.shift}"] result.concat(folded_lines) result.join("\r\n\s") end