Skip to content

Commit

Permalink
Merge branch 'rails_issue_3824-2' of https://github.com/kennyj/mail i…
Browse files Browse the repository at this point in the history
…nto kennyj-rails_issue_3824-2
  • Loading branch information
Mikel Lindsaar committed Jan 19, 2012
2 parents 3258b4f + bd5e5f3 commit 7cc37ab
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 57 deletions.
115 changes: 62 additions & 53 deletions lib/mail/fields/unstructured_field.rb
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -103,10 +93,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
Expand All @@ -118,64 +105,86 @@ 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

def fold(prepend = 0) # :nodoc:
encoding = @charset.to_s.upcase.gsub('_', '-')
while !@unfolded_line.empty?
encoded = false
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?
limit = 78 - prepend
limit = limit - 7 - encoding.length if should_encode
line = ""
while !@unfolded_line.empty?
break unless word = @unfolded_line.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)
while !words.empty?
break unless word = words.first.dup
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 ...
@unfolded_line.shift
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_line.last && @folded_line.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_line << line
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
Expand Down
2 changes: 1 addition & 1 deletion spec/mail/encodings_spec.rb
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions spec/mail/fields/unstructured_field_spec.rb
Expand Up @@ -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'
Expand All @@ -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'
Expand All @@ -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

Expand Down

0 comments on commit 7cc37ab

Please sign in to comment.