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,24 +1,21 @@
1
1
  module Sisimai::Lhost
2
- # Sisimai::Lhost::Exim parses a bounce email which created by Exim.
3
- # Methods in the module are called from only Sisimai::Message.
2
+ # Sisimai::Lhost::Exim parses a bounce email which created by Exim. Methods in the module are called
3
+ # from only Sisimai::Message.
4
4
  module Exim
5
5
  class << self
6
- # Imported from p5-Sisimail/lib/Sisimai/Lhost/Exim.pm
7
6
  require 'sisimai/lhost'
8
7
 
9
8
  Indicators = Sisimai::Lhost.INDICATORS
10
- # deliver.c:6423| if (bounce_return_body) fprintf(f,
11
- # deliver.c:6424|"------ This is a copy of the message, including all the headers. ------\n");
12
- # deliver.c:6425| else fprintf(f,
13
- # deliver.c:6426|"------ This is a copy of the message's headers. ------\n");
14
- ReBackbone = %r{^(?:
15
- [-]+[ ]This[ ]is[ ]a[ ]copy[ ]of[ ](?:the|your)[ ]message,[ ]including[ ]all[ ]the[ ]headers[.][ ][-]+
16
- |Content-Type:[ ]*message/rfc822\n(?:[\s\t]+.*?\n\n)?
17
- )
18
- }x.freeze
19
- StartingOf = { deliverystatus: ['Content-type: message/delivery-status'] }.freeze
20
- MarkingsOf = {
21
- # Error text regular expressions which defined in exim/src/deliver.c
9
+ Boundaries = [
10
+ # deliver.c:6423| if (bounce_return_body) fprintf(f,
11
+ # deliver.c:6424|"------ This is a copy of the message, including all the headers. ------\n");
12
+ # deliver.c:6425| else fprintf(f,
13
+ # deliver.c:6426|"------ This is a copy of the message's headers. ------\n");
14
+ '------ This is a copy of the message, including all the headers. ------',
15
+ 'Content-Type: message/rfc822',
16
+ ].freeze
17
+ StartingOf = {
18
+ # Error text strings which defined in exim/src/deliver.c
22
19
  #
23
20
  # deliver.c:6292| fprintf(f,
24
21
  # deliver.c:6293|"This message was created automatically by mail delivery software.\n");
@@ -35,19 +32,19 @@ module Sisimai::Lhost
35
32
  # deliver.c:6304|"could not be delivered to one or more of its recipients. The following\n"
36
33
  # deliver.c:6305|"address(es) failed:\n", sender_address);
37
34
  # deliver.c:6306| }
38
- alias: %r/\A([ ]+an undisclosed address)\z/,
39
- frozen: %r/\AMessage [^ ]+ (?:has been frozen|was frozen on arrival)/,
40
- message: %r{\A(?>
41
- This[ ]message[ ]was[ ]created[ ]automatically[ ]by[ ]mail[ ]delivery[ ]software[.]
42
- |A[ ]message[ ]that[ ]you[ ]sent[ ]was[ ]rejected[ ]by[ ]the[ ]local[ ]scanning[ ]code
43
- |A[ ]message[ ]that[ ]you[ ]sent[ ]contained[ ]one[ ]or[ ]more[ ]recipient[ ]addresses[ ]
44
- |A[ ]message[ ]that[ ]you[ ]sent[ ]could[ ]not[ ]be[ ]delivered[ ]to[ ]all[ ]of[ ]its[ ]recipients
45
- |Message[ ][^ ]+[ ](?:has[ ]been[ ]frozen|was[ ]frozen[ ]on[ ]arrival)
46
- |The[ ][^ ]+[ ]router[ ]encountered[ ]the[ ]following[ ]error[(]s[)]:
47
- )
48
- }x,
35
+ deliverystatus: ['Content-Type: message/delivery-status'],
36
+ frozen: [' has been frozen', ' was frozen on arrival'],
37
+ message: [
38
+ 'This message was created automatically by mail delivery software.',
39
+ 'A message that you sent was rejected by the local scannning code',
40
+ 'A message that you sent contained one or more recipient addresses ',
41
+ 'A message that you sent could not be delivered to all of its recipients',
42
+ ' has been frozen',
43
+ ' was frozen on arrival',
44
+ ' router encountered the following error(s):',
45
+ ],
49
46
  }.freeze
50
-
47
+ MarkingsOf = { alias: ' an undisclosed address' }.freeze
51
48
  ReCommands = [
52
49
  # transports/smtp.c:564| *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
53
50
  # transports/smtp.c:837| string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
@@ -125,30 +122,28 @@ module Sisimai::Lhost
125
122
  # @param [String] mbody Message body of a bounce email
126
123
  # @return [Hash] Bounce data list and message/rfc822 part
127
124
  # @return [Nil] it failed to parse or the arguments are missing
128
- def make(mhead, mbody)
129
- return nil if mhead['from'] =~ /[@].+[.]mail[.]ru[>]?/
125
+ def inquire(mhead, mbody)
126
+ return nil if mhead['from'].include?('.mail.ru')
130
127
 
131
128
  # Message-Id: <E1P1YNN-0003AD-Ga@example.org>
132
129
  # X-Failed-Recipients: kijitora@example.ed.jp
133
130
  match = 0
131
+ msgid = mhead['message-id'] || ''
134
132
  match += 1 if mhead['from'].start_with?('Mail Delivery System')
135
- match += 1 if mhead['message-id'].to_s =~ %r/\A[<]\w{7}[-]\w{6}[-]\w{2}[@]/
136
- match += 1 if mhead['subject'] =~ %r{(?:
137
- Mail[ ]delivery[ ]failed(:[ ]returning[ ]message[ ]to[ ]sender)?
138
- |Warning:[ ]message[ ][^ ]+[ ]delayed[ ]+
139
- |Delivery[ ]Status[ ]Notification
140
- |Mail[ ]failure
141
- |Message[ ]frozen
142
- |error[(]s[)][ ]in[ ]forwarding[ ]or[ ]filtering
143
- )
144
- }x
133
+ match += 1 if msgid.start_with?('<') && msgid.index('-') == 8 && msgid.index('@') == 18
134
+ match += 1 if %w[
135
+ 'Delivery Status Notification',
136
+ 'Mail delivery failed',
137
+ 'Mail failure',
138
+ 'Message frozen',
139
+ 'Warning: message ',
140
+ 'error(s) in forwarding or filtering'].any? { |a| mhead['subject'].include?(a) }
145
141
  return nil if match < 2
146
142
 
147
- require 'sisimai/rfc1894'
148
143
  fieldtable = Sisimai::RFC1894.FIELDTABLE
149
144
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
150
- emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
151
- bodyslices = emailsteak[0].split("\n")
145
+ emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
146
+ bodyslices = emailparts[0].split("\n")
152
147
  readcursor = 0 # (Integer) Points the current cursor position
153
148
  nextcursor = 0
154
149
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
@@ -157,19 +152,18 @@ module Sisimai::Lhost
157
152
  v = nil
158
153
 
159
154
  if mhead['content-type']
160
- # Get the boundary string and set regular expression for matching with
161
- # the boundary string.
162
- boundary00 = Sisimai::MIME.boundary(mhead['content-type']) || ''
155
+ # Get the boundary string and set regular expression for matching with the boundary string.
156
+ boundary00 = Sisimai::RFC2045.boundary(mhead['content-type']) || ''
163
157
  end
164
158
 
165
159
  while e = bodyslices.shift do
166
- # Read error messages and delivery status lines from the head of the email
167
- # to the previous line of the beginning of the original message.
160
+ # Read error messages and delivery status lines from the head of the email to the previous
161
+ # line of the beginning of the original message.
168
162
  if readcursor == 0
169
163
  # Beginning of the bounce message or message/delivery-status part
170
- if e =~ MarkingsOf[:message]
164
+ if StartingOf[:message].any? { |a| e.include?(a) }
171
165
  readcursor |= Indicators[:deliverystatus]
172
- next unless e =~ MarkingsOf[:frozen]
166
+ next unless StartingOf[:frozen].any? { |a| e.include?(a) }
173
167
  end
174
168
  end
175
169
  next if (readcursor & Indicators[:deliverystatus]) == 0
@@ -185,109 +179,130 @@ module Sisimai::Lhost
185
179
  # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown
186
180
  v = dscontents[-1]
187
181
 
188
- if cv = e.match(/\A[ \t]{2}([^ \t]+[@][^ \t]+[.]?[a-zA-Z]+)(:.+)?\z/) ||
189
- e.match(/\A[ \t]{2}[^ \t]+[@][^ \t]+[.][a-zA-Z]+[ ]<(.+?[@].+?)>:.+\z/) ||
190
- e.match(MarkingsOf[:alias])
191
- # kijitora@example.jp
192
- # sabineko@example.jp: forced freeze
193
- # mikeneko@example.jp <nekochan@example.org>: ...
194
- #
195
- # deliver.c:4549| printed = US"an undisclosed address";
196
- # an undisclosed address
197
- # (generated from kijitora@example.jp)
198
- r = cv[1]
182
+ cv = ''
183
+ ce = false
184
+ while true
185
+ # Check if the line matche the following patterns:
186
+ break unless e.start_with?(' ') # The line should start with " " (2 spaces)
187
+ break unless e.include?('@') # "@" should be included (email)
188
+ break unless e.include?('.') # "." should be included (domain part)
189
+ break unless e.include?('pipe to |') == false # Exclude "pipe to /path/to/prog" line
190
+
191
+ break unless e[2, 1] != ' '
192
+ break unless e[2, 1] != '<'
193
+ ce = true
194
+ break
195
+ end
199
196
 
197
+ if ce || e.include?(MarkingsOf[:alias])
198
+ # The line is including an email address
200
199
  if v['recipient']
201
200
  # There are multiple recipient addresses in the message body.
202
201
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
203
202
  v = dscontents[-1]
204
203
  end
205
- # v['recipient'] = cv[1]
206
-
207
- if cv = e.match(/\A[ \t]+[^ \t]+[@][^ \t]+[.][a-zA-Z]+[ ]<(.+?[@].+?)>:.+\z/)
208
- # parser.c:743| while (bracket_count-- > 0) if (*s++ != '>')
209
- # parser.c:744| {
210
- # parser.c:745| *errorptr = s[-1] == 0
211
- # parser.c:746| ? US"'>' missing at end of address"
212
- # parser.c:747| : string_sprintf("malformed address: %.32s may not follow %.*s",
213
- # parser.c:748| s-1, (int)(s - US mailbox - 1), mailbox);
214
- # parser.c:749| goto PARSE_FAILED;
215
- # parser.c:750| }
216
- r = cv[1]
217
- v['diagnosis'] = e
204
+
205
+ if e.include?(MarkingsOf[:alias])
206
+ # The line does not include an email address
207
+ # deliver.c:4549| printed = US"an undisclosed address";
208
+ # an undisclosed address
209
+ # (generated from kijitora@example.jp)
210
+ cv = e[2, e.size]
211
+ else
212
+ # kijitora@example.jp
213
+ # sabineko@example.jp: forced freeze
214
+ # mikeneko@example.jp <nekochan@example.org>: ...
215
+ p1 = e.index(' <') || -1
216
+ p2 = e.index('>:') || -1
217
+
218
+ if p1 > 1 && p2 > 1
219
+ # There are an email address and an error message in the line
220
+ # parser.c:743| while (bracket_count-- > 0) if (*s++ != '>')
221
+ # parser.c:744| {
222
+ # parser.c:745| *errorptr = s[-1] == 0
223
+ # parser.c:746| ? US"'>' missing at end of address"
224
+ # parser.c:747| : string_sprintf("malformed address: %.32s may not follow %.*s",
225
+ # parser.c:748| s-1, (int)(s - US mailbox - 1), mailbox);
226
+ # parser.c:749| goto PARSE_FAILED;
227
+ # parser.c:750| }
228
+ cv = Sisimai::Address.s3s4(e[p1 + 1, p2 - p1 - 1])
229
+ v['diagnosis'] = Sisimai::String.sweep(e[p2 + 1, e.size])
230
+ else
231
+ # There is an email address only in the line
232
+ # kijitora@example.jp
233
+ cv = Sisimai::Address.s3s4(e[2, e.size])
234
+ end
218
235
  end
219
- v['recipient'] = r
236
+ v['recipient'] = cv
220
237
  recipients += 1
221
238
 
222
- elsif cv = e.match(/\A[ ]+[(]generated[ ]from[ ](.+)[)]\z/) ||
223
- e.match(/\A[ ]+generated[ ]by[ ]([^ \t]+[@][^ \t]+)/)
239
+ elsif e.include?(' (generated from ') || e.include?(' generated by ')
224
240
  # (generated from kijitora@example.jp)
225
241
  # pipe to |/bin/echo "Some pipe output"
226
242
  # generated by userx@myhost.test.ex
227
- v['alias'] = cv[1]
243
+ v['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size])
244
+
228
245
  else
229
246
  next if e.empty?
230
247
 
231
- if e =~ MarkingsOf[:frozen]
248
+ if StartingOf[:frozen].any? { |a| e.include?(a) }
232
249
  # Message *** has been frozen by the system filter.
233
250
  # Message *** was frozen on arrival by ACL.
234
251
  v['alterrors'] ||= ''
235
252
  v['alterrors'] << e + ' '
236
- else
237
- if !boundary00.empty?
238
- # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
239
- # Content-type: message/delivery-status
240
- # ...
241
- if Sisimai::RFC1894.match(e)
242
- # "e" matched with any field defined in RFC3464
243
- next unless o = Sisimai::RFC1894.field(e)
244
-
245
- if o[-1] == 'addr'
246
- # Final-Recipient: rfc822; kijitora@example.jp
247
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
248
- next unless o[0] == 'final-recipient'
249
- v['spec'] ||= o[2].include?('@') ? 'SMTP' : 'X-UNIX'
250
-
251
- elsif o[-1] == 'code'
252
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
253
- v['spec'] = o[1]
254
- v['diagnosis'] = o[2]
255
-
256
- else
257
- # Other DSN fields defined in RFC3464
258
- next unless fieldtable[o[0]]
259
- v[fieldtable[o[0]]] = o[2]
260
- end
253
+ elsif !boundary00.empty?
254
+ # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
255
+ # Content-type: message/delivery-status
256
+ # ...
257
+ if Sisimai::RFC1894.match(e)
258
+ # "e" matched with any field defined in RFC3464
259
+ next unless o = Sisimai::RFC1894.field(e)
260
+
261
+ if o[-1] == 'addr'
262
+ # Final-Recipient: rfc822; kijitora@example.jp
263
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
264
+ next unless o[0] == 'final-recipient'
265
+ v['spec'] ||= o[2].include?('@') ? 'SMTP' : 'X-UNIX'
266
+
267
+ elsif o[-1] == 'code'
268
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
269
+ v['spec'] = o[1]
270
+ v['diagnosis'] = o[2]
271
+
261
272
  else
262
- # Error message ?
263
- next if nextcursor == 1
273
+ # Other DSN fields defined in RFC3464
274
+ next unless fieldtable[o[0]]
275
+ v[fieldtable[o[0]]] = o[2]
276
+ end
277
+ else
278
+ # Error message ?
279
+ next if nextcursor == 1
264
280
 
265
- # Content-type: message/delivery-status
266
- nextcursor = 1 if e.start_with?(StartingOf[:deliverystatus][0])
267
- v['alterrors'] ||= ''
268
- if e.start_with?("\s", "\t")
269
- e.sub!(/\A[\s\t]+/, '')
270
- v['alterrors'] << e + ' ' unless v['alterrors'].include?(e)
271
- end
281
+ # Content-type: message/delivery-status
282
+ nextcursor = 1 if e.start_with?(StartingOf[:deliverystatus][0])
283
+ v['alterrors'] ||= ''
284
+ if e.start_with?("\s", "\t")
285
+ e.sub!(/\A[\s\t]+/, '')
286
+ v['alterrors'] << e + ' ' unless v['alterrors'].include?(e)
272
287
  end
288
+ end
289
+ else
290
+ # There is no boundary string in $boundary00
291
+ if dscontents.size == recipients
292
+ # Error message
293
+ next if e.empty?
294
+ v['diagnosis'] ||= ''
295
+ v['diagnosis'] << e + ' '
273
296
  else
274
- if dscontents.size == recipients
275
- # Error message
276
- next if e.empty?
277
- v['diagnosis'] ||= ''
278
- v['diagnosis'] << e + ' '
297
+ # Error message when email address above does not include '@' and domain part.
298
+ if e.include?(' pipe to |/')
299
+ # pipe to |/path/to/prog ...
300
+ # generated by kijitora@example.com
301
+ v['diagnosis'] = e
279
302
  else
280
- # Error message when email address above does not include '@'
281
- # and domain part.
282
- if e =~ %r<\A[ ]+pipe[ ]to[ ][|]/[^ ]+>
283
- # pipe to |/path/to/prog ...
284
- # generated by kijitora@example.com
285
- v['diagnosis'] = e
286
- else
287
- next unless e.start_with?(' ')
288
- v['alterrors'] ||= ''
289
- v['alterrors'] << e + ' '
290
- end
303
+ next unless e.start_with?(' ')
304
+ v['alterrors'] ||= ''
305
+ v['alterrors'] << e + ' '
291
306
  end
292
307
  end
293
308
  end
@@ -330,7 +345,9 @@ module Sisimai::Lhost
330
345
  unless mhead['received'].empty?
331
346
  # Get the name of local MTA
332
347
  # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
333
- if cv = mhead['received'][-1].match(/from[ \t]([^ ]+)/) then localhost0 = cv[1] end
348
+ p1 = mhead['received'][-1].index('from ') || -1
349
+ p2 = mhead['received'][-1].index(' ', p1 + 5) || -1
350
+ localhost0 = mhead['received'][-1][p1 + 5, p2 - p1 - 5] if p1 > -1 && p2 > -1
334
351
  end
335
352
 
336
353
  dscontents.each do |e|
@@ -371,25 +388,23 @@ module Sisimai::Lhost
371
388
  if e['diagnosis'].start_with?('-') || e['diagnosis'].end_with?('__')
372
389
  # Override the value of diagnostic code message
373
390
  e['diagnosis'] = e['alterrors'] unless e['alterrors'].empty?
374
- else
375
- # Check the both value and try to match
376
- if e['diagnosis'].size < e['alterrors'].size
377
- # Check the value of alterrors
378
- rxdiagnosis = %r/e['diagnosis']/i
379
- # Override the value of diagnostic code message because
380
- # the value of alterrors includes the value of diagnosis.
381
- e['diagnosis'] = e['alterrors'] if e['alterrors'].downcase.include?(e['diagnosis'].downcase)
382
- end
391
+
392
+ elsif e['diagnosis'].size < e['alterrors'].size
393
+ # Override the value of diagnostic code message because the value of alterrors includes
394
+ # the value of diagnosis.
395
+ e['diagnosis'] = e['alterrors'] if e['alterrors'].downcase.include?(e['diagnosis'].downcase)
383
396
  end
384
397
  e.delete('alterrors')
385
398
  end
386
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''
387
- e['diagnosis'].sub!(/\b__.+\z/, '')
399
+ e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''; p1 = e['diagnosis'].index('__') || -1
400
+ e['diagnosis'] = e['diagnosis'][0, p1] if p1 > 1
388
401
 
389
402
  unless e['rhost']
390
403
  # Get the remote host name
391
404
  # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown
392
- if cv = e['diagnosis'].match(/host[ \t]+([^ \t]+)[ \t]\[.+\]:[ \t]/) then e['rhost'] = cv[1] end
405
+ p1 = e['diagnosis'].index('host ') || -1
406
+ p2 = e['diagnosis'].index(' ', p1 + 5) || -1
407
+ e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
393
408
 
394
409
  unless e['rhost']
395
410
  # Get localhost and remote host name from Received header.
@@ -426,55 +441,54 @@ module Sisimai::Lhost
426
441
 
427
442
  unless e['reason']
428
443
  # The reason "expired"
429
- e['reason'] = 'expired' if DelayedFor.any? { |a| e['diagnosis'].include?(a) }
444
+ e['reason'] = 'expired' if DelayedFor.any? { |a| e['diagnosis'].include?(a) }
445
+ e['reason'] ||= 'mailererror' if e['diagnosis'].include?('pipe to |')
430
446
  end
431
447
  end
432
448
  end
433
449
 
434
450
  # Prefer the value of smtp reply code in Diagnostic-Code:
435
- # See eg/maildir-as-a-sample/new/exim-20.eml
436
451
  # Action: failed
437
452
  # Final-Recipient: rfc822;userx@test.ex
438
453
  # Status: 5.0.0
439
454
  # Remote-MTA: dns; 127.0.0.1
440
455
  # Diagnostic-Code: smtp; 450 TEMPERROR: retry timeout exceeded
441
- # The value of "Status:" indicates permanent error but the value
442
- # of SMTP reply code in Diagnostic-Code: field is "TEMPERROR"!!!!
443
- sv = e['status'] || Sisimai::SMTP::Status.find(e['diagnosis'])
444
- rv = e['replycode'] || Sisimai::SMTP::Reply.find(e['diagnosis'])
456
+ #
457
+ # The value of "Status:" indicates permanent error but the value of SMTP reply code in
458
+ # Diagnostic-Code: field is "TEMPERROR"!!!!
459
+ cs = e['status'] || Sisimai::SMTP::Status.find(e['diagnosis'])
460
+ cr = e['replycode'] || Sisimai::SMTP::Reply.find(e['diagnosis'])
445
461
  s1 = 0 # First character of Status as integer
446
462
  r1 = 0 # First character of SMTP reply code as integer
447
463
 
448
464
  while true
449
465
  # "Status:" field did not exist in the bounce message
450
- break if sv
451
- break unless rv
466
+ break if cs
467
+ break unless cr
452
468
 
453
- # Check SMTP reply code
454
- # Generate pseudo DSN code from SMTP reply code
455
- r1 = rv[0, 1].to_i
469
+ # Check SMTP reply code, Generate pseudo DSN code from SMTP reply code
470
+ r1 = cr[0, 1].to_i
456
471
  if r1 == 4
457
472
  # Get the internal DSN(temporary error)
458
- sv = Sisimai::SMTP::Status.code(e['reason'], true)
473
+ cs = Sisimai::SMTP::Status.code(e['reason'], true)
459
474
 
460
475
  elsif r1 == 5
461
476
  # Get the internal DSN(permanent error)
462
- sv = Sisimai::SMTP::Status.code(e['reason'], false)
477
+ cs = Sisimai::SMTP::Status.code(e['reason'], false)
463
478
  end
464
479
  break
465
480
  end
466
481
 
467
- s1 = sv[0, 1].to_i if sv
482
+ s1 = cs[0, 1].to_i if cs
468
483
  v1 = s1 + r1
469
484
  v1 << e['status'][0, 1].to_i if e['status']
470
485
 
471
486
  if v1 > 0
472
- # Status or SMTP reply code exists
473
- # Set pseudo DSN into the value of "status" accessor
474
- e['status'] = sv if r1 > 0
487
+ # Status or SMTP reply code exists, Set pseudo DSN into the value of "status" accessor
488
+ e['status'] = cs if r1 > 0
475
489
  else
476
490
  # Neither Status nor SMTP reply code exist
477
- sv = if %w[expired mailboxfull].include?(e['reason'])
491
+ cs = if %w[expired mailboxfull].include?(e['reason'])
478
492
  # Set pseudo DSN (temporary error)
479
493
  Sisimai::SMTP::Status.code(e['reason'], true)
480
494
  else
@@ -482,10 +496,10 @@ module Sisimai::Lhost
482
496
  Sisimai::SMTP::Status.code(e['reason'], false)
483
497
  end
484
498
  end
485
- e['status'] ||= sv.to_s
499
+ e['status'] ||= cs.to_s
486
500
  end
487
501
 
488
- return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
502
+ return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
489
503
  end
490
504
  def description; return 'Exim'; end
491
505
  end