sisimai 5.4.1-java → 5.5.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.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codecovio.yml +1 -1
  3. data/.github/workflows/rake-test.yml +1 -1
  4. data/ChangeLog.md +35 -0
  5. data/Makefile +2 -4
  6. data/README-JA.md +23 -20
  7. data/README.md +23 -20
  8. data/lib/sisimai/address.rb +92 -44
  9. data/lib/sisimai/arf.rb +7 -8
  10. data/lib/sisimai/datetime.rb +2 -2
  11. data/lib/sisimai/fact/json.rb +1 -2
  12. data/lib/sisimai/fact/yaml.rb +1 -2
  13. data/lib/sisimai/fact.rb +60 -35
  14. data/lib/sisimai/lda.rb +2 -2
  15. data/lib/sisimai/lhost/activehunter.rb +4 -5
  16. data/lib/sisimai/lhost/amazonses.rb +3 -4
  17. data/lib/sisimai/lhost/apachejames.rb +2 -2
  18. data/lib/sisimai/lhost/biglobe.rb +6 -6
  19. data/lib/sisimai/lhost/courier.rb +7 -7
  20. data/lib/sisimai/lhost/domino.rb +6 -6
  21. data/lib/sisimai/lhost/dragonfly.rb +5 -5
  22. data/lib/sisimai/lhost/einsundeins.rb +4 -4
  23. data/lib/sisimai/lhost/exchange2003.rb +7 -7
  24. data/lib/sisimai/lhost/exchange2007.rb +3 -3
  25. data/lib/sisimai/lhost/exim.rb +7 -7
  26. data/lib/sisimai/lhost/ezweb.rb +3 -2
  27. data/lib/sisimai/lhost/fml.rb +9 -9
  28. data/lib/sisimai/lhost/gmail.rb +9 -9
  29. data/lib/sisimai/lhost/gmx.rb +3 -3
  30. data/lib/sisimai/lhost/googlegroups.rb +6 -7
  31. data/lib/sisimai/lhost/googleworkspace.rb +5 -6
  32. data/lib/sisimai/lhost/imailserver.rb +4 -4
  33. data/lib/sisimai/lhost/kddi.rb +4 -4
  34. data/lib/sisimai/lhost/mailfoundry.rb +3 -3
  35. data/lib/sisimai/lhost/{mailmarshalsmtp.rb → mailmarshal.rb} +5 -5
  36. data/lib/sisimai/lhost/messagingserver.rb +8 -8
  37. data/lib/sisimai/lhost/mfilter.rb +8 -4
  38. data/lib/sisimai/lhost/mimecast.rb +105 -0
  39. data/lib/sisimai/lhost/notes.rb +5 -5
  40. data/lib/sisimai/lhost/opensmtpd.rb +5 -5
  41. data/lib/sisimai/lhost/postfix.rb +38 -32
  42. data/lib/sisimai/lhost/qmail.rb +6 -6
  43. data/lib/sisimai/lhost/sendmail.rb +13 -13
  44. data/lib/sisimai/lhost/{interscanmss.rb → trendmicro.rb} +8 -9
  45. data/lib/sisimai/lhost/v5sendmail.rb +7 -7
  46. data/lib/sisimai/lhost/verizon.rb +3 -3
  47. data/lib/sisimai/lhost/x1.rb +7 -4
  48. data/lib/sisimai/lhost/x2.rb +40 -12
  49. data/lib/sisimai/lhost/x3.rb +3 -3
  50. data/lib/sisimai/lhost/x6.rb +2 -2
  51. data/lib/sisimai/lhost/zoho.rb +5 -5
  52. data/lib/sisimai/lhost.rb +18 -17
  53. data/lib/sisimai/mail/maildir.rb +4 -4
  54. data/lib/sisimai/mail/mbox.rb +4 -4
  55. data/lib/sisimai/mail/memory.rb +1 -1
  56. data/lib/sisimai/mail/stdin.rb +2 -2
  57. data/lib/sisimai/message.rb +34 -34
  58. data/lib/sisimai/order.rb +5 -4
  59. data/lib/sisimai/reason/authfailure.rb +1 -1
  60. data/lib/sisimai/reason/badreputation.rb +1 -1
  61. data/lib/sisimai/reason/blocked.rb +2 -1
  62. data/lib/sisimai/reason/contenterror.rb +1 -2
  63. data/lib/sisimai/reason/exceedlimit.rb +1 -1
  64. data/lib/sisimai/reason/expired.rb +1 -1
  65. data/lib/sisimai/reason/failedstarttls.rb +1 -1
  66. data/lib/sisimai/reason/filtered.rb +1 -1
  67. data/lib/sisimai/reason/hasmoved.rb +1 -1
  68. data/lib/sisimai/reason/hostunknown.rb +2 -2
  69. data/lib/sisimai/reason/mailboxfull.rb +1 -1
  70. data/lib/sisimai/reason/mailererror.rb +1 -1
  71. data/lib/sisimai/reason/mesgtoobig.rb +1 -1
  72. data/lib/sisimai/reason/networkerror.rb +1 -1
  73. data/lib/sisimai/reason/norelaying.rb +2 -2
  74. data/lib/sisimai/reason/notaccept.rb +2 -3
  75. data/lib/sisimai/reason/notcompliantrfc.rb +1 -1
  76. data/lib/sisimai/reason/policyviolation.rb +2 -4
  77. data/lib/sisimai/reason/rejected.rb +5 -3
  78. data/lib/sisimai/reason/requireptr.rb +1 -1
  79. data/lib/sisimai/reason/securityerror.rb +1 -1
  80. data/lib/sisimai/reason/spamdetected.rb +1 -1
  81. data/lib/sisimai/reason/speeding.rb +1 -1
  82. data/lib/sisimai/reason/suspend.rb +2 -1
  83. data/lib/sisimai/reason/systemerror.rb +1 -1
  84. data/lib/sisimai/reason/systemfull.rb +1 -1
  85. data/lib/sisimai/reason/toomanyconn.rb +1 -1
  86. data/lib/sisimai/reason/userunknown.rb +4 -3
  87. data/lib/sisimai/reason/vacation.rb +1 -1
  88. data/lib/sisimai/reason/virusdetected.rb +1 -1
  89. data/lib/sisimai/reason.rb +12 -12
  90. data/lib/sisimai/rfc1123.rb +58 -18
  91. data/lib/sisimai/rfc1894.rb +6 -8
  92. data/lib/sisimai/rfc2045.rb +25 -13
  93. data/lib/sisimai/rfc3464/thirdparty.rb +2 -3
  94. data/lib/sisimai/rfc3464.rb +6 -6
  95. data/lib/sisimai/rfc3834.rb +18 -8
  96. data/lib/sisimai/rfc5322.rb +9 -9
  97. data/lib/sisimai/rfc791.rb +2 -2
  98. data/lib/sisimai/rhost/aol.rb +4 -1
  99. data/lib/sisimai/rhost/apple.rb +11 -7
  100. data/lib/sisimai/rhost/cox.rb +9 -5
  101. data/lib/sisimai/rhost/facebook.rb +9 -3
  102. data/lib/sisimai/rhost/franceptt.rb +86 -37
  103. data/lib/sisimai/rhost/godaddy.rb +10 -1
  104. data/lib/sisimai/rhost/google.rb +6 -6
  105. data/lib/sisimai/rhost/gsuite.rb +1 -1
  106. data/lib/sisimai/rhost/kddi.rb +1 -1
  107. data/lib/sisimai/rhost/messagelabs.rb +160 -2
  108. data/lib/sisimai/rhost/microsoft.rb +77 -26
  109. data/lib/sisimai/rhost/mimecast.rb +30 -21
  110. data/lib/sisimai/rhost/nttdocomo.rb +69 -89
  111. data/lib/sisimai/rhost/outlook.rb +1 -1
  112. data/lib/sisimai/rhost/spectrum.rb +1 -1
  113. data/lib/sisimai/rhost/tencent.rb +5 -4
  114. data/lib/sisimai/rhost/yahooinc.rb +2 -2
  115. data/lib/sisimai/rhost/zoho.rb +72 -0
  116. data/lib/sisimai/rhost.rb +4 -3
  117. data/lib/sisimai/smtp/command.rb +2 -2
  118. data/lib/sisimai/smtp/reply.rb +11 -4
  119. data/lib/sisimai/smtp/status.rb +17 -8
  120. data/lib/sisimai/smtp/transcript.rb +3 -3
  121. data/lib/sisimai/string.rb +6 -10
  122. data/lib/sisimai/version.rb +1 -1
  123. data/lib/sisimai.rb +1 -1
  124. data/set-of-emails/maildir/bsd/lhost-exim-56.eml +40 -40
  125. data/set-of-emails/maildir/bsd/lhost-mfilter-05.eml +41 -0
  126. data/set-of-emails/maildir/bsd/lhost-mimecast-01.eml +46 -0
  127. data/set-of-emails/maildir/bsd/lhost-mimecast-02.eml +59 -0
  128. data/set-of-emails/maildir/bsd/lhost-postfix-79.eml +81 -0
  129. data/set-of-emails/maildir/bsd/lhost-postfix-80.eml +84 -0
  130. data/set-of-emails/maildir/bsd/lhost-x1-03.eml +26 -0
  131. data/set-of-emails/maildir/bsd/lhost-x1-04.eml +45 -0
  132. data/set-of-emails/maildir/bsd/lhost-x2-07.eml +30 -0
  133. data/set-of-emails/maildir/bsd/rfc3464-66.eml +170 -0
  134. data/set-of-emails/maildir/bsd/rfc3834-06.eml +59 -0
  135. data/set-of-emails/maildir/bsd/rhost-zoho-01.eml +88 -0
  136. data/set-of-emails/maildir/bsd/rhost-zoho-02.eml +86 -0
  137. data/set-of-emails/maildir/bsd/rhost-zoho-03.eml +87 -0
  138. data/set-of-emails/maildir/bsd/rhost-zoho-04.eml +86 -0
  139. data/set-of-emails/maildir/not/rb-issue-368-bug.eml +39 -0
  140. data/sisimai-java.gemspec +1 -1
  141. data/sisimai.gemspec +1 -1
  142. metadata +26 -9
  143. /data/set-of-emails/maildir/bsd/{lhost-mailmarshalsmtp-02.eml → lhost-mailmarshal-02.eml} +0 -0
  144. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-01.eml → lhost-trendmicro-01.eml} +0 -0
  145. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-02.eml → lhost-trendmicro-02.eml} +0 -0
  146. /data/set-of-emails/maildir/bsd/{lhost-interscanmss-03.eml → lhost-trendmicro-03.eml} +0 -0
@@ -16,106 +16,86 @@ module Sisimai
16
16
  # @param [Sisimai::Fact] argvs Decoded email object
17
17
  # @return [String] The bounce reason for docomo.ne.jp
18
18
  def find(argvs)
19
- return argvs['reason'] unless argvs['reason'].empty?
19
+ return argvs['reason'] if argvs['reason'].empty? == false
20
20
  statuscode = argvs['deliverystatus'] || ''
21
21
  thecommand = argvs['command'] || ''
22
22
  esmtperror = argvs['diagnosticcode'].downcase || ''
23
- reasontext = ''
24
23
 
25
24
  # Check the value of Status: field, an SMTP Reply Code, and the SMTP Command
26
- if statuscode == '5.1.1' || statuscode == '5.0.911'
27
- # ----- Transcript of session follows -----
28
- # ... while talking to mfsmax.docomo.ne.jp.:
29
- # >>> RCPT To:<***@docomo.ne.jp>
30
- # <<< 550 Unknown user ***@docomo.ne.jp
31
- # 550 5.1.1 <***@docomo.ne.jp>... User unknown
32
- # >>> DATA
33
- # <<< 503 Bad sequence of commands
34
- reasontext = 'userunknown'
35
-
36
- elsif statuscode == '5.2.0'
37
- # ----- The following addresses had permanent fatal errors -----
38
- # <***@docomo.ne.jp>
39
- # (reason: 550 Unknown user ***@docomo.ne.jp)
40
- #
41
- # ----- Transcript of session follows -----
42
- # ... while talking to mfsmax.docomo.ne.jp.:
43
- # >>> DATA
44
- # <<< 550 Unknown user ***@docomo.ne.jp
45
- # 554 5.0.0 Service unavailable
46
- # ...
47
- # Final-Recipient: RFC822; ***@docomo.ne.jp
48
- # Action: failed
49
- # Status: 5.2.0
50
- reasontext = 'filtered'
25
+ #
26
+ # ----- Transcript of session follows -----
27
+ # ... while talking to mfsmax.docomo.ne.jp.:
28
+ # >>> RCPT To:<***@docomo.ne.jp>
29
+ # <<< 550 Unknown user ***@docomo.ne.jp
30
+ # 550 5.1.1 <***@docomo.ne.jp>... User unknown
31
+ # >>> DATA
32
+ # <<< 503 Bad sequence of commands
33
+ # ---------------------------------------------------------------------------------------
34
+ # ----- The following addresses had permanent fatal errors -----
35
+ # <***@docomo.ne.jp>
36
+ # (reason: 550 Unknown user ***@docomo.ne.jp)
37
+ #
38
+ # ----- Transcript of session follows -----
39
+ # ... while talking to mfsmax.docomo.ne.jp.:
40
+ # >>> DATA
41
+ # <<< 550 Unknown user ***@docomo.ne.jp
42
+ # 554 5.0.0 Service unavailable
43
+ # ...
44
+ # Final-Recipient: RFC822; ***@docomo.ne.jp
45
+ # Action: failed
46
+ # Status: 5.2.0
47
+ return 'userunknown' if statuscode == '5.1.1' || statuscode == '5.0.911'
48
+ return 'filtered' if statuscode == '5.2.0'
51
49
 
52
- else
50
+ MessagesOf.each_key do |e|
53
51
  # The value of "Diagnostic-Code:" field is not empty
54
- MessagesOf.each_key do |e|
55
- # - The key name is a bounce reason name
56
- # - https://github.com/sisimai/go-sisimai/issues/64
57
- # - After March 12, 2025, if an error message contains "550 Unknown user", the
58
- # bounce reason will be definitively "userunknown". This is because NTT DOCOMO
59
- # no longer rejects emails via SMTP for domain-specific rejection or specified
60
- # reception filters.
61
- next unless MessagesOf[e].any? { |a| esmtperror.include?(a) }
62
- reasontext = e
63
- break
64
- end
52
+ # - The key name is a bounce reason name
53
+ # - https://github.com/sisimai/go-sisimai/issues/64
54
+ # - After March 12, 2025, if an error message contains "550 Unknown user", the
55
+ # bounce reason will be definitively "userunknown". This is because NTT DOCOMO
56
+ # no longer rejects emails via SMTP for domain-specific rejection or specified
57
+ # reception filters.
58
+ return e if MessagesOf[e].any? { |a| esmtperror.include?(a) }
65
59
  end
66
60
 
67
- if reasontext.empty?
68
- # A bounce reason did not decide from a status code, an error message.
69
- if statuscode == '5.0.0'
70
- # Status: 5.0.0
71
- if thecommand == 'RCPT'
72
- # Your message to the following recipients cannot be delivered:
73
- #
74
- # <***@docomo.ne.jp>:
75
- # mfsmax.docomo.ne.jp [203.138.181.112]:
76
- # >>> RCPT TO:<***@docomo.ne.jp>
77
- # <<< 550 Unknown user ***@docomo.ne.jp
78
- # ...
79
- #
80
- # Final-Recipient: rfc822; ***@docomo.ne.jp
81
- # Action: failed
82
- # Status: 5.0.0
83
- # Remote-MTA: dns; mfsmax.docomo.ne.jp [203.138.181.112]
84
- # Diagnostic-Code: smtp; 550 Unknown user ***@docomo.ne.jp
85
- reasontext = 'userunknown'
86
-
87
- elsif thecommand == 'DATA'
88
- # <***@docomo.ne.jp>: host mfsmax.docomo.ne.jp[203.138.181.240] said:
89
- # 550 Unknown user ***@docomo.ne.jp (in reply to end of DATA
90
- # command)
91
- # ...
92
- # Final-Recipient: rfc822; ***@docomo.ne.jp
93
- # Original-Recipient: rfc822;***@docomo.ne.jp
94
- # Action: failed
95
- # Status: 5.0.0
96
- # Remote-MTA: dns; mfsmax.docomo.ne.jp
97
- # Diagnostic-Code: smtp; 550 Unknown user ***@docomo.ne.jp
98
- reasontext = 'rejected'
99
-
100
- else
101
- # Rejected by other SMTP commands: AUTH, MAIL,
102
- # もしもこのブロックを通過するNTTドコモからのエラーメッセージを見つけたら
103
- # https://github.com/sisimai/rb-sisimai/issues からご連絡ねがいます。
104
- #
105
- # If you found a error message from mfsmax.docomo.ne.jp which passes this block,
106
- # please open an issue at https://github.com/sisimai/rb-sisimai/issues .
107
- end
108
- else
109
- # Status: field is neither 5.0.0 nor values defined in code above
110
- # もしもこのブロックを通過するNTTドコモからのエラーメッセージを見つけたら
111
- # https://github.com/sisimai/rb-sisimai/issues からご連絡ねがいます。
112
- #
113
- # If you found a error message from mfsmax.docomo.ne.jp which passes this block,
114
- #
115
- end
61
+ # A bounce reason did not decide from a status code, an error message.
62
+ if statuscode == '5.0.0'
63
+ # Your message to the following recipients cannot be delivered:
64
+ #
65
+ # <***@docomo.ne.jp>:
66
+ # mfsmax.docomo.ne.jp [203.138.181.112]:
67
+ # >>> RCPT TO:<***@docomo.ne.jp>
68
+ # <<< 550 Unknown user ***@docomo.ne.jp
69
+ # ...
70
+ #
71
+ # Final-Recipient: rfc822; ***@docomo.ne.jp
72
+ # Action: failed
73
+ # Status: 5.0.0
74
+ # Remote-MTA: dns; mfsmax.docomo.ne.jp [203.138.181.112]
75
+ # Diagnostic-Code: smtp; 550 Unknown user ***@docomo.ne.jp
76
+ # -------------------------------------------------------------------------------------
77
+ # <***@docomo.ne.jp>: host mfsmax.docomo.ne.jp[203.138.181.240] said:
78
+ # 550 Unknown user ***@docomo.ne.jp (in reply to end of DATA
79
+ # command)
80
+ # ...
81
+ # Final-Recipient: rfc822; ***@docomo.ne.jp
82
+ # Original-Recipient: rfc822;***@docomo.ne.jp
83
+ # Action: failed
84
+ # Status: 5.0.0
85
+ # Remote-MTA: dns; mfsmax.docomo.ne.jp
86
+ # Diagnostic-Code: smtp; 550 Unknown user ***@docomo.ne.jp
87
+ return 'userunknown' if thecommand == 'RCPT'
88
+ return 'rejected' if thecommand == 'DATA'
116
89
  end
117
90
 
118
- return reasontext
91
+ # 1. Rejected by other SMTP commands: AUTH, MAIL,
92
+ # 2. Status: field is neither 5.0.0 nor values defined in code above
93
+ # もしもこのブロックを通過するNTTドコモからのエラーメッセージを見つけたら
94
+ # https://github.com/sisimai/rb-sisimai/issues からご連絡ねがいます。
95
+ #
96
+ # If you found a error message from mfsmax.docomo.ne.jp which passes this block,
97
+ # please open an issue at https://github.com/sisimai/rb-sisimai/issues .
98
+ return ""
119
99
  end
120
100
 
121
101
  end
@@ -21,7 +21,7 @@ module Sisimai
21
21
 
22
22
  MessagesOf.each_key do |e|
23
23
  # Try to match the error message with message patterns defined in $MessagesOf
24
- next unless MessagesOf[e].any? { |a| issuedcode.include?(a) }
24
+ next if MessagesOf[e].none? { |a| issuedcode.include?(a) }
25
25
  reasontext = e
26
26
  break
27
27
  end
@@ -101,7 +101,7 @@ module Sisimai
101
101
  # @return [String, Nil] The bounce reason at Spectrum
102
102
  # @since v4.25.8
103
103
  def find(argvs)
104
- return argvs['reason'] unless argvs['reason'].empty?
104
+ return argvs['reason'] if argvs['reason'].empty? == false
105
105
  issuedcode = argvs['diagnosticcode']
106
106
  reasontext = ''
107
107
  codenumber = if cv = issuedcode.match(/AUP#[-A-Za-z]*(\d{4})/) then cv[1].to_i else 0 end
@@ -22,12 +22,13 @@ module Sisimai
22
22
  'suspected spam', # https://service.mail.qq.com/detail/122/71
23
23
  'mail is rejected by recipients', # https://service.mail.qq.com/detail/122/92
24
24
  ],
25
- 'spandetected' => [
25
+ 'spamdetected' => [
26
26
  'spam is embedded in the email', # https://service.mail.qq.com/detail/122/59
27
27
  'mail content denied', # https://service.mail.qq.com/detail/122/171
28
28
  ],
29
29
  'speeding' => [
30
- 'mailbox unavailable or access denined', # https://service.mail.qq.com/detail/122/166
30
+ 'mailbox unavailable or access denined', # https://service.mail.qq.com/detail/122/166
31
+ "frequency of receiving messages is limited", # https://service.mail.qq.com/detail/122/1011
31
32
  ],
32
33
  'suspend' => [
33
34
  'is a deactivated mailbox', # http://service.mail.qq.com/cgi-bin/help?subtype=1&&id=20022&&no=1000742
@@ -50,13 +51,13 @@ module Sisimai
50
51
  # @param [Sisimai::Fact] argvs Decoded email object
51
52
  # @return [String] The bounce reason at Tencent QQ
52
53
  def find(argvs)
53
- return argvs['reason'] unless argvs['reason'].empty?
54
+ return argvs['reason'] if argvs['reason'].empty? == false
54
55
  issuedcode = argvs['diagnosticcode'].downcase
55
56
  reasontext = ''
56
57
 
57
58
  MessagesOf.each_key do |e|
58
59
  MessagesOf[e].each do |f|
59
- next unless issuedcode.include?(f)
60
+ next if issuedcode.include?(f) == false
60
61
  reasontext = e
61
62
  break
62
63
  end
@@ -88,13 +88,13 @@ module Sisimai
88
88
  # https://www.postmastery.com/yahoo-postmaster/
89
89
  # @since v5.1.0
90
90
  def find(argvs)
91
- return argvs['reason'] unless argvs['reason'].empty?
91
+ return argvs['reason'] if argvs['reason'].empty? == false
92
92
  issuedcode = argvs['diagnosticcode'].downcase
93
93
  reasontext = ''
94
94
 
95
95
  MessagesOf.each_key do |e|
96
96
  MessagesOf[e].each do |f|
97
- next unless issuedcode.include?(f)
97
+ next if issuedcode.include?(f) == false
98
98
  reasontext = e
99
99
  break
100
100
  end
@@ -0,0 +1,72 @@
1
+ module Sisimai
2
+ module Rhost
3
+ # Sisimai::Rhost detects the bounce reason from the content of Sisimai::Fact object as an argument
4
+ # of find() method when the value of "destination" of the object is ".zoho.com" or ".zoho.eu".
5
+ # This class is called only Sisimai::Fact class.
6
+ module Zoho
7
+ class << self
8
+ MessagesOf = {
9
+ 'authfailure' => [
10
+ # - <*******@zoho.com>: host smtpin.zoho.com[204.141.33.23] said: 550 5.7.1 Email
11
+ # rejected per DMARC policy for zoho.com
12
+ "Email rejected per DMARC policy",
13
+ ],
14
+ 'blocked' => [
15
+ # - mx.zoho.com[204.141.33.44]:25, delay=1202, delays=1200/0/0.91/0.30, dsn=4.7.1,
16
+ # status=deferred (host mx.zoho.com[204.141.33.44] said:
17
+ # 451 4.7.1 Greylisted, try again after some time (in reply to RCPT TO command))
18
+ "Greylisted, try again after some time",
19
+ ],
20
+ 'rejected' => [
21
+ # - <*******@zoho.com>: host smtpin.zoho.com[204.141.33.23] said: 554 5.7.1 Email
22
+ # cannot be delivered. Reason: Email flagged as Spam. (in reply to RCPT TO command)
23
+ # - <***@zoho.com>: host mx.zoho.com[136.143.183.44] said: 541 5.4.1 Mail rejected
24
+ # by destination domain (in reply to RCPT TO command)
25
+ "Email cannot be delivered. Reason: Email flagged as Spam",
26
+ "Mail rejected by destination domain",
27
+ ],
28
+ 'policyviolation' => [
29
+ # - <*******@zoho.com>: host smtpin.zoho.com[204.141.33.23] said: 554 5.7.7 Email
30
+ # policy violation detected (in reply to end of DATA command)
31
+ "Email policy violation detected",
32
+ "Mailbox delivery restricted by policy error",
33
+ ],
34
+ 'systemerror' => [
35
+ # - https://github.com/zoho/zohodesk-oas/blob/main/v1.0/EmailFailureAlert.json#L168
36
+ # 452 4.3.1 Temporary System Error
37
+ "Temporary System Error",
38
+ ],
39
+ 'userunknown' => [
40
+ # - <*******@zoho.com>: host smtpin.zoho.com[204.141.33.23] said:
41
+ # 550 5.1.1 User does not exist - <***@zoho.com> (in reply to RCPT TO command)
42
+ # - 552 5.1.1 <****@zoho.com> Mailbox delivery failure policy error
43
+ "User does not exist",
44
+ ],
45
+ 'virusdetected' => [
46
+ # - 552 5.7.1 virus **** detected by Zoho Mail
47
+ " detected by Zoho Mail",
48
+ ],
49
+ }.freeze
50
+
51
+ # Detect bounce reason from Apple iCloud Mail
52
+ # @param [Sisimai::Fact] argvs Decoded email object
53
+ # @return [String] The bounce reason for Apple
54
+ # @see
55
+ # @since v5.5.0
56
+ # - Zoho Mail: https://www.zoho.com/mail/
57
+ # - Reasons an email is marked as Spam: https://www.zoho.com/mail/help/spam-reason.html
58
+ # - https://github.com/zoho/zohodesk-oas/blob/main/v1.0/EmailFailureAlert.json
59
+ # - Zoho SMTP Error Codes | SMTP Field Manual: https://smtpfieldmanual.com/provider/zoho
60
+ def find(argvs)
61
+ return '' if argvs.nil? || argvs['diagnosticcode'].size == 0
62
+ MessagesOf.each_key do |e|
63
+ return e if MessagesOf[e].any? { |a| argvs['diagnosticcode'].include?(a) }
64
+ end
65
+ return ''
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
72
+
data/lib/sisimai/rhost.rb CHANGED
@@ -24,6 +24,7 @@ module Sisimai
24
24
  "Spectrum" => ["charter.net"],
25
25
  "Tencent" => [".qq.com"],
26
26
  "YahooInc" => [".yahoodns.net"],
27
+ "Zoho" => [".zoho.com", ".zoho.eu"],
27
28
  }.freeze
28
29
 
29
30
  # Returns the rhost class name
@@ -44,14 +45,14 @@ module Sisimai
44
45
  # 3. lhost: local MTA hostname
45
46
  RhostClass.each_key do |e|
46
47
  # Try to match the domain part of the recipient address with each value of RhostClass
47
- next unless RhostClass[e].any? { |a| a.end_with?(domainpart) }
48
+ next if RhostClass[e].none? { |a| a.end_with?(domainpart) }
48
49
  rhostclass = e
49
50
  throw :FINDRHOST
50
51
  end
51
52
 
52
53
  RhostClass.each_key do |e|
53
54
  # Try to match the remote host with each value of RhostClass
54
- next unless RhostClass[e].any? { |a| remotehost.end_with?(a) }
55
+ next if RhostClass[e].none? { |a| remotehost.end_with?(a) }
55
56
  rhostclass = e
56
57
  throw :FINDRHOST
57
58
  end
@@ -59,7 +60,7 @@ module Sisimai
59
60
  # Neither the remote host nor the destination did not matched with any value of RhostClass
60
61
  RhostClass.each_key do |e|
61
62
  # Try to match the client host with each value of RhostClass
62
- next unless RhostClass[e].any? { |a| clienthost.end_with?(a) }
63
+ next if RhostClass[e].none? { |a| clienthost.end_with?(a) }
63
64
  rhostclass = e
64
65
  throw :FINDRHOST
65
66
  end
@@ -28,7 +28,7 @@ module Sisimai
28
28
  # @return [String] An SMTP command
29
29
  # @since v5.0.0
30
30
  def find(argv0 = '')
31
- return "" unless Sisimai::SMTP::Command.test(argv0)
31
+ return "" if Sisimai::SMTP::Command.test(argv0) == false
32
32
 
33
33
  issuedcode = " " + argv0.downcase + " "
34
34
  commandmap = {"STAR" => "STARTTLS", "XFOR" => "XFORWARD"}
@@ -36,7 +36,7 @@ module Sisimai
36
36
 
37
37
  Detectable.each do |e|
38
38
  # Find an SMTP command from the given string
39
- p0 = argv0.index(e); next unless p0
39
+ p0 = argv0.index(e); next if p0.nil?
40
40
  if e.include?(" ") == false
41
41
  # For example, "RCPT T" does not appear in an email address or a domain name
42
42
  cx = true; while true do
@@ -113,18 +113,25 @@ module Sisimai
113
113
  Associated = {
114
114
  "422" => ["AUTH", "4.7.12", "securityerror"], # RFC5238
115
115
  "432" => ["AUTH", "4.7.12", "securityerror"], # RFC4954, RFC5321
116
+ "451" => ["", "", "systemerror"], # RFC2465, RFC5321
117
+ "452" => ["", "", "systemfull"], # RFC5321
118
+ "454" => ["AUTH", "4.7.0", "securityerror"], # RFC3027, RFC4954
119
+ "455" => ["", "", "syntaxerror"], # RFC5321
116
120
  "500" => ["", "", "syntaxerror"], # RFC5321
117
121
  "501" => ["", "", "syntaxerror"], # RFC5321
118
122
  "502" => ["", "", "syntaxerror"], # RFC5321
119
123
  "503" => ["", "", "syntaxerror"], # RFC5321
120
124
  "504" => ["", "", "syntaxerror"], # RFC5321
121
125
  "521" => ["CONN", "", "notaccept"], # RFC7504
122
- "523" => ["AUTH", "", "securityerror"], # RFC5248
123
- "524" => ["AUTH", "", "securityerror"], # RFC5248
124
- "525" => ["AUTH", "", "securityerror"], # RFC5248
126
+ "523" => ["AUTH", "5.7.10", "securityerror"], # RFC5248
127
+ "524" => ["AUTH", "5.7.11", "securityerror"], # RFC5248
128
+ "525" => ["AUTH", "5.7.13", "securityerror"], # RFC5248
125
129
  "534" => ["AUTH", "5.7.9", "securityerror"], # RFC4954, RFC5248
126
130
  "535" => ["AUTH", "5.7.8", "securityerror"], # RFC4954, RFC5248
127
131
  "538" => ["AUTH", "5.7.11", "securityerror"], # RFC4954, RFC5248
132
+ "551" => ["", "", "hasmoved"], # RFC5321, RFC5336, RFC6531
133
+ "552" => ["", "", "mailboxfull"], # RFC5321
134
+ "555" => ["", "", "syntaxerror"], # RFC5321
128
135
  "556" => ["RCPT", "", "notaccept"], # RFC7504
129
136
  }.freeze
130
137
 
@@ -153,7 +160,7 @@ module Sisimai
153
160
 
154
161
  if first == 3
155
162
  # 3yz
156
- return false unless reply == 334 || reply == 354
163
+ return false if reply != 334 && reply != 354
157
164
  return true
158
165
  end
159
166
 
@@ -554,7 +554,7 @@ module Sisimai
554
554
  '5.4.0' => 'networkerror', # Other or undefined network or routing status
555
555
  '5.4.3' => 'systemerror', # Directory server failure
556
556
  '5.4.4' => 'hostunknown', # Unable to route
557
- '5.5.2' => 'syntaxerror', # If the server cannot BASE64 decode any client response (AUTH)
557
+ '5.5.2' => 'systemerror', # If the server cannot BASE64 decode any client response (AUTH)
558
558
  '5.5.3' => 'toomanyconn', # Too many recipients
559
559
  '5.5.4' => 'systemerror', # Invalid command arguments
560
560
  '5.5.5' => 'systemerror', # Wrong protocol version
@@ -692,7 +692,7 @@ module Sisimai
692
692
  # @return [String] Reason name or an empty string
693
693
  # @see code
694
694
  def name(argv1 = nil)
695
- return "" unless Sisimai::SMTP::Status.test(argv1.to_s)
695
+ return "" if Sisimai::SMTP::Status.test(argv1.to_s) == false
696
696
  return StandardCode[argv1] || ""
697
697
  end
698
698
 
@@ -706,7 +706,7 @@ module Sisimai
706
706
 
707
707
  token = []
708
708
  argv1.split('.').each { |e| token << e.to_i }
709
- return false unless token.size == 3
709
+ return false if token.size != 3
710
710
  return false if token[0] < 2 || token[0] == 3 || token[0] > 5
711
711
  return false if token[1] < 0 || token[1] > 7
712
712
  return false if token[2] < 0
@@ -755,7 +755,7 @@ module Sisimai
755
755
 
756
756
  lookingfor.sort_by(&:first).each do |e|
757
757
  # Try to find an SMTP Status Code from the given string
758
- indexofees = esmtperror.index(e[1], e[0]); next unless indexofees
758
+ indexofees = esmtperror.index(e[1], e[0]); next if indexofees.nil?
759
759
  characters = [esmtperror[indexofees - 1, 1].ord] # [0] The previous character of the status
760
760
  [2, 3].each do |i|
761
761
  # [1] The value of the "Subject", "5.[7].261"
@@ -788,7 +788,7 @@ module Sisimai
788
788
  next if characters[3] < 48 || characters[3] > 57 # The 1st digit of the detail is not a number
789
789
  readbuffer << characters[3].chr
790
790
 
791
- if readbuffer.index('.0.0') || readbuffer == '4.4.7'
791
+ if Sisimai::SMTP::Status.is_ambiguous(readbuffer) || readbuffer == "4.4.7"
792
792
  # Find another status code except *.0.0, 4.4.7
793
793
  anotherone = readbuffer
794
794
  next
@@ -827,8 +827,8 @@ module Sisimai
827
827
  # @return [String] The preferred value
828
828
  # @since v5.0.0
829
829
  def prefer(argv0 = nil, argv1 = nil, argv2 = nil)
830
- return argv1 unless argv0; return argv1 unless argv0.size > 4
831
- return argv0 unless argv1; return argv0 unless argv1.size > 4
830
+ return argv1 if argv0.nil?; return argv1 if argv0.size < 5
831
+ return argv0 if argv1.nil?; return argv0 if argv1.size < 5
832
832
 
833
833
  statuscode = argv0
834
834
  codeinmesg = argv1
@@ -883,7 +883,7 @@ module Sisimai
883
883
  return statuscode
884
884
  end
885
885
 
886
- # is_explicit() returns 0 when the argument is empty or is an internal code
886
+ # is_explicit() returns false when the argument is empty or is an internal code
887
887
  # @param string argv1 Status code
888
888
  # @return bool false: The delivery status code is not explicit
889
889
  def is_explicit(argv1 = '')
@@ -891,6 +891,15 @@ module Sisimai
891
891
  return false if argv1.size == 7 && argv1.start_with?("5.0.9", "4.0.9")
892
892
  return true
893
893
  end
894
+
895
+ # is_ambiguous() returns true when the argument is not empty and ends with ".0.0".
896
+ # @param string argv1 Status code
897
+ # @return bool false: The delivery status code is not ambiguous
898
+ def is_ambiguous(argv1 = '')
899
+ return true if argv1.nil? || argv1.empty?
900
+ return true if argv1.size == 5 && argv1.end_with?(".0.0")
901
+ return false
902
+ end
894
903
  end
895
904
  end
896
905
  end
@@ -17,8 +17,8 @@ module Sisimai
17
17
  return nil if argv0.size == 0
18
18
 
19
19
  # 1. Replace label strings of SMTP client/server at the each line
20
- argv0.gsub!(/^[ ]+#{argv1}\s+/m, '>>> '); return nil unless argv0.include?('>>> ')
21
- argv0.gsub!(/^[ ]+#{argv2}\s+/m, '<<< '); return nil unless argv0.include?('<<< ')
20
+ argv0.gsub!(/^[ ]+#{argv1}\s+/m, '>>> '); return nil if argv0.include?('>>> ') == false
21
+ argv0.gsub!(/^[ ]+#{argv2}\s+/m, '<<< '); return nil if argv0.include?('<<< ') == false
22
22
 
23
23
  # 2. Remove strings until the first '<<<' or '>>>'
24
24
  esmtp = []
@@ -96,7 +96,7 @@ module Sisimai
96
96
  end
97
97
  else
98
98
  # SMTP server sent a response "<<< response text"
99
- p = e.index('<<< '); next unless p == 0
99
+ p = e.index('<<< '); next if p.nil? && p != 0
100
100
  e = e[4, e.size].sub(/\A<<<[ ]/, '')
101
101
 
102
102
  if cv = e.match(/\A([2-5]\d\d)[ ]/) then cx['response']['reply'] = cv[1] end
@@ -14,10 +14,8 @@ module Sisimai
14
14
  # @return [String] Message token(MD5 hex digest) or blank(failed to create token)
15
15
  # @see http://en.wikipedia.org/wiki/ASCII
16
16
  def token(addr1, addr2, epoch)
17
- return "" unless addr1.is_a?(::String)
18
- return "" unless addr2.is_a?(::String)
19
- return "" unless epoch.is_a?(Integer)
20
- return "" if addr1.empty? || addr2.empty?
17
+ return "" if addr1.is_a?(::String) == false || addr2.is_a?(::String) == false
18
+ return "" if addr1.empty? || addr2.empty? || epoch.is_a?(Integer) == false
21
19
 
22
20
  # Format: STX(0x02) Sender-Address RS(0x1e) Recipient-Address ETX(0x03)
23
21
  require 'digest/sha1'
@@ -30,7 +28,7 @@ module Sisimai
30
28
  def is_8bit(argvs)
31
29
  v = argvs.to_s
32
30
  return false if v.empty?
33
- return true unless v =~ /\A[\x00-\x7f]*\z/
31
+ return true if v !~ /\A[\x00-\x7f]*\z/
34
32
  return false
35
33
  end
36
34
 
@@ -40,7 +38,7 @@ module Sisimai
40
38
  # @example Clean up text
41
39
  # sweep(' neko ') #=> 'neko'
42
40
  def sweep(argv1)
43
- return argv1 unless argv1.is_a?(::String)
41
+ return argv1 if argv1.is_a?(::String) == false
44
42
  argv1 = argv1.chomp.squeeze(' ').strip
45
43
  argv1 = argv1.sub(/ [-]{2,}[^ ].+\z/, '')
46
44
  return argv1
@@ -52,16 +50,14 @@ module Sisimai
52
50
  # @return [Boolean]
53
51
  # @since v5.0.0
54
52
  def aligned(argv1, argv2)
55
- return false if argv1.to_s.empty?
56
- return false unless argv2.is_a? Array
57
- return false unless argv2.size > 1
53
+ return false if argv1.to_s.empty? || argv2.is_a?(Array) == false || argv2.size < 2
58
54
 
59
55
  align = -1
60
56
  right = 0
61
57
  argv2.each do |e|
62
58
  # Get the position of each element in the 1st argument using index()
63
59
  p = argv1.index(e, align + 1)
64
- break unless p # Break this loop when there is no string in the 1st argument
60
+ break if p == nil # Break this loop when there is no string in the 1st argument
65
61
  align = e.length + p - 1 # There is an aligned string in the 1st argument
66
62
  right += 1
67
63
  end
@@ -1,4 +1,4 @@
1
1
  # Define the version number of Sisimai
2
2
  module Sisimai
3
- VERSION = '5.4.1'.freeze
3
+ VERSION = '5.5.0'.freeze
4
4
  end
data/lib/sisimai.rb CHANGED
@@ -44,7 +44,7 @@ module Sisimai
44
44
  end
45
45
  end
46
46
 
47
- sisi += fact unless fact.empty?
47
+ sisi += fact if fact.empty? == false
48
48
  end
49
49
 
50
50
  return nil if sisi.empty?