diff --git a/app/models/calendar.rb b/app/models/calendar.rb index 9cd2a670a..1fcf20bdb 100644 --- a/app/models/calendar.rb +++ b/app/models/calendar.rb @@ -251,10 +251,7 @@ returns def self.day_and_comment_by_event(event, start_time) day = "#{start_time.year}-#{format('%02d', start_time.month)}-#{format('%02d', start_time.day)}" comment = event.summary || event.description - comment = Encode.conv( 'utf8', comment.to_s.force_encoding('utf-8') ) - if !comment.valid_encoding? - comment = comment.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') - end + comment = comment.to_utf8(fallback: :read_as_sanitized_binary) # ignore daylight saving time entries return if comment.match?(/(daylight saving|sommerzeit|summertime)/i) diff --git a/app/models/channel/email_parser.rb b/app/models/channel/email_parser.rb index 05576f4f1..bb1cb367e 100644 --- a/app/models/channel/email_parser.rb +++ b/app/models/channel/email_parser.rb @@ -69,18 +69,14 @@ class Channel::EmailParser def parse(msg) data = {} - # Rectify invalid encodings - encoding = CharDet.detect(msg)['encoding'] - msg.force_encoding(encoding) if !msg.valid_encoding? - - mail = Mail.new(msg) + mail = Mail.new(msg.utf8_encode) # set all headers mail.header.fields.each do |field| # full line, encode, ready for storage begin - value = Encode.conv('utf8', field.to_s) + value = field.to_utf8 if value.blank? value = field.raw_value end @@ -150,24 +146,17 @@ class Channel::EmailParser # html attachment/body may exists and will be converted to strict html if mail.html_part&.body data[:body] = mail.html_part.body.to_s - data[:body] = Encode.conv(mail.html_part.charset.to_s, data[:body]) - data[:body] = data[:body].html2html_strict.to_s.force_encoding('utf-8') + data[:body] = data[:body].utf8_encode(from: mail.html_part.charset, fallback: :read_as_sanitized_binary) + data[:body] = data[:body].html2html_strict - if !data[:body].force_encoding('UTF-8').valid_encoding? - data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') - end data[:content_type] = 'text/html' end # text attachment/body exists if data[:body].blank? && mail.text_part data[:body] = mail.text_part.body.decoded - data[:body] = Encode.conv(mail.text_part.charset, data[:body]) - data[:body] = data[:body].to_s.force_encoding('utf-8') + data[:body] = data[:body].utf8_encode(from: mail.text_part.charset, fallback: :read_as_sanitized_binary) - if !data[:body].valid_encoding? - data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') - end data[:content_type] = 'text/plain' end @@ -217,12 +206,9 @@ class Channel::EmailParser elsif mail.mime_type && mail.mime_type.to_s.casecmp('text/html').zero? filename = 'message.html' data[:body] = mail.body.decoded - data[:body] = Encode.conv(mail.charset, data[:body]) - data[:body] = data[:body].html2html_strict.to_s.force_encoding('utf-8') + data[:body] = data[:body].utf8_encode(from: mail.charset, fallback: :read_as_sanitized_binary) + data[:body] = data[:body].html2html_strict - if !data[:body].valid_encoding? - data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') - end data[:content_type] = 'text/html' # add body as attachment @@ -246,11 +232,8 @@ class Channel::EmailParser # text part only elsif !mail.mime_type || mail.mime_type.to_s == '' || mail.mime_type.to_s.casecmp('text/plain').zero? data[:body] = mail.body.decoded - data[:body] = Encode.conv(mail.charset, data[:body]) + data[:body] = data[:body].utf8_encode(from: mail.charset, fallback: :read_as_sanitized_binary) - if !data[:body].force_encoding('UTF-8').valid_encoding? - data[:body] = data[:body].encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') - end data[:content_type] = 'text/plain' else filename = '-no name-' @@ -319,7 +302,7 @@ class Channel::EmailParser # full line, encode, ready for storage begin - value = Encode.conv('utf8', field.to_s) + value = field.to_utf8 if value.blank? value = field.raw_value end @@ -330,10 +313,7 @@ class Channel::EmailParser end # cleanup content id, <> will be added automatically later - if headers_store['Content-ID'] - headers_store['Content-ID'].gsub!(/^$/, '') - end + headers_store['Content-ID']&.gsub!(/(^<|>$)/, '') # get filename from content-disposition filename = nil @@ -669,7 +649,7 @@ returns mail[:attachments]&.each do |attachment| filename = attachment[:filename].force_encoding('utf-8') if !filename.force_encoding('UTF-8').valid_encoding? - filename = filename.encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') + filename = filename.utf8_encode(fallback: :read_as_sanitized_binary) end Store.add( object: 'Ticket::Article', @@ -762,7 +742,7 @@ returns end # do extra decoding because we needed to use field.value - data[:from_display_name] = Mail::Field.new('X-From', Encode.conv('utf8', data[:from_display_name])).to_s + data[:from_display_name] = Mail::Field.new('X-From', data[:from_display_name].to_utf8).to_s data[:from_display_name].delete!('"') data[:from_display_name].strip! data[:from_display_name].gsub!(/^'/, '') @@ -862,7 +842,7 @@ module Mail # workaround to get content of no parseable headers - in most cases with non 7 bit ascii signs class Field def raw_value - value = Encode.conv('utf8', @raw_value) + value = @raw_value.try(:utf8_encode) return value if value.blank? value.sub(/^.+?:(\s|)/, '') end diff --git a/lib/core_ext/object.rb b/lib/core_ext/object.rb new file mode 100644 index 000000000..a0f47d617 --- /dev/null +++ b/lib/core_ext/object.rb @@ -0,0 +1,5 @@ +class Object + def to_utf8(**options) + to_s.utf8_encode(**options) + end +end diff --git a/lib/core_ext/string.rb b/lib/core_ext/string.rb index 83c13626b..f1c2efacd 100644 --- a/lib/core_ext/string.rb +++ b/lib/core_ext/string.rb @@ -1,3 +1,5 @@ +require 'rchardet' + class String alias old_strip strip alias old_strip! strip! @@ -8,7 +10,7 @@ class String sub!(/[[[:space:]]\u{200B}\u{FEFF}]+\Z/, '') # if incompatible encoding regexp match (UTF-8 regexp with ASCII-8BIT string) (Encoding::CompatibilityError), use default - rescue + rescue Encoding::CompatibilityError old_strip! end self @@ -20,7 +22,7 @@ class String new_string.sub!(/[[[:space:]]\u{200B}\u{FEFF}]+\Z/, '') # if incompatible encoding regexp match (UTF-8 regexp with ASCII-8BIT string) (Encoding::CompatibilityError), use default - rescue + rescue Encoding::CompatibilityError new_string = old_strip end new_string @@ -451,4 +453,59 @@ class String string end + + # Returns a copied string whose encoding is UTF-8. + # If both the provided and current encodings are invalid, + # an auto-detected encoding is tried. + # + # Supports some fallback strategies if a valid encoding cannot be found. + # + # Options: + # + # * from: An encoding to try first. + # Takes precedence over the current and auto-detected encodings. + # + # * fallback: The strategy to follow if no valid encoding can be found. + # * `:output_to_binary` returns an ASCII-8BIT-encoded string. + # * `:read_as_sanitized_binary` returns a UTF-8-encoded string with all + # invalid byte sequences replaced with "?" characters. + def utf8_encode(**options) + dup.utf8_encode!(options) + end + + def utf8_encode!(**options) + return self if (encoding == Encoding::UTF_8) && valid_encoding? + + input_encoding = viable_encodings(try_first: options[:from]).first + return encode!('utf-8', input_encoding) if input_encoding.present? + + case options[:fallback] + when :output_to_binary + force_encoding('ascii-8bit') + when :read_as_sanitized_binary + encode!('utf-8', 'ascii-8bit', invalid: :replace, undef: :replace, replace: '?') + else + raise EncodingError, 'could not find a valid input encoding' + end + end + + private + + def viable_encodings(try_first: nil) + return dup.viable_encodings(try_first: try_first) if frozen? + + provided = Encoding.find(try_first) if try_first.present? + original = encoding + detected = CharDet.detect(self)['encoding'] + + [provided, original, detected] + .compact + .reject { |e| Encoding.find(e) == Encoding::ASCII_8BIT } + .select { |e| force_encoding(e).valid_encoding? } + .tap { force_encoding(original) } # clean up changes from previous line + + # if `try_first` is not a valid encoding, try_first again without it + rescue ArgumentError + try_first.present? ? viable_encodings : raise + end end diff --git a/lib/encode.rb b/lib/encode.rb deleted file mode 100644 index b844197ea..000000000 --- a/lib/encode.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Encode - def self.conv (charset, string) - - # return if string is false - return string if !string - - # if no charset is given, use LATIN1 as default - if !charset || charset == 'US-ASCII' || charset == 'ASCII-8BIT' - charset = 'ISO-8859-15' - end - - # validate already existing utf8 strings - if charset.casecmp('utf8').zero? || charset.casecmp('utf-8').zero? - begin - # return if encoding is valid - utf8 = string.dup.force_encoding('UTF-8') - return utf8 if utf8.valid_encoding? - - # try to encode from Windows-1252 to utf8 - string.encode!('UTF-8', 'Windows-1252') - rescue EncodingError => e - Rails.logger.error "Bad encoding: #{string.inspect}" - string = string.encode!('UTF-8', invalid: :replace, undef: :replace, replace: '?') - end - return string - end - - # convert string - begin - string.encode!('UTF-8', charset) - rescue => e - Rails.logger.error 'ERROR: ' + e.inspect - string - end - #Iconv.conv( 'UTF8', charset, string) - end -end diff --git a/lib/html_sanitizer.rb b/lib/html_sanitizer.rb index f8b7a09ba..00fbfd896 100644 --- a/lib/html_sanitizer.rb +++ b/lib/html_sanitizer.rb @@ -374,7 +374,7 @@ cleanup html string: end def self.cleanup_target(string, keep_spaces: false) - string = CGI.unescape(string).encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') + string = CGI.unescape(string).utf8_encode(fallback: :read_as_sanitized_binary) blank_regex = if keep_spaces /\t|\n|\r/ else @@ -405,8 +405,8 @@ cleanup html string: end def self.url_same?(url_new, url_old) - url_new = CGI.unescape(url_new.to_s).encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?').downcase.gsub(%r{/$}, '').gsub(/[[:space:]]|\t|\n|\r/, '').strip - url_old = CGI.unescape(url_old.to_s).encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?').downcase.gsub(%r{/$}, '').gsub(/[[:space:]]|\t|\n|\r/, '').strip + url_new = CGI.unescape(url_new.to_s).utf8_encode(fallback: :read_as_sanitized_binary).downcase.gsub(%r{/$}, '').gsub(/[[:space:]]|\t|\n|\r/, '').strip + url_old = CGI.unescape(url_old.to_s).utf8_encode(fallback: :read_as_sanitized_binary).downcase.gsub(%r{/$}, '').gsub(/[[:space:]]|\t|\n|\r/, '').strip url_new = html_decode(url_new).sub('/?', '?') url_old = html_decode(url_old).sub('/?', '?') return true if url_new == url_old diff --git a/lib/import/helper.rb b/lib/import/helper.rb index 3a2d5f7f2..e0b1d1a66 100644 --- a/lib/import/helper.rb +++ b/lib/import/helper.rb @@ -22,8 +22,8 @@ module Import def utf8_encode(data) data.each do |key, value| next if !value - next if value.class != String - data[key] = Encode.conv('utf8', value) + next if !value.respond_to?(:utf8_encode) + data[key] = value.utf8_encode end end diff --git a/lib/import/helper/attributes_examples.rb b/lib/import/helper/attributes_examples.rb index 1ae3c00a9..ef2e9ba4d 100644 --- a/lib/import/helper/attributes_examples.rb +++ b/lib/import/helper/attributes_examples.rb @@ -58,7 +58,7 @@ module Import next if value.nil? - example = value.to_s.force_encoding('UTF-8').encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') + example = value.to_utf8(fallback: :read_as_sanitized_binary) example.gsub!(/^(.{20,}?).*$/m, '\1...') @examples[attribute] = "#{attribute} (e. g. #{example})" diff --git a/lib/import/otrs/requester.rb b/lib/import/otrs/requester.rb index adbcc4c27..2e27c6f7f 100644 --- a/lib/import/otrs/requester.rb +++ b/lib/import/otrs/requester.rb @@ -94,9 +94,9 @@ module Import end def handle_response(response) - encoded_body = Encode.conv('utf8', response.body.to_s) + encoded_body = response.body.to_utf8 # remove null bytes otherwise PostgreSQL will fail - encoded_body.gsub!('\u0000', '') + encoded_body.delete('\u0000') JSON.parse(encoded_body) end diff --git a/lib/ldap/user.rb b/lib/ldap/user.rb index 3dd2143f6..4b228fb27 100644 --- a/lib/ldap/user.rb +++ b/lib/ldap/user.rb @@ -144,7 +144,7 @@ class Ldap next if value.blank? next if value[0].blank? - example_value = value[0].force_encoding('UTF-8').encode('utf-8', 'binary', invalid: :replace, undef: :replace, replace: '?') + example_value = value[0].force_encoding('UTF-8').utf8_encode(fallback: :read_as_sanitized_binary) attributes[attribute] = "#{attribute} (e. g. #{example_value})" end diff --git a/spec/lib/core_ext/string_spec.rb b/spec/lib/core_ext/string_spec.rb new file mode 100644 index 000000000..52bd0ba8f --- /dev/null +++ b/spec/lib/core_ext/string_spec.rb @@ -0,0 +1,58 @@ +require 'rails_helper' + +RSpec.describe String do + describe '#utf8_encode' do + context 'for valid, UTF-8-encoded strings' do + let(:subject) { 'hello' } + + it 'returns an identical copy' do + expect(subject.utf8_encode).to eq(subject) + expect(subject.utf8_encode.encoding).to be(subject.encoding) + expect(subject.utf8_encode).not_to be(subject) + end + end + + context 'for strings in other encodings' do + let(:subject) { original_string.encode(input_encoding) } + + context 'with no from: option' do + let(:original_string) { 'Tschüss!' } + let(:input_encoding) { Encoding::ISO_8859_2 } + + it 'detects the input encoding' do + expect(subject.utf8_encode).to eq(original_string) + end + end + + context 'with a valid from: option' do + let(:original_string) { 'Tschüss!' } + let(:input_encoding) { Encoding::ISO_8859_2 } + + it 'uses the specified input encoding' do + expect(subject.utf8_encode(from: 'iso-8859-2')).to eq(original_string) + end + + it 'uses any valid input encoding, even if not correct' do + expect(subject.utf8_encode(from: 'gb18030')).to eq('Tsch黶s!') + end + end + + context 'with an invalid from: option' do + let(:original_string) { '―陈志' } + let(:input_encoding) { Encoding::GB18030 } + + it 'does not try it' do + expect { subject.encode('utf-8', 'gb2312') } + .to raise_error(Encoding::InvalidByteSequenceError) + + expect { subject.utf8_encode(from: 'gb2312') } + .not_to raise_error(Encoding::InvalidByteSequenceError) + end + + it 'uses the detected input encoding instead' do + expect(subject.utf8_encode(from: 'gb2312')).to eq(original_string) + end + end + end + end +end diff --git a/test/unit/email_parser_test.rb b/test/unit/email_parser_test.rb index 54ee3d61b..48168cfd4 100644 --- a/test/unit/email_parser_test.rb +++ b/test/unit/email_parser_test.rb @@ -464,7 +464,7 @@ Managing Director: Martin Edenhofer # mutt c1abb5fb77a9d2ab2017749a7987c074 { md5: '2ef81e47872d42efce7ef34bfa2de043', - filename: '¼¨Ð§¹ÜÀí,¾¿¾¹Ë­´íÁË.xls', + filename: '绩效管理,究竟谁错了.xls', }, ], params: { @@ -619,7 +619,7 @@ Managing Director: Martin Edenhofer { data: IO.read('test/fixtures/mail21.box'), source: 'test/fixtures/mail21.box', - body_md5: '7cb50fe6b37420fe9aea61eb5badc25a', + body_md5: 'dea7a8979172261f61fb799b6c83742e', params: { from: 'Viagra Super Force Online ', from_email: 'pharmacy_affordable1@ertelecom.ru', @@ -631,20 +631,20 @@ Managing Director: Martin Edenhofer { data: IO.read('test/fixtures/mail22.box'), source: 'test/fixtures/mail22.box', - body_md5: '56223b1ea04a63269020cb64be7a70b0', + body_md5: '1af1f68f66713b63ce8ec4cc20c7887e', params: { from: 'Gilbertina Suthar ', from_email: 'ireoniqla@lipetsk.ru', from_display_name: 'Gilbertina Suthar', subject: 'P..E..N-I..S__-E N L A R-G E-M..E..N T-___P..I-L-L..S...Info.', to: 'Info ', - body: 'Puzzled by judith bronte dave. Melvin will want her way through with.
Continued adam helped charlie cried. Soon joined the master bathroom. Grinned adam rubbed his arms she nodded.
Freemont and they talked with beppe.
Thinking of bed and whenever adam.
Mike was too tired man to hear.
I10PQSHEJl2Nwf&tilde;2113S173 &Icirc;1mEbb5N371L&piv;C7AlFnR1&diams;HG64B242&brvbar;M2242zk&Iota;N&rceil;7&rceil;TBN&ETH; T2xPI&ograve;gI2&Atilde;lL2&Otilde;ML&perp;22Sa&Psi;RBreathed adam gave the master bedroom door.
Better get charlie took the wall.
Charlotte clark smile he saw charlie.
Dave and leaned her tears adam.
Maybe we want any help me that.
Next morning charlie gazed at their father.
Well as though adam took out here. Melvin will be more money. Called him into this one last night.
Men joined the pickup truck pulled away. Chuck could make sure that.[1] &dagger;p1C?L&thinsp;I?C&ensp;K?88&ensp;5 E R?EEOD !Chuckled adam leaned forward and le? charlie.
Just then returned to believe it here.
Freemont and pulling out several minutes.

[1] &#104;&#116;&#116;&#112;&#58;&#47;&#47;&#1072;&#1086;&#1089;&#1082;&#46;&#1088;&#1092;?jmlfwnwe&ucwkiyyc' + body: 'Puzzled by judith bronte dave. Melvin will want her way through with.
Continued adam helped charlie cried. Soon joined the master bathroom. Grinned adam rubbed his arms she nodded.
Freemont and they talked with beppe.
Thinking of bed and whenever adam.
Mike was too tired man to hear.
I°0PQSHEJlÔNwf˜Ì1§3S¬73 Î1mEbb5N37¢LϖC7AlFnRº♦HG64BÉ4Ò¦Måâ4ÊzkΙN⌉7⌉TBNÐ T×xPIògIÎÃlLøÕML⊥ÞøSaΨRBreathed adam gave the master bedroom door.
Better get charlie took the wall.
Charlotte clark smile he saw charlie.
Dave and leaned her tears adam.
Maybe we want any help me that.
Next morning charlie gazed at their father.
Well as though adam took out here. Melvin will be more money. Called him into this one last night.
Men joined the pickup truck pulled away. Chuck could make sure that.†p­C L I C K Ȟ E R EEOD !Chuckled adam leaned forward and leî charlie.
Just then returned to believe it here.
Freemont and pulling out several minutes.' }, }, { data: IO.read('test/fixtures/mail23.box'), source: 'test/fixtures/mail23.box', - body_md5: '545a1b067fd10ac636c20b44f5df8868', + body_md5: '23967dfbbc2e167332b2ecb78fb9e397', params: { from: 'marketingmanager@nthcpghana.com', from_email: 'marketingmanager@nthcpghana.com', @@ -794,7 +794,7 @@ end { data: IO.read('test/fixtures/mail31.box'), source: 'test/fixtures/mail31.box', - body_md5: '10484f3b096e85e7001da387c18871d5', + body_md5: 'd5448d34bf7f5db0a525fc83735dc11b', params: { from: '"bertha mou" ', from_email: 'zhengkang@ha.chinamobile.com', @@ -806,7 +806,7 @@ end { data: IO.read('test/fixtures/mail32.box'), source: 'test/fixtures/mail32.box', - body_md5: '6bed82e0d079e521f506e4e5d3529107', + body_md5: '9ccf94a31ace1c27e71138c3803ff178', params: { from: '"Dana.Qin" ', from_email: 'Dana.Qin6e1@gmail.com', @@ -1036,7 +1036,7 @@ end { data: IO.read('test/fixtures/mail44.box'), source: 'test/fixtures/mail44.box', - body_md5: '2f0f5a21e4393c174d4670a188fc5548', + body_md5: 'ee930244edd3b7c19494e688aa9cc41c', params: { from: '"Clement.Si" ', from_email: 'Claudia.Shu@yahoo.com.', @@ -1373,7 +1373,7 @@ delete your own text from the attached returned message. ] count = 0 - files.each { |file| + files.each { |file, i| count += 1 #p "Count: #{count}" parser = Channel::EmailParser.new diff --git a/test/unit/email_process_test.rb b/test/unit/email_process_test.rb index a9922d390..d0d620c60 100644 --- a/test/unit/email_process_test.rb +++ b/test/unit/email_process_test.rb @@ -415,96 +415,96 @@ Some Text", }, 1 => { content_type: 'text/html', - body: "_________________________________________________________________________________Please beth saw his head
+ body: %{_________________________________________________________________________________Please beth saw his head
- +
- + - + - - - + + - - + + - - + + - - - + + - - + + - + - - + + - - + + - - + + - + - + - + - + - +
9õhH3ÿoIÚõ´GÿiH±6u-û◊NQ4ùäU¹awAq¹JLZμÒIicgT1ζ2Y7⊆t 63‘Mñ36EßÝ→DAå†I048CvJ9A↑3iTc4ÉIΥvXO50ñNÁFJSð­r 154F1HPOÀ£CRxZp tLîT9öXH1b3Es±W mNàBg3õEbPŒSúfτTóY4 sUÖPÒζΔRFkcIÕ1™CÓZ3EΛRq!Cass is good to ask what that9õhH3ÿoIÚõ´GÿiH±6u-û◊NQ4ùäU¹awAq¹JLZμÒIicgT1ζ2Y7⊆t 63‘Mñ36EßÝ→DAå†I048CvJ9A↑3iTc4ÉIΥvXO50ñNÁFJSð­r 154F1HPOÀ£CRxZp tLîT9öXH1b3Es±W mNàBg3õEbPŒSúfτTóY4 sUÖPÒζΔRFkcIÕ1™CÓZ3EΛRq!Cass is good to ask what that
86ËÏuÕC L I C K H E R E28MLuke had been thinking about that.
Shannon said nothing in fact they. Matt placed the sofa with amy smiled. Since the past him with more. Maybe he checked the phone. Neither did her name only. Ryan then went inside matt.
Maybe we can have anything you sure.
86ËÏuÕC L I C K H E R E28MLuke had been thinking about that.
Shannon said nothing in fact they. Matt placed the sofa with amy smiled. Since the past him with more. Maybe he checked the phone. Neither did her name only. Ryan then went inside matt.
Maybe we can have anything you sure.
á•XMYÍÅEE£ÓN°kP'dÄÅS4⌉d √p¨HΣ>jE4y4ACüûLì“vT∧4tHXÆX: +á•XMYÍÅEE£ÓN°kP'dÄÅS4⌉d √p¨HΣ>jE4y4ACüûLì“vT∧4tHXÆX:
x5VV\"¹tiçÂaaΦ3fg¦zèr«°haeJw n§Va879sÆ3j f¶ïlÞ9lo5F¾wν¶1 κψ›a9f4sLsL ùVo$v3x1¸nz.uȦ1H4s35Ô7yoQCÄFMiMzda¯ZεlÝHNi¬cÚsù–ϖ DYhaã7Ns4Ö· n3dl1XÆo¯µ¶wpN↑ YQ7aé39s1qÓ QyL$fcÕ1ΝS5.5Wy62­d5ĶHx5VV"¹tiçÂaaΦ3fg¦zèr«°haeJw n§Va879sÆ3j f¶ïlÞ9lo5F¾wν¶1 κψ›a9f4sLsL ùVo$v3x1¸nz.uȦ1H4s35Ô7yoQCÄFMiMzda¯ZεlÝHNi¬cÚsù–ϖ DYhaã7Ns4Ö· n3dl1XÆo¯µ¶wpN↑ YQ7aé39s1qÓ QyL$fcÕ1ΝS5.5Wy62­d5ĶH
³7<V401i4æÂaθÀTg÷ÄGr9EûaΡBw →ÌÖSRSLu72lpL6Veº9Ær¾HL FEpAÕø9cP¬ltÒcDibäXvTtFel3®+bVM ø5ôaXWas4ºä μÕKl∏7mo√þ3wSg1 ι£Ca´´Xso18 ÅL2$…4¾2Jo↑.0Λa53iè55WÕî3IV4◊9iFÊVaßÕóg8³9r℘buaf®2 fc7Pg3⊆rzç8oÜ−⋅fÿ≥ZeaPÑs5⇐TsiΨ∋i9ÌuoU8RnΨ⌉•aw1flfùë TQNaU›ésvDu BÇIl6Θlo∠HfwNX8 36Xa∼α»sT½d ŠHG$Îõ¬3QWÀ.‰›Y5Ôg80¦ao
³7<V401i4æÂaθÀTg÷ÄGr9EûaΡBw →ÌÖSRSLu72lpL6Veº9Ær¾HL FEpAÕø9cP¬ltÒcDibäXvTtFel3®+bVM ø5ôaXWas4ºä μÕKl∏7mo√þ3wSg1 ι£Ca´´Xso18 ÅL2$…4¾2Jo↑.0Λa53iè55WÕî3IV4◊9iFÊVaßÕóg8³9r℘buaf®2 fc7Pg3⊆rzç8oÜ−⋅fÿ≥ZeaPÑs5⇐TsiΨ∋i9ÌuoU8RnΨ⌉•aw1flfùë TQNaU›ésvDu BÇIl6Θlo∠HfwNX8 36Xa∼α»sT½d ŠHG$Îõ¬3QWÀ.‰›Y5Ôg80¦ao
LKNV0ÄwiM4xafsJgFJär27”a⇐MÔ ∠O5SQØMuté«p÷ÅÃe¨ûHrZ4Ä 1UΛF¨TsoûwXrú4Ickyçe½qY 074aÙl⌊sÐH1 4Ùplø4Xob0aw4FÔ 28∴a70lsA30 ßWF$Z¸v4AEG.Î6¨2t9p5¶¼QM9¯Cε92i0qPa¹AölW5Pi5Vusi8ë ðO0SE2Euù∈èpòY3eTs6r6ý2 lªÌAyîjcQpet½3õiiqXvPVOe8­V+«“G ¤ó6a®Π7sJÕg ¡JÈl♥Š¾oÐolwBVà →AmaηÒ¯saÑÚ Häð$2Ef2∈n5.Œ8H95¨19⊃ƒõLKNV0ÄwiM4xafsJgFJär27”a⇐MÔ ∠O5SQØMuté«p÷ÅÃe¨ûHrZ4Ä 1UΛF¨TsoûwXrú4Ickyçe½qY 074aÙl⌊sÐH1 4Ùplø4Xob0aw4FÔ 28∴a70lsA30 ßWF$Z¸v4AEG.Î6¨2t9p5¶¼QM9¯Cε92i0qPa¹AölW5Pi5Vusi8ë ðO0SE2Euù∈èpòY3eTs6r6ý2 lªÌAyîjcQpet½3õiiqXvPVOe8­V+«“G ¤ó6a®Π7sJÕg ¡JÈl♥Š¾oÐolwBVà →AmaηÒ¯saÑÚ Häð$2Ef2∈n5.Œ8H95¨19⊃ƒõ
Up dylan in love and found herself. Sorry for beth smiled at some time Whatever you on one who looked. Except for another man and ready.
ÚúeACíøN˵UT3L♠ICë9-BŒfAoÓCL5ΒÉLHοNE5∂7RScdGX­ªIpΣuCCw∨/D¤6A´vâS0d⊂TÇ'BHfóΔMåß7A63B: +ÚúeACíøN˵UT3L♠ICë9-BŒfAoÓCL5ΒÉLHοNE5∂7RScdGX­ªIpΣuCCw∨/D¤6A´vâS0d⊂TÇ'BHfóΔMåß7A63B:
2UýV5¦Ueý¿×nRm2tæÓOoγ1øly¼Wi6pxnÀZ« câSa8ï¤sGï⊂ ΜJll1£„onbéw⌉ö1 vY8aΘmgs0Ú4 å¥G$·592KkU1®b0.½Âℜ54Èh0º´hZf­A0j¸dc1ξv™Xpagl×ib8YrSf0 ¨WiaÀ4»sÁ×7 TAwll¨dom1Gw2¿z ΒÿÀaˆyÎsN8η 3oo$D012Λp³4cìz.PA∅9ϒ7354ú92UýV5¦Ueý¿×nRm2tæÓOoγ1øly¼Wi6pxnÀZ« câSa8ï¤sGï⊂ ΜJll1£„onbéw⌉ö1 vY8aΘmgs0Ú4 å¥G$·592KkU1®b0.½Âℜ54Èh0º´hZf­A0j¸dc1ξv™Xpagl×ib8YrSf0 ¨WiaÀ4»sÁ×7 TAwll¨dom1Gw2¿z ΒÿÀaˆyÎsN8η 3oo$D012Λp³4cìz.PA∅9ϒ7354ú9
RãíNn¨2aYRøs≅←ÍoPÀynCΧ»efõoxÕ∪h E18aNÿÜsiÿ5 f47lÃ47oFÂjwGÎÉ ·08aºedsjÛS ¿e®$KèR1LDÍ7üoè.4·O99Ý£9íϖn¶ú↵Sι3”pÝó‾iEuerΓy0iY30vΤA6a2\"Y 465a1m6sgÁs C∀ilΑÒΠor6yw7¿ð 1KΩaÐ32s∇Δ¤ 9Χ9$MWN2P0É8óËβ.Ö∩S93íñ0RQ’RãíNn¨2aYRøs≅←ÍoPÀynCΧ»efõoxÕ∪h E18aNÿÜsiÿ5 f47lÃ47oFÂjwGÎÉ ·08aºedsjÛS ¿e®$KèR1LDÍ7üoè.4·O99Ý£9íϖn¶ú↵Sι3”pÝó‾iEuerΓy0iY30vΤA6a2"Y 465a1m6sgÁs C∀ilΑÒΠor6yw7¿ð 1KΩaÐ32s∇Δ¤ 9Χ9$MWN2P0É8óËβ.Ö∩S93íñ0RQ’
Have anything but matty is taking care. Voice sounded in name only the others Mouth shut and while he returned with. Herself with one who is your life
ÿ²íGu8NEZ3FNFsôEÆRnRÇC9AK4xLÀ5Ç Ì5bH97CE«Ì0AÎq¢Lµk→TªJkHe3š:Taking care about matt li? ed ryan. Knowing he should be there.ÿ²íGu8NEZ3FNFsôEÆRnRÇC9AK4xLÀ5Ç Ì5bH97CE«Ì0AÎq¢Lµk→TªJkHe3š:Taking care about matt liî ed ryan. Knowing he should be there.
Ks£TäbIr74EaãDZmœH¡a³7odÅ∪voÒozlP3S 23‹azy∝sÚ°Q 4â¹ll21ovh7w2D2 ©Qwa⇑cΒs¨wH Iµe$⇐J517Tñ.t5f36ÅB06ãΨ5z℘Z4nGiý89t←f4hvnàrbŸTo1s9m¥Ëqand·xxO6 Iÿ∪ak½0sÙ£M ûΗ¡løȾorztw170 —♣≅ar6qsvDv 76T$3×D0erÍ.d¼07WoI5ÀKúKs£TäbIr74EaãDZmœH¡a³7odÅ∪voÒozlP3S 23‹azy∝sÚ°Q 4â¹ll21ovh7w2D2 ©Qwa⇑cΒs¨wH Iµe$⇐J517Tñ.t5f36ÅB06ãΨ5z℘Z4nGiý89t←f4hvnàrbŸTo1s9m¥Ëqand·xxO6 Iÿ∪ak½0sÙ£M ûΗ¡løȾorztw170 —♣≅ar6qsvDv 76T$3×D0erÍ.d¼07WoI5ÀKú
ϒa9P'¶¯rP74o2ψÈzχfþaÃàñc3qY →®7aaRgsN©k ¯‰ΣlÍpÃo7R⊂wÆðe 3Iha♣d˜s3g7 È3M$≡⋅ª0AY4.Uq√3Û±k5SUΜZr2A8Ö6cZŸdoΡeumpq¼pAoUlèI2ieYÒaK>∂ 3n6ax1Qs20b °Häl9¶ÑoÏ6aw≡dä ΗÅ2a¢Óvs⊃Á7 C⊆Ä$2Bz2sló.∫Pb5ØMx0oQdϒa9P'¶¯rP74o2ψÈzχfþaÃàñc3qY →®7aaRgsN©k ¯‰ΣlÍpÃo7R⊂wÆðe 3Iha♣d˜s3g7 È3M$≡⋅ª0AY4.Uq√3Û±k5SUΜZr2A8Ö6cZŸdoΡeumpq¼pAoUlèI2ieYÒaK>∂ 3n6ax1Qs20b °Häl9¶ÑoÏ6aw≡dä ΗÅ2a¢Óvs⊃Á7 C⊆Ä$2Bz2sló.∫Pb5ØMx0oQd
ZΙμPCqmrµp0eAΦ♥dô‾Ωn∠2si4y2s÷8«o6∀ClDeÌoPbqnd¡Jelè× ÿˆ5aWl〈sbPÔ ï²çl8¢OoH¸ew’90 Υ66aÕÆdsh6K r6Ç$7Ey0WcÎ.£—012C857Aþi·σS€53yxµèn80ntΡΠmhç≡hrB²doµS¥ih÷rdOKK 7½öa←ãIs2⌉V Cssl±´RoT1QwyÉΔ •∏∞aïYGsÂ8E 1πx$04ò0gMF.bTQ3Íx658ùςZΙμPCqmrµp0eAΦ♥dô‾Ωn∠2si4y2s÷8«o6∀ClDeÌoPbqnd¡Jelè× ÿˆ5aWl〈sbPÔ ï²çl8¢OoH¸ew’90 Υ66aÕÆdsh6K r6Ç$7Ey0WcÎ.£—012C857Aþi·σS€53yxµèn80ntΡΠmhç≡hrB²doµS¥ih÷rdOKK 7½öa←ãIs2⌉V Cssl±´RoT1QwyÉΔ •∏∞aïYGsÂ8E 1πx$04ò0gMF.bTQ3Íx658ùς
Maybe even though she followed. Does this mean you talking about. Whatever else to sit on them back
←4BC3éhAGAWNrÛjAGυ»D¬f4Iðm√AHM9N〉1è ‚¬HDÁ9ÜRâ3∨U90IG¾99S¶∪”T¥ì3OË°cR0E⇑E2°1 4ÖaA″XΝDµ4ℑVAK8Aµd9NrÅDT¦12A5khGA3mE98ÔS9KC!5TU←4BC3éhAGAWNrÛjAGυ»D¬f4Iðm√AHM9N〉1è ‚¬HDÁ9ÜRâ3∨U90IG¾99S¶∪”T¥ì3OË°cR0E⇑E2°1 4ÖaA″XΝDµ4ℑVAK8Aµd9NrÅDT¦12A5khGA3mE98ÔS9KC!5TU
AMm>EjL w∗LWυIaoKd¹rΘ22l2IΚdê5PwO4Hiây6dÖH⌊eÃìg j14Dr­5e700lH·ÐiJ±ùvY…öe¦mhr¸«4yrÆÔ!∑η2 ÷¬υOΔfδrKZwd4KVeB¶órℜ0Ç PΖ×341o+A7Y ¬æ6GM17oGOºos7∑d×7ûs¤8P ο♦QaRn–n5b2d0ìw ËrϒGIÑℑem0∀t³bæ 20rF4O7Rä2°EÇò⊆ESΥ4 KF0AÒÂßi5ïcrt⊆€mRJ7aNΛÿinÕ6l5bQ ¸ϒtSZbwh3¶3ig♠9p2″Ìp×¢êiK»´nsWsgdXW!tBOAMm>EjL w∗LWυIaoKd¹rΘ22l2IΚdê5PwO4Hiây6dÖH⌊eÃìg j14Dr­5e700lH·ÐiJ±ùvY…öe¦mhr¸«4yrÆÔ!∑η2 ÷¬υOΔfδrKZwd4KVeB¶órℜ0Ç PΖ×341o+A7Y ¬æ6GM17oGOºos7∑d×7ûs¤8P ο♦QaRn–n5b2d0ìw ËrϒGIÑℑem0∀t³bæ 20rF4O7Rä2°EÇò⊆ESΥ4 KF0AÒÂßi5ïcrt⊆€mRJ7aNΛÿinÕ6l5bQ ¸ϒtSZbwh3¶3ig♠9p2″Ìp×¢êiK»´nsWsgdXW!tBO
m0W>YÙ b¬u1xΔd03¯¬0vHK%Þ¹ó 674Aj3öuQ←ÏtÈH¨houqeyªYnÑ21t⌋BZi¦V2c¬Tn >ZΓMöÜÊe3Å1dís5s2ø›!³0û 2¡ÌEmè1xéV2p1∨6iâdârB9ra72mtSzIiMlVo0NLngΒû ú2LD7⇑maNx3tUζ∪etcù 90ìo¶Ù3fv49 w≅»O0givÅýYeXïNryfT 3fP3xZÕ FñÃY8q¯eEÂÜaâyfrΜpls9âÂ!qκÊm0W>YÙ b¬u1xΔd03¯¬0vHK%Þ¹ó 674Aj3öuQ←ÏtÈH¨houqeyªYnÑ21t⌋BZi¦V2c¬Tn >ZΓMöÜÊe3Å1dís5s2ø›!³0û 2¡ÌEmè1xéV2p1∨6iâdârB9ra72mtSzIiMlVo0NLngΒû ú2LD7⇑maNx3tUζ∪etcù 90ìo¶Ù3fv49 w≅»O0givÅýYeXïNryfT 3fP3xZÕ FñÃY8q¯eEÂÜaâyfrΜpls9âÂ!qκÊ
î5A>∀pƒ ZµÍSδ3éem2sc⊕7vu41JrÒ°weÊyh qaρOÏp¼nΣxZlrN¡i♠Êcnl4jeN¶Q y2≅Sb63h17〉ofµypÅAÆpþh0iÔcbnec4gIù1 h2Uw23‹i9çktSÅÏh6Vº g±sVŒóuipV¯seÈ⋅a4üV,T6D 2ý8MΡY©a⊃ºΕs5ùýt9IDeFDℑrXpOCe“μan·Mr¾1Kd¥ëð,eø7 DfmAæ¤NM9ïhEUË∨XσψG 4j0a°81nhTAdmTü «9öEνμr-U4fc¨Þ1h8ª¸eoycc9xjk⁄ko!ë9Kî5A>∀pƒ ZµÍSδ3éem2sc⊕7vu41JrÒ°weÊyh qaρOÏp¼nΣxZlrN¡i♠Êcnl4jeN¶Q y2≅Sb63h17〉ofµypÅAÆpþh0iÔcbnec4gIù1 h2Uw23‹i9çktSÅÏh6Vº g±sVŒóuipV¯seÈ⋅a4üV,T6D 2ý8MΡY©a⊃ºΕs5ùýt9IDeFDℑrXpOCe“μan·Mr¾1Kd¥ëð,eø7 DfmAæ¤NM9ïhEUË∨XσψG 4j0a°81nhTAdmTü «9öEνμr-U4fc¨Þ1h8ª¸eoycc9xjk⁄ko!ë9K
¬Û…>J6Á ¢〉8EÖ22a³41s¬17y3â8 °f2R6olewtzfw¹suýoQn⇓³³d×4Gs¢7« AlDa°H¶n9Ejdtg› ¯ôθ2ε¥⊇4¯″A/4Øv72z→ Ü3¥C6ú2u56Xs9⁄1t∑ΙioxÉjmØRùe1WÔrH25 o¥ßS≥gmuX2gp3yip·³2oD£3rc3μtks∪!sWK¬Û…>J6Á ¢〉8EÖ22a³41s¬17y3â8 °f2R6olewtzfw¹suýoQn⇓³³d×4Gs¢7« AlDa°H¶n9Ejdtg› ¯ôθ2ε¥⊇4¯″A/4Øv72z→ Ü3¥C6ú2u56Xs9⁄1t∑ΙioxÉjmØRùe1WÔrH25 o¥ßS≥gmuX2gp3yip·³2oD£3rc3μtks∪!sWK
-
When she were there you here. Lott to need for amy said.
Once more than ever since matt. Lott said turning o? ered. Tell you so matt kept going.
Homegrown dandelions by herself into her lips. Such an excuse to stop thinking about. Leave us and be right.

+When she were there you here. Lott to need for amy said.
Once more than ever since matt. Lott said turning oď ered. Tell you so matt kept going.
Homegrown dandelions by herself into her lips. Such an excuse to stop thinking about. Leave us and be right.


- +
- -
- + + -

?????? ?????????????????? ???????????????? ???? ?????????????? ?? ???????????????????????? ???? ?????????????????? avast! Antivirus ???????????? ??????????????.

+

Это сообщение свободно от вирусов и вредоносного ПО благодаря avast! Antivirus защита активна.

", +}, sender: 'Customer', type: 'email', internal: false, @@ -521,7 +521,7 @@ Some Text", }, 1 => { content_type: 'text/html', - body: 'Puzzled by judith bronte dave. Melvin will want her way through with.
Continued adam helped charlie cried. Soon joined the master bathroom. Grinned adam rubbed his arms she nodded.
Freemont and they talked with beppe.
Thinking of bed and whenever adam.
Mike was too tired man to hear.
I10PQSHEJl2Nwf&tilde;2113S173 &Icirc;1mEbb5N371L&piv;C7AlFnR1&diams;HG64B242&brvbar;M2242zk&Iota;N&rceil;7&rceil;TBN&ETH; T2xPI&ograve;gI2&Atilde;lL2&Otilde;ML&perp;22Sa&Psi;RBreathed adam gave the master bedroom door.
Better get charlie took the wall.
Charlotte clark smile he saw charlie.
Dave and leaned her tears adam.
Maybe we want any help me that.
Next morning charlie gazed at their father.
Well as though adam took out here. Melvin will be more money. Called him into this one last night.
Men joined the pickup truck pulled away. Chuck could make sure that.[1] &dagger;p1C?L&thinsp;I?C&ensp;K?88&ensp;5 E R?EEOD !Chuckled adam leaned forward and le? charlie.
Just then returned to believe it here.
Freemont and pulling out several minutes.

[1] &#104;&#116;&#116;&#112;&#58;&#47;&#47;&#1072;&#1086;&#1089;&#1082;&#46;&#1088;&#1092;?jmlfwnwe&ucwkiyyc', + body: 'Puzzled by judith bronte dave. Melvin will want her way through with.
Continued adam helped charlie cried. Soon joined the master bathroom. Grinned adam rubbed his arms she nodded.
Freemont and they talked with beppe.
Thinking of bed and whenever adam.
Mike was too tired man to hear.
I°0PQSHEJlÔNwf˜Ì1§3S¬73 Î1mEbb5N37¢LϖC7AlFnRº♦HG64BÉ4Ò¦Måâ4ÊzkΙN⌉7⌉TBNÐ T×xPIògIÎÃlLøÕML⊥ÞøSaΨRBreathed adam gave the master bedroom door.
Better get charlie took the wall.
Charlotte clark smile he saw charlie.
Dave and leaned her tears adam.
Maybe we want any help me that.
Next morning charlie gazed at their father.
Well as though adam took out here. Melvin will be more money. Called him into this one last night.
Men joined the pickup truck pulled away. Chuck could make sure that.†p­C L I C K Ȟ E R EEOD !Chuckled adam leaned forward and leî charlie.
Just then returned to believe it here.
Freemont and pulling out several minutes.', sender: 'Customer', type: 'email', internal: false, @@ -538,7 +538,7 @@ Some Text", }, 1 => { from: 'marketingmanager@nthcpghana.com', - body: '»ú·¿»·¾³·¨¹æ + body: '机房环境法规 Message-ID: <20140911055224675615@nthcpghana.com> From: =?utf-8?B?6IOh5qW35ZKM?= To: , @@ -2524,10 +2524,10 @@ Some Text', result: { 0 => { priority: '2 normal', - title: 'ת·¢£ºÕûÌåÌáÉýÆóÒµ·þÎñˮƽ', + title: '转发:整体提升企业服务水平', }, 1 => { - from: '"ÎäÀ¼³É" ', + from: '"武兰成" ', sender: 'Customer', type: 'email', }, @@ -2535,9 +2535,9 @@ Some Text', verify: { users: [ { - firstname: 'ÎäÀ¼³É', + firstname: '武兰成', lastname: '', - fullname: 'ÎäÀ¼³É', + fullname: '武兰成', email: 'glopelf7121@example.com', }, ], @@ -2655,7 +2655,7 @@ Some Text', from: 'Martin Edenhofer ', sender: 'Customer', type: 'email', - body: 'Here it goes - ?????? - ?????????Here it goes - ??? - hi ?', + body: "Here it goes - äÜß - 塎ĺ\u0087şäşşHere it goes - äöü - hi ­", }, }, verify: {