sisimai 5.2.1-java → 5.4.0-java
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.
- checksums.yaml +4 -4
- data/.github/workflows/rake-test.yml +1 -1
- data/ChangeLog.md +24 -0
- data/Makefile +3 -2
- data/README-JA.md +4 -4
- data/README.md +8 -8
- data/lib/sisimai/address.rb +45 -56
- data/lib/sisimai/arf.rb +11 -16
- data/lib/sisimai/datetime.rb +16 -50
- data/lib/sisimai/fact/json.rb +5 -5
- data/lib/sisimai/fact/yaml.rb +3 -3
- data/lib/sisimai/fact.rb +21 -12
- data/lib/sisimai/lda.rb +3 -3
- data/lib/sisimai/lhost/activehunter.rb +4 -6
- data/lib/sisimai/lhost/amazonses.rb +5 -6
- data/lib/sisimai/lhost/apachejames.rb +7 -9
- data/lib/sisimai/lhost/biglobe.rb +3 -5
- data/lib/sisimai/lhost/courier.rb +4 -6
- data/lib/sisimai/lhost/domino.rb +4 -5
- data/lib/sisimai/lhost/dragonfly.rb +3 -5
- data/lib/sisimai/lhost/einsundeins.rb +6 -8
- data/lib/sisimai/lhost/exchange2003.rb +10 -12
- data/lib/sisimai/lhost/exchange2007.rb +4 -5
- data/lib/sisimai/lhost/exim.rb +6 -8
- data/lib/sisimai/lhost/ezweb.rb +10 -12
- data/lib/sisimai/lhost/fml.rb +2 -3
- data/lib/sisimai/lhost/gmail.rb +4 -6
- data/lib/sisimai/lhost/gmx.rb +6 -8
- data/lib/sisimai/lhost/googlegroups.rb +1 -2
- data/lib/sisimai/lhost/googleworkspace.rb +3 -4
- data/lib/sisimai/lhost/imailserver.rb +6 -7
- data/lib/sisimai/lhost/interscanmss.rb +1 -2
- data/lib/sisimai/lhost/kddi.rb +5 -8
- data/lib/sisimai/lhost/mailfoundry.rb +4 -7
- data/lib/sisimai/lhost/mailmarshalsmtp.rb +4 -6
- data/lib/sisimai/lhost/messagingserver.rb +5 -7
- data/lib/sisimai/lhost/mfilter.rb +4 -6
- data/lib/sisimai/lhost/notes.rb +7 -9
- data/lib/sisimai/lhost/opensmtpd.rb +2 -4
- data/lib/sisimai/lhost/postfix.rb +8 -11
- data/lib/sisimai/lhost/qmail.rb +5 -8
- data/lib/sisimai/lhost/sendmail.rb +7 -10
- data/lib/sisimai/lhost/v5sendmail.rb +15 -17
- data/lib/sisimai/lhost/verizon.rb +9 -14
- data/lib/sisimai/lhost/x1.rb +4 -6
- data/lib/sisimai/lhost/x2.rb +5 -7
- data/lib/sisimai/lhost/x3.rb +3 -4
- data/lib/sisimai/lhost/x6.rb +4 -6
- data/lib/sisimai/lhost/zoho.rb +6 -8
- data/lib/sisimai/lhost.rb +1 -1
- data/lib/sisimai/mail/mbox.rb +1 -1
- data/lib/sisimai/mail/memory.rb +1 -1
- data/lib/sisimai/mail.rb +8 -8
- data/lib/sisimai/message.rb +11 -13
- data/lib/sisimai/order.rb +12 -11
- data/lib/sisimai/reason/authfailure.rb +10 -10
- data/lib/sisimai/reason/badreputation.rb +4 -6
- data/lib/sisimai/reason/blocked.rb +6 -8
- data/lib/sisimai/reason/contenterror.rb +5 -6
- data/lib/sisimai/reason/delivered.rb +2 -2
- data/lib/sisimai/reason/exceedlimit.rb +7 -8
- data/lib/sisimai/reason/expired.rb +6 -7
- data/lib/sisimai/reason/failedstarttls.rb +5 -7
- data/lib/sisimai/reason/feedback.rb +2 -2
- data/lib/sisimai/reason/filtered.rb +7 -10
- data/lib/sisimai/reason/hasmoved.rb +4 -5
- data/lib/sisimai/reason/hostunknown.rb +6 -7
- data/lib/sisimai/reason/mailboxfull.rb +7 -8
- data/lib/sisimai/reason/mailererror.rb +5 -8
- data/lib/sisimai/reason/mesgtoobig.rb +5 -6
- data/lib/sisimai/reason/networkerror.rb +5 -8
- data/lib/sisimai/reason/norelaying.rb +4 -5
- data/lib/sisimai/reason/notaccept.rb +5 -8
- data/lib/sisimai/reason/notcompliantrfc.rb +5 -6
- data/lib/sisimai/reason/onhold.rb +6 -9
- data/lib/sisimai/reason/policyviolation.rb +6 -9
- data/lib/sisimai/reason/rejected.rb +5 -6
- data/lib/sisimai/reason/requireptr.rb +6 -7
- data/lib/sisimai/reason/securityerror.rb +6 -9
- data/lib/sisimai/reason/spamdetected.rb +8 -9
- data/lib/sisimai/reason/speeding.rb +6 -7
- data/lib/sisimai/reason/suppressed.rb +3 -7
- data/lib/sisimai/reason/suspend.rb +5 -7
- data/lib/sisimai/reason/syntaxerror.rb +3 -5
- data/lib/sisimai/reason/systemerror.rb +6 -9
- data/lib/sisimai/reason/systemfull.rb +5 -8
- data/lib/sisimai/reason/toomanyconn.rb +5 -6
- data/lib/sisimai/reason/undefined.rb +2 -2
- data/lib/sisimai/reason/userunknown.rb +8 -9
- data/lib/sisimai/reason/vacation.rb +4 -5
- data/lib/sisimai/reason/virusdetected.rb +4 -5
- data/lib/sisimai/reason.rb +13 -13
- data/lib/sisimai/rfc1123.rb +4 -8
- data/lib/sisimai/rfc1894.rb +5 -6
- data/lib/sisimai/rfc2045.rb +27 -31
- data/lib/sisimai/rfc3464/thirdparty.rb +1 -1
- data/lib/sisimai/rfc3464.rb +7 -9
- data/lib/sisimai/rfc3834.rb +5 -9
- data/lib/sisimai/rfc5322.rb +8 -26
- data/lib/sisimai/rfc791.rb +6 -4
- data/lib/sisimai/rhost/google.rb +8 -0
- data/lib/sisimai/rhost/microsoft.rb +17 -5
- data/lib/sisimai/rhost.rb +2 -2
- data/lib/sisimai/smtp/command.rb +1 -1
- data/lib/sisimai/smtp/failure.rb +5 -12
- data/lib/sisimai/smtp/reply.rb +33 -12
- data/lib/sisimai/smtp/status.rb +21 -22
- data/lib/sisimai/smtp/transcript.rb +1 -10
- data/lib/sisimai/string.rb +20 -30
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +11 -11
- data/set-of-emails/maildir/bsd/rhost-microsoft-06.eml +45 -0
- metadata +8 -10
@@ -145,24 +145,23 @@ module Sisimai
|
|
145
145
|
|
146
146
|
# Try to match that the given text and regular expressions
|
147
147
|
# @param [String] argv1 String to be matched with regular expressions
|
148
|
-
# @return [
|
149
|
-
# true: Matched
|
148
|
+
# @return [Boolean] false: Did not match, true: Matched
|
150
149
|
def match(argv1)
|
151
|
-
return
|
152
|
-
return true
|
153
|
-
return true
|
150
|
+
return false unless argv1
|
151
|
+
return true if Index.any? { |a| argv1.include?(a) }
|
152
|
+
return true if Pairs.any? { |a| Sisimai::String.aligned(argv1, a) }
|
154
153
|
return false
|
155
154
|
end
|
156
155
|
|
157
156
|
# Whether the address is "userunknown" or not
|
158
157
|
# @param [Sisimai::Fact] argvs Object to be detected the reason
|
159
|
-
# @return [
|
158
|
+
# @return [Boolean] true: is unknown user
|
160
159
|
# false: is not unknown user.
|
161
160
|
# @see http://www.ietf.org/rfc/rfc2822.txt
|
162
161
|
def true(argvs)
|
163
162
|
return true if argvs['reason'] == 'userunknown'
|
164
163
|
|
165
|
-
tempreason = Sisimai::SMTP::Status.name(argvs['deliverystatus'])
|
164
|
+
tempreason = Sisimai::SMTP::Status.name(argvs['deliverystatus'])
|
166
165
|
return false if tempreason == 'suspend'
|
167
166
|
|
168
167
|
issuedcode = argvs['diagnosticcode'].downcase
|
@@ -174,13 +173,13 @@ module Sisimai
|
|
174
173
|
matchother = false
|
175
174
|
PreMatches.each do |e|
|
176
175
|
# Check the value of "Diagnostic-Code" with other error patterns.
|
177
|
-
p =
|
176
|
+
p = "Sisimai::Reason::#{e}"
|
178
177
|
r = nil
|
179
178
|
begin
|
180
179
|
require ModulePath[p]
|
181
180
|
r = Module.const_get(p)
|
182
181
|
rescue
|
183
|
-
warn
|
182
|
+
warn "***warning: Failed to load #{p}"
|
184
183
|
next
|
185
184
|
end
|
186
185
|
|
@@ -16,15 +16,14 @@ module Sisimai
|
|
16
16
|
|
17
17
|
# Try to match that the given text and regular expressions
|
18
18
|
# @param [String] argv1 String to be matched with regular expressions
|
19
|
-
# @return [
|
20
|
-
# true: Matched
|
19
|
+
# @return [Boolean] false: Did not match, true: Matched
|
21
20
|
def match(argv1)
|
22
|
-
return
|
23
|
-
return true
|
21
|
+
return false unless argv1
|
22
|
+
return true if Index.any? { |a| argv1.include?(a) }
|
24
23
|
return false
|
25
24
|
end
|
26
25
|
|
27
|
-
def true(*); return
|
26
|
+
def true(*); return false; end
|
28
27
|
end
|
29
28
|
end
|
30
29
|
end
|
@@ -28,18 +28,17 @@ module Sisimai
|
|
28
28
|
|
29
29
|
# Try to match that the given text and regular expressions
|
30
30
|
# @param [String] argv1 String to be matched with regular expressions
|
31
|
-
# @return [
|
32
|
-
# true: Matched
|
31
|
+
# @return [Boolean] false: Did not match, true: Matched
|
33
32
|
# @since 4.22.0
|
34
33
|
def match(argv1)
|
35
|
-
return
|
36
|
-
return true
|
34
|
+
return false unless argv1
|
35
|
+
return true if Index.any? { |a| argv1.include?(a) }
|
37
36
|
return false
|
38
37
|
end
|
39
38
|
|
40
39
|
# The bounce reason is "virusdetected" or not
|
41
40
|
# @param [Sisimai::Fact] argvs Object to be detected the reason
|
42
|
-
# @return [
|
41
|
+
# @return [Boolean] true: virus detected
|
43
42
|
# false: virus was not detected
|
44
43
|
# @since 4.22.0
|
45
44
|
# @see http://www.ietf.org/rfc/rfc2822.txt
|
data/lib/sisimai/reason.rb
CHANGED
@@ -30,7 +30,7 @@ module Sisimai
|
|
30
30
|
def path
|
31
31
|
index = Sisimai::Reason.index
|
32
32
|
table = {}
|
33
|
-
index.each { |e| table[
|
33
|
+
index.each { |e| table["Sisimai::Reason::#{e}"] = "sisimai/reason/#{e.downcase}" }
|
34
34
|
return table
|
35
35
|
end
|
36
36
|
|
@@ -65,10 +65,10 @@ module Sisimai
|
|
65
65
|
|
66
66
|
# Detect the bounce reason
|
67
67
|
# @param [Hash] argvs Decoded email object
|
68
|
-
# @return [String
|
68
|
+
# @return [String] Bounce reason or an empty string if the argument is missing or not Hash
|
69
69
|
# @see anotherone
|
70
70
|
def find(argvs)
|
71
|
-
return
|
71
|
+
return "" unless argvs
|
72
72
|
unless GetRetried[argvs['reason']]
|
73
73
|
# Return reason text already decided except reason match with the regular expression of
|
74
74
|
# retry() method.
|
@@ -85,13 +85,13 @@ module Sisimai
|
|
85
85
|
ClassOrder[0].each do |e|
|
86
86
|
# Check the value of Diagnostic-Code: and the value of Status:, it is a deliverystats,
|
87
87
|
# with true() method in each Sisimai::Reason::* class.
|
88
|
-
p =
|
88
|
+
p = "Sisimai::Reason::#{e}"
|
89
89
|
r = nil
|
90
90
|
begin
|
91
91
|
require ModulePath[p]
|
92
92
|
r = Module.const_get(p)
|
93
93
|
rescue
|
94
|
-
warn
|
94
|
+
warn " ***warning: Failed to load #{p}"
|
95
95
|
next
|
96
96
|
end
|
97
97
|
next unless r.true(argvs)
|
@@ -132,7 +132,7 @@ module Sisimai
|
|
132
132
|
codeformat = argvs['diagnostictype'] || ''
|
133
133
|
actiontext = argvs['action'] || ''
|
134
134
|
statuscode = argvs['deliverystatus'] || ''
|
135
|
-
reasontext = Sisimai::SMTP::Status.name(statuscode)
|
135
|
+
reasontext = Sisimai::SMTP::Status.name(statuscode)
|
136
136
|
trytomatch = reasontext.empty? ? true : false
|
137
137
|
trytomatch ||= true if GetRetried[reasontext] || codeformat != 'SMTP'
|
138
138
|
|
@@ -140,13 +140,13 @@ module Sisimai
|
|
140
140
|
# Could not decide the reason by the value of Status:
|
141
141
|
ClassOrder[1].each do |e|
|
142
142
|
# Trying to match with other patterns in Sisimai::Reason::* classes
|
143
|
-
p =
|
143
|
+
p = "Sisimai::Reason::#{e}"
|
144
144
|
r = nil
|
145
145
|
begin
|
146
146
|
require ModulePath[p]
|
147
147
|
r = Module.const_get(p)
|
148
148
|
rescue
|
149
|
-
warn
|
149
|
+
warn " ***warning: Failed to load #{p}"
|
150
150
|
next
|
151
151
|
end
|
152
152
|
|
@@ -195,7 +195,7 @@ module Sisimai
|
|
195
195
|
# @param [String] argv1 Error message
|
196
196
|
# @return [String] Bounce reason
|
197
197
|
def match(argv1)
|
198
|
-
return
|
198
|
+
return "" unless argv1
|
199
199
|
|
200
200
|
reasontext = ''
|
201
201
|
issuedcode = argv1.downcase
|
@@ -204,13 +204,13 @@ module Sisimai
|
|
204
204
|
ClassOrder[2].each do |e|
|
205
205
|
# Check the value of Diagnostic-Code: and the value of Status:, it is a deliverystats, with
|
206
206
|
# true() method in each Sisimai::Reason::* class.
|
207
|
-
p =
|
207
|
+
p = "Sisimai::Reason::#{e}"
|
208
208
|
r = nil
|
209
209
|
begin
|
210
210
|
require ModulePath[p]
|
211
211
|
r = Module.const_get(p)
|
212
212
|
rescue
|
213
|
-
warn
|
213
|
+
warn " ***warning: Failed to load #{p}"
|
214
214
|
next
|
215
215
|
end
|
216
216
|
|
@@ -226,8 +226,8 @@ module Sisimai
|
|
226
226
|
else
|
227
227
|
# Detect the bounce reason from "Status:" code
|
228
228
|
require 'sisimai/smtp/status'
|
229
|
-
|
230
|
-
reasontext =
|
229
|
+
reasontext = Sisimai::SMTP::Status.name(Sisimai::SMTP::Status.find(argv1))
|
230
|
+
reasontext = "undefined" if reasontext.empty?
|
231
231
|
end
|
232
232
|
return reasontext
|
233
233
|
end
|
data/lib/sisimai/rfc1123.rb
CHANGED
@@ -24,7 +24,7 @@ module Sisimai
|
|
24
24
|
].freeze
|
25
25
|
StartAfter = [
|
26
26
|
"generating server: ", # (Exchange2007) en-US/Generating server: mta4.example.org
|
27
|
-
"serveur de g", # (Exchange2007) fr-FR/Serveur de g
|
27
|
+
"serveur de g", # (Exchange2007) fr-FR/Serveur de g辿?辿?ation
|
28
28
|
"server di generazione", # (Exchange2007) it-CH
|
29
29
|
"genererande server", # (Exchange2007) sv-SE
|
30
30
|
].freeze
|
@@ -38,14 +38,10 @@ module Sisimai
|
|
38
38
|
# @since v5.2.0
|
39
39
|
def is_internethost(argv0 = '')
|
40
40
|
return false unless argv0
|
41
|
-
return false if argv0.size <
|
42
|
-
return false if argv0.size > 255
|
41
|
+
return false if argv0.size < 4 || argv0.size > 255
|
43
42
|
return false if argv0.include?(".") == false
|
44
|
-
return false if argv0.include?("..")
|
45
|
-
return false if argv0.
|
46
|
-
return false if argv0.start_with?(".")
|
47
|
-
return false if argv0.start_with?("-")
|
48
|
-
return false if argv0.end_with?("-")
|
43
|
+
return false if argv0.include?("..") || argv0.include?("--")
|
44
|
+
return false if argv0.start_with?(".", "-") || argv0.end_with?("-")
|
49
45
|
|
50
46
|
hostnameok = true
|
51
47
|
characters = argv0.upcase.split("")
|
data/lib/sisimai/rfc1894.rb
CHANGED
@@ -57,9 +57,9 @@ module Sisimai
|
|
57
57
|
#"text" => ["X-Original-Message-ID", "Final-Log-ID", "Original-Envelope-ID"],
|
58
58
|
}.freeze
|
59
59
|
|
60
|
-
SubtypeSet = {
|
60
|
+
SubtypeSet = {"addr" => "RFC822", "cdoe" => "SMTP", "host" => "DNS"}.freeze
|
61
61
|
ActionList = ["failed", "delayed", "delivered", "relayed", "expanded"].freeze
|
62
|
-
Correction = {
|
62
|
+
Correction = {'deliverable' => 'delivered', 'expired' => 'delayed', 'failure' => 'failed'}
|
63
63
|
FieldGroup = {
|
64
64
|
'original-recipient' => 'addr',
|
65
65
|
'final-recipient' => 'addr',
|
@@ -108,9 +108,8 @@ module Sisimai
|
|
108
108
|
# 2: Matched with per-recipient field
|
109
109
|
# @since v4.25.0
|
110
110
|
def match(argv0 = '')
|
111
|
-
return 0
|
112
|
-
return 0
|
113
|
-
label = Sisimai::RFC1894.label(argv0); return 0 unless label
|
111
|
+
return 0 if argv0.to_s == ""
|
112
|
+
label = Sisimai::RFC1894.label(argv0); return 0 if label.empty?
|
114
113
|
match = 0
|
115
114
|
|
116
115
|
FieldNames[0].each_key do |e|
|
@@ -135,7 +134,7 @@ module Sisimai
|
|
135
134
|
# @return [String] Field name as a label
|
136
135
|
# @since v4.25.15
|
137
136
|
def label(argv0 = '')
|
138
|
-
return
|
137
|
+
return "" if argv0.empty?
|
139
138
|
return argv0.split(':', 2).shift.downcase
|
140
139
|
end
|
141
140
|
|
data/lib/sisimai/rfc2045.rb
CHANGED
@@ -7,10 +7,9 @@ module Sisimai
|
|
7
7
|
|
8
8
|
# Check that the argument is MIME-Encoded string or not
|
9
9
|
# @param [String] argvs String to be checked
|
10
|
-
# @return [
|
11
|
-
# true: MIME encoded string
|
10
|
+
# @return [Boolean] false: Not MIME encoded string, true: MIME encoded string
|
12
11
|
def is_encoded(argv1)
|
13
|
-
return
|
12
|
+
return false unless argv1
|
14
13
|
|
15
14
|
text1 = argv1.delete('"')
|
16
15
|
mime1 = false
|
@@ -60,13 +59,12 @@ module Sisimai
|
|
60
59
|
textblocks[-1].gsub!(/\r\n/, '')
|
61
60
|
textblocks << cv[5]
|
62
61
|
else
|
63
|
-
textblocks << if textblocks.empty? then e else
|
62
|
+
textblocks << if textblocks.empty? then e else " #{e}" end
|
64
63
|
end
|
65
64
|
end
|
66
|
-
|
67
65
|
return '' if textblocks.empty?
|
68
|
-
p = textblocks.join('')
|
69
66
|
|
67
|
+
p = textblocks.join('')
|
70
68
|
if ctxcharset && qbencoding
|
71
69
|
# utf8 => UTF-8
|
72
70
|
ctxcharset = 'UTF-8' if ctxcharset.casecmp('UTF8') == 0
|
@@ -74,32 +72,32 @@ module Sisimai
|
|
74
72
|
unless ctxcharset.casecmp('UTF-8') == 0
|
75
73
|
# Characterset is not UTF-8
|
76
74
|
begin
|
77
|
-
p .encode!('UTF-8', ctxcharset)
|
75
|
+
p = p.encode!('UTF-8', ctxcharset)
|
78
76
|
rescue
|
79
77
|
p = 'FAILED TO CONVERT THE SUBJECT'
|
80
78
|
end
|
81
79
|
end
|
82
80
|
end
|
83
|
-
|
84
|
-
return
|
81
|
+
q = p.dup
|
82
|
+
return q.force_encoding('UTF-8').scrub('?')
|
85
83
|
end
|
86
84
|
|
87
85
|
# Decode MIME BASE64 Encoded string
|
88
86
|
# @param [String] argv0 MIME Encoded text
|
89
87
|
# @return [String] MIME-Decoded text
|
90
88
|
def decodeB(argv0 = nil)
|
91
|
-
return nil
|
89
|
+
return "" if argv0.nil? || argv0.empty?
|
92
90
|
|
93
91
|
p = nil
|
94
92
|
if cv = argv0.match(%r|([+/\=0-9A-Za-z\r\n]+)|) then p = Base64.decode64(cv[1]) end
|
95
|
-
return p ? p.scrub('?') :
|
93
|
+
return p ? p.scrub('?') : ""
|
96
94
|
end
|
97
95
|
|
98
96
|
# Decode MIME Quoted-Printable Encoded string
|
99
97
|
# @param [String] argv0 MIME Encoded text
|
100
98
|
# @return [String] MIME Decoded text
|
101
99
|
def decodeQ(argv0 = nil)
|
102
|
-
return nil
|
100
|
+
return "" if argv0.nil? || argv0.empty?
|
103
101
|
return argv0.unpack('M').first.scrub('?')
|
104
102
|
end
|
105
103
|
|
@@ -109,7 +107,7 @@ module Sisimai
|
|
109
107
|
# @return [String] The value of the parameter
|
110
108
|
# @since v5.0.0
|
111
109
|
def parameter(argv0 = '', argv1 = '')
|
112
|
-
return
|
110
|
+
return "" if argv0.empty?
|
113
111
|
parameterq = argv1.size > 0 ? argv1 + '=' : ''
|
114
112
|
paramindex = argv1.size > 0 ? argv0.index(parameterq) : 0
|
115
113
|
return '' unless paramindex
|
@@ -128,7 +126,7 @@ module Sisimai
|
|
128
126
|
# 1: End of boundary
|
129
127
|
# @return [String] Boundary string
|
130
128
|
def boundary(argv0 = '', start = -1)
|
131
|
-
return
|
129
|
+
return "" if argv0.empty?
|
132
130
|
btext = parameter(argv0, 'boundary')
|
133
131
|
return '' if btext.empty?
|
134
132
|
|
@@ -175,8 +173,8 @@ module Sisimai
|
|
175
173
|
elsif e.index('boundary=') || e.index('charset=')
|
176
174
|
# "Content-Type" field has boundary="..." or charset="utf-8"
|
177
175
|
next if headerpart[0].empty?
|
178
|
-
headerpart[0]
|
179
|
-
headerpart[0].gsub
|
176
|
+
headerpart[0] += " #{e}"
|
177
|
+
headerpart[0] = headerpart[0].gsub(/\s\s+/, ' ')
|
180
178
|
end
|
181
179
|
end
|
182
180
|
return headerpart if heads
|
@@ -197,7 +195,7 @@ module Sisimai
|
|
197
195
|
break if mediatypev.index('/feedback-report')
|
198
196
|
break if ctencoding.empty?
|
199
197
|
|
200
|
-
multipart1[2]
|
198
|
+
multipart1[2] += sprintf("Content-Transfer-Encoding: %s\n", ctencoding)
|
201
199
|
break
|
202
200
|
end
|
203
201
|
|
@@ -206,10 +204,10 @@ module Sisimai
|
|
206
204
|
break if lowerchunk.empty?
|
207
205
|
break if lowerchunk[0, 1] == "\n"
|
208
206
|
|
209
|
-
multipart1[2]
|
207
|
+
multipart1[2] += "\n"
|
210
208
|
break
|
211
209
|
end
|
212
|
-
multipart1[2]
|
210
|
+
multipart1[2] += lowerchunk
|
213
211
|
return multipart1
|
214
212
|
end
|
215
213
|
|
@@ -264,10 +262,8 @@ module Sisimai
|
|
264
262
|
# @param [String] argv1 A pointer to multipart/* message blocks
|
265
263
|
# @return [String] Message body
|
266
264
|
def makeflat(argv0 = '', argv1 = '')
|
267
|
-
return nil
|
268
|
-
return
|
269
|
-
return '' unless argv0.index('multipart/')
|
270
|
-
return '' unless argv0.index('boundary=')
|
265
|
+
return "" if argv0.nil? || argv1.nil?
|
266
|
+
return "" if argv0.index('multipart/') == false || argv0.index('boundary=') == false
|
271
267
|
|
272
268
|
# Some bounce messages include lower-cased "content-type:" field such as the followings:
|
273
269
|
# - content-type: message/delivery-status => Content-Type: message/delivery-status
|
@@ -284,7 +280,7 @@ module Sisimai
|
|
284
280
|
# - text/plain, text/rfc822-headers
|
285
281
|
# - message/delivery-status, message/rfc822, message/partial, message/feedback-report
|
286
282
|
istexthtml = false
|
287
|
-
mediatypev = parameter(e[0])
|
283
|
+
mediatypev = parameter(e[0]); mediatypev = "text/plain" if mediatypev.empty?
|
288
284
|
next if mediatypev.start_with?('text/', 'message/') == false
|
289
285
|
|
290
286
|
if mediatypev == 'text/html'
|
@@ -312,7 +308,7 @@ module Sisimai
|
|
312
308
|
# Content-Transfer-Encoding: 7bit
|
313
309
|
if cv = e[0].downcase.match(iso2022set)
|
314
310
|
# Content-Type: text/plain; charset=ISO-2022-JP
|
315
|
-
bodystring = Sisimai::String.to_utf8(bodyinside, cv[1])
|
311
|
+
bodystring = Sisimai::String.to_utf8(bodyinside, cv[1])
|
316
312
|
else
|
317
313
|
# No "charset" parameter in the value of Content-Type: header
|
318
314
|
bodystring = bodyinside
|
@@ -324,7 +320,7 @@ module Sisimai
|
|
324
320
|
|
325
321
|
if istexthtml
|
326
322
|
# Try to delete HTML tags inside of text/html part whenever possible
|
327
|
-
bodystring = Sisimai::String.to_plain(bodystring)
|
323
|
+
bodystring = Sisimai::String.to_plain(bodystring)
|
328
324
|
end
|
329
325
|
next if bodystring.empty?
|
330
326
|
|
@@ -341,16 +337,16 @@ module Sisimai
|
|
341
337
|
bodystring.scrub!('?')
|
342
338
|
else
|
343
339
|
# ISO-8859-1, GB2312, and so on
|
344
|
-
bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset)
|
340
|
+
bodystring = Sisimai::String.to_utf8(bodystring, ctxcharset)
|
345
341
|
end
|
346
|
-
bodystring
|
342
|
+
bodystring += "\n\n"
|
347
343
|
end
|
348
344
|
|
349
345
|
bodystring.gsub!(/\r\n/, "\n") if bodystring.include?("\r\n") # Convert CRLF to LF
|
350
346
|
|
351
347
|
else
|
352
348
|
# There is no Content-Transfer-Encoding header in the part
|
353
|
-
bodystring
|
349
|
+
bodystring += bodyinside
|
354
350
|
end
|
355
351
|
|
356
352
|
if delimiters.any? { |a| mediatypev.include?(a) }
|
@@ -361,8 +357,8 @@ module Sisimai
|
|
361
357
|
end
|
362
358
|
|
363
359
|
# Append "\n" when the last character of $bodystring is not LF
|
364
|
-
bodystring
|
365
|
-
flattenout
|
360
|
+
bodystring += "\n\n" unless bodystring[-2, 2] == "\n\n"
|
361
|
+
flattenout += bodystring
|
366
362
|
end
|
367
363
|
|
368
364
|
return flattenout
|
@@ -39,7 +39,7 @@ module Sisimai
|
|
39
39
|
def xfield(argv1 = "")
|
40
40
|
return [] if argv1.nil? || argv1.empty?
|
41
41
|
party = Sisimai::RFC3464::ThirdParty.returnedby(argv1); return [] if party.empty?
|
42
|
-
return Module.const_get("Sisimai::RFC3464::ThirdParty
|
42
|
+
return Module.const_get("Sisimai::RFC3464::ThirdParty::#{party}").xfield(argv1)
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
data/lib/sisimai/rfc3464.rb
CHANGED
@@ -20,7 +20,7 @@ module Sisimai
|
|
20
20
|
"Content-Type: message/partial",
|
21
21
|
"Content-Disposition: inline", # See lhost-amavis-*.eml, lhost-facebook-*.eml
|
22
22
|
].freeze
|
23
|
-
StartingOf = {
|
23
|
+
StartingOf = {message: ["Content-Type: message/delivery-status"]}.freeze
|
24
24
|
FieldTable = Sisimai::RFC1894.FIELDTABLE
|
25
25
|
|
26
26
|
# Decode a bounce mail which have fields defined in RFC3464
|
@@ -37,7 +37,7 @@ module Sisimai
|
|
37
37
|
end
|
38
38
|
|
39
39
|
permessage = {}
|
40
|
-
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
40
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
|
41
41
|
alternates = Sisimai::Lhost.DELIVERYSTATUS
|
42
42
|
emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
|
43
43
|
readslices = [""]
|
@@ -46,7 +46,6 @@ module Sisimai
|
|
46
46
|
beforemesg = "" # (String) String before StartingOf[:message]
|
47
47
|
goestonext = false # (Bool) Flag: do not append the line into beforemesg
|
48
48
|
isboundary = [Sisimai::RFC2045.boundary(mhead["content-type"], 0)]; isboundary[0] ||= ""
|
49
|
-
v = nil
|
50
49
|
|
51
50
|
while emailparts[0].index('@').nil? do
|
52
51
|
# There is no email address in the first element of emailparts
|
@@ -134,12 +133,11 @@ module Sisimai
|
|
134
133
|
break if e.start_with?("--") # Boundary string
|
135
134
|
break if e.include?("--- The follow") # ----- The following addresses had delivery problems -----
|
136
135
|
break if e.include?("--- Transcript") # ----- Transcript of session follows -----
|
137
|
-
beforemesg
|
136
|
+
beforemesg += "#{e} "; break
|
138
137
|
end
|
139
138
|
next
|
140
139
|
end
|
141
|
-
next if (readcursor & Indicators[:deliverystatus]) == 0
|
142
|
-
next if e.empty?
|
140
|
+
next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty?
|
143
141
|
|
144
142
|
f = Sisimai::RFC1894.match(e)
|
145
143
|
if f > 0
|
@@ -177,7 +175,7 @@ module Sisimai
|
|
177
175
|
# There are other error messages as a comment such as the following:
|
178
176
|
# Status: 5.0.0 (permanent failure)
|
179
177
|
# Status: 4.0.0 (cat.example.net: host name lookup failure)
|
180
|
-
v["diagnosis"]
|
178
|
+
v["diagnosis"] += " #{o[4]} "
|
181
179
|
end
|
182
180
|
next unless FieldTable[o[0]]
|
183
181
|
next if o[3] == "host" && Sisimai::RFC1123.is_internethost(o[2]) == false
|
@@ -208,13 +206,13 @@ module Sisimai
|
|
208
206
|
# In the case of multiple "message/delivery-status" line
|
209
207
|
next if e.start_with?("Content-") # Content-Disposition:, ...
|
210
208
|
next if e.start_with?("--") # Boundary string
|
211
|
-
beforemesg
|
209
|
+
beforemesg += "#{e} "; next
|
212
210
|
end
|
213
211
|
|
214
212
|
# Diagnostic-Code: SMTP; 550-5.7.26 The MAIL FROM domain [email.example.jp]
|
215
213
|
# has an SPF record with a hard fail
|
216
214
|
next unless e.start_with?(" ")
|
217
|
-
v["diagnosis"]
|
215
|
+
v["diagnosis"] += " #{Sisimai::String.sweep(e)}"
|
218
216
|
end
|
219
217
|
end
|
220
218
|
end
|
data/lib/sisimai/rfc3834.rb
CHANGED
@@ -3,7 +3,7 @@ module Sisimai
|
|
3
3
|
module RFC3834
|
4
4
|
class << self
|
5
5
|
# http://tools.ietf.org/html/rfc3834
|
6
|
-
MarkingsOf = {
|
6
|
+
MarkingsOf = {:boundary => '__SISIMAI_PSEUDO_BOUNDARY__'}
|
7
7
|
LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
|
8
8
|
DoNotParse = {
|
9
9
|
'from' => ['root@', 'postmaster@', 'mailer-daemon@'],
|
@@ -66,7 +66,7 @@ module Sisimai
|
|
66
66
|
return nil if match < 1
|
67
67
|
|
68
68
|
require 'sisimai/lhost'
|
69
|
-
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
|
69
|
+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = dscontents[-1]
|
70
70
|
bodyslices = mbody.scrub('?').split("\n")
|
71
71
|
rfc822part = '' # (String) message/rfc822-headers part
|
72
72
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
@@ -74,13 +74,11 @@ module Sisimai
|
|
74
74
|
haveloaded = 0 # (Integer) The number of lines loaded from message body
|
75
75
|
blanklines = 0 # (Integer) Counter for countinuous blank lines
|
76
76
|
countuntil = 1 # (Integer) Maximum value of blank lines in the body part
|
77
|
-
v = dscontents[-1]
|
78
77
|
|
79
78
|
# RECIPIENT_ADDRESS
|
80
79
|
%w[from return-path].each do |e|
|
81
80
|
# Try to get the address of the recipient
|
82
81
|
next unless mhead[e]
|
83
|
-
next unless mhead[e]
|
84
82
|
v['recipient'] = mhead[e]
|
85
83
|
break
|
86
84
|
end
|
@@ -109,12 +107,10 @@ module Sisimai
|
|
109
107
|
break if blanklines > countuntil
|
110
108
|
next
|
111
109
|
end
|
112
|
-
next
|
113
|
-
next if e.start_with?('Content-Type')
|
114
|
-
next if e.start_with?('Content-Transfer')
|
110
|
+
next if !e.include?(' ') || e.start_with?('Content-Type', 'Content-Transfer')
|
115
111
|
|
116
112
|
v['diagnosis'] ||= ''
|
117
|
-
v['diagnosis']
|
113
|
+
v['diagnosis'] += "#{e }"
|
118
114
|
haveloaded += 1
|
119
115
|
break if haveloaded >= maxmsgline
|
120
116
|
end
|
@@ -126,7 +122,7 @@ module Sisimai
|
|
126
122
|
|
127
123
|
if cv = lower['subject'].match(SubjectSet)
|
128
124
|
# Get the Subject header from the original message
|
129
|
-
rfc822part =
|
125
|
+
rfc822part = "Subject: #{cv[1]}\n"
|
130
126
|
end
|
131
127
|
return { 'ds' => dscontents, 'rfc822' => rfc822part }
|
132
128
|
end
|
data/lib/sisimai/rfc5322.rb
CHANGED
@@ -32,19 +32,12 @@ module Sisimai
|
|
32
32
|
return []
|
33
33
|
end
|
34
34
|
|
35
|
-
# Fields that might be long
|
36
|
-
# @return [Hash] Long filed(email header) list
|
37
|
-
def LONGFIELDS
|
38
|
-
return { 'to' => true, 'from' => true, 'subject' => true, 'message-id' => true }
|
39
|
-
end
|
40
|
-
|
41
35
|
# Convert Received headers to a structured data
|
42
36
|
# @param [String] argv1 Received header
|
43
37
|
# @return [Array] Received header as a structured data
|
44
38
|
def received(argv1)
|
45
39
|
return [] unless argv1.is_a?(::String)
|
46
|
-
return [] if argv1.include?(' invoked by uid')
|
47
|
-
return [] if argv1.include?(' invoked from network')
|
40
|
+
return [] if argv1.include?(' invoked by uid') || argv1.include?(' invoked from network')
|
48
41
|
|
49
42
|
# - https://datatracker.ietf.org/doc/html/rfc5322
|
50
43
|
# received = "Received:" *received-token ";" date-time CRLF
|
@@ -105,41 +98,30 @@ module Sisimai
|
|
105
98
|
# Check alternatives in "other", and then delete uninformative values.
|
106
99
|
next if e.nil?
|
107
100
|
next if e.size < 4
|
108
|
-
next if e == 'unknown'
|
109
|
-
next if e == '
|
110
|
-
next if e == '[127.0.0.1]'
|
111
|
-
next if e == '[IPv6:::1]'
|
112
|
-
next unless e.include?('.')
|
113
|
-
next if e.include?('=')
|
101
|
+
next if e == 'unknown' || e == 'localhost' || e == '[127.0.0.1]' || e == '[IPv6:::1]'
|
102
|
+
next if e.include?('.') == false || e.include?('=') == true
|
114
103
|
alter << e
|
115
104
|
end
|
116
105
|
|
117
106
|
%w[from by].each do |e|
|
118
107
|
# Remove square brackets from the IP address such as "[192.0.2.25]"
|
119
|
-
next if token[e].
|
120
|
-
next if token[e].empty?
|
121
|
-
next unless token[e].start_with?('[')
|
108
|
+
next if token[e].to_s.empty? || token[e].start_with?('[') == false
|
122
109
|
token[e] = Sisimai::RFC791.find(token[e]).shift || ''
|
123
110
|
end
|
124
111
|
token['from'] ||= ''
|
125
112
|
|
126
113
|
while true do
|
127
114
|
# Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
|
128
|
-
break if token['from'] == 'localhost'
|
129
|
-
break if token['from'] == '
|
130
|
-
break unless token['from'].include?('.') # A hostname without a domain name
|
131
|
-
break unless Sisimai::RFC791.find(token['from']).empty?
|
115
|
+
break if token['from'] == 'localhost' || token['from'] == 'localhost.localdomain'
|
116
|
+
break if token['from'].include?('.') == false || Sisimai::RFC791.find(token['from']).empty? == false
|
132
117
|
|
133
|
-
# No need to rewrite token['from']
|
134
|
-
right = true
|
118
|
+
right = true # No need to rewrite token['from']
|
135
119
|
break
|
136
120
|
end
|
137
121
|
|
138
122
|
while true do
|
139
123
|
# Try to rewrite uninformative hostnames and IP addresses in token['from']
|
140
|
-
break if right
|
141
|
-
break if alter.empty? # There is no alternative to rewriting
|
142
|
-
break if alter[0].include?(token['from'])
|
124
|
+
break if right || alter.empty? || alter[0].include?(token['from'])
|
143
125
|
|
144
126
|
if token['from'].start_with?('localhost')
|
145
127
|
# localhost or localhost.localdomain
|