sisimai 4.25.16-java → 5.0.2-java

Sign up to get free protection for your applications and to get access to all the features.
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)