sisimai 4.25.17-java → 5.0.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 (178) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +3 -3
  3. data/ANALYTICAL-PRECISION +2 -2
  4. data/Benchmarks.mk +3 -3
  5. data/CONTRIBUTING +1 -1
  6. data/ChangeLog.md +406 -407
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +12 -12
  10. data/README-JA.md +142 -94
  11. data/README.md +282 -150
  12. data/Rakefile +9 -3
  13. data/Repository.mk +2 -3
  14. data/lib/sisimai/address.rb +118 -74
  15. data/lib/sisimai/arf.rb +84 -82
  16. data/lib/sisimai/datetime.rb +5 -52
  17. data/lib/sisimai/{data → fact}/json.rb +7 -9
  18. data/lib/sisimai/fact/yaml.rb +31 -0
  19. data/lib/sisimai/fact.rb +468 -0
  20. data/lib/sisimai/lhost/activehunter.rb +12 -14
  21. data/lib/sisimai/lhost/amavis.rb +11 -14
  22. data/lib/sisimai/lhost/amazonses.rb +37 -41
  23. data/lib/sisimai/lhost/amazonworkmail.rb +15 -18
  24. data/lib/sisimai/lhost/aol.rb +12 -14
  25. data/lib/sisimai/lhost/apachejames.rb +19 -21
  26. data/lib/sisimai/lhost/barracuda.rb +10 -12
  27. data/lib/sisimai/lhost/bigfoot.rb +21 -21
  28. data/lib/sisimai/lhost/biglobe.rb +15 -16
  29. data/lib/sisimai/lhost/courier.rb +20 -20
  30. data/lib/sisimai/lhost/domino.rb +23 -19
  31. data/lib/sisimai/lhost/einsundeins.rb +20 -16
  32. data/lib/sisimai/lhost/exchange2003.rb +30 -29
  33. data/lib/sisimai/lhost/exchange2007.rb +70 -58
  34. data/lib/sisimai/lhost/exim.rb +175 -161
  35. data/lib/sisimai/lhost/ezweb.rb +31 -56
  36. data/lib/sisimai/lhost/facebook.rb +21 -33
  37. data/lib/sisimai/lhost/fml.rb +43 -48
  38. data/lib/sisimai/lhost/gmail.rb +29 -29
  39. data/lib/sisimai/lhost/gmx.rb +18 -17
  40. data/lib/sisimai/lhost/googlegroups.rb +9 -10
  41. data/lib/sisimai/lhost/gsuite.rb +21 -27
  42. data/lib/sisimai/lhost/imailserver.rb +25 -39
  43. data/lib/sisimai/lhost/interscanmss.rb +28 -31
  44. data/lib/sisimai/lhost/kddi.rb +22 -28
  45. data/lib/sisimai/lhost/mailfoundry.rb +11 -12
  46. data/lib/sisimai/lhost/mailmarshalsmtp.rb +25 -29
  47. data/lib/sisimai/lhost/mailru.rb +33 -27
  48. data/lib/sisimai/lhost/mcafee.rb +21 -31
  49. data/lib/sisimai/lhost/messagelabs.rb +17 -20
  50. data/lib/sisimai/lhost/messagingserver.rb +40 -37
  51. data/lib/sisimai/lhost/mfilter.rb +15 -16
  52. data/lib/sisimai/lhost/mxlogic.rb +24 -23
  53. data/lib/sisimai/lhost/notes.rb +17 -17
  54. data/lib/sisimai/lhost/office365.rb +63 -27
  55. data/lib/sisimai/lhost/opensmtpd.rb +12 -13
  56. data/lib/sisimai/lhost/outlook.rb +12 -15
  57. data/lib/sisimai/lhost/postfix.rb +179 -129
  58. data/lib/sisimai/lhost/powermta.rb +12 -14
  59. data/lib/sisimai/lhost/qmail.rb +44 -47
  60. data/lib/sisimai/lhost/receivingses.rb +15 -20
  61. data/lib/sisimai/lhost/sendgrid.rb +34 -32
  62. data/lib/sisimai/lhost/sendmail.rb +66 -53
  63. data/lib/sisimai/lhost/surfcontrol.rb +19 -19
  64. data/lib/sisimai/lhost/v5sendmail.rb +45 -39
  65. data/lib/sisimai/lhost/verizon.rb +35 -39
  66. data/lib/sisimai/lhost/x1.rb +18 -17
  67. data/lib/sisimai/lhost/x2.rb +17 -14
  68. data/lib/sisimai/lhost/x3.rb +19 -19
  69. data/lib/sisimai/lhost/x4.rb +72 -57
  70. data/lib/sisimai/lhost/x5.rb +17 -19
  71. data/lib/sisimai/lhost/x6.rb +41 -17
  72. data/lib/sisimai/lhost/yahoo.rb +17 -16
  73. data/lib/sisimai/lhost/yandex.rb +16 -20
  74. data/lib/sisimai/lhost/zoho.rb +16 -15
  75. data/lib/sisimai/lhost.rb +8 -10
  76. data/lib/sisimai/mail/maildir.rb +1 -3
  77. data/lib/sisimai/mail/mbox.rb +3 -4
  78. data/lib/sisimai/mail/memory.rb +0 -1
  79. data/lib/sisimai/mail/stdin.rb +1 -3
  80. data/lib/sisimai/mail.rb +3 -7
  81. data/lib/sisimai/mda.rb +28 -42
  82. data/lib/sisimai/message.rb +435 -325
  83. data/lib/sisimai/order.rb +5 -5
  84. data/lib/sisimai/reason/authfailure.rb +64 -0
  85. data/lib/sisimai/reason/badreputation.rb +53 -0
  86. data/lib/sisimai/reason/blocked.rb +94 -160
  87. data/lib/sisimai/reason/contenterror.rb +8 -9
  88. data/lib/sisimai/reason/delivered.rb +4 -6
  89. data/lib/sisimai/reason/exceedlimit.rb +10 -12
  90. data/lib/sisimai/reason/expired.rb +6 -8
  91. data/lib/sisimai/reason/feedback.rb +2 -3
  92. data/lib/sisimai/reason/filtered.rb +17 -19
  93. data/lib/sisimai/reason/hasmoved.rb +9 -10
  94. data/lib/sisimai/reason/hostunknown.rb +15 -15
  95. data/lib/sisimai/reason/mailboxfull.rb +10 -12
  96. data/lib/sisimai/reason/mailererror.rb +18 -20
  97. data/lib/sisimai/reason/mesgtoobig.rb +9 -11
  98. data/lib/sisimai/reason/networkerror.rb +5 -8
  99. data/lib/sisimai/reason/norelaying.rb +8 -11
  100. data/lib/sisimai/reason/notaccept.rb +13 -14
  101. data/lib/sisimai/reason/notcompliantrfc.rb +43 -0
  102. data/lib/sisimai/reason/onhold.rb +6 -9
  103. data/lib/sisimai/reason/policyviolation.rb +14 -12
  104. data/lib/sisimai/reason/rejected.rb +26 -24
  105. data/lib/sisimai/reason/requireptr.rb +69 -0
  106. data/lib/sisimai/reason/securityerror.rb +33 -36
  107. data/lib/sisimai/reason/spamdetected.rb +114 -147
  108. data/lib/sisimai/reason/speeding.rb +49 -0
  109. data/lib/sisimai/reason/suspend.rb +11 -11
  110. data/lib/sisimai/reason/syntaxerror.rb +11 -10
  111. data/lib/sisimai/reason/systemerror.rb +7 -9
  112. data/lib/sisimai/reason/systemfull.rb +7 -8
  113. data/lib/sisimai/reason/toomanyconn.rb +9 -11
  114. data/lib/sisimai/reason/undefined.rb +2 -3
  115. data/lib/sisimai/reason/userunknown.rb +129 -146
  116. data/lib/sisimai/reason/vacation.rb +3 -4
  117. data/lib/sisimai/reason/virusdetected.rb +10 -11
  118. data/lib/sisimai/reason.rb +59 -64
  119. data/lib/sisimai/rfc1894.rb +55 -28
  120. data/lib/sisimai/rfc2045.rb +373 -0
  121. data/lib/sisimai/rfc3464.rb +250 -308
  122. data/lib/sisimai/rfc3834.rb +42 -45
  123. data/lib/sisimai/rfc5322.rb +75 -100
  124. data/lib/sisimai/rfc5965.rb +31 -0
  125. data/lib/sisimai/rhost/cox.rb +5 -6
  126. data/lib/sisimai/rhost/franceptt.rb +6 -8
  127. data/lib/sisimai/rhost/godaddy.rb +12 -12
  128. data/lib/sisimai/rhost/{googleapps.rb → google.rb} +80 -72
  129. data/lib/sisimai/rhost/iua.rb +9 -10
  130. data/lib/sisimai/rhost/kddi.rb +6 -8
  131. data/lib/sisimai/rhost/{exchangeonline.rb → microsoft.rb} +115 -114
  132. data/lib/sisimai/rhost/mimecast.rb +42 -40
  133. data/lib/sisimai/rhost/nttdocomo.rb +13 -18
  134. data/lib/sisimai/rhost/spectrum.rb +10 -12
  135. data/lib/sisimai/rhost/{tencentqq.rb → tencent.rb} +7 -8
  136. data/lib/sisimai/rhost.rb +23 -31
  137. data/lib/sisimai/smtp/command.rb +59 -0
  138. data/lib/sisimai/smtp/error.rb +4 -7
  139. data/lib/sisimai/smtp/reply.rb +161 -74
  140. data/lib/sisimai/smtp/status.rb +504 -393
  141. data/lib/sisimai/smtp/transcript.rb +124 -0
  142. data/lib/sisimai/smtp.rb +0 -1
  143. data/lib/sisimai/string.rb +74 -5
  144. data/lib/sisimai/time.rb +1 -2
  145. data/lib/sisimai/version.rb +1 -1
  146. data/lib/sisimai.rb +35 -21
  147. data/set-of-emails/maildir/bsd/lhost-domino-02.eml +6 -3
  148. data/set-of-emails/maildir/bsd/lhost-googlegroups-15.eml +174 -0
  149. data/set-of-emails/maildir/bsd/lhost-gsuite-15.eml +229 -0
  150. data/set-of-emails/maildir/bsd/lhost-postfix-75.eml +51 -0
  151. data/set-of-emails/maildir/bsd/lhost-postfix-76.eml +101 -0
  152. data/set-of-emails/maildir/bsd/lhost-postfix-77.eml +74 -0
  153. data/set-of-emails/maildir/bsd/lhost-postfix-78.eml +91 -0
  154. data/set-of-emails/maildir/bsd/lhost-receivingses-08.eml +88 -0
  155. data/set-of-emails/maildir/bsd/rfc3464-43.eml +88 -0
  156. data/set-of-emails/maildir/bsd/rhost-google-03.eml +101 -0
  157. data/set-of-emails/maildir/bsd/rhost-google-04.eml +102 -0
  158. data/set-of-emails/maildir/bsd/rhost-google-05.eml +82 -0
  159. data/set-of-emails/maildir/bsd/rhost-google-06.eml +102 -0
  160. data/set-of-emails/maildir/bsd/rhost-google-07.eml +69 -0
  161. data/set-of-emails/maildir/bsd/rhost-google-08.eml +99 -0
  162. data/sisimai-java.gemspec +1 -1
  163. data/sisimai.gemspec +1 -1
  164. metadata +42 -23
  165. data/.rspec +0 -2
  166. data/lib/sisimai/data/yaml.rb +0 -33
  167. data/lib/sisimai/data.rb +0 -411
  168. data/lib/sisimai/mime.rb +0 -456
  169. data/set-of-emails/maildir/mac/reported-from-nick4tech-san-01.eml +0 -6
  170. /data/set-of-emails/maildir/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-03.eml → rhost-tencent-03.eml} +0 -0
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::X3 parses a bounce email which created by Unknown MTA #3.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::X3 parses a bounce email which created by Unknown MTA #3. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module X3
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/X3.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^Content-Type:[ ]message/rfc822|.freeze
9
+ Boundaries = ['Content-Type: message/rfc822'].freeze
11
10
  StartingOf = { message: [' This is an automatically generated Delivery Status Notification.'] }.freeze
12
11
 
13
12
  # Parse bounce messages from Unknown MTA #3
@@ -15,20 +14,21 @@ module Sisimai::Lhost
15
14
  # @param [String] mbody Message body of a bounce email
16
15
  # @return [Hash] Bounce data list and message/rfc822 part
17
16
  # @return [Nil] it failed to parse or the arguments are missing
18
- def make(mhead, mbody)
17
+ def inquire(mhead, mbody)
19
18
  return nil unless mhead['subject'].start_with?('Delivery status notification')
20
19
  return nil unless mhead['from'].start_with?('Mail Delivery System')
21
20
 
21
+ require 'sisimai/smtp/command'
22
22
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
23
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
24
- bodyslices = emailsteak[0].split("\n")
23
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
24
+ bodyslices = emailparts[0].split("\n")
25
25
  readcursor = 0 # (Integer) Points the current cursor position
26
26
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
27
27
  v = nil
28
28
 
29
29
  while e = bodyslices.shift do
30
- # Read error messages and delivery status lines from the head of the email
31
- # to the previous line of the beginning of the original message.
30
+ # Read error messages and delivery status lines from the head of the email to the previous
31
+ # line of the beginning of the original message.
32
32
  if readcursor == 0
33
33
  # Beginning of the bounce message or delivery status part
34
34
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -54,29 +54,29 @@ module Sisimai::Lhost
54
54
  # ============================================================================
55
55
  v = dscontents[-1]
56
56
 
57
- if cv = e.match(/\A[ \t]+[*][ ]([^ ]+[@][^ ]+)\z/)
57
+ if e.include?(' * ') && e.include?('@')
58
58
  # * kijitora@example.com
59
59
  if v['recipient']
60
60
  # There are multiple recipient addresses in the message body.
61
61
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
62
62
  v = dscontents[-1]
63
63
  end
64
- v['recipient'] = cv[1]
64
+ v['recipient'] = e[e.index(' * ') + 3, e.size]
65
65
  recipients += 1
66
66
  else
67
67
  # Detect error message
68
- if cv = e.match(/\ASMTP:([^ ]+)[ ](.+)\z/)
68
+ if e.start_with?('SMTP:')
69
69
  # SMTP:RCPT host 192.0.2.8: 553 5.3.0 <kijitora@example.com>... No such user here
70
- v['command'] = cv[1].upcase
71
- v['diagnosis'] = cv[2]
70
+ v['command'] = Sisimai::SMTP::Command.find(e)
71
+ v['diagnosis'] = e
72
72
 
73
- elsif cv = e.match(/\ARouting: (.+)/)
73
+ elsif e.start_with?('Routing: ')
74
74
  # Routing: Could not find a gateway for kijitora@example.co.jp
75
- v['diagnosis'] = cv[1]
75
+ v['diagnosis'] = e[9, e.size]
76
76
 
77
- elsif cv = e.match(/\ADiagnostic-Code: smtp; (.+)/)
77
+ elsif e.start_with?('Diagnostic-Code: smtp; ')
78
78
  # Diagnostic-Code: smtp; 552 5.2.2 Over quota
79
- v['diagnosis'] = cv[1]
79
+ v['diagnosis'] = e[e.index(';') + 2, e.size]
80
80
  end
81
81
  end
82
82
  end
@@ -87,7 +87,7 @@ module Sisimai::Lhost
87
87
  e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || ''
88
88
  end
89
89
 
90
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
90
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
91
91
  end
92
92
  def description; return 'Unknown MTA #3'; end
93
93
  end
@@ -1,16 +1,14 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::X4 parses a bounce email which created by some qmail clone.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::X4 parses a bounce email which created by some qmail clone. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module X4
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/X4.pm
7
6
  # MTA module for qmail clones
8
7
  require 'sisimai/lhost'
9
8
 
10
9
  Indicators = Sisimai::Lhost.INDICATORS
11
- ReBackbone = %r/^---[ ](?:Below this line is a copy of the message|Original message follows)[.]/.freeze
12
- StartingOf = { error: ['Remote host said:'] }.freeze
13
- MarkingsOf = {
10
+ Boundaries = ['--- Below this line is a copy of the message.', 'Original message follows.'].freeze
11
+ StartingOf = {
14
12
  # qmail-remote.c:248| if (code >= 500) {
15
13
  # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
16
14
  # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command");
@@ -18,24 +16,39 @@ module Sisimai::Lhost
18
16
  #
19
17
  # Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c
20
18
  # K = success, Z = temporary error, D = permanent error
21
- message: %r{\A(?>
22
- He/Her[ ]is[ ]not[ ].+[ ]user
23
- |Hi[.][ ].+[ ]unable[ ]to[ ]deliver[ ]your[ ]message[ ]to[ ]
24
- the[ ]following[ ]addresses
25
- |Su[ ]mensaje[ ]no[ ]pudo[ ]ser[ ]entregado
26
- |This[ ]is[ ]the[ ](?:
27
- machine[ ]generated[ ]message[ ]from[ ]mail[ ]service
28
- |mail[ ]delivery[ ]agent[ ]at
29
- )
30
- |Unable[ ]to[ ]deliver[ ]message[ ]to[ ]the[ ]following[ ]address
31
- |Unfortunately,[ ]your[ ]mail[ ]was[ ]not[ ]delivered[ ]to[ ]the[ ]following[ ]address:
32
- |Your[ ](?:
33
- mail[ ]message[ ]to[ ]the[ ]following[ ]address
34
- |message[ ]to[ ]the[ ]following[ ]addresses
35
- )
36
- |We're[ ]sorry[.]
37
- )
38
- }x,
19
+ error: ['Remote host said:'],
20
+ message: [
21
+ 'He/Her is not ',
22
+ 'unable to deliver your message to the following addresses',
23
+ 'Su mensaje no pudo ser entregado',
24
+ 'This is the machine generated message from mail service',
25
+ 'This is the mail delivery agent at',
26
+ 'Unable to deliver message to the following address',
27
+ 'Unfortunately, your mail was not delivered to the following address:',
28
+ 'Your mail message to the following address',
29
+ 'Your message to the following addresses',
30
+ "We're sorry.",
31
+ ],
32
+ rhost: ['Giving up on ', 'Connected to ', 'remote host '],
33
+ }.freeze
34
+ CommandSet = {
35
+ # Error text regular expressions which defined in qmail-remote.c
36
+ # qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed");
37
+ 'conn' => [' but greeting failed.'],
38
+ # qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected");
39
+ 'ehlo' => [' but my name was rejected.'],
40
+ # qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected");
41
+ # reason = rejected
42
+ 'mail' => [' but sender was rejected.'],
43
+ # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n");
44
+ # qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n");
45
+ # reason = userunknown
46
+ 'rcpt' => [' does not like recipient.'],
47
+ # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command");
48
+ # qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command");
49
+ # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message");
50
+ # qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message");
51
+ 'data' => [' failed on DATA command', ' failed after I sent the message'],
39
52
  }.freeze
40
53
 
41
54
  ReSMTP = {
@@ -61,18 +74,10 @@ module Sisimai::Lhost
61
74
  )
62
75
  }x,
63
76
  }.freeze
64
- # qmail-remote.c:261| if (!flagbother) quit("DGiving up on ","");
65
- ReHost = %r{(?:
66
- Giving[ ]up[ ]on[ ]([^ ]+[0-9a-zA-Z])[.]?\z
67
- |Connected[ ]to[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]
68
- |remote[ ]host[ ]([-0-9a-zA-Z.]+[0-9a-zA-Z])[ ]said:
69
- )
70
- }x
71
77
 
72
78
  # qmail-send.c:922| ... (&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
73
79
  HasExpired = 'this message has been in the queue too long.'
74
- ReIsOnHold = %r/\A[^ ]+ does not like recipient[.][ ]+.+this message has been in the queue too long[.]\z/
75
- ReCommands = %r/Sorry, no SMTP connection got far enough; most progress was ([A-Z]{4})[ ]/
80
+ OnHoldPair = [' does not like recipient.', 'this message has been in the queue too long.'].freeze
76
81
  FailOnLDAP = {
77
82
  # qmail-ldap-1.03-20040101.patch:19817 - 19866
78
83
  'suspend' => ['Mailaddress is administrative?le?y disabled'], # 5.2.1
@@ -122,30 +127,34 @@ module Sisimai::Lhost
122
127
  # @param [String] mbody Message body of a bounce email
123
128
  # @return [Hash] Bounce data list and message/rfc822 part
124
129
  # @return [Nil] it failed to parse or the arguments are missing
125
- def make(mhead, mbody)
126
- # Pre process email headers and the body part of the message which generated
127
- # by qmail, see https://cr.yp.to/qmail.html
130
+ def inquire(mhead, mbody)
131
+ # Pre process email headers and the body part of the message which generated by qmail, see
132
+ # https://cr.yp.to/qmail.html
128
133
  # e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000
129
134
  # Subject: failure notice
130
- tryto = %r/\A[(]qmail[ ]+\d+[ ]+invoked[ ]+for[ ]+bounce[)]/
135
+ tryto = [['(qmail ', 'invoked for bounce)'], ['(qmail ', 'invoked from ', 'network)']].freeze
131
136
  match = 0
132
137
  match += 1 if mhead['subject'].start_with?('failure notice', 'Permanent Delivery Failure')
133
- match += 1 if mhead['received'].any? { |a| a =~ tryto }
138
+ mhead['received'].each do |e|
139
+ # Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900
140
+ # Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900
141
+ match += 1 if tryto.any? { |a| Sisimai::String.aligned(e, a) }
142
+ end
134
143
  return nil unless match > 0
135
144
 
136
145
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
137
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
138
- bodyslices = emailsteak[0].split("\n")
146
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
147
+ bodyslices = emailparts[0].split("\n")
139
148
  readcursor = 0 # (Integer) Points the current cursor position
140
149
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
141
150
  v = nil
142
151
 
143
152
  while e = bodyslices.shift do
144
- # Read error messages and delivery status lines from the head of the email
145
- # to the previous line of the beginning of the original message.
153
+ # Read error messages and delivery status lines from the head of the email to the previous
154
+ # line of the beginning of the original message.
146
155
  if readcursor == 0
147
156
  # Beginning of the bounce message or delivery status part
148
- readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message]
157
+ readcursor |= Indicators[:deliverystatus] if StartingOf[:message].any? { |a| e.include?(a) }
149
158
  next
150
159
  end
151
160
  next if (readcursor & Indicators[:deliverystatus]) == 0
@@ -157,14 +166,14 @@ module Sisimai::Lhost
157
166
  # Giving up on 192.0.2.153.
158
167
  v = dscontents[-1]
159
168
 
160
- if cv = e.match(/\A(?:To[ ]*:)?[<](.+[@].+)[>]:[ ]*\z/)
169
+ if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>', ':'])
161
170
  # <kijitora@example.jp>:
162
171
  if v['recipient']
163
172
  # There are multiple recipient addresses in the message body.
164
173
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
165
174
  v = dscontents[-1]
166
175
  end
167
- v['recipient'] = cv[1]
176
+ v['recipient'] = Sisimai::Address.s3s4(e[e.index('<'), e.size])
168
177
  recipients += 1
169
178
 
170
179
  elsif dscontents.size == recipients
@@ -175,8 +184,15 @@ module Sisimai::Lhost
175
184
  v['alterrors'] = e if e.start_with?(StartingOf[:error][0])
176
185
 
177
186
  next if v['rhost']
178
- next unless cv = e.match(ReHost)
179
- v['rhost'] = cv[1]
187
+ StartingOf[:rhost].each do |r|
188
+ # Find a remote host name
189
+ p1 = e.index(r); next unless p1
190
+ cm = r.size
191
+ p2 = e.index(' ', p1 + cm + 1) || p2 = e.rindex('.')
192
+
193
+ v['rhost'] = e[p1 + cm, p2 - p1 - cm]
194
+ break
195
+ end
180
196
  end
181
197
  end
182
198
  return nil unless recipients > 0
@@ -186,17 +202,16 @@ module Sisimai::Lhost
186
202
 
187
203
  unless e['command']
188
204
  # Get the SMTP command name for the session
189
- ReSMTP.each_key do |r|
205
+ CommandSet.each_key do |r|
190
206
  # Verify each regular expression of SMTP commands
191
- next unless e['diagnosis'] =~ ReSMTP[r]
207
+ next unless CommandSet[r].any? { |a| e['diagnosis'].include?(a) }
192
208
  e['command'] = r.upcase
193
209
  break
194
210
  end
195
211
 
196
- unless e['command']
197
- # Verify each regular expression of patches
198
- if cv = e['diagnosis'].match(ReCommands) then e['command'] = cv[1].upcase end
199
- e['command'] ||= ''
212
+ if e['diagnosis'].include?('Sorry, no SMTP connection got far enough; most progress was ')
213
+ # Get the last SMTP command:from the error message
214
+ e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis']) || ''
200
215
  end
201
216
  end
202
217
 
@@ -210,9 +225,8 @@ module Sisimai::Lhost
210
225
  e['reason'] = 'blocked'
211
226
  else
212
227
  # Try to match with each error message in the table
213
- if e['diagnosis'] =~ ReIsOnHold
214
- # To decide the reason require pattern match with
215
- # Sisimai::Reason::* modules
228
+ if Sisimai::String.aligned(e['diagnosis'], OnHoldPair)
229
+ # To decide the reason require pattern match with Sisimai::Reason::* modules
216
230
  e['reason'] = 'onhold'
217
231
  else
218
232
  MessagesOf.each_key do |r|
@@ -243,9 +257,10 @@ module Sisimai::Lhost
243
257
  end
244
258
  end
245
259
  end
260
+ e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
246
261
  end
247
262
 
248
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
263
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
249
264
  end
250
265
  def description; return 'Unknown MTA #4'; end
251
266
  end
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::X5 parses a bounce email which created by Unknown MTA #5.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::X5 parses a bounce email which created by Unknown MTA #5. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module X5
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/X5.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^Content-Type:[ ]message/rfc822|.freeze
9
+ Boundaries = ['Content-Type: message/rfc822'].freeze
11
10
  StartingOf = { message: ['Content-Type: message/delivery-status'] }.freeze
12
11
 
13
12
  # Parse bounce messages from Unknown MTA #5
@@ -15,7 +14,7 @@ module Sisimai::Lhost
15
14
  # @param [String] mbody Message body of a bounce email
16
15
  # @return [Hash] Bounce data list and message/rfc822 part
17
16
  # @return [Nil] it failed to parse or the arguments are missing
18
- def make(mhead, mbody)
17
+ def inquire(mhead, mbody)
19
18
  match = 0
20
19
  match += 1 if mhead['to'].to_s.include?('NotificationRecipients')
21
20
  if mhead['from'].include?('TWFpbCBEZWxpdmVyeSBTdWJzeXN0ZW0')
@@ -23,20 +22,19 @@ module Sisimai::Lhost
23
22
  # Mail Delivery Subsystem
24
23
  mhead['from'].split(' ').each do |f|
25
24
  # Check each element of From: header
26
- next unless Sisimai::MIME.is_mimeencoded(f)
27
- match += 1 if Sisimai::MIME.mimedecode([f]).include?('Mail Delivery Subsystem')
25
+ next unless Sisimai::RFC2045.is_encoded(f)
26
+ match += 1 if Sisimai::RFC2045.decodeH([f]).include?('Mail Delivery Subsystem')
28
27
  break
29
28
  end
30
29
  end
31
30
 
32
- if Sisimai::MIME.is_mimeencoded(mhead['subject'])
31
+ if Sisimai::RFC2045.is_encoded(mhead['subject'])
33
32
  # Subject: =?iso-2022-jp?B?UmV0dXJuZWQgbWFpbDogVXNlciB1bmtub3du?=
34
- plain = Sisimai::MIME.mimedecode([mhead['subject']])
33
+ plain = Sisimai::RFC2045.decodeH([mhead['subject']])
35
34
  match += 1 if plain.include?('Mail Delivery Subsystem')
36
35
  end
37
36
  return nil if match < 2
38
37
 
39
- require 'sisimai/rfc1894'
40
38
  fieldtable = Sisimai::RFC1894.FIELDTABLE
41
39
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
42
40
  readslices = ['']
@@ -45,14 +43,14 @@ module Sisimai::Lhost
45
43
  v = nil
46
44
 
47
45
  # Pick the second message/rfc822 part because the format of email-x5-*.eml is nested structure
48
- prefillets = mbody.split(ReBackbone, 2)
49
- prefillets[1].sub!(/\A.+?\n\n/s, '')
50
- emailsteak = Sisimai::RFC5322.fillet(prefillets[1], ReBackbone)
51
- bodyslices = emailsteak[0].split("\n")
46
+ cutsbefore = mbody.split(Boundaries[0], 2)
47
+ cutsbefore[1] = cutsbefore[1][cutsbefore[1].index("\n\n") + 2, cutsbefore[1].size]
48
+ emailparts = Sisimai::RFC5322.part(cutsbefore[1], Boundaries)
49
+ bodyslices = emailparts[0].split("\n")
52
50
 
53
51
  while e = bodyslices.shift do
54
- # Read error messages and delivery status lines from the head of the email
55
- # to the previous line of the beginning of the original message.
52
+ # Read error messages and delivery status lines from the head of the email to the previous
53
+ # line of the beginning of the original message.
56
54
  readslices << e # Save the current line for the next loop
57
55
 
58
56
  if readcursor == 0
@@ -97,15 +95,15 @@ module Sisimai::Lhost
97
95
  else
98
96
  # Continued line of the value of Diagnostic-Code field
99
97
  next unless readslices[-2].start_with?('Diagnostic-Code:')
100
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
101
- v['diagnosis'] << ' ' << cv[1]
98
+ next unless e.start_with?(' ')
99
+ v['diagnosis'] << ' ' << Sisimai::String.sweep(e)
102
100
  readslices[-1] = 'Diagnostic-Code: ' << e
103
101
  end
104
102
  end
105
103
  return nil unless recipients > 0
106
104
 
107
105
  dscontents.each { |e| e['diagnosis'] ||= Sisimai::String.sweep(e['diagnosis']) }
108
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
106
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
109
107
  end
110
108
  def description; return 'Unknown MTA #5'; end
111
109
  end
@@ -1,14 +1,13 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::X6 parses a bounce email which created by Unknown MTA #6.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::X6 parses a bounce email which created by Unknown MTA #6. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module X6
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/X6.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r/^The attachment contains the original mail headers.+$/.freeze
11
- MarkingsOf = { message: %r/\A\d+[ ]*error[(]s[)]:/ }.freeze
9
+ Boundaries = ['The attachment contains the original mail headers'].freeze
10
+ StartingOf = { message: ['We had trouble delivering your message. Full details follow:'] }.freeze
12
11
 
13
12
  # Parse bounce messages from Unknown MTA #6
14
13
  # @param [Hash] mhead Message headers of a bounce email
@@ -16,33 +15,41 @@ module Sisimai::Lhost
16
15
  # @return [Hash] Bounce data list and message/rfc822 part
17
16
  # @return [Nil] it failed to parse or the arguments are missing
18
17
  # @since v4.25.6
19
- def make(mhead, mbody)
18
+ def inquire(mhead, mbody)
20
19
  return nil unless mhead['subject'].start_with?('There was an error sending your mail')
21
20
 
22
21
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
23
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
24
- bodyslices = emailsteak[0].split("\n")
22
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
23
+ bodyslices = emailparts[0].split("\n")
25
24
  readcursor = 0 # (Integer) Points the current cursor position
26
25
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
27
26
  v = nil
28
27
 
29
28
  while e = bodyslices.shift do
30
- # Read error messages and delivery status lines from the head of the email
31
- # to the previous line of the beginning of the original message.
29
+ # Read error messages and delivery status lines from the head of the email to the previous
30
+ # line of the beginning of the original message.
32
31
  if readcursor == 0
33
32
  # Beginning of the bounce message or delivery status part
34
- readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message]
33
+ readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
35
34
  next
36
35
  end
37
36
  next if (readcursor & Indicators[:deliverystatus]) == 0
38
37
  next if e.empty?
39
38
 
39
+ # We had trouble delivering your message. Full details follow:
40
+ #
41
+ # Subject: 'Nyaan'
42
+ # Date: 'Thu, 29 Apr 2012 23:34:45 +0000'
43
+ #
40
44
  # 1 error(s):
41
45
  #
42
46
  # SMTP Server <mta2.example.jp> rejected recipient <kijitora@examplejp>
43
47
  # (Error following RCPT command). It responded as follows: [550 5.1.1 User unknown]v = dscontents[-1]
44
- v = dscontents[-1]
45
- if cv = e.match(/<([^ @]+[@][^ @]+)>/) || e.match(/errors:[ ]*([^ ]+[@][^ ]+)/)
48
+ v = dscontents[-1]
49
+ p1 = e.index('The following recipients returned permanent errors: ')
50
+ p2 = e.index('SMTP Server <')
51
+
52
+ if p1 == 0 || p2 == 0
46
53
  # SMTP Server <mta2.example.jp> rejected recipient <kijitora@examplejp>
47
54
  # The following recipients returned permanent errors: neko@example.jp.
48
55
  if v['recipient']
@@ -50,22 +57,39 @@ module Sisimai::Lhost
50
57
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
51
58
  v = dscontents[-1]
52
59
  end
53
- v['recipient'] = Sisimai::Address.s3s4(cv[1])
60
+
61
+ if p1 == 0
62
+ # The following recipients returned permanent errors: neko@example.jp.
63
+ p1 = e.index('errors: ')
64
+ p2 = e.index(' ', p1 + 8)
65
+ v['recipient'] = Sisimai::Address.s3s4(e[p1 + 8, p2 - p1 - 8])
66
+
67
+ elsif p2 == 0
68
+ # SMTP Server <mta2.example.jp> rejected recipient <kijitora@examplejp>
69
+ p1 = e.rindex('<')
70
+ p2 = e.rindex('>')
71
+ v['recipient'] = Sisimai::Address.s3s4(e[p1, p2 - p1])
72
+
73
+ else
74
+ next
75
+ end
76
+
54
77
  v['diagnosis'] = e
55
78
  recipients += 1
56
79
  end
57
80
  end
58
81
  return nil unless recipients > 0
59
82
 
83
+ require 'sisimai/smtp/command'
60
84
  dscontents.each do |e|
61
- if cv = e['diagnosis'].match(/\b(HELO|EHLO|MAIL|RCPT|DATA)\b/)
85
+ if cv = Sisimai::SMTP::Command.find(e['diagnosis'])
62
86
  # ...(Error following RCPT command).
63
- e['command'] = cv[1]
87
+ e['command'] = cv
64
88
  end
65
89
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
66
90
  end
67
91
 
68
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
92
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
69
93
  end
70
94
  def description; return 'Unknown MTA #6'; end
71
95
  end
@@ -1,13 +1,12 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Yahoo parses a bounce email which created by Yahoo! MAIL.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Yahoo parses a bounce email which created by Yahoo! MAIL. Methods in the module
3
+ # are called from only Sisimai::Message.
4
4
  module Yahoo
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/Yahoo.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- ReBackbone = %r|^--- Below this line is a copy of the message[.]|.freeze
9
+ Boundaries = ['--- Below this line is a copy of the message.'].freeze
11
10
  StartingOf = { message: ['Sorry, we were unable to deliver your message'] }.freeze
12
11
 
13
12
  # Parse bounce messages from Yahoo! MAIL
@@ -15,23 +14,24 @@ module Sisimai::Lhost
15
14
  # @param [String] mbody Message body of a bounce email
16
15
  # @return [Hash] Bounce data list and message/rfc822 part
17
16
  # @return [Nil] it failed to parse or the arguments are missing
18
- def make(mhead, mbody)
17
+ def inquire(mhead, mbody)
19
18
  # X-YMailISG: YtyUVyYWLDsbDh...
20
19
  # X-YMail-JAS: Pb65aU4VM1mei...
21
20
  # X-YMail-OSG: bTIbpDEVM1lHz...
22
21
  # X-Originating-IP: [192.0.2.9]
23
22
  return nil unless mhead['x-ymailisg']
24
23
 
24
+ require 'sisimai/smtp/command'
25
25
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
26
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
27
- bodyslices = emailsteak[0].split("\n")
26
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
27
+ bodyslices = emailparts[0].split("\n")
28
28
  readcursor = 0 # (Integer) Points the current cursor position
29
29
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
30
30
  v = nil
31
31
 
32
32
  while e = bodyslices.shift do
33
- # Read error messages and delivery status lines from the head of the email
34
- # to the previous line of the beginning of the original message.
33
+ # Read error messages and delivery status lines from the head of the email to the previous
34
+ # line of the beginning of the original message.
35
35
  if readcursor == 0
36
36
  # Beginning of the bounce message or delivery status part
37
37
  readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
@@ -46,23 +46,24 @@ module Sisimai::Lhost
46
46
  # Remote host said: 550 5.1.1 <kijitora@example.org>... User Unknown [RCPT_TO]
47
47
  v = dscontents[-1]
48
48
 
49
- if cv = e.match(/\A[<](.+[@].+)[>]:[ \t]*\z/)
49
+ if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:'])
50
50
  # <kijitora@example.org>:
51
51
  if v['recipient']
52
52
  # There are multiple recipient addresses in the message body.
53
53
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
54
54
  v = dscontents[-1]
55
55
  end
56
- v['recipient'] = cv[1]
56
+ v['recipient'] = Sisimai::Address.s3s4(e[0, e.index('>:')])
57
57
  v['diagnosis'] = ''
58
58
  recipients += 1
59
+
59
60
  else
60
61
  if e.start_with?('Remote host said:')
61
62
  # Remote host said: 550 5.1.1 <kijitora@example.org>... User Unknown [RCPT_TO]
62
63
  v['diagnosis'] = e
63
64
 
64
65
  # Get SMTP command from the value of "Remote host said:"
65
- if cv = e.match(/\[([A-Z]{4}).*\]\z/) then v['command'] = cv[1] end
66
+ v['command'] = Sisimai::SMTP::Command.find(e)
66
67
  else
67
68
  # <mailboxfull@example.jp>:
68
69
  # Remote host said:
@@ -71,9 +72,9 @@ module Sisimai::Lhost
71
72
  if v['diagnosis'].start_with?('Remote host said:')
72
73
  # Remote host said:
73
74
  # 550 5.2.2 <mailboxfull@example.jp>... Mailbox Full
74
- if cv = e.match(/\[([A-Z]{4}).*\]\z/)
75
+ if cv = Sisimai::SMTP::Command.find(e)
75
76
  # [RCPT_TO]
76
- v['command'] = cv[1]
77
+ v['command'] = cv
77
78
  else
78
79
  # 550 5.2.2 <mailboxfull@example.jp>... Mailbox Full
79
80
  v['diagnosis'] = e
@@ -89,10 +90,10 @@ module Sisimai::Lhost
89
90
 
90
91
  dscontents.each do |e|
91
92
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].gsub(/\\n/, ' '))
92
- e['command'] ||= 'RCPT' if e['diagnosis'] =~ /[<].+[@].+[>]/
93
+ e['command'] ||= 'RCPT' if Sisimai::String.aligned(e['diagnosis'], ['<', '@', '>'])
93
94
  end
94
95
 
95
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
96
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
96
97
  end
97
98
  def description; return 'Yahoo! MAIL: https://www.yahoo.com'; end
98
99
  end