sisimai 4.25.17 → 5.0.0

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 +4 -4
  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 +41 -21
  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,108 +1,112 @@
1
1
  module Sisimai
2
2
  # Sisimai::RFC3464 - bounce mail parser class for Fallback.
3
3
  module RFC3464
4
- # Imported from p5-Sisimail/lib/Sisimai/RFC3464.pm
5
4
  class << self
6
5
  require 'sisimai/lhost'
6
+ require 'sisimai/address'
7
+ require 'sisimai/rfc1894'
7
8
 
8
9
  # http://tools.ietf.org/html/rfc3464
9
10
  Indicators = Sisimai::Lhost.INDICATORS
10
- MarkingsOf = {
11
- message: %r{\A(?>
12
- content-type:[ ]*(?:
13
- message/x?delivery-status
14
- |message/disposition-notification
15
- |text/plain;[ ]charset=
16
- )
17
- |the[ ]original[ ]message[ ]was[ ]received[ ]at[ ]
18
- |this[ ]report[ ]relates[ ]to[ ]your[ ]message
19
- |your[ ]message[ ](?:
20
- could[ ]not[ ]be[ ]delivered
21
- |was[ ]not[ ]delivered[ ]to[ ]the[ ]following[ ]recipients
22
- )
23
- )
24
- }x,
25
- rfc822: %r{\A(?>
26
- content-type:[ ]*(?:message/rfc822|text/rfc822-headers)
27
- |return-path:[ ]*[<].+[>]
28
- )\z
29
- }x,
30
- error: %r/\A(?:[45]\d\d[ \t]+|[<][^@]+[@][^@]+[>]:?[ \t]+)/,
31
- command: %r/[ ](RCPT|MAIL|DATA)[ ]+command\b/,
11
+ StartingOf = {
12
+ message: [
13
+ 'content-type: message/delivery-status',
14
+ 'content-type: message/disposition-notification',
15
+ 'content-type: text/plain; charset=',
16
+ 'the original message was received at ',
17
+ 'this report relates to your message',
18
+ 'your message could not be delivered',
19
+ 'your message was not delivered to ',
20
+ 'your message was not delivered to the following recipients',
21
+ ],
22
+ rfc822: [
23
+ 'content-type: message/rfc822',
24
+ 'content-type: text/rfc822-headers',
25
+ 'return-path: <'
26
+ ],
32
27
  }.freeze
33
- ReSkip = %r{(?>
34
- \A[-]+=
35
- |\A\s+\z
36
- |\A\s*--
37
- |\A\s+[=]\d+
38
- |\Ahi[ ][!]
39
- |content-(?:description|disposition|transfer-encoding|type):[ ]
40
- |(?:name|charset)=
41
- |--\z
42
- |:[ ]--------
43
- )
44
- }x.freeze
45
- ReStop = %r{(?:
46
- \A[*][*][*][ ].+[ ].+[ ][*][*][*]
47
- |\Acontent-type:[ ]message/delivery-status
48
- |\Ahere[ ]is[ ]a[ ]copy[ ]of[ ]the[ ]first[ ]part[ ]of[ ]the[ ]message
49
- |\Athe[ ]non-delivered[ ]message[ ]is[ ]attached[ ]to[ ]this[ ]message.
50
- |\Areceived:[ \t]*
51
- |\Areceived-from-mta:[ \t]*
52
- |\Areporting-mta:[ \t]*
53
- |\Areturn-path:[ \t]*
54
- |\Aa[ ]copy[ ]of[ ]the[ ]original[ ]message[ ]below[ ]this[ ]line:
55
- |attachment[ ]is[ ]a[ ]copy[ ]of[ ]the[ ]message
56
- |below[ ]is[ ]a[ ]copy[ ]of[ ]the[ ]original[ ]message:
57
- |below[ ]this[ ]line[ ]is[ ]a[ ]copy[ ]of[ ]the[ ]message
58
- |message[ ]contains[ ].+[ ]file[ ]attachments
59
- |message[ ]text[ ]follows:[ ]
60
- |original[ ]message[ ]follows
61
- |the[ ]attachment[ ]contains[ ]the[ ]original[ ]mail[ ]headers
62
- |the[ ]first[ ]\d+[ ]lines[ ]
63
- |unsent[ ]Message[ ]below
64
- |your[ ]message[ ]reads[ ][(]in[ ]part[)]:
65
- )
66
- }x.freeze
67
- ReAddr = %r{(?:
68
- \A\s*
69
- |\A["].+["]\s*
70
- |\A[ \t]*Recipient:[ \t]*
71
- |\A[ ]*Address:[ ]
72
- |addressed[ ]to[ ]
73
- |Could[ ]not[ ]be[ ]delivered[ ]to:[ ]
74
- |delivered[ ]to[ ]+
75
- |delivery[ ]failed:[ ]
76
- |Did[ ]not[ ]reach[ ]the[ ]following[ ]recipient:[ ]
77
- |Error-for:[ ]+
78
- |Failed[ ]Recipient:[ ]
79
- |Failed[ ]to[ ]deliver[ ]to[ ]
80
- |Intended[ ]recipient:[ ]
81
- |Mailbox[ ]is[ ]full:[ ]
82
- |RCPT[ ]To:
83
- |SMTP[ ]Server[ ][<].+[>][ ]rejected[ ]recipient[ ]
84
- |The[ ]following[ ]recipients[ ]returned[ ]permanent[ ]errors:[ ]
85
- |The[ ]following[ ]message[ ]to[ ]
86
- |Unknown[ ]user:[ ]
87
- |undeliverable[ ]to[ ]
88
- |Undeliverable[ ]Address:[ ]*
89
- |You[ ]sent[ ]mail[ ]to[ ]
90
- |Your[ ]message[ ]to[ ]
91
- )
92
- ['"]?[<]?([^\s\n\r@=<>]+[@][-.0-9A-Za-z]+[.][0-9A-Za-z]+)[>]?['"]?
93
- }xi.freeze
28
+ ReadUntil0 = [
29
+ # Stop reading when the following string have appeared at the first of a line
30
+ 'a copy of the original message below this line:',
31
+ 'content-type: message/delivery-status',
32
+ 'for further assistance, please contact ',
33
+ 'here is a copy of the first part of the message',
34
+ 'received:',
35
+ 'received-from-mta:',
36
+ 'reporting-mta:',
37
+ 'reporting-ua:',
38
+ 'return-path:',
39
+ 'the non-delivered message is attached to this message',
40
+ ].freeze
41
+ ReadUntil1 = [
42
+ # Stop reading when the following string have appeared in a line
43
+ 'attachment is a copy of the message',
44
+ 'below is a copy of the original message:',
45
+ 'below this line is a copy of the message',
46
+ 'message contains ',
47
+ 'message text follows: ',
48
+ 'original message follows',
49
+ 'the attachment contains the original mail headers',
50
+ 'the first ',
51
+ 'unsent message below',
52
+ 'your message reads (in part):',
53
+ ].freeze
54
+ ReadAfter0 = [
55
+ # Do not read before the following strings
56
+ ' the postfix ',
57
+ 'a summary of the undelivered message you sent follows:',
58
+ 'the following is the error message',
59
+ 'the message that you sent was undeliverable to the following',
60
+ 'your message was not delivered to ',
61
+ ].freeze
62
+ DoNotRead0 = [' -----', ' -----', '--', '|--', '*'].freeze
63
+ DoNotRead1 = ['mail from:', 'message-id:', ' from: '].freeze
64
+ ReadEmail0 = [' ', '"', '<',].freeze
65
+ ReadEmail1 = [
66
+ # There is an email address around the following strings
67
+ 'address:',
68
+ 'addressed to',
69
+ 'could not be delivered to:',
70
+ 'delivered to',
71
+ 'delivery failed:',
72
+ 'did not reach the following recipient:',
73
+ 'error-for:',
74
+ 'failed recipient:',
75
+ 'failed to deliver to',
76
+ 'intended recipient:',
77
+ 'mailbox is full:',
78
+ 'recipient:',
79
+ 'rcpt to:',
80
+ 'smtp server <',
81
+ 'the following recipients returned permanent errors:',
82
+ 'the following addresses had permanent errors',
83
+ 'the following message to',
84
+ 'to: ',
85
+ 'unknown user:',
86
+ 'unable to deliver mail to the following recipient',
87
+ 'undeliverable to',
88
+ 'undeliverable address:',
89
+ 'you sent mail to',
90
+ 'your message has encountered delivery problems to the following recipients:',
91
+ 'was automatically rejected',
92
+ 'was rejected due to',
93
+ ].freeze
94
94
 
95
95
  # Detect an error for RFC3464
96
96
  # @param [Hash] mhead Message headers of a bounce email
97
97
  # @param [String] mbody Message body of a bounce email
98
98
  # @return [Hash] Bounce data list and message/rfc822 part
99
99
  # @return [Nil] it failed to parse or the arguments are missing
100
- def make(mhead, mbody)
100
+ def inquire(mhead, mbody)
101
+ fieldtable = Sisimai::RFC1894.FIELDTABLE
102
+ permessage = {} # (Hash) Store values of each Per-Message field
103
+
101
104
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
102
105
  bodyslices = mbody.scrub('?').split("\n")
103
106
  readslices = ['']
104
107
  rfc822text = '' # (String) message/rfc822 part text
105
108
  maybealias = nil # (String) Original-Recipient Field
109
+ lowercased = '' # (String) Lowercased each line of the loop
106
110
  blanklines = 0 # (Integer) The number of blank lines
107
111
  readcursor = 0 # (Integer) Points the current cursor position
108
112
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
@@ -115,14 +119,14 @@ module Sisimai
115
119
  v = nil
116
120
 
117
121
  while e = bodyslices.shift do
118
- # Read error messages and delivery status lines from the head of the email
119
- # to the previous line of the beginning of the original message.
122
+ # Read error messages and delivery status lines from the head of the email to the previous
123
+ # line of the beginning of the original message.
120
124
  readslices << e # Save the current line for the next loop
121
- d = e.downcase
125
+ lowercased = e.downcase
122
126
 
123
127
  if readcursor == 0
124
128
  # Beginning of the bounce message or delivery status part
125
- if d =~ MarkingsOf[:message]
129
+ if StartingOf[:message].any? { |a| lowercased.start_with?(a) }
126
130
  readcursor |= Indicators[:deliverystatus]
127
131
  next
128
132
  end
@@ -130,7 +134,7 @@ module Sisimai
130
134
 
131
135
  if (readcursor & Indicators[:'message-rfc822']) == 0
132
136
  # Beginning of the original message part
133
- if d =~ MarkingsOf[:rfc822]
137
+ if StartingOf[:rfc822].any? { |a| lowercased == a }
134
138
  readcursor |= Indicators[:'message-rfc822']
135
139
  next
136
140
  end
@@ -150,189 +154,75 @@ module Sisimai
150
154
  next if e.empty?
151
155
 
152
156
  v = dscontents[-1]
153
- if cv = e.match(/\A(Final|Original)-[Rr]ecipient:[ ]*.+;[ ]*([^ ]+)\z/)
154
- # 2.3.2 Final-Recipient field
155
- # The Final-Recipient field indicates the recipient for which this set
156
- # of per-recipient fields applies. This field MUST be present in each
157
- # set of per-recipient data.
158
- # The syntax of the field is as follows:
159
- #
160
- # final-recipient-field =
161
- # "Final-Recipient" ":" address-type ";" generic-address
162
- #
163
- # 2.3.1 Original-Recipient field
164
- # The Original-Recipient field indicates the original recipient address
165
- # as specified by the sender of the message for which the DSN is being
166
- # issued.
167
- #
168
- # original-recipient-field =
169
- # "Original-Recipient" ":" address-type ";" generic-address
170
- #
171
- # generic-address = *text
172
- if cv[1] == 'Original'
173
- # Original-Recipient: ...
174
- maybealias = cv[2]
175
- else
176
- # Final-Recipient: ...
177
- x = v['recipient'] || ''
178
- y = Sisimai::Address.s3s4(cv[2])
179
- y = maybealias unless Sisimai::RFC5322.is_emailaddress(y)
180
-
181
- if !x.empty? && x != y
182
- # There are multiple recipient addresses in the message body.
183
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
184
- v = dscontents[-1]
157
+ if f = Sisimai::RFC1894.match(e)
158
+ # "e" matched with any field defined in RFC3464
159
+ next unless o = Sisimai::RFC1894.field(e)
160
+
161
+ if o[-1] == 'addr'
162
+ # Final-Recipient: rfc822; kijitora@example.jp
163
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
164
+ if o[0] == 'final-recipient' || o[0] == 'original-recipient'
165
+ # Final-Recipient: rfc822; kijitora@example.jp
166
+ if o[0] == 'original-recipient'
167
+ # Original-Recipient: ...
168
+ maybealias = o[2]
169
+ else
170
+ # Final-Recipient: ...
171
+ x = v['recipient'] || ''
172
+ y = Sisimai::Address.s3s4(o[2])
173
+ y = maybealias unless Sisimai::Address.is_emailaddress(y)
174
+
175
+ if !x.empty? && x != y
176
+ # There are multiple recipient addresses in the message body.
177
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
178
+ v = dscontents[-1]
179
+ end
180
+ v['recipient'] = y
181
+ recipients += 1
182
+ itisbounce ||= true
183
+
184
+ v['alias'] ||= maybealias
185
+ maybealias = nil
186
+ end
187
+ elsif o[0] == 'x-actual-recipient'
188
+ # X-Actual-Recipient: RFC822; |IFS=' ' && exec procmail -f- || exit 75 ...
189
+ # X-Actual-Recipient: rfc822; kijitora@neko.example.jp
190
+ v['alias'] = o[2] unless o[2].include?(' ')
185
191
  end
186
- v['recipient'] = y
187
- recipients += 1
188
- itisbounce ||= true
192
+ elsif o[-1] == 'code'
193
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
194
+ v['spec'] = o[1]
195
+ v['diagnosis'] = o[2]
196
+ else
197
+ # Other DSN fields defined in RFC3464
198
+ next unless fieldtable[o[0]]
199
+ v[fieldtable[o[0]]] = o[2]
189
200
 
190
- v['alias'] ||= maybealias
191
- maybealias = nil
201
+ next unless f
202
+ permessage[fieldtable[o[0]]] = o[2]
192
203
  end
193
-
194
- elsif cv = e.match(/\AX-Actual-Recipient:[ ]*(?:RFC|rfc)822;[ ]*([^ ]+)\z/)
195
- # X-Actual-Recipient: RFC822; |IFS=' ' && exec procmail -f- || exit 75 ...
196
- # X-Actual-Recipient: rfc822; kijitora@neko.example.jp
197
- v['alias'] = cv[1] unless cv[1] =~ /[ \t]+/
198
-
199
- elsif cv = e.match(/\AAction:[ ]*(.+)\z/)
200
- # 2.3.3 Action field
201
- # The Action field indicates the action performed by the Reporting-MTA
202
- # as a result of its attempt to deliver the message to this recipient
203
- # address. This field MUST be present for each recipient named in the
204
- # DSN.
205
- # The syntax for the action-field is:
206
- #
207
- # action-field = "Action" ":" action-value
208
- # action-value =
209
- # "failed" / "delayed" / "delivered" / "relayed" / "expanded"
210
- #
211
- # The action-value may be spelled in any combination of upper and lower
212
- # case characters.
213
- v['action'] = cv[1].downcase
214
-
215
- # failed (bad destination mailbox address)
216
- if cv = v['action'].match(/\A([^ ]+)[ ]/) then v['action'] = cv[1] end
217
-
218
- elsif cv = e.match(/\AStatus:[ ]*(\d[.]\d+[.]\d+)/)
219
- # 2.3.4 Status field
220
- # The per-recipient Status field contains a transport-independent
221
- # status code that indicates the delivery status of the message to that
222
- # recipient. This field MUST be present for each delivery attempt
223
- # which is described by a DSN.
224
- #
225
- # The syntax of the status field is:
226
- #
227
- # status-field = "Status" ":" status-code
228
- # status-code = DIGIT "." 1*3DIGIT "." 1*3DIGIT
229
- v['status'] = cv[1]
230
-
231
- elsif cv = e.match(/\AStatus:[ ]*(\d+[ ]+.+)\z/)
232
- # Status: 553 Exceeded maximum inbound message size
233
- v['alterrors'] = cv[1]
234
-
235
- elsif cv = e.match(/\ARemote-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
236
- # 2.3.5 Remote-MTA field
237
- # The value associated with the Remote-MTA DSN field is a printable
238
- # ASCII representation of the name of the "remote" MTA that reported
239
- # delivery status to the "reporting" MTA.
240
- #
241
- # remote-mta-field = "Remote-MTA" ":" mta-name-type ";" mta-name
242
- #
243
- # NOTE: The Remote-MTA field preserves the "while talking to"
244
- # information that was provided in some pre-existing nondelivery
245
- # reports.
246
- #
247
- # This field is optional. It MUST NOT be included if no remote MTA was
248
- # involved in the attempted delivery of the message to that recipient.
249
- v['rhost'] = cv[1].downcase
250
-
251
- elsif cv = e.match(/\ALast-Attempt-Date:[ ]*(.+)\z/)
252
- # 2.3.7 Last-Attempt-Date field
253
- # The Last-Attempt-Date field gives the date and time of the last
254
- # attempt to relay, gateway, or deliver the message (whether successful
255
- # or unsuccessful) by the Reporting MTA. This is not necessarily the
256
- # same as the value of the Date field from the header of the message
257
- # used to transmit this delivery status notification: In cases where
258
- # the DSN was generated by a gateway, the Date field in the message
259
- # header contains the time the DSN was sent by the gateway and the DSN
260
- # Last-Attempt-Date field contains the time the last delivery attempt
261
- # occurred.
262
- #
263
- # last-attempt-date-field = "Last-Attempt-Date" ":" date-time
264
- v['date'] = cv[1]
265
204
  else
266
- if cv = e.match(/\ADiagnostic-Code:[ ]*(.+?);[ ]*(.+)\z/)
267
- # 2.3.6 Diagnostic-Code field
268
- # For a "failed" or "delayed" recipient, the Diagnostic-Code DSN field
269
- # contains the actual diagnostic code issued by the mail transport.
270
- # Since such codes vary from one mail transport to another, the
271
- # diagnostic-type sub-field is needed to specify which type of
272
- # diagnostic code is represented.
273
- #
274
- # diagnostic-code-field =
275
- # "Diagnostic-Code" ":" diagnostic-type ";" *text
276
- v['spec'] = cv[1].upcase
277
- v['diagnosis'] = cv[2]
278
-
279
- elsif cv = e.match(/\ADiagnostic-Code:[ ]*(.+)\z/)
280
- # No value of "diagnostic-type"
281
- # Diagnostic-Code: 554 ...
282
- v['diagnosis'] = cv[1]
283
-
284
- elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.match(/\A[ \t]+(.+)\z/)
205
+ # The line did not match with any fields defined in RFC3464
206
+ if e.start_with?('Diagnostic-Code: ') && e.include?(';') == false
207
+ # There is no value of "diagnostic-type" such as Diagnostic-Code: 554 ...
208
+ v['diagnosis'] = e[e.index(' ') + 1, e.size]
209
+
210
+ elsif e.start_with?('Status: ') && Sisimai::SMTP::Reply.find(e[8, 3])
211
+ # Status: 553 Exceeded maximum inbound message size
212
+ v['alterrors'] = e[8, e.size]
213
+
214
+ elsif readslices[-2].start_with?('Diagnostic-Code:') && cv = e.start_with?(' ')
285
215
  # Continued line of the value of Diagnostic-Code header
286
- v['diagnosis'] << ' ' << cv[1]
216
+ v['diagnosis'] << ' ' << e
287
217
  readslices[-1] = 'Diagnostic-Code: ' << e
288
218
  else
289
- if cv = e.match(/\AReporting-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
290
- # 2.2.2 The Reporting-MTA DSN field
291
- #
292
- # reporting-mta-field =
293
- # "Reporting-MTA" ":" mta-name-type ";" mta-name
294
- # mta-name = *text
295
- #
296
- # The Reporting-MTA field is defined as follows:
297
- #
298
- # A DSN describes the results of attempts to deliver, relay, or gateway
299
- # a message to one or more recipients. In all cases, the Reporting-MTA
300
- # is the MTA that attempted to perform the delivery, relay, or gateway
301
- # operation described in the DSN. This field is required.
302
- connheader['rhost'] ||= cv[1].downcase
303
-
304
- elsif cv = e.match(/\AReceived-From-MTA:[ ]*(?:DNS|dns);[ ]*(.+)\z/)
305
- # 2.2.4 The Received-From-MTA DSN field
306
- # The optional Received-From-MTA field indicates the name of the MTA
307
- # from which the message was received.
308
- #
309
- # received-from-mta-field =
310
- # "Received-From-MTA" ":" mta-name-type ";" mta-name
311
- #
312
- # If the message was received from an Internet host via SMTP, the
313
- # contents of the mta-name sub-field SHOULD be the Internet domain name
314
- # supplied in the HELO or EHLO command, and the network address used by
315
- # the SMTP client SHOULD be included as a comment enclosed in
316
- # parentheses. (In this case, the MTA-name-type will be "dns".)
317
- connheader['lhost'] = cv[1].downcase
318
-
319
- elsif cv = e.match(/\AArrival-Date:[ ]*(.+)\z/)
320
- # 2.2.5 The Arrival-Date DSN field
321
- # The optional Arrival-Date field indicates the date and time at which
322
- # the message arrived at the Reporting MTA. If the Last-Attempt-Date
323
- # field is also provided in a per-recipient field, this can be used to
324
- # determine the interval between when the message arrived at the
325
- # Reporting MTA and when the report was issued for that recipient.
326
- #
327
- # arrival-date-field = "Arrival-Date" ":" date-time
328
- connheader['date'] = cv[1]
329
- else
330
- # Get error message
331
- next if e.start_with?(' ', '-')
332
- next unless e =~ MarkingsOf[:error]
333
-
334
- # 500 User Unknown
335
- # <kijitora@example.jp> Unknown
219
+ # Get error messages which is written in the message body directly
220
+ next if e.start_with?(' ', ' ', 'X')
221
+ cr = Sisimai::SMTP::Reply.find(e) || ''
222
+ ca = Sisimai::Address.find(e) || []
223
+ co = Sisimai::String.aligned(e, ['<', '@', '>'])
224
+
225
+ if cr.size > 0 || (ca.size > 0 && co)
336
226
  v['alterrors'] ||= ' '
337
227
  v['alterrors'] << ' ' << e
338
228
  end
@@ -341,50 +231,101 @@ module Sisimai
341
231
  end # End of if: rfc822
342
232
  end
343
233
 
234
+ # -----------------------------------------------------------------------------------------
344
235
  while true
345
236
  # Fallback, parse entire message body
346
237
  break if recipients > 0
347
- match = 0
348
- mfrom = mhead['from'].downcase
349
238
 
350
239
  # Failed to get a recipient address at code above
351
- match += 1 if mfrom.include?('postmaster@') || mfrom.include?('mailer-daemon@') || mfrom.include?('root@')
352
- match += 1 if mhead['subject'].downcase =~ %r{(?>
353
- delivery[ ](?:failed|failure|report)
354
- |failure[ ]notice
355
- |mail[ ](?:delivery|error)
356
- |non[-]delivery
357
- |returned[ ]mail
358
- |undeliverable[ ]mail
359
- |warning:[ ]
360
- )
361
- }x
362
-
363
- if mhead['return-path']
364
- # Check the value of Return-Path of the message
365
- rpath = mhead['return-path'].downcase
366
- match += 1 if rpath.include?('<>') || rpath.include?('mailer-daemon')
367
- end
368
- break unless match > 0
240
+ returnpath = (mhead['return-path'] || '').downcase
241
+ headerfrom = (mhead['from'] || '').downcase
242
+ errortitle = (mhead['subject'] || '').downcase
243
+ patternsof = {
244
+ 'from' => ['postmaster@', 'mailer-daemon@', 'root@'],
245
+ 'return-path' => ['<>', 'mailer-daemon'],
246
+ 'subject' => ['delivery fail', 'delivery report', 'failure notice', 'mail delivery',
247
+ 'mail failed', 'mail error', 'non-delivery', 'returned mail',
248
+ 'undeliverable mail', 'warning: '],
249
+ }
250
+
251
+ match = nil
252
+ match ||= patternsof['from'].any? { |v| headerfrom.include?(v) }
253
+ match ||= patternsof['subject'].any? { |v| errortitle.include?(v) }
254
+ match ||= patternsof['return-path'].any? { |v| returnpath.include?(v) }
255
+ break unless match
369
256
 
370
257
  b = dscontents[-1]
258
+ hasmatched = 0 # There may be an email address around the line
259
+ readslices = [] # Previous line of this loop
260
+
261
+ ReadAfter0.each do |e|
262
+ # Cut strings from the begining of "mbody" to the strings defined in ReadAfter0
263
+ i = mbody.downcase.index(e)
264
+ next unless i
265
+ mbody = mbody[i, mbody.size - i]
266
+ end
267
+ lowercased = mbody.downcase
371
268
  bodyslices = mbody.split("\n")
269
+
372
270
  while e = bodyslices.shift do
373
271
  # Get the recipient's email address and error messages.
374
- d = e.downcase
375
- break if d =~ MarkingsOf[:rfc822]
376
- break if d =~ ReStop
377
-
378
272
  next if e.empty?
379
- next if d =~ ReSkip
380
- next if e.start_with?('*')
273
+ hasmatched = 0
274
+ lowercased = e.downcase
275
+ readslices << lowercased
276
+
277
+ break if StartingOf[:rfc822].include?(lowercased)
278
+ break if ReadUntil0.any? { |v| lowercased.start_with?(v) }
279
+ break if ReadUntil1.any? { |v| lowercased.include?(v) }
280
+ next if DoNotRead0.any? { |v| lowercased.start_with?(v) }
281
+ next if DoNotRead1.any? { |v| lowercased.include?(v) }
282
+
283
+ while true do
284
+ # There is an email address with an error message at this line(1)
285
+ break unless ReadEmail0.any? { |v| lowercased.start_with?(v) }
286
+ break unless lowercased.include?('@')
287
+
288
+ hasmatched = 1
289
+ break
290
+ end
291
+
292
+ while true do
293
+ # There is an email address with an error message at this line(2)
294
+ break if hasmatched > 0
295
+ break unless ReadEmail1.any? { |v| lowercased.include?(v) }
296
+ break unless lowercased.include?('@')
297
+
298
+ hasmatched = 2
299
+ break
300
+ end
381
301
 
382
- if cv = e.match(ReAddr)
302
+ while true do
303
+ # There is an email address without an error message at this line
304
+ break if hasmatched > 0
305
+ break if readslices.size < 2
306
+ break unless ReadEmail1.any? { |v| readslices[-2].include?(v) }
307
+ break unless lowercased.include?('@') # Must contain '@'
308
+ break unless lowercased.include?('.') # Must contain '.'
309
+ break if lowercased.include?('$')
310
+
311
+ hasmatched = 3
312
+ break
313
+ end
314
+
315
+ if hasmatched > 0 && lowercased.include?('@')
383
316
  # May be an email address
317
+ w = e.split(' ')
384
318
  x = b['recipient'] || ''
385
- y = Sisimai::Address.s3s4(cv[1])
386
- next unless Sisimai::RFC5322.is_emailaddress(y)
387
-
319
+ y = ''
320
+
321
+ w.each do |ee|
322
+ # Find an email address (including "@")
323
+ next unless ee.include?('@')
324
+ y = Sisimai::Address.s3s4(ee)
325
+ next unless Sisimai::Address.is_emailaddress(y)
326
+ break
327
+ end
328
+
388
329
  if !x.empty? && x != y
389
330
  # There are multiple recipient addresses in the message body.
390
331
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
@@ -394,9 +335,9 @@ module Sisimai
394
335
  recipients += 1
395
336
  itisbounce ||= true
396
337
 
397
- elsif cv = e.match(/[(](?:expanded|generated)[ ]from:?[ ]([^@]+[@][^@]+)[)]/)
338
+ elsif e.include?('(expanded from') || e.include?('(generated from')
398
339
  # (expanded from: neko@example.jp)
399
- b['alias'] = Sisimai::Address.s3s4(cv[1])
340
+ b['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size])
400
341
  end
401
342
  b['diagnosis'] ||= ''
402
343
  b['diagnosis'] << ' ' << e
@@ -406,9 +347,11 @@ module Sisimai
406
347
  end
407
348
  return nil unless itisbounce
408
349
 
409
- if recipients == 0 && cv = rfc822text.match(/^To:[ ]*(.+)/)
350
+ p1 = rfc822text.index("\nTo: ") || -1
351
+ p2 = rfc822text.index("\n", p1 + 6) || -1
352
+ if recipients == 0 && p1 > 0
410
353
  # Try to get a recipient address from "To:" header of the original message
411
- if r = Sisimai::Address.find(cv[1], true)
354
+ if r = Sisimai::Address.find(rfc822text[p1 + 5, p2 - p1 - 5], true)
412
355
  # Found a recipient address
413
356
  dscontents << Sisimai::Lhost.DELIVERYSTATUS if dscontents.size == recipients
414
357
  b = dscontents[-1]
@@ -418,8 +361,9 @@ module Sisimai
418
361
  end
419
362
  return nil unless recipients > 0
420
363
 
364
+ require 'sisimai/smtp/command'
421
365
  require 'sisimai/mda'
422
- mdabounced = Sisimai::MDA.make(mhead, mbody)
366
+ mdabounced = Sisimai::MDA.inquire(mhead, mbody)
423
367
  dscontents.each do |e|
424
368
  # Set default values if each value is empty.
425
369
  connheader.each_key { |a| e[a] ||= connheader[a] || '' }
@@ -438,18 +382,16 @@ module Sisimai
438
382
  e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
439
383
 
440
384
  if mdabounced
441
- # Make bounce data by the values returned from Sisimai::MDA.make()
385
+ # Make bounce data by the values returned from Sisimai::MDA.inquire()
442
386
  e['agent'] = mdabounced['mda'] || 'RFC3464'
443
387
  e['reason'] = mdabounced['reason'] || 'undefined'
444
388
  e['diagnosis'] = mdabounced['message'] unless mdabounced['message'].empty?
445
- e['command'] = ''
389
+ e['command'] = nil
446
390
  end
447
391
 
448
- e['status'] ||= Sisimai::SMTP::Status.find(e['diagnosis']) || ''
449
- if cv = e['diagnosis'].match(MarkingsOf[:command])
450
- e['command'] = cv[1]
451
- end
452
- e['date'] ||= mhead['date']
392
+ e['date'] ||= mhead['date']
393
+ e['status'] ||= Sisimai::SMTP::Status.find(e['diagnosis']) || ''
394
+ e['command'] ||= Sisimai::SMTP::Command.find(e['diagnosis'])
453
395
  end
454
396
 
455
397
  return { 'ds' => dscontents, 'rfc822' => rfc822text }