tmail 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +21 -0
- data/README +157 -0
- data/bat/changelog +19 -0
- data/bat/clobber/package +10 -0
- data/bat/compile +42 -0
- data/bat/config.yaml +8 -0
- data/bat/prepare +8 -0
- data/bat/publish +51 -0
- data/bat/rdoc +42 -0
- data/bat/release +12 -0
- data/bat/setup +1616 -0
- data/bat/stats +138 -0
- data/bat/tag +25 -0
- data/bat/test +25 -0
- data/ext/tmail/Makefile +25 -0
- data/ext/tmail/base64/MANIFEST +4 -0
- data/ext/tmail/base64/base64.c +264 -0
- data/ext/tmail/base64/depend +1 -0
- data/ext/tmail/base64/extconf.rb +38 -0
- data/ext/tmail/scanner_c/MANIFEST +4 -0
- data/ext/tmail/scanner_c/depend +1 -0
- data/ext/tmail/scanner_c/extconf.rb +38 -0
- data/ext/tmail/scanner_c/scanner_c.c +582 -0
- data/lib/tmail.rb +4 -0
- data/lib/tmail/Makefile +19 -0
- data/lib/tmail/address.rb +245 -0
- data/lib/tmail/attachments.rb +47 -0
- data/lib/tmail/base64.rb +75 -0
- data/lib/tmail/compat.rb +39 -0
- data/lib/tmail/config.rb +71 -0
- data/lib/tmail/core_extensions.rb +67 -0
- data/lib/tmail/encode.rb +524 -0
- data/lib/tmail/header.rb +931 -0
- data/lib/tmail/index.rb +8 -0
- data/lib/tmail/interface.rb +540 -0
- data/lib/tmail/loader.rb +1 -0
- data/lib/tmail/mail.rb +507 -0
- data/lib/tmail/mailbox.rb +435 -0
- data/lib/tmail/mbox.rb +1 -0
- data/lib/tmail/net.rb +282 -0
- data/lib/tmail/obsolete.rb +137 -0
- data/lib/tmail/parser.rb +1475 -0
- data/lib/tmail/parser.y +381 -0
- data/lib/tmail/port.rb +379 -0
- data/lib/tmail/quoting.rb +142 -0
- data/lib/tmail/require_arch.rb +56 -0
- data/lib/tmail/scanner.rb +44 -0
- data/lib/tmail/scanner_r.rb +263 -0
- data/lib/tmail/stringio.rb +279 -0
- data/lib/tmail/tmail.rb +1 -0
- data/lib/tmail/utils.rb +281 -0
- data/lib/tmail/version.rb +38 -0
- data/meta/icli.yaml +16 -0
- data/meta/tmail-1.1.1.roll +24 -0
- data/sample/data/multipart +23 -0
- data/sample/data/normal +29 -0
- data/sample/data/sendtest +5 -0
- data/sample/data/simple +14 -0
- data/sample/data/test +27 -0
- data/sample/extract-attachements.rb +33 -0
- data/sample/from-check.rb +26 -0
- data/sample/multipart.rb +26 -0
- data/sample/parse-bench.rb +68 -0
- data/sample/parse-test.rb +19 -0
- data/sample/sendmail.rb +94 -0
- data/test/extctrl.rb +6 -0
- data/test/fixtures/raw_base64_decoded_string +0 -0
- data/test/fixtures/raw_base64_email +83 -0
- data/test/fixtures/raw_base64_encoded_string +1 -0
- data/test/fixtures/raw_email +14 -0
- data/test/fixtures/raw_email10 +20 -0
- data/test/fixtures/raw_email11 +34 -0
- data/test/fixtures/raw_email12 +32 -0
- data/test/fixtures/raw_email13 +29 -0
- data/test/fixtures/raw_email2 +114 -0
- data/test/fixtures/raw_email3 +70 -0
- data/test/fixtures/raw_email4 +59 -0
- data/test/fixtures/raw_email5 +19 -0
- data/test/fixtures/raw_email6 +20 -0
- data/test/fixtures/raw_email7 +66 -0
- data/test/fixtures/raw_email8 +47 -0
- data/test/fixtures/raw_email9 +28 -0
- data/test/fixtures/raw_email_quoted_with_0d0a +14 -0
- data/test/fixtures/raw_email_simple +11 -0
- data/test/fixtures/raw_email_with_illegal_boundary +58 -0
- data/test/fixtures/raw_email_with_multipart_mixed_quoted_boundary +50 -0
- data/test/fixtures/raw_email_with_nested_attachment +100 -0
- data/test/fixtures/raw_email_with_partially_quoted_subject +14 -0
- data/test/fixtures/raw_email_with_quoted_illegal_boundary +58 -0
- data/test/kcode.rb +14 -0
- data/test/test_address.rb +1128 -0
- data/test/test_attachments.rb +35 -0
- data/test/test_base64.rb +63 -0
- data/test/test_encode.rb +77 -0
- data/test/test_header.rb +885 -0
- data/test/test_helper.rb +2 -0
- data/test/test_mail.rb +623 -0
- data/test/test_mbox.rb +126 -0
- data/test/test_port.rb +430 -0
- data/test/test_scanner.rb +209 -0
- data/test/test_utils.rb +37 -0
- 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
|