~mark-mims/ubuntu/oneiric/ruby-actionmailer-2.3/fix-2.3.14

« back to all changes in this revision

Viewing changes to lib/action_mailer/vendor/tmail-1.2.7/tmail/utils.rb

  • Committer: Bazaar Package Importer
  • Author(s): Ondřej Surý
  • Date: 2011-06-01 16:49:50 UTC
  • Revision ID: james.westby@ubuntu.com-20110601164950-zd4413lamwzyln51
Tags: upstream-2.3.11
Import upstream version 2.3.11

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# encoding: us-ascii
 
2
#--
 
3
# Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
 
4
#
 
5
# Permission is hereby granted, free of charge, to any person obtaining
 
6
# a copy of this software and associated documentation files (the
 
7
# "Software"), to deal in the Software without restriction, including
 
8
# without limitation the rights to use, copy, modify, merge, publish,
 
9
# distribute, sublicense, and/or sell copies of the Software, and to
 
10
# permit persons to whom the Software is furnished to do so, subject to
 
11
# the following conditions:
 
12
#
 
13
# The above copyright notice and this permission notice shall be
 
14
# included in all copies or substantial portions of the Software.
 
15
#
 
16
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 
17
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 
18
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 
19
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 
20
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 
21
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 
22
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
23
#
 
24
# Note: Originally licensed under LGPL v2+. Using MIT license for Rails
 
25
# with permission of Minero Aoki.
 
26
#++
 
27
 
 
28
# = TMail - The EMail Swiss Army Knife for Ruby
 
29
#
 
30
# The TMail library provides you with a very complete way to handle and manipulate EMails
 
31
# from within your Ruby programs.
 
32
#
 
33
# Used as the backbone for email handling by the Ruby on Rails and Nitro web frameworks as
 
34
# well as a bunch of other Ruby apps including the Ruby-Talk mailing list to newsgroup email
 
35
# gateway, it is a proven and reliable email handler that won't let you down.
 
36
#
 
37
# Originally created by Minero Aoki, TMail has been recently picked up by Mikel Lindsaar and
 
38
# is being actively maintained.  Numerous backlogged bug fixes have been applied as well as
 
39
# Ruby 1.9 compatibility and a swath of documentation to boot.
 
40
#
 
41
# TMail allows you to treat an email totally as an object and allow you to get on with your
 
42
# own programming without having to worry about crafting the perfect email address validation
 
43
# parser, or assembling an email from all it's component parts.
 
44
#
 
45
# TMail handles the most complex part of the email - the header.  It generates and parses
 
46
# headers and provides you with instant access to their innards through simple and logically
 
47
# named accessor and setter methods.
 
48
#
 
49
# TMail also provides a wrapper to Net/SMTP as well as Unix Mailbox handling methods to
 
50
# directly read emails from your unix mailbox, parse them and use them.
 
51
#
 
52
# Following is the comprehensive list of methods to access TMail::Mail objects.  You can also
 
53
# check out TMail::Mail, TMail::Address and TMail::Headers for other lists.
 
54
module TMail
 
55
 
 
56
  # Provides an exception to throw on errors in Syntax within TMail's parsers
 
57
  class SyntaxError < StandardError; end
 
58
 
 
59
  # Provides a new email boundary to separate parts of the email.  This is a random
 
60
  # string based off the current time, so should be fairly unique.
 
61
  #
 
62
  # For Example:
 
63
  #
 
64
  #  TMail.new_boundary
 
65
  #  #=> "mimepart_47bf656968207_25a8fbb80114"
 
66
  #  TMail.new_boundary
 
67
  #  #=> "mimepart_47bf66051de4_25a8fbb80240"
 
68
  def TMail.new_boundary
 
69
    'mimepart_' + random_tag
 
70
  end
 
71
 
 
72
  # Provides a new email message ID.  You can use this to generate unique email message
 
73
  # id's for your email so you can track them.
 
74
  #
 
75
  # Optionally takes a fully qualified domain name (default to the current hostname
 
76
  # returned by Socket.gethostname) that will be appended to the message ID.
 
77
  #
 
78
  # For Example:
 
79
  #
 
80
  #  email.message_id = TMail.new_message_id
 
81
  #  #=> "<47bf66845380e_25a8fbb80332@baci.local.tmail>"
 
82
  #  email.to_s
 
83
  #  #=> "Message-Id: <47bf668b633f1_25a8fbb80475@baci.local.tmail>\n\n"
 
84
  #  email.message_id = TMail.new_message_id("lindsaar.net")
 
85
  #  #=> "<47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>"
 
86
  #  email.to_s
 
87
  #  #=> "Message-Id: <47bf668b633f1_25a8fbb80475@lindsaar.net.tmail>\n\n"
 
88
  def TMail.new_message_id( fqdn = nil )
 
89
    fqdn ||= ::Socket.gethostname
 
90
    "<#{random_tag()}@#{fqdn}.tmail>"
 
91
  end
 
92
 
 
93
  #:stopdoc:
 
94
  def TMail.random_tag #:nodoc:
 
95
    @uniq += 1
 
96
    t = Time.now
 
97
    sprintf('%x%x_%x%x%d%x',
 
98
            t.to_i, t.tv_usec,
 
99
            $$, Thread.current.object_id, @uniq, rand(255))
 
100
  end
 
101
  private_class_method :random_tag
 
102
 
 
103
  @uniq = 0
 
104
 
 
105
  #:startdoc:
 
106
 
 
107
  # Text Utils provides a namespace to define TOKENs, ATOMs, PHRASEs and CONTROL characters that
 
108
  # are OK per RFC 2822.
 
109
  #
 
110
  # It also provides methods you can call to determine if a string is safe
 
111
  module TextUtils
 
112
 
 
113
    aspecial     = %Q|()<>[]:;.\\,"|
 
114
    tspecial     = %Q|()<>[];:\\,"/?=|
 
115
    lwsp         = %Q| \t\r\n|
 
116
    control      = %Q|\x00-\x1f\x7f-\xff|
 
117
 
 
118
    CONTROL_CHAR  = /[#{control}]/n
 
119
    ATOM_UNSAFE   = /[#{Regexp.quote aspecial}#{control}#{lwsp}]/n
 
120
    PHRASE_UNSAFE = /[#{Regexp.quote aspecial}#{control}]/n
 
121
    TOKEN_UNSAFE  = /[#{Regexp.quote tspecial}#{control}#{lwsp}]/n
 
122
 
 
123
    # Returns true if the string supplied is free from characters not allowed as an ATOM
 
124
    def atom_safe?( str )
 
125
      not ATOM_UNSAFE === str
 
126
    end
 
127
 
 
128
    # If the string supplied has ATOM unsafe characters in it, will return the string quoted
 
129
    # in double quotes, otherwise returns the string unmodified
 
130
    def quote_atom( str )
 
131
      (ATOM_UNSAFE === str) ? dquote(str) : str
 
132
    end
 
133
 
 
134
    # If the string supplied has PHRASE unsafe characters in it, will return the string quoted
 
135
    # in double quotes, otherwise returns the string unmodified
 
136
    def quote_phrase( str )
 
137
      (PHRASE_UNSAFE === str) ? dquote(str) : str
 
138
    end
 
139
 
 
140
    # Returns true if the string supplied is free from characters not allowed as a TOKEN
 
141
    def token_safe?( str )
 
142
      not TOKEN_UNSAFE === str
 
143
    end
 
144
 
 
145
    # If the string supplied has TOKEN unsafe characters in it, will return the string quoted
 
146
    # in double quotes, otherwise returns the string unmodified
 
147
    def quote_token( str )
 
148
      (TOKEN_UNSAFE === str) ? dquote(str) : str
 
149
    end
 
150
 
 
151
    # Wraps supplied string in double quotes unless it is already wrapped
 
152
    # Returns double quoted string
 
153
    def dquote( str ) #:nodoc:
 
154
      unless str =~ /^".*?"$/
 
155
        '"' + str.gsub(/["\\]/n) {|s| '\\' + s } + '"'
 
156
      else
 
157
        str
 
158
      end
 
159
    end
 
160
    private :dquote
 
161
 
 
162
    # Unwraps supplied string from inside double quotes
 
163
    # Returns unquoted string
 
164
    def unquote( str )
 
165
      str =~ /^"(.*?)"$/m ? $1 : str
 
166
    end
 
167
 
 
168
    # Provides a method to join a domain name by it's parts and also makes it
 
169
    # ATOM safe by quoting it as needed
 
170
    def join_domain( arr )
 
171
      arr.map {|i|
 
172
          if /\A\[.*\]\z/ === i
 
173
            i
 
174
          else
 
175
            quote_atom(i)
 
176
          end
 
177
      }.join('.')
 
178
    end
 
179
 
 
180
    #:stopdoc:
 
181
    ZONESTR_TABLE = {
 
182
      'jst' =>   9 * 60,
 
183
      'eet' =>   2 * 60,
 
184
      'bst' =>   1 * 60,
 
185
      'met' =>   1 * 60,
 
186
      'gmt' =>   0,
 
187
      'utc' =>   0,
 
188
      'ut'  =>   0,
 
189
      'nst' => -(3 * 60 + 30),
 
190
      'ast' =>  -4 * 60,
 
191
      'edt' =>  -4 * 60,
 
192
      'est' =>  -5 * 60,
 
193
      'cdt' =>  -5 * 60,
 
194
      'cst' =>  -6 * 60,
 
195
      'mdt' =>  -6 * 60,
 
196
      'mst' =>  -7 * 60,
 
197
      'pdt' =>  -7 * 60,
 
198
      'pst' =>  -8 * 60,
 
199
      'a'   =>  -1 * 60,
 
200
      'b'   =>  -2 * 60,
 
201
      'c'   =>  -3 * 60,
 
202
      'd'   =>  -4 * 60,
 
203
      'e'   =>  -5 * 60,
 
204
      'f'   =>  -6 * 60,
 
205
      'g'   =>  -7 * 60,
 
206
      'h'   =>  -8 * 60,
 
207
      'i'   =>  -9 * 60,
 
208
      # j not use
 
209
      'k'   => -10 * 60,
 
210
      'l'   => -11 * 60,
 
211
      'm'   => -12 * 60,
 
212
      'n'   =>   1 * 60,
 
213
      'o'   =>   2 * 60,
 
214
      'p'   =>   3 * 60,
 
215
      'q'   =>   4 * 60,
 
216
      'r'   =>   5 * 60,
 
217
      's'   =>   6 * 60,
 
218
      't'   =>   7 * 60,
 
219
      'u'   =>   8 * 60,
 
220
      'v'   =>   9 * 60,
 
221
      'w'   =>  10 * 60,
 
222
      'x'   =>  11 * 60,
 
223
      'y'   =>  12 * 60,
 
224
      'z'   =>   0 * 60
 
225
    }
 
226
    #:startdoc:
 
227
 
 
228
    # Takes a time zone string from an EMail and converts it to Unix Time (seconds)
 
229
    def timezone_string_to_unixtime( str )
 
230
      if m = /([\+\-])(\d\d?)(\d\d)/.match(str)
 
231
        sec = (m[2].to_i * 60 + m[3].to_i) * 60
 
232
        m[1] == '-' ? -sec : sec
 
233
      else
 
234
        min = ZONESTR_TABLE[str.downcase] or
 
235
                raise SyntaxError, "wrong timezone format '#{str}'"
 
236
        min * 60
 
237
      end
 
238
    end
 
239
 
 
240
    #:stopdoc:
 
241
    WDAY = %w( Sun Mon Tue Wed Thu Fri Sat TMailBUG )
 
242
    MONTH = %w( TMailBUG Jan Feb Mar Apr May Jun
 
243
                         Jul Aug Sep Oct Nov Dec TMailBUG )
 
244
 
 
245
    def time2str( tm )
 
246
      # [ruby-list:7928]
 
247
      gmt = Time.at(tm.to_i)
 
248
      gmt.gmtime
 
249
      offset = tm.to_i - Time.local(*gmt.to_a[0,6].reverse).to_i
 
250
 
 
251
      # DO NOT USE strftime: setlocale() breaks it
 
252
      sprintf '%s, %s %s %d %02d:%02d:%02d %+.2d%.2d',
 
253
              WDAY[tm.wday], tm.mday, MONTH[tm.month],
 
254
              tm.year, tm.hour, tm.min, tm.sec,
 
255
              *(offset / 60).divmod(60)
 
256
    end
 
257
 
 
258
 
 
259
    MESSAGE_ID = /<[^\@>]+\@[^>]+>/
 
260
 
 
261
    def message_id?( str )
 
262
      MESSAGE_ID === str
 
263
    end
 
264
 
 
265
 
 
266
    MIME_ENCODED = /=\?[^\s?=]+\?[QB]\?[^\s?=]+\?=/i
 
267
 
 
268
    def mime_encoded?( str )
 
269
      MIME_ENCODED === str
 
270
    end
 
271
 
 
272
 
 
273
    def decode_params( hash )
 
274
      new = Hash.new
 
275
      encoded = nil
 
276
      hash.each do |key, value|
 
277
        if m = /\*(?:(\d+)\*)?\z/.match(key)
 
278
          ((encoded ||= {})[m.pre_match] ||= [])[(m[1] || 0).to_i] = value
 
279
        else
 
280
          new[key] = to_kcode(value)
 
281
        end
 
282
      end
 
283
      if encoded
 
284
        encoded.each do |key, strings|
 
285
          new[key] = decode_RFC2231(strings.join(''))
 
286
        end
 
287
      end
 
288
 
 
289
      new
 
290
    end
 
291
 
 
292
    NKF_FLAGS = {
 
293
      'EUC'  => '-e -m',
 
294
      'SJIS' => '-s -m'
 
295
    }
 
296
 
 
297
    def to_kcode( str )
 
298
      flag = NKF_FLAGS[TMail.KCODE] or return str
 
299
      NKF.nkf(flag, str)
 
300
    end
 
301
 
 
302
    RFC2231_ENCODED = /\A(?:iso-2022-jp|euc-jp|shift_jis|us-ascii)?'[a-z]*'/in
 
303
 
 
304
    def decode_RFC2231( str )
 
305
      m = RFC2231_ENCODED.match(str) or return str
 
306
      begin
 
307
        to_kcode(m.post_match.gsub(/%[\da-f]{2}/in) {|s| s[1,2].hex.chr })
 
308
      rescue
 
309
        m.post_match.gsub(/%[\da-f]{2}/in, "")
 
310
      end
 
311
    end
 
312
 
 
313
    def quote_boundary
 
314
      # Make sure the Content-Type boundary= parameter is quoted if it contains illegal characters
 
315
      # (to ensure any special characters in the boundary text are escaped from the parser
 
316
      # (such as = in MS Outlook's boundary text))
 
317
      if @body =~ /^(.*)boundary=(.*)$/m
 
318
        preamble = $1
 
319
        remainder = $2
 
320
        if remainder =~ /;/
 
321
          remainder =~ /^(.*?)(;.*)$/m
 
322
          boundary_text = $1
 
323
          post = $2.chomp
 
324
        else
 
325
          boundary_text = remainder.chomp
 
326
        end
 
327
        if boundary_text =~ /[\/\?\=]/
 
328
          boundary_text = "\"#{boundary_text}\"" unless boundary_text =~ /^".*?"$/
 
329
          @body = "#{preamble}boundary=#{boundary_text}#{post}"
 
330
        end
 
331
      end
 
332
    end
 
333
 
 
334
    # AppleMail generates illegal character contained Content-Type parameter like:
 
335
    #   name==?ISO-2022-JP?B?...=?=
 
336
    # so quote. (This case is only value fits in one line.)
 
337
    def quote_unquoted_bencode
 
338
      @body = @body.gsub(%r"(;\s+[-a-z]+=)(=\?.+?)([;\r\n ]|\z)"m) {
 
339
        head, should_quoted, tail = $~.captures
 
340
        # head: "; name="
 
341
        # should_quoted: "=?ISO-2022-JP?B?...=?="
 
342
 
 
343
        head << quote_token(should_quoted) << tail
 
344
      }
 
345
    end
 
346
 
 
347
    # AppleMail generates name=filename attributes in the content type that
 
348
    # contain spaces.  Need to handle this so the TMail Parser can.
 
349
    def quote_unquoted_name
 
350
      @body = @body.gsub(%r|(name=)([\w\s.]+)(.*)|m) {
 
351
        head, should_quoted, tail = $~.captures
 
352
        # head: "; name="
 
353
        # should_quoted: "=?ISO-2022-JP?B?...=?="
 
354
        head  << quote_token(should_quoted) << tail
 
355
      }
 
356
    end
 
357
 
 
358
    #:startdoc:
 
359
 
 
360
  end
 
361
 
 
362
end