tmail 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/LICENSE +21 -0
  2. data/README +157 -0
  3. data/bat/changelog +19 -0
  4. data/bat/clobber/package +10 -0
  5. data/bat/compile +42 -0
  6. data/bat/config.yaml +8 -0
  7. data/bat/prepare +8 -0
  8. data/bat/publish +51 -0
  9. data/bat/rdoc +42 -0
  10. data/bat/release +12 -0
  11. data/bat/setup +1616 -0
  12. data/bat/stats +138 -0
  13. data/bat/tag +25 -0
  14. data/bat/test +25 -0
  15. data/ext/tmail/Makefile +25 -0
  16. data/ext/tmail/base64/MANIFEST +4 -0
  17. data/ext/tmail/base64/base64.c +264 -0
  18. data/ext/tmail/base64/depend +1 -0
  19. data/ext/tmail/base64/extconf.rb +38 -0
  20. data/ext/tmail/scanner_c/MANIFEST +4 -0
  21. data/ext/tmail/scanner_c/depend +1 -0
  22. data/ext/tmail/scanner_c/extconf.rb +38 -0
  23. data/ext/tmail/scanner_c/scanner_c.c +582 -0
  24. data/lib/tmail.rb +4 -0
  25. data/lib/tmail/Makefile +19 -0
  26. data/lib/tmail/address.rb +245 -0
  27. data/lib/tmail/attachments.rb +47 -0
  28. data/lib/tmail/base64.rb +75 -0
  29. data/lib/tmail/compat.rb +39 -0
  30. data/lib/tmail/config.rb +71 -0
  31. data/lib/tmail/core_extensions.rb +67 -0
  32. data/lib/tmail/encode.rb +524 -0
  33. data/lib/tmail/header.rb +931 -0
  34. data/lib/tmail/index.rb +8 -0
  35. data/lib/tmail/interface.rb +540 -0
  36. data/lib/tmail/loader.rb +1 -0
  37. data/lib/tmail/mail.rb +507 -0
  38. data/lib/tmail/mailbox.rb +435 -0
  39. data/lib/tmail/mbox.rb +1 -0
  40. data/lib/tmail/net.rb +282 -0
  41. data/lib/tmail/obsolete.rb +137 -0
  42. data/lib/tmail/parser.rb +1475 -0
  43. data/lib/tmail/parser.y +381 -0
  44. data/lib/tmail/port.rb +379 -0
  45. data/lib/tmail/quoting.rb +142 -0
  46. data/lib/tmail/require_arch.rb +56 -0
  47. data/lib/tmail/scanner.rb +44 -0
  48. data/lib/tmail/scanner_r.rb +263 -0
  49. data/lib/tmail/stringio.rb +279 -0
  50. data/lib/tmail/tmail.rb +1 -0
  51. data/lib/tmail/utils.rb +281 -0
  52. data/lib/tmail/version.rb +38 -0
  53. data/meta/icli.yaml +16 -0
  54. data/meta/tmail-1.1.1.roll +24 -0
  55. data/sample/data/multipart +23 -0
  56. data/sample/data/normal +29 -0
  57. data/sample/data/sendtest +5 -0
  58. data/sample/data/simple +14 -0
  59. data/sample/data/test +27 -0
  60. data/sample/extract-attachements.rb +33 -0
  61. data/sample/from-check.rb +26 -0
  62. data/sample/multipart.rb +26 -0
  63. data/sample/parse-bench.rb +68 -0
  64. data/sample/parse-test.rb +19 -0
  65. data/sample/sendmail.rb +94 -0
  66. data/test/extctrl.rb +6 -0
  67. data/test/fixtures/raw_base64_decoded_string +0 -0
  68. data/test/fixtures/raw_base64_email +83 -0
  69. data/test/fixtures/raw_base64_encoded_string +1 -0
  70. data/test/fixtures/raw_email +14 -0
  71. data/test/fixtures/raw_email10 +20 -0
  72. data/test/fixtures/raw_email11 +34 -0
  73. data/test/fixtures/raw_email12 +32 -0
  74. data/test/fixtures/raw_email13 +29 -0
  75. data/test/fixtures/raw_email2 +114 -0
  76. data/test/fixtures/raw_email3 +70 -0
  77. data/test/fixtures/raw_email4 +59 -0
  78. data/test/fixtures/raw_email5 +19 -0
  79. data/test/fixtures/raw_email6 +20 -0
  80. data/test/fixtures/raw_email7 +66 -0
  81. data/test/fixtures/raw_email8 +47 -0
  82. data/test/fixtures/raw_email9 +28 -0
  83. data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
  84. data/test/fixtures/raw_email_simple +11 -0
  85. data/test/fixtures/raw_email_with_illegal_boundary +58 -0
  86. data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
  87. data/test/fixtures/raw_email_with_nested_attachment +100 -0
  88. data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
  89. data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
  90. data/test/kcode.rb +14 -0
  91. data/test/test_address.rb +1128 -0
  92. data/test/test_attachments.rb +35 -0
  93. data/test/test_base64.rb +63 -0
  94. data/test/test_encode.rb +77 -0
  95. data/test/test_header.rb +885 -0
  96. data/test/test_helper.rb +2 -0
  97. data/test/test_mail.rb +623 -0
  98. data/test/test_mbox.rb +126 -0
  99. data/test/test_port.rb +430 -0
  100. data/test/test_scanner.rb +209 -0
  101. data/test/test_utils.rb +37 -0
  102. metadata +205 -0
@@ -0,0 +1,142 @@
1
+ =begin rdoc
2
+
3
+ = Quoting methods
4
+
5
+ =end
6
+ module TMail
7
+ class Mail
8
+ def subject(to_charset = 'utf-8')
9
+ Unquoter.unquote_and_convert_to(quoted_subject, to_charset)
10
+ end
11
+
12
+ def unquoted_body(to_charset = 'utf-8')
13
+ from_charset = sub_header("content-type", "charset")
14
+ case (content_transfer_encoding || "7bit").downcase
15
+ when "quoted-printable"
16
+ # the default charset is set to iso-8859-1 instead of 'us-ascii'.
17
+ # This is needed as many mailer do not set the charset but send in ISO. This is only used if no charset is set.
18
+ if !from_charset.blank? && from_charset.downcase == 'us-ascii'
19
+ from_charset = 'iso-8859-1'
20
+ end
21
+
22
+ Unquoter.unquote_quoted_printable_and_convert_to(quoted_body,
23
+ to_charset, from_charset, true)
24
+ when "base64"
25
+ Unquoter.unquote_base64_and_convert_to(quoted_body, to_charset,
26
+ from_charset)
27
+ when "7bit", "8bit"
28
+ Unquoter.convert_to(quoted_body, to_charset, from_charset)
29
+ when "binary"
30
+ quoted_body
31
+ else
32
+ quoted_body
33
+ end
34
+ end
35
+
36
+ def body(to_charset = 'utf-8', &block)
37
+ attachment_presenter = block || Proc.new { |file_name| "Attachment: #{file_name}\n" }
38
+
39
+ if multipart?
40
+ parts.collect { |part|
41
+ header = part["content-type"]
42
+
43
+ if part.multipart?
44
+ part.body(to_charset, &attachment_presenter)
45
+ elsif header.nil?
46
+ ""
47
+ elsif !attachment?(part)
48
+ part.unquoted_body(to_charset)
49
+ else
50
+ attachment_presenter.call(header["name"] || "(unnamed)")
51
+ end
52
+ }.join
53
+ else
54
+ unquoted_body(to_charset)
55
+ end
56
+ end
57
+ end
58
+
59
+ class Unquoter
60
+ class << self
61
+ def unquote_and_convert_to(text, to_charset, from_charset = "iso-8859-1", preserve_underscores=false)
62
+ return "" if text.nil?
63
+ text.gsub(/(.*?)(?:(?:=\?(.*?)\?(.)\?(.*?)\?=)|$)/) do
64
+ before = $1
65
+ from_charset = $2
66
+ quoting_method = $3
67
+ text = $4
68
+
69
+ before = convert_to(before, to_charset, from_charset) if before.length > 0
70
+ before + case quoting_method
71
+ when "q", "Q" then
72
+ unquote_quoted_printable_and_convert_to(text, to_charset, from_charset, preserve_underscores)
73
+ when "b", "B" then
74
+ unquote_base64_and_convert_to(text, to_charset, from_charset)
75
+ when nil then
76
+ # will be nil at the end of the string, due to the nature of
77
+ # the regex used.
78
+ ""
79
+ else
80
+ raise "unknown quoting method #{quoting_method.inspect}"
81
+ end
82
+ end
83
+ end
84
+
85
+ def unquote_quoted_printable_and_convert_to(text, to, from, preserve_underscores=false)
86
+ text = text.gsub(/_/, " ") unless preserve_underscores
87
+ text = text.gsub(/\r\n|\r/, "\n") # normalize newlines
88
+ convert_to(text.unpack("M*").first, to, from)
89
+ end
90
+
91
+ def unquote_base64_and_convert_to(text, to, from)
92
+ convert_to(Base64.decode(text), to, from)
93
+ end
94
+
95
+ begin
96
+ require 'iconv'
97
+ def convert_to(text, to, from)
98
+ return text unless to && from
99
+ text ? Iconv.iconv(to, from, text).first : ""
100
+ rescue Iconv::IllegalSequence, Iconv::InvalidEncoding, Errno::EINVAL
101
+ # the 'from' parameter specifies a charset other than what the text
102
+ # actually is...not much we can do in this case but just return the
103
+ # unconverted text.
104
+ #
105
+ # Ditto if either parameter represents an unknown charset, like
106
+ # X-UNKNOWN.
107
+ text
108
+ end
109
+ rescue LoadError
110
+ # Not providing quoting support
111
+ def convert_to(text, to, from)
112
+ warn "Action Mailer: iconv not loaded; ignoring conversion from #{from} to #{to} (#{__FILE__}:#{__LINE__})"
113
+ text
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ if __FILE__ == $0
121
+ require 'test/unit'
122
+
123
+ class TC_Unquoter < Test::Unit::TestCase
124
+ def test_unquote_quoted_printable
125
+ a ="=?ISO-8859-1?Q?[166417]_Bekr=E6ftelse_fra_Rejsefeber?="
126
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
127
+ assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
128
+ end
129
+
130
+ def test_unquote_base64
131
+ a ="=?ISO-8859-1?B?WzE2NjQxN10gQmVrcuZmdGVsc2UgZnJhIFJlanNlZmViZXI=?="
132
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
133
+ assert_equal "[166417] Bekr\303\246ftelse fra Rejsefeber", b
134
+ end
135
+
136
+ def test_unquote_without_charset
137
+ a ="[166417]_Bekr=E6ftelse_fra_Rejsefeber"
138
+ b = TMail::Unquoter.unquote_and_convert_to(a, 'utf-8')
139
+ assert_equal "[166417]_Bekr=E6ftelse_fra_Rejsefeber", b
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,56 @@
1
+ require 'rbconfig'
2
+
3
+ # Attempts to require anative extension.
4
+ # Falls back to pure-ruby version, if it fails.
5
+ #
6
+ # This uses Config::CONFIG['arch'] from rbconfig.
7
+
8
+ def require_arch(fname)
9
+ arch = Config::CONFIG['arch']
10
+ begin
11
+ path = File.join("tmail", arch, fname)
12
+ require path
13
+ rescue LoadError => e
14
+ # try pre-built Windows binaries
15
+ if arch =~ /mswin/
16
+ require File.join("tmail", 'mswin32', fname)
17
+ else
18
+ raise e
19
+ end
20
+ end
21
+ end
22
+
23
+
24
+ # def require_arch(fname)
25
+ # dext = Config::CONFIG['DLEXT']
26
+ # begin
27
+ # if File.extname(fname) == dext
28
+ # path = fname
29
+ # else
30
+ # path = File.join("tmail","#{fname}.#{dext}")
31
+ # end
32
+ # require path
33
+ # rescue LoadError => e
34
+ # begin
35
+ # arch = Config::CONFIG['arch']
36
+ # path = File.join("tmail", arch, "#{fname}.#{dext}")
37
+ # require path
38
+ # rescue LoadError
39
+ # case path
40
+ # when /i686/
41
+ # path.sub!('i686', 'i586')
42
+ # when /i586/
43
+ # path.sub!('i586', 'i486')
44
+ # when /i486/
45
+ # path.sub!('i486', 'i386')
46
+ # else
47
+ # begin
48
+ # require fname + '.rb'
49
+ # rescue LoadError
50
+ # raise e
51
+ # end
52
+ # end
53
+ # retry
54
+ # end
55
+ # end
56
+ # end
@@ -0,0 +1,44 @@
1
+ =begin rdoc
2
+
3
+ = Scanner for TMail
4
+
5
+ =end
6
+ #--
7
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29
+ # with permission of Minero Aoki.
30
+ #++
31
+
32
+ require 'tmail/require_arch'
33
+ require 'tmail/utils'
34
+
35
+ module TMail
36
+ require 'tmail/scanner_r.rb'
37
+ begin
38
+ raise LoadError, 'Turn off Ruby extention by user choice' if ENV['NORUBYEXT']
39
+ require_arch('scanner_c') #require 'tmail/scanner_c.so'
40
+ Scanner = Scanner_C
41
+ rescue LoadError
42
+ Scanner = Scanner_R
43
+ end
44
+ end
@@ -0,0 +1,263 @@
1
+ #
2
+ # scanner_r.rb
3
+ #
4
+ #--
5
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining
8
+ # a copy of this software and associated documentation files (the
9
+ # "Software"), to deal in the Software without restriction, including
10
+ # without limitation the rights to use, copy, modify, merge, publish,
11
+ # distribute, sublicense, and/or sell copies of the Software, and to
12
+ # permit persons to whom the Software is furnished to do so, subject to
13
+ # the following conditions:
14
+ #
15
+ # The above copyright notice and this permission notice shall be
16
+ # included in all copies or substantial portions of the Software.
17
+ #
18
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+ #
26
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
27
+ # with permission of Minero Aoki.
28
+ #++
29
+
30
+ require 'tmail/config'
31
+
32
+
33
+ module TMail
34
+
35
+ class Scanner_R
36
+
37
+ Version = '0.10.7'
38
+ Version.freeze
39
+
40
+ MIME_HEADERS = {
41
+ :CTYPE => true,
42
+ :CENCODING => true,
43
+ :CDISPOSITION => true
44
+ }
45
+
46
+ alnum = 'a-zA-Z0-9'
47
+ atomsyms = %q[ _#!$%&`'*+-{|}~^@/=? ].strip
48
+ tokensyms = %q[ _#!$%&`'*+-{|}~^@. ].strip
49
+
50
+ atomchars = alnum + Regexp.quote(atomsyms)
51
+ tokenchars = alnum + Regexp.quote(tokensyms)
52
+ iso2022str = '\e(?!\(B)..(?:[^\e]+|\e(?!\(B)..)*\e\(B'
53
+
54
+ eucstr = '(?:[\xa1-\xfe][\xa1-\xfe])+'
55
+ sjisstr = '(?:[\x81-\x9f\xe0-\xef][\x40-\x7e\x80-\xfc])+'
56
+ utf8str = '(?:[\xc0-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf][\x80-\xbf])+'
57
+
58
+ quoted_with_iso2022 = /\A(?:[^\\\e"]+|#{iso2022str})+/n
59
+ domlit_with_iso2022 = /\A(?:[^\\\e\]]+|#{iso2022str})+/n
60
+ comment_with_iso2022 = /\A(?:[^\\\e()]+|#{iso2022str})+/n
61
+
62
+ quoted_without_iso2022 = /\A[^\\"]+/n
63
+ domlit_without_iso2022 = /\A[^\\\]]+/n
64
+ comment_without_iso2022 = /\A[^\\()]+/n
65
+
66
+ PATTERN_TABLE = {}
67
+ PATTERN_TABLE['EUC'] =
68
+ [
69
+ /\A(?:[#{atomchars}]+|#{iso2022str}|#{eucstr})+/n,
70
+ /\A(?:[#{tokenchars}]+|#{iso2022str}|#{eucstr})+/n,
71
+ quoted_with_iso2022,
72
+ domlit_with_iso2022,
73
+ comment_with_iso2022
74
+ ]
75
+ PATTERN_TABLE['SJIS'] =
76
+ [
77
+ /\A(?:[#{atomchars}]+|#{iso2022str}|#{sjisstr})+/n,
78
+ /\A(?:[#{tokenchars}]+|#{iso2022str}|#{sjisstr})+/n,
79
+ quoted_with_iso2022,
80
+ domlit_with_iso2022,
81
+ comment_with_iso2022
82
+ ]
83
+ PATTERN_TABLE['UTF8'] =
84
+ [
85
+ /\A(?:[#{atomchars}]+|#{utf8str})+/n,
86
+ /\A(?:[#{tokenchars}]+|#{utf8str})+/n,
87
+ quoted_without_iso2022,
88
+ domlit_without_iso2022,
89
+ comment_without_iso2022
90
+ ]
91
+ PATTERN_TABLE['NONE'] =
92
+ [
93
+ /\A[#{atomchars}]+/n,
94
+ /\A[#{tokenchars}]+/n,
95
+ quoted_without_iso2022,
96
+ domlit_without_iso2022,
97
+ comment_without_iso2022
98
+ ]
99
+
100
+
101
+ def initialize( str, scantype, comments )
102
+ init_scanner str
103
+ @comments = comments || []
104
+ @debug = false
105
+
106
+ # fix scanner mode
107
+ @received = (scantype == :RECEIVED)
108
+ @is_mime_header = MIME_HEADERS[scantype]
109
+
110
+ atom, token, @quoted_re, @domlit_re, @comment_re = PATTERN_TABLE[$KCODE]
111
+ @word_re = (MIME_HEADERS[scantype] ? token : atom)
112
+ end
113
+
114
+ attr_accessor :debug
115
+
116
+ def scan( &block )
117
+ if @debug
118
+ scan_main do |arr|
119
+ s, v = arr
120
+ printf "%7d %-10s %s\n",
121
+ rest_size(),
122
+ s.respond_to?(:id2name) ? s.id2name : s.inspect,
123
+ v.inspect
124
+ yield arr
125
+ end
126
+ else
127
+ scan_main(&block)
128
+ end
129
+ end
130
+
131
+ private
132
+
133
+ RECV_TOKEN = {
134
+ 'from' => :FROM,
135
+ 'by' => :BY,
136
+ 'via' => :VIA,
137
+ 'with' => :WITH,
138
+ 'id' => :ID,
139
+ 'for' => :FOR
140
+ }
141
+
142
+ def scan_main
143
+ until eof?
144
+ if skip(/\A[\n\r\t ]+/n) # LWSP
145
+ break if eof?
146
+ end
147
+
148
+ if s = readstr(@word_re)
149
+ if @is_mime_header
150
+ yield :TOKEN, s
151
+ else
152
+ # atom
153
+ if /\A\d+\z/ === s
154
+ yield :DIGIT, s
155
+ elsif @received
156
+ yield RECV_TOKEN[s.downcase] || :ATOM, s
157
+ else
158
+ yield :ATOM, s
159
+ end
160
+ end
161
+
162
+ elsif skip(/\A"/)
163
+ yield :QUOTED, scan_quoted_word()
164
+
165
+ elsif skip(/\A\[/)
166
+ yield :DOMLIT, scan_domain_literal()
167
+
168
+ elsif skip(/\A\(/)
169
+ @comments.push scan_comment()
170
+
171
+ else
172
+ c = readchar()
173
+ yield c, c
174
+ end
175
+ end
176
+
177
+ yield false, '$'
178
+ end
179
+
180
+ def scan_quoted_word
181
+ scan_qstr(@quoted_re, /\A"/, 'quoted-word')
182
+ end
183
+
184
+ def scan_domain_literal
185
+ '[' + scan_qstr(@domlit_re, /\A\]/, 'domain-literal') + ']'
186
+ end
187
+
188
+ def scan_qstr( pattern, terminal, type )
189
+ result = ''
190
+ until eof?
191
+ if s = readstr(pattern) then result << s
192
+ elsif skip(terminal) then return result
193
+ elsif skip(/\A\\/) then result << readchar()
194
+ else
195
+ raise "TMail FATAL: not match in #{type}"
196
+ end
197
+ end
198
+ scan_error! "found unterminated #{type}"
199
+ end
200
+
201
+ def scan_comment
202
+ result = ''
203
+ nest = 1
204
+ content = @comment_re
205
+
206
+ until eof?
207
+ if s = readstr(content) then result << s
208
+ elsif skip(/\A\)/) then nest -= 1
209
+ return result if nest == 0
210
+ result << ')'
211
+ elsif skip(/\A\(/) then nest += 1
212
+ result << '('
213
+ elsif skip(/\A\\/) then result << readchar()
214
+ else
215
+ raise 'TMail FATAL: not match in comment'
216
+ end
217
+ end
218
+ scan_error! 'found unterminated comment'
219
+ end
220
+
221
+ # string scanner
222
+
223
+ def init_scanner( str )
224
+ @src = str
225
+ end
226
+
227
+ def eof?
228
+ @src.empty?
229
+ end
230
+
231
+ def rest_size
232
+ @src.size
233
+ end
234
+
235
+ def readstr( re )
236
+ if m = re.match(@src)
237
+ @src = m.post_match
238
+ m[0]
239
+ else
240
+ nil
241
+ end
242
+ end
243
+
244
+ def readchar
245
+ readstr(/\A./)
246
+ end
247
+
248
+ def skip( re )
249
+ if m = re.match(@src)
250
+ @src = m.post_match
251
+ true
252
+ else
253
+ false
254
+ end
255
+ end
256
+
257
+ def scan_error!( msg )
258
+ raise SyntaxError, msg
259
+ end
260
+
261
+ end
262
+
263
+ end # module TMail