sisimai 4.25.16-java → 5.0.2-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.
Files changed (180) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/rake-test.yml +55 -0
  3. data/.travis.yml +3 -3
  4. data/ANALYTICAL-PRECISION +2 -2
  5. data/Benchmarks.mk +3 -3
  6. data/CONTRIBUTING +1 -1
  7. data/ChangeLog.md +451 -393
  8. data/Developers.mk +5 -6
  9. data/Gemfile +1 -1
  10. data/Makefile +15 -15
  11. data/README-JA.md +323 -149
  12. data/README.md +319 -149
  13. data/Rakefile +9 -3
  14. data/Repository.mk +2 -3
  15. data/lib/sisimai/address.rb +118 -74
  16. data/lib/sisimai/arf.rb +84 -82
  17. data/lib/sisimai/datetime.rb +5 -52
  18. data/lib/sisimai/{data → fact}/json.rb +7 -9
  19. data/lib/sisimai/fact/yaml.rb +31 -0
  20. data/lib/sisimai/fact.rb +506 -0
  21. data/lib/sisimai/lhost/activehunter.rb +12 -14
  22. data/lib/sisimai/lhost/amavis.rb +11 -14
  23. data/lib/sisimai/lhost/amazonses.rb +37 -42
  24. data/lib/sisimai/lhost/amazonworkmail.rb +15 -19
  25. data/lib/sisimai/lhost/aol.rb +12 -15
  26. data/lib/sisimai/lhost/apachejames.rb +19 -21
  27. data/lib/sisimai/lhost/barracuda.rb +10 -12
  28. data/lib/sisimai/lhost/bigfoot.rb +21 -22
  29. data/lib/sisimai/lhost/biglobe.rb +15 -16
  30. data/lib/sisimai/lhost/courier.rb +20 -20
  31. data/lib/sisimai/lhost/domino.rb +23 -20
  32. data/lib/sisimai/lhost/einsundeins.rb +23 -18
  33. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  34. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  35. data/lib/sisimai/lhost/exim.rb +179 -174
  36. data/lib/sisimai/lhost/ezweb.rb +31 -56
  37. data/lib/sisimai/lhost/facebook.rb +21 -34
  38. data/lib/sisimai/lhost/fml.rb +43 -48
  39. data/lib/sisimai/lhost/gmail.rb +29 -29
  40. data/lib/sisimai/lhost/gmx.rb +18 -17
  41. data/lib/sisimai/lhost/googlegroups.rb +11 -11
  42. data/lib/sisimai/lhost/gsuite.rb +21 -28
  43. data/lib/sisimai/lhost/imailserver.rb +25 -39
  44. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  45. data/lib/sisimai/lhost/kddi.rb +22 -28
  46. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  47. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  48. data/lib/sisimai/lhost/mailru.rb +37 -40
  49. data/lib/sisimai/lhost/mcafee.rb +21 -31
  50. data/lib/sisimai/lhost/messagelabs.rb +17 -21
  51. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  52. data/lib/sisimai/lhost/mfilter.rb +16 -17
  53. data/lib/sisimai/lhost/mxlogic.rb +24 -33
  54. data/lib/sisimai/lhost/notes.rb +17 -17
  55. data/lib/sisimai/lhost/office365.rb +64 -28
  56. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  57. data/lib/sisimai/lhost/outlook.rb +12 -16
  58. data/lib/sisimai/lhost/postfix.rb +179 -130
  59. data/lib/sisimai/lhost/powermta.rb +12 -14
  60. data/lib/sisimai/lhost/qmail.rb +44 -47
  61. data/lib/sisimai/lhost/receivingses.rb +15 -21
  62. data/lib/sisimai/lhost/sendgrid.rb +34 -34
  63. data/lib/sisimai/lhost/sendmail.rb +65 -53
  64. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  65. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  66. data/lib/sisimai/lhost/verizon.rb +35 -39
  67. data/lib/sisimai/lhost/x1.rb +18 -17
  68. data/lib/sisimai/lhost/x2.rb +17 -14
  69. data/lib/sisimai/lhost/x3.rb +19 -19
  70. data/lib/sisimai/lhost/x4.rb +72 -57
  71. data/lib/sisimai/lhost/x5.rb +17 -19
  72. data/lib/sisimai/lhost/x6.rb +41 -17
  73. data/lib/sisimai/lhost/yahoo.rb +17 -16
  74. data/lib/sisimai/lhost/yandex.rb +16 -21
  75. data/lib/sisimai/lhost/zoho.rb +16 -15
  76. data/lib/sisimai/lhost.rb +8 -10
  77. data/lib/sisimai/mail/maildir.rb +1 -3
  78. data/lib/sisimai/mail/mbox.rb +3 -4
  79. data/lib/sisimai/mail/memory.rb +0 -1
  80. data/lib/sisimai/mail/stdin.rb +1 -3
  81. data/lib/sisimai/mail.rb +3 -7
  82. data/lib/sisimai/mda.rb +28 -42
  83. data/lib/sisimai/message.rb +444 -326
  84. data/lib/sisimai/order.rb +5 -5
  85. data/lib/sisimai/reason/authfailure.rb +65 -0
  86. data/lib/sisimai/reason/badreputation.rb +53 -0
  87. data/lib/sisimai/reason/blocked.rb +96 -160
  88. data/lib/sisimai/reason/contenterror.rb +8 -9
  89. data/lib/sisimai/reason/delivered.rb +4 -6
  90. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  91. data/lib/sisimai/reason/expired.rb +7 -8
  92. data/lib/sisimai/reason/feedback.rb +2 -3
  93. data/lib/sisimai/reason/filtered.rb +17 -19
  94. data/lib/sisimai/reason/hasmoved.rb +9 -10
  95. data/lib/sisimai/reason/hostunknown.rb +15 -15
  96. data/lib/sisimai/reason/mailboxfull.rb +11 -12
  97. data/lib/sisimai/reason/mailererror.rb +18 -20
  98. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  99. data/lib/sisimai/reason/networkerror.rb +5 -8
  100. data/lib/sisimai/reason/norelaying.rb +8 -11
  101. data/lib/sisimai/reason/notaccept.rb +13 -14
  102. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  103. data/lib/sisimai/reason/onhold.rb +6 -9
  104. data/lib/sisimai/reason/policyviolation.rb +14 -12
  105. data/lib/sisimai/reason/rejected.rb +26 -24
  106. data/lib/sisimai/reason/requireptr.rb +69 -0
  107. data/lib/sisimai/reason/securityerror.rb +34 -36
  108. data/lib/sisimai/reason/spamdetected.rb +115 -147
  109. data/lib/sisimai/reason/speeding.rb +49 -0
  110. data/lib/sisimai/reason/suspend.rb +12 -11
  111. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  112. data/lib/sisimai/reason/systemerror.rb +7 -9
  113. data/lib/sisimai/reason/systemfull.rb +7 -8
  114. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  115. data/lib/sisimai/reason/undefined.rb +2 -3
  116. data/lib/sisimai/reason/userunknown.rb +129 -146
  117. data/lib/sisimai/reason/vacation.rb +3 -4
  118. data/lib/sisimai/reason/virusdetected.rb +10 -11
  119. data/lib/sisimai/reason.rb +59 -64
  120. data/lib/sisimai/rfc1894.rb +55 -28
  121. data/lib/sisimai/rfc2045.rb +373 -0
  122. data/lib/sisimai/rfc3464.rb +250 -308
  123. data/lib/sisimai/rfc3834.rb +42 -45
  124. data/lib/sisimai/rfc5322.rb +177 -146
  125. data/lib/sisimai/rfc5965.rb +31 -0
  126. data/lib/sisimai/rhost/cox.rb +5 -6
  127. data/lib/sisimai/rhost/franceptt.rb +6 -8
  128. data/lib/sisimai/rhost/godaddy.rb +12 -12
  129. data/lib/sisimai/rhost/google.rb +530 -0
  130. data/lib/sisimai/rhost/iua.rb +9 -10
  131. data/lib/sisimai/rhost/kddi.rb +6 -8
  132. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  133. data/lib/sisimai/rhost/mimecast.rb +51 -42
  134. data/lib/sisimai/rhost/nttdocomo.rb +12 -12
  135. data/lib/sisimai/rhost/spectrum.rb +10 -12
  136. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  137. data/lib/sisimai/rhost.rb +23 -31
  138. data/lib/sisimai/smtp/command.rb +59 -0
  139. data/lib/sisimai/smtp/error.rb +4 -7
  140. data/lib/sisimai/smtp/reply.rb +161 -74
  141. data/lib/sisimai/smtp/status.rb +507 -393
  142. data/lib/sisimai/smtp/transcript.rb +124 -0
  143. data/lib/sisimai/smtp.rb +0 -1
  144. data/lib/sisimai/string.rb +74 -5
  145. data/lib/sisimai/time.rb +1 -2
  146. data/lib/sisimai/version.rb +1 -1
  147. data/lib/sisimai.rb +46 -31
  148. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  149. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  150. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  154. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  155. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  156. data/set-of-emails/maildir/bsd/lhost-sendmail-60.eml +85 -0
  157. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  162. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  163. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  164. data/sisimai-java.gemspec +1 -1
  165. data/sisimai.gemspec +1 -1
  166. metadata +48 -26
  167. data/.rspec +0 -2
  168. data/lib/sisimai/data/yaml.rb +0 -33
  169. data/lib/sisimai/data.rb +0 -411
  170. data/lib/sisimai/mime.rb +0 -456
  171. data/lib/sisimai/rhost/googleapps.rb +0 -261
  172. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  179. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  180. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -1,67 +1,65 @@
1
1
  module Sisimai
2
2
  # Sisimai::RFC3834 - RFC3834 auto reply message detector
3
3
  module RFC3834
4
- # Imported from p5-Sisimail/lib/Sisimai/RFC3834.pm
5
4
  class << self
6
5
  # http://tools.ietf.org/html/rfc3834
7
- MarkingsOf = { :boundary => %r/\A__SISIMAI_PSEUDO_BOUNDARY__\z/ }
8
- AutoReply1 = {
9
- # http://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xhtml
10
- 'auto-submitted' => %r/\Aauto-(?:generated|replied|notified)/,
11
- 'x-apple-action' => %r/\Avacation\z/,
12
- 'precedence' => %r/\Aauto_reply\z/,
13
- 'subject' => %r/\A(?>
14
- auto:
15
- |auto[ ]response:
16
- |automatic[ ]reply:
17
- |out[ ]of[ ](?:the[ ])*office:
18
- )
19
- /x,
6
+ MarkingsOf = { :boundary => '__SISIMAI_PSEUDO_BOUNDARY__' }
7
+ LowerLabel = %w[from to subject auto-submitted precedence x-apple-action].freeze
8
+ DoNotParse = {
9
+ 'from' => ['root@', 'postmaster@', 'mailer-daemon@'],
10
+ 'to' => ['root@'],
11
+ 'subject' => [
12
+ 'security information for', # sudo(1)
13
+ 'mail failure -', # Exim
14
+ ],
20
15
  }.freeze
21
- Excludings = {
22
- 'subject' => %r/(?:
23
- security[ ]information[ ]for # sudo
24
- |mail[ ]failure[ ][-] # Exim
25
- )
26
- /x,
27
- 'from' => %r/(?:root|postmaster|mailer-daemon)[@]/,
28
- 'to' => %r/root[@]/,
16
+ AutoReply0 = {
17
+ # http://www.iana.org/assignments/auto-submitted-keywords/auto-submitted-keywords.xhtml
18
+ 'auto-submitted' => ['auto-generated', 'auto-replied', 'auto-notified'],
19
+ 'precedence' => ['auto_reply'],
20
+ 'subject' => ['auto:', 'auto response:', 'automatic reply:', 'out of office:', 'out of the office:'],
21
+ 'x-apple-action' => ['vacation'],
29
22
  }.freeze
30
23
  SubjectSet = %r{\A(?>
31
- (?:.+?)?Re:
32
- |Auto(?:[ ]Response):
33
- |Automatic[ ]reply:
34
- |Out[ ]of[ ]Office:
24
+ (?:.+?)?re:
25
+ |auto(?:[ ]response):
26
+ |automatic[ ]reply:
27
+ |out[ ]of[ ]office:
35
28
  )
36
29
  [ ]*(.+)\z
37
- }xi.freeze
30
+ }x.freeze
38
31
 
39
32
  # Detect auto reply message as RFC3834
40
33
  # @param [Hash] mhead Message headers of a bounce email
41
34
  # @param [String] mbody Message body of a bounce email
42
35
  # @return [Hash] Bounce data list and message/rfc822 part
43
36
  # @return [Nil] it failed to parse or the arguments are missing
44
- def make(mhead, mbody)
37
+ def inquire(mhead, mbody)
45
38
  leave = 0
46
39
  match = 0
40
+ lower = {}
41
+
42
+ LowerLabel.each do |e|
43
+ # Set lower-cased value of each header related to auto-response
44
+ next unless mhead.has_key?(e)
45
+ lower[e] = mhead[e].downcase
46
+ end
47
47
 
48
48
  # DETECT_EXCLUSION_MESSAGE
49
- Excludings.each_key do |e|
49
+ DoNotParse.each_key do |e|
50
50
  # Exclude message from root@
51
- next unless mhead[e]
52
- next unless mhead[e]
53
- next unless mhead[e].downcase =~ Excludings[e]
51
+ next unless lower[e]
52
+ next unless DoNotParse[e].any? { |a| lower[e].include?(a) }
54
53
  leave = 1
55
54
  break
56
55
  end
57
56
  return nil if leave > 0
58
57
 
59
- # DETECT_AUTO_REPLY_MESSAGE
60
- AutoReply1.each_key do |e|
58
+ # DETECT_AUTO_REPLY_MESSAGE0
59
+ AutoReply0.each_key do |e|
61
60
  # RFC3834 Auto-Submitted and other headers
62
- next unless mhead[e]
63
- next unless mhead[e]
64
- next unless mhead[e].downcase =~ AutoReply1[e]
61
+ next unless lower[e]
62
+ next unless AutoReply0[e].any? { |a| lower[e].include?(a) }
65
63
  match += 1
66
64
  break
67
65
  end
@@ -95,16 +93,15 @@ module Sisimai
95
93
  return nil unless recipients > 0
96
94
 
97
95
  if mhead['content-type']
98
- # Get the boundary string and set regular expression for matching with
99
- # the boundary string.
100
- b0 = Sisimai::MIME.boundary(mhead['content-type'], 0) || ''
101
- MarkingsOf[:boundary] = %r/\A\Q#{b0}\E\z/ unless b0.empty?
96
+ # Get the boundary string and set regular expression for matching with the boundary string.
97
+ q = Sisimai::RFC2045.boundary(mhead['content-type'], 0) || ''
98
+ MarkingsOf[:boundary] = q unless q.empty?
102
99
  end
103
100
 
104
101
  # BODY_PARSER: Get vacation message
105
102
  while e = bodyslices.shift do
106
103
  # Read the first 5 lines except a blank line
107
- countuntil += 1 if e =~ MarkingsOf[:boundary]
104
+ countuntil += 1 if e.include?(MarkingsOf[:boundary])
108
105
 
109
106
  if e.empty?
110
107
  # Check a blank line
@@ -113,8 +110,8 @@ module Sisimai
113
110
  next
114
111
  end
115
112
  next unless e.include?(' ')
116
- next if e.start_with?('Content-Type')
117
- next if e.start_with?('Content-Transfer')
113
+ next if e.start_with?('Content-Type')
114
+ next if e.start_with?('Content-Transfer')
118
115
 
119
116
  v['diagnosis'] ||= ''
120
117
  v['diagnosis'] << e + ' '
@@ -127,7 +124,7 @@ module Sisimai
127
124
  v['date'] = mhead['date']
128
125
  v['status'] = ''
129
126
 
130
- if cv = mhead['subject'].match(SubjectSet)
127
+ if cv = lower['subject'].match(SubjectSet)
131
128
  # Get the Subject header from the original message
132
129
  rfc822part = 'Subject: ' << cv[1] + "\n"
133
130
  end
@@ -1,8 +1,9 @@
1
1
  module Sisimai
2
2
  # Sisimai::RFC5322 provide methods for checking email address.
3
3
  module RFC5322
4
- # Imported from p5-Sisimail/lib/Sisimai/RFC5322.pm
5
4
  class << self
5
+ require 'sisimai/string'
6
+ require 'sisimai/address'
6
7
  HeaderTable = {
7
8
  :messageid => %w[message-id],
8
9
  :subject => %w[subject],
@@ -12,46 +13,26 @@ module Sisimai
12
13
  :recipient => %w[to delivered-to forward-path envelope-to x-envelope-to resent-to apparently-to],
13
14
  }.freeze
14
15
 
15
- build_regular_expressions = lambda do
16
- # See http://www.ietf.org/rfc/rfc5322.txt
17
- # or http://www.ex-parrot.com/pdw/Mail-RFC822-Address.html ...
18
- # addr-spec = local-part "@" domain
19
- # local-part = dot-atom / quoted-string / obs-local-part
20
- # domain = dot-atom / domain-literal / obs-domain
21
- # domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
22
- # dcontent = dtext / quoted-pair
23
- # dtext = NO-WS-CTL / ; Non white space controls
24
- # %d33-90 / ; The rest of the US-ASCII
25
- # %d94-126 ; characters not including "[",
26
- # ; "]", or "\"
27
- re = { rfc5322: nil, ignored: nil, domain: nil }
28
- atom = %r([a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+)o
29
- quoted_string = %r/"(?:\\[^\r\n]|[^\\"])*"/o
30
- domain_literal = %r/\[(?:\\[\x01-\x09\x0B-\x0c\x0e-\x7f]|[\x21-\x5a\x5e-\x7e])*\]/o
31
- dot_atom = %r/#{atom}(?:[.]#{atom})*/o
32
- local_part = %r/(?:#{dot_atom}|#{quoted_string})/o
33
- domain = %r/(?:#{dot_atom}|#{domain_literal})/o
34
-
35
- re[:rfc5322] = %r/\A#{local_part}[@]#{domain}\z/o
36
- re[:ignored] = %r/\A#{local_part}[.]*[@]#{domain}\z/o
37
- re[:domain] = %r/\A#{domain}\z/o
38
-
39
- return re
40
- end
41
-
42
16
  build_flatten_rfc822header_list = lambda do
43
- # Convert HEADER: structured hash table to flatten hash table for being
44
- # called from Sisimai::Lhost::*
17
+ # Convert HEADER: structured hash table to flatten hash table for being called from Sisimai::Lhost::*
45
18
  fv = {}
46
19
  HeaderTable.each_value do |e|
47
20
  e.each { |ee| fv[ee] = true }
48
21
  end
49
22
  return fv
50
23
  end
51
-
52
- Re = build_regular_expressions.call
53
24
  HeaderIndex = build_flatten_rfc822header_list.call
54
25
 
26
+ def FIELDINDEX
27
+ return %w[
28
+ Resent-Date From Sender Reply-To To Message-ID Subject Return-Path Received Date X-Mailer
29
+ Content-Type Content-Transfer-Encoding Content-Description Content-Disposition
30
+ ]
31
+ # The following fields are not referred in Sisimai
32
+ # Resent-From Resent-Sender Resent-Cc Cc Bcc Resent-Bcc In-Reply-To References
33
+ # Comments Keywords
34
+ end
35
+
55
36
  # Grouped RFC822 headers
56
37
  # @param [Symbol] group RFC822 Header group name
57
38
  # @return [Array,Hash] RFC822 Header list
@@ -67,140 +48,190 @@ module Sisimai
67
48
  return { 'to' => true, 'from' => true, 'subject' => true, 'message-id' => true }
68
49
  end
69
50
 
70
- # Check that the argument is an email address or not
71
- # @param [String] email Email address string
72
- # @return [True,False] true: is an email address
73
- # false: is not an email address
74
- def is_emailaddress(email)
75
- return false unless email.is_a?(::String)
76
- return false if email =~ %r/(?:[\x00-\x1f]|\x1f)/
77
- return false if email.size > 254
78
- return true if email =~ Re[:ignored]
79
- return false
80
- end
81
-
82
- # Check that the argument is mailer-daemon or not
83
- # @param [String] email Email address
84
- # @return [True,False] true: mailer-daemon
85
- # false: Not mailer-daemon
86
- def is_mailerdaemon(email)
87
- return false unless email.is_a?(::String)
88
- regex = %r/(?:
89
- (?:mailer-daemon|postmaster)[@]
90
- |[<(](?:mailer-daemon|postmaster)[)>]
91
- |\A(?:mailer-daemon|postmaster)\z
92
- |[ ]?mailer-daemon[ ]
93
- )
94
- /x.freeze
95
- return true if email.downcase =~ regex
96
- return false
97
- end
98
-
99
51
  # Convert Received headers to a structured data
100
- # @param [String] argvs Received header
52
+ # @param [String] argv1 Received header
101
53
  # @return [Array] Received header as a structured data
102
- def received(argvs)
103
- return [] unless argvs.is_a?(::String)
104
-
105
- hosts = []
106
- value = { 'from' => '', 'by' => '' }
107
-
108
- # Received: (qmail 10000 invoked by uid 999); 24 Apr 2013 00:00:00 +0900
109
- return [] if argvs =~ /qmail[ ]+.+invoked[ ]+/
110
-
111
- if cr = argvs.match(/\Afrom[ ]+(.+)[ ]+by[ ]+([^ ]+)/)
112
- # Received: from localhost (localhost)
113
- # by nijo.example.jp (V8/cf) id s1QB5ma0018057;
114
- # Wed, 26 Feb 2014 06:05:48 -0500
115
- value['from'] = cr[1]
116
- value['by'] = cr[2]
117
-
118
- elsif cr = argvs.match(/\bby[ ]+([^ ]+)(.+)/)
119
- # Received: by 10.70.22.98 with SMTP id c2mr1838265pdf.3; Fri, 18 Jul 2014
120
- # 00:31:02 -0700 (PDT)
121
- value['from'] = cr[1] + cr[2]
122
- value['by'] = cr[1]
54
+ def received(argv1)
55
+ return [] unless argv1.is_a?(::String)
56
+ return [] if argv1.include?(' invoked by uid')
57
+ return [] if argv1.include?(' invoked from network')
58
+
59
+ # - https://datatracker.ietf.org/doc/html/rfc5322
60
+ # received = "Received:" *received-token ";" date-time CRLF
61
+ # received-token = word / angle-addr / addr-spec / domain
62
+ #
63
+ # - Appendix A.4. Message with Trace Fields
64
+ # Received:
65
+ # from x.y.test
66
+ # by example.net
67
+ # via TCP
68
+ # with ESMTP
69
+ # id ABC12345
70
+ # for <mary@example.net>; 21 Nov 1997 10:05:43 -0600
71
+ recvd = argv1.split(' ')
72
+ label = %w[from by via with id for]
73
+ token = {}
74
+ other = []
75
+ alter = []
76
+ right = false
77
+ range = recvd.size
78
+ index = -1
79
+
80
+ recvd.each do |e|
81
+ # Look up each label defined in "label" from Received header
82
+ index += 1
83
+ break unless index < range; f = e.downcase
84
+ next unless label.any? { |a| f == a }
85
+ token[f] = recvd[index + 1] || next
86
+ token[f] = token[f].downcase.delete('();')
87
+
88
+ next unless f == 'from'
89
+ break unless index + 2 < range
90
+ next unless recvd[index + 2].start_with?('(')
91
+
92
+ # Get and keep a hostname in the comment as follows:
93
+ # from mx1.example.com (c213502.kyoto.example.ne.jp [192.0.2.135]) by mx.example.jp (V8/cf)
94
+ # [
95
+ # "from", # index + 0
96
+ # "mx1.example.com", # index + 1
97
+ # "(c213502.kyoto.example.ne.jp", # index + 2
98
+ # "[192.0.2.135])", # index + 3
99
+ # "by",
100
+ # "mx.example.jp",
101
+ # "(V8/cf)",
102
+ # ...
103
+ # ]
104
+ # The 2nd element after the current element is NOT a continuation of the current element
105
+ # such as "(c213502.kyoto.example.ne.jp)"
106
+ other << recvd[index + 2].delete('();')
107
+
108
+ # The 2nd element after the current element is a continuation of the current element.
109
+ # such as "(c213502.kyoto.example.ne.jp", "[192.0.2.135])"
110
+ break unless index + 3 < range
111
+ other << recvd[index + 3].delete('();')
123
112
  end
124
113
 
125
- if value['from'].include?(' ')
126
- # Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
127
- # (authenticated bits=0)
128
- # by nijo.example.jp (V8/cf) with ESMTP id s1QB5ka0018055;
129
- # Wed, 26 Feb 2014 06:05:47 -0500
130
- received = value['from'].split(' ')
131
- namelist = []
132
- addrlist = []
133
- hostname = ''
134
- hostaddr = ''
135
-
136
- while e = received.shift do
137
- # Received: from [10.22.22.222] (smtp-gateway.kyoto.ocn.ne.jp [192.0.2.222])
138
- if e =~ /\A[\[(]\d+[.]\d+[.]\d+[.]\d+[)\]]\z/
139
- # [192.0.2.1] or (192.0.2.1)
140
- e = e.delete('[]()')
141
- addrlist << e
142
- else
143
- # hostname
144
- e = e.delete('()')
145
- namelist << e
146
- end
147
- end
114
+ other.each do |e|
115
+ # Check alternatives in "other", and then delete uninformative values.
116
+ next if e.nil?
117
+ next if e.size < 4
118
+ next if e == 'unknown'
119
+ next if e == 'localhost'
120
+ next if e == '[127.0.0.1]'
121
+ next if e == '[IPv6:::1]'
122
+ next unless e.include?('.')
123
+ next if e.include?('=')
124
+ alter << e
125
+ end
148
126
 
149
- while e = namelist.shift do
150
- # 1. Hostname takes priority over all other IP addresses
151
- next unless e.include?('.')
152
- hostname = e
153
- break
154
- end
127
+ %w[from by].each do |e|
128
+ # Remove square brackets from the IP address such as "[192.0.2.25]"
129
+ next if token[e].nil?
130
+ next if token[e].empty?
131
+ next unless token[e].start_with?('[')
132
+ token[e] = Sisimai::String.ipv4(token[e]).shift || ''
133
+ end
134
+ token['from'] ||= ''
135
+
136
+ while true do
137
+ # Prefer hostnames over IP addresses, except for localhost.localdomain and similar.
138
+ break if token['from'] == 'localhost'
139
+ break if token['from'] == 'localhost.localdomain'
140
+ break unless token['from'].include?('.') # A hostname without a domain name
141
+ break unless Sisimai::String.ipv4(token['from']).empty?
142
+
143
+ # No need to rewrite token['from']
144
+ right = true
145
+ break
146
+ end
155
147
 
156
- if hostname.empty?
157
- # 2. Use IP address as a remote host name
158
- addrlist.each do |e|
159
- # Skip if the address is a private address
160
- next if e.start_with?('10.', '127.', '192.168.')
161
- next if e =~ /\A172[.](?:1[6-9]|2[0-9]|3[0-1])[.]/
162
- hostaddr = e
163
- break
164
- end
148
+ while true do
149
+ # Try to rewrite uninformative hostnames and IP addresses in token['from']
150
+ break if right # There is no need to rewrite
151
+ break if alter.empty? # There is no alternative to rewriting
152
+ break if alter[0].include?(token['from'])
153
+
154
+ if token['from'].start_with?('localhost')
155
+ # localhost or localhost.localdomain
156
+ token['from'] = alter[0]
157
+ elsif token['from'].index('.')
158
+ # A hostname without a domain name such as "mail", "mx", or "mbox"
159
+ token['from'] = alter[0] if alter[0].include?('.')
160
+ else
161
+ # An IPv4 address
162
+ token['from'] = alter[0]
165
163
  end
166
-
167
- value['from'] = hostname || hostaddr || addrlist[-1]
164
+ break
168
165
  end
169
-
170
- %w[from by].each do |e|
171
- # Copy entries into hosts
172
- next if value[e].empty?
173
- value[e] = value[e].delete('[]();?')
174
- hosts << value[e]
166
+ token.delete('from') if token['from'].nil?
167
+ token.delete('by') if token['by'].nil?
168
+ token['for'] = Sisimai::Address.s3s4(token['for']) if token.has_key?('for')
169
+
170
+ token.keys.each do |e|
171
+ # Delete an invalid value
172
+ token[e] = '' if token[e].include?(' ')
173
+ token[e].delete!('[]') # Remove "[]" from the IP address
175
174
  end
176
- return hosts
175
+
176
+ return [
177
+ token['from'] || '',
178
+ token['by'] || '',
179
+ token['via'] || '',
180
+ token['with'] || '',
181
+ token['id'] || '',
182
+ token['for'] || '',
183
+ ]
177
184
  end
178
185
 
179
- # Split given entire message body into error message lines and the original
180
- # message part only include email headers
181
- # @param [String] mbody Entire message body
182
- # @param [Regexp] regex Regular expression of the message/rfc822 or the
183
- # beginning of the original message part
186
+ # Split given entire message body into error message lines and the original message part only
187
+ # include email headers
188
+ # @param [String] email Entire message body
189
+ # @param [Array] cutby List of strings which is a boundary of the original message part
190
+ # @param [Bool] keeps Flag for keeping strings after "\n\n"
184
191
  # @return [Array] [Error message lines, The original message]
185
- # @since v4.25.5
186
- def fillet(mbody = '', regex)
187
- return nil if mbody.empty?
188
- return nil unless regex
192
+ # @since v5.0.0
193
+ def part(email = '', cutby = [], keeps = false)
194
+ return nil if email.empty?
195
+ return nil if cutby.empty?
196
+
197
+ boundaryor = '' # A boundary string divides the error message part and the original message part
198
+ positionor = nil # A Position of the boundary string
199
+ formerpart = '' # The error message part
200
+ latterpart = '' # The original message part
201
+
202
+ cutby.each do |e|
203
+ # Find a boundary string(2nd argument) from the 1st argument
204
+ positionor = email.index(e); next unless positionor
205
+ boundaryor = e
206
+ break
207
+ end
189
208
 
190
- v = mbody.split(regex, 2)
191
- v[1] ||= ''
209
+ if positionor
210
+ # There is the boundary string in the message body
211
+ formerpart = email[0, positionor]
212
+ latterpart = email[positionor + boundaryor.size + 1, email.size - positionor]
213
+ else
214
+ # Substitute the entire message to the former part when the boundary string is not included
215
+ # the "email"
216
+ formerpart = email
217
+ latterpart = ''
218
+ end
192
219
 
193
- unless v[1].empty?
220
+ if latterpart.size > 0
194
221
  # Remove blank lines, the message body of the original message, and append "\n" at the end
195
222
  # of the original message headers
196
223
  # 1. Remove leading blank lines
197
224
  # 2. Remove text after the first blank line: \n\n
198
225
  # 3. Append "\n" at the end of test block when the last character is not "\n"
199
- v[1].sub!(/\A[\r\n\s]+/, '')
200
- v[1] = v[1][0, v[1].index("\n\n")] if v[1].include?("\n\n")
201
- v[1] << "\n" unless v[1].end_with?("\n")
226
+ latterpart.sub!(/\A[\r\n\s]+/, '')
227
+ if keeps == false
228
+ # Remove text after the first blank line: \n\n when "keeps" is false
229
+ latterpart = latterpart[0, latterpart.index("\n\n")] if latterpart.include?("\n\n")
230
+ end
231
+ latterpart << "\n" unless latterpart.end_with?("\n")
202
232
  end
203
- return v
233
+
234
+ return [formerpart, latterpart]
204
235
  end
205
236
 
206
237
  end
@@ -0,0 +1,31 @@
1
+ module Sisimai
2
+ # Sisimai::RFC5965 - A class for An Extensible Format for Email Feedback Reports
3
+ module RFC5965
4
+ class << self
5
+ # https://datatracker.ietf.org/doc/html/rfc5965
6
+ def FIELDINDEX
7
+ return [
8
+ # Required Fields
9
+ # The following report header fields MUST appear exactly once:
10
+ 'Feedback-Type', 'User-Agent', 'Version',
11
+
12
+ # Optional Fields Appearing Once
13
+ # The following header fields are optional and MUST NOT appear more than once:
14
+ # - "Reporting-MTA" is defined in Sisimai::RFC1894->FIELDINDEX()
15
+ 'Original-Envelope-Id', 'Original-Mail-From', 'Arrival-Date', 'Source-IP', 'Incidents',
16
+
17
+ # Optional Fields Appearing Multiple Times
18
+ # The following set of header fields are optional and may appear any number of times as
19
+ # appropriate:
20
+ 'Authentication-Results', 'Original-Rcpt-To', 'Reported-Domain', 'Reported-URI',
21
+
22
+ # The historic field "Received-Date" SHOULD also be accepted and interpreted identically
23
+ # to "Arrival-Date". However, if both are present, the report is malformed and SHOULD be
24
+ # treated as described in Section 4.
25
+ 'Received-Date',
26
+ ]
27
+ end
28
+ end
29
+ end
30
+ end
31
+
@@ -1,11 +1,10 @@
1
1
  module Sisimai
2
2
  module Rhost
3
- # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Data
4
- # object as an argument of get() method when the value of "destination" of
5
- # the object is "charter.net". This class is called only Sisimai::Data class.
3
+ # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument
4
+ # of get() method when the value of "destination" of the object is "charter.net". This class is
5
+ # called only Sisimai::Fact class.
6
6
  module Cox
7
7
  class << self
8
- # Imported from p5-Sisimail/lib/Sisimai/Rhost/Cox.pm
9
8
  ErrorCodes = {
10
9
  # https://www.cox.com/residential/support/email-error-codes.html
11
10
  'CXBL' => 'blocked', # The sending IP address has been blocked by Cox due to exhibiting spam-like behavior.
@@ -79,11 +78,11 @@ module Sisimai
79
78
  }.freeze
80
79
 
81
80
  # Detect bounce reason from https://cox.com/
82
- # @param [Sisimai::Data] argvs Parsed email object
81
+ # @param [Sisimai::Fact] argvs Parsed email object
83
82
  # @return [String, Nil] The bounce reason at Cox
84
83
  # @since v4.25.8
85
84
  def get(argvs)
86
- statusmesg = argvs.diagnosticcode
85
+ statusmesg = argvs['diagnosticcode']
87
86
  codenumber = 0
88
87
 
89
88
  if cv = statusmesg.match(/AUP#([0-9A-Z]+)/)
@@ -1,12 +1,10 @@
1
1
  module Sisimai
2
2
  module Rhost
3
- # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Data
4
- # object as an argument of get() method when the value of "rhost" of the object
5
- # is "*.laposte.net" or "*.orange.fr".
6
- # This class is called only Sisimai::Data class.
3
+ # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument
4
+ # of get() method when the value of "rhost" of the object is "*.laposte.net" or "*.orange.fr".
5
+ # This class is called only Sisimai::Fact class.
7
6
  module FrancePTT
8
7
  class << self
9
- # Imported from p5-Sisimail/lib/Sisimai/Rhost/FrancePTT.pm
10
8
  ErrorCodes = {
11
9
  # 550 5.7.1 Service unavailable; client [X.X.X.X] blocked using Spamhaus
12
10
  # Les emails envoyes vers la messagerie Laposte.net ont ete bloques par nos services.
@@ -133,12 +131,12 @@ module Sisimai
133
131
  }.freeze
134
132
 
135
133
  # Detect bounce reason from Oranage or La Poste
136
- # @param [Sisimai::Data] argvs Parsed email object
134
+ # @param [Sisimai::Fact] argvs Parsed email object
137
135
  # @return [String] The bounce reason for Orange or La Poste
138
136
  def get(argvs)
139
- return argvs.reason unless argvs.reason.empty?
137
+ return argvs['reason'] unless argvs['reason'].empty?
140
138
 
141
- statusmesg = argvs.diagnosticcode
139
+ statusmesg = argvs['diagnosticcode']
142
140
  reasontext = ''
143
141
 
144
142
  if cv = statusmesg.match(/\b(LPN|LPNAAA|OFR|OUK)(_[0-9]{3}|[0-9]{3}[-_][0-9]{3})\b/i)