Froze rails gems
[depot.git] / vendor / rails / actionmailer / lib / action_mailer / vendor / tmail-1.2.3 / tmail / scanner_r.rb
1 # scanner_r.rb
2 #
3 #--
4 # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
5 #
6 # Permission is hereby granted, free of charge, to any person obtaining
7 # a copy of this software and associated documentation files (the
8 # "Software"), to deal in the Software without restriction, including
9 # without limitation the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the Software, and to
11 # permit persons to whom the Software is furnished to do so, subject to
12 # the following conditions:
13 #
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
16 #
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 #
25 # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
26 # with permission of Minero Aoki.
27 #++
28 #:stopdoc:
29 require 'tmail/config'
30
31 module TMail
32
33 class TMailScanner
34
35 Version = '1.2.3'
36 Version.freeze
37
38 MIME_HEADERS = {
39 :CTYPE => true,
40 :CENCODING => true,
41 :CDISPOSITION => true
42 }
43
44 alnum = 'a-zA-Z0-9'
45 atomsyms = %q[ _#!$%&`'*+-{|}~^/=? ].strip
46 tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
47 atomchars = alnum + Regexp.quote(atomsyms)
48 tokenchars = alnum + Regexp.quote(tokensyms)
49 iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
50
51 eucstr = "(?:[\xa1-\xfe][\xa1-\xfe])+"
52 sjisstr = "(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+"
53 utf8str = "(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+"
54
55 quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
56 domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
57 comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
58
59 quoted_without_iso2022 = /\A[^\\"]+/n
60 domlit_without_iso2022 = /\A[^\\\]]+/n
61 comment_without_iso2022 = /\A[^\\()]+/n
62
63 PATTERN_TABLE = {}
64 PATTERN_TABLE['EUC'] =
65 [
66 /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
67 /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
68 quoted_with_iso2022,
69 domlit_with_iso2022,
70 comment_with_iso2022
71 ]
72 PATTERN_TABLE['SJIS'] =
73 [
74 /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
75 /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
76 quoted_with_iso2022,
77 domlit_with_iso2022,
78 comment_with_iso2022
79 ]
80 PATTERN_TABLE['UTF8'] =
81 [
82 /\A(?:[#{atomchars}]+|#{utf8str})+/n,
83 /\A(?:[#{tokenchars}]+|#{utf8str})+/n,
84 quoted_without_iso2022,
85 domlit_without_iso2022,
86 comment_without_iso2022
87 ]
88 PATTERN_TABLE['NONE'] =
89 [
90 /\A[#{atomchars}]+/n,
91 /\A[#{tokenchars}]+/n,
92 quoted_without_iso2022,
93 domlit_without_iso2022,
94 comment_without_iso2022
95 ]
96
97
98 def initialize( str, scantype, comments )
99 init_scanner str
100 @comments = comments || []
101 @debug = false
102
103 # fix scanner mode
104 @received = (scantype == :RECEIVED)
105 @is_mime_header = MIME_HEADERS[scantype]
106
107 atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[TMail.KCODE]
108 @word_re = (MIME_HEADERS[scantype] ? token : atom)
109 end
110
111 attr_accessor :debug
112
113 def scan( &block )
114 if @debug
115 scan_main do |arr|
116 s, v = arr
117 printf "%7d %-10s %s\n",
118 rest_size(),
119 s.respond_to?(:id2name) ? s.id2name : s.inspect,
120 v.inspect
121 yield arr
122 end
123 else
124 scan_main(&block)
125 end
126 end
127
128 private
129
130 RECV_TOKEN = {
131 'from' => :FROM,
132 'by' => :BY,
133 'via' => :VIA,
134 'with' => :WITH,
135 'id' => :ID,
136 'for' => :FOR
137 }
138
139 def scan_main
140 until eof?
141 if skip(/\A[\n\r\t ]+/n) # LWSP
142 break if eof?
143 end
144
145 if s = readstr(@word_re)
146 if @is_mime_header
147 yield [:TOKEN, s]
148 else
149 # atom
150 if /\A\d+\z/ === s
151 yield [:DIGIT, s]
152 elsif @received
153 yield [RECV_TOKEN[s.downcase] || :ATOM, s]
154 else
155 yield [:ATOM, s]
156 end
157 end
158
159 elsif skip(/\A"/)
160 yield [:QUOTED, scan_quoted_word()]
161
162 elsif skip(/\A\[/)
163 yield [:DOMLIT, scan_domain_literal()]
164
165 elsif skip(/\A\(/)
166 @comments.push scan_comment()
167
168 else
169 c = readchar()
170 yield [c, c]
171 end
172 end
173
174 yield [false, '$']
175 end
176
177 def scan_quoted_word
178 scan_qstr(@quoted_re, /\A"/, 'quoted-word')
179 end
180
181 def scan_domain_literal
182 '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
183 end
184
185 def scan_qstr( pattern, terminal, type )
186 result = ''
187 until eof?
188 if s = readstr(pattern) then result << s
189 elsif skip(terminal) then return result
190 elsif skip(/\A\\/) then result << readchar()
191 else
192 raise "TMail FATAL: not match in #{type}"
193 end
194 end
195 scan_error! "found unterminated #{type}"
196 end
197
198 def scan_comment
199 result = ''
200 nest = 1
201 content = @comment_re
202
203 until eof?
204 if s = readstr(content) then result << s
205 elsif skip(/\A\)/) then nest -= 1
206 return result if nest == 0
207 result << ')'
208 elsif skip(/\A\(/) then nest += 1
209 result << '('
210 elsif skip(/\A\\/) then result << readchar()
211 else
212 raise 'TMail FATAL: not match in comment'
213 end
214 end
215 scan_error! 'found unterminated comment'
216 end
217
218 # string scanner
219
220 def init_scanner( str )
221 @src = str
222 end
223
224 def eof?
225 @src.empty?
226 end
227
228 def rest_size
229 @src.size
230 end
231
232 def readstr( re )
233 if m = re.match(@src)
234 @src = m.post_match
235 m[0]
236 else
237 nil
238 end
239 end
240
241 def readchar
242 readstr(/\A./)
243 end
244
245 def skip( re )
246 if m = re.match(@src)
247 @src = m.post_match
248 true
249 else
250 false
251 end
252 end
253
254 def scan_error!( msg )
255 raise SyntaxError, msg
256 end
257
258 end
259
260 end # module TMail
261 #:startdoc: