sisimai 4.25.15 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) 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 +419 -388
  7. data/Developers.mk +5 -6
  8. data/Gemfile +1 -1
  9. data/Makefile +15 -15
  10. data/README-JA.md +140 -78
  11. data/README.md +290 -143
  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 +23 -18
  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 -313
  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 -47
  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 +12 -12
  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 -20
  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/bsd/{rfc3464-41.eml → rfc3834-05.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{rhost-googleapps-01.eml → rhost-google-01.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{rhost-googleapps-02.eml → rhost-google-02.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-01.eml → rhost-microsoft-01.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-02.eml → rhost-microsoft-02.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{rhost-exchangeonline-03.eml → rhost-microsoft-03.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-01.eml → rhost-tencent-01.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{rhost-tencentqq-02.eml → rhost-tencent-02.eml} +0 -0
  177. /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 }