aboutsummaryrefslogtreecommitdiffstats
path: root/lib/mail_handler/backends/mail_extensions.rb
blob: 611b44c4cc80a6df739f293f3231ff8604f4fa3f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
require 'mail/message'
require 'mail/fields/common/parameter_hash'
module Mail
    class Message
        attr_accessor :url_part_number
        attr_accessor :rfc822_attachment # when a whole email message is attached as text
        attr_accessor :within_rfc822_attachment # for parts within a message attached as text (for getting subject mainly)
        attr_accessor :count_parts_count
        attr_accessor :count_first_uudecode_count

        # A patched version of the message initializer to work around a bug where stripping the original
        # input removes meaningful spaces - e.g. in the case of uuencoded bodies.
        def initialize(*args, &block)
            @body = nil
            @body_raw = nil
            @separate_parts = false
            @text_part = nil
            @html_part = nil
            @errors = nil
            @header = nil
            @charset = 'UTF-8'
            @defaulted_charset = true

            @perform_deliveries = true
            @raise_delivery_errors = true

            @delivery_handler = nil

            @delivery_method = Mail.delivery_method.dup

            @transport_encoding = Mail::Encodings.get_encoding('7bit')

            @mark_for_delete = false

            if args.flatten.first.respond_to?(:each_pair)
                init_with_hash(args.flatten.first)
            else
                # The replacement of this commented out line is the change.
                # init_with_string(args.flatten[0].to_s.strip)
                init_with_string(args.flatten[0].to_s)
            end

            if block_given?
                instance_eval(&block)
            end

            self
        end

        # HACK: Backported from Mail 2.5 for Ruby 1.8 support
        # Can be removed when we no longer support Ruby 1.8
        def to_yaml(opts = {})
          hash = {}
          hash['headers'] = {}
          header.fields.each do |field|
            hash['headers'][field.name] = field.value
          end
          hash['delivery_handler'] = delivery_handler.to_s if delivery_handler
          hash['transport_encoding'] = transport_encoding.to_s
          special_variables = [:@header, :@delivery_handler, :@transport_encoding]
          if multipart?
            hash['multipart_body'] = []
            body.parts.map { |part| hash['multipart_body'] << part.to_yaml }
            special_variables.push(:@body, :@text_part, :@html_part)
          end
          (instance_variables.map(&:to_sym) - special_variables).each do |var|
            hash[var.to_s] = instance_variable_get(var)
          end
          hash.to_yaml(opts)
        end
    end

    # A patched version of the parameter hash that handles nil values without throwing
    # an error.
    class ParameterHash < IndifferentHash

        def encoded
          map.sort { |a,b| a.first.to_s <=> b.first.to_s }.map do |key_name, value|
            # The replacement of this commented out line is the change
            # unless value.ascii_only?
            unless value.nil? || value.ascii_only?
              value = Mail::Encodings.param_encode(value)
              key_name = "#{key_name}*"
            end
            %Q{#{key_name}=#{quote_token(value)}}
          end.join(";\r\n\s")
        end
    end

    # HACK: Backport encoding fixes for Ruby 1.8 from Mail 2.5
    # Can be removed when we no longer support Ruby 1.8
    class Ruby18
        def Ruby18.b_value_decode(str)
            match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
            if match
                encoding = match[1]
                str = Ruby18.decode_base64(match[2])
                str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str)
            end
            str
        end

        def Ruby18.q_value_decode(str)
          match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
          if match
              encoding = match[1]
              string = match[2].gsub(/_/, '=20')
              # Remove trailing = if it exists in a Q encoding
              string = string.sub(/\=$/, '')
              str = Encodings::QuotedPrintable.decode(string)
              str = Iconv.conv('UTF-8//IGNORE', fix_encoding(encoding), str)
          end
          str
        end

        private

        def Ruby18.fix_encoding(encoding)
            case encoding.upcase
            when 'UTF8'
                'UTF-8'
            else
                encoding
            end
        end
    end

    # HACK: Backport encoding fixes for Ruby 1.9 from Mail 2.5
    # Can be removed when Rails relies on Mail > 2.5
    class Ruby19
        def Ruby19.b_value_decode(str)
            match = str.match(/\=\?(.+)?\?[Bb]\?(.+)?\?\=/m)
            if match
                encoding = match[1]
                str = Ruby19.decode_base64(match[2])
                str.force_encoding(fix_encoding(encoding))
            end
            decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
            decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
        end

        def Ruby19.q_value_decode(str)
            match = str.match(/\=\?(.+)?\?[Qq]\?(.+)?\?\=/m)
            if match
                encoding = match[1]
                str = Encodings::QuotedPrintable.decode(match[2])
                str.force_encoding(fix_encoding(encoding))
            end
            decoded = str.encode("utf-8", :invalid => :replace, :replace => "")
            decoded.valid_encoding? ? decoded : decoded.encode("utf-16le", :invalid => :replace, :replace => "").encode("utf-8")
        end

        # mails somtimes includes invalid encodings like iso885915 or utf8 so we transform them to iso885915 or utf8
        # TODO: add this as a test somewhere
        # Encoding.list.map{|e| [e.to_s.upcase==fix_encoding(e.to_s.downcase.gsub("-", "")), e.to_s] }.select {|a,b| !b}
        #  Encoding.list.map{|e| [e.to_s==fix_encoding(e.to_s), e.to_s] }.select {|a,b| !b}
        def Ruby19.fix_encoding(encoding)
            case encoding
                # ISO-8859-15, ISO-2022-JP and alike
                when /iso-?(\d{4})-?(\w{1,2})/i then return "ISO-#{$1}-#{$2}"
                # "ISO-2022-JP-KDDI"  and alike
                when /iso-?(\d{4})-?(\w{1,2})-?(\w*)/i then return "ISO-#{$1}-#{$2}-#{$3}"
                # UTF-8, UTF-32BE and alike
                when /utf-?(\d{1,2})?(\w{1,2})/i then return "UTF-#{$1}#{$2}"
                # Windows-1252 and alike
                when /Windows-?(.*)/i then return "Windows-#{$1}"
                #more aliases to be added if needed
                else return encoding
            end
        end
    end
end