~michaelforrest/use-case-mapper/trunk

« back to all changes in this revision

Viewing changes to vendor/rails/actionmailer/lib/action_mailer/vendor/tmail-1.2.3/tmail/utils.rb

  • Committer: Richard Lee (Canonical)
  • Date: 2010-10-15 15:17:58 UTC
  • mfrom: (190.1.3 use-case-mapper)
  • Revision ID: richard.lee@canonical.com-20101015151758-wcvmfxrexsongf9d
Merge

Show diffs side-by-side

added added

removed removed

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