sisimai 4.25.4 → 4.25.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sisimai might be problematic. Click here for more details.

Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/ChangeLog.md +50 -0
  4. data/README-JA.md +10 -37
  5. data/README.md +10 -37
  6. data/lib/sisimai.rb +15 -64
  7. data/lib/sisimai/address.rb +13 -17
  8. data/lib/sisimai/arf.rb +4 -4
  9. data/lib/sisimai/data.rb +3 -5
  10. data/lib/sisimai/lhost.rb +0 -14
  11. data/lib/sisimai/lhost/activehunter.rb +31 -55
  12. data/lib/sisimai/lhost/amavis.rb +41 -66
  13. data/lib/sisimai/lhost/amazonses.rb +189 -235
  14. data/lib/sisimai/lhost/amazonworkmail.rb +48 -71
  15. data/lib/sisimai/lhost/aol.rb +49 -75
  16. data/lib/sisimai/lhost/apachejames.rb +60 -83
  17. data/lib/sisimai/lhost/bigfoot.rb +61 -85
  18. data/lib/sisimai/lhost/biglobe.rb +40 -62
  19. data/lib/sisimai/lhost/courier.rb +60 -82
  20. data/lib/sisimai/lhost/domino.rb +50 -76
  21. data/lib/sisimai/lhost/einsundeins.rb +57 -55
  22. data/lib/sisimai/lhost/exchange2003.rb +79 -101
  23. data/lib/sisimai/lhost/exchange2007.rb +66 -74
  24. data/lib/sisimai/lhost/exim.rb +119 -142
  25. data/lib/sisimai/lhost/ezweb.rb +53 -73
  26. data/lib/sisimai/lhost/facebook.rb +49 -75
  27. data/lib/sisimai/lhost/fml.rb +25 -50
  28. data/lib/sisimai/lhost/gmx.rb +55 -79
  29. data/lib/sisimai/lhost/google.rb +39 -66
  30. data/lib/sisimai/lhost/gsuite.rb +74 -94
  31. data/lib/sisimai/lhost/imailserver.rb +34 -67
  32. data/lib/sisimai/lhost/interscanmss.rb +33 -67
  33. data/lib/sisimai/lhost/kddi.rb +30 -52
  34. data/lib/sisimai/lhost/mailfoundry.rb +35 -57
  35. data/lib/sisimai/lhost/mailmarshalsmtp.rb +66 -89
  36. data/lib/sisimai/lhost/mailru.rb +51 -76
  37. data/lib/sisimai/lhost/mcafee.rb +53 -79
  38. data/lib/sisimai/lhost/messagelabs.rb +49 -75
  39. data/lib/sisimai/lhost/messagingserver.rb +91 -113
  40. data/lib/sisimai/lhost/mfilter.rb +50 -70
  41. data/lib/sisimai/lhost/mxlogic.rb +38 -63
  42. data/lib/sisimai/lhost/notes.rb +53 -82
  43. data/lib/sisimai/lhost/office365.rb +64 -81
  44. data/lib/sisimai/lhost/opensmtpd.rb +30 -52
  45. data/lib/sisimai/lhost/outlook.rb +49 -75
  46. data/lib/sisimai/lhost/postfix.rb +116 -117
  47. data/lib/sisimai/lhost/qmail.rb +33 -55
  48. data/lib/sisimai/lhost/receivingses.rb +49 -75
  49. data/lib/sisimai/lhost/sendgrid.rb +68 -203
  50. data/lib/sisimai/lhost/sendmail.rb +101 -125
  51. data/lib/sisimai/lhost/surfcontrol.rb +53 -79
  52. data/lib/sisimai/lhost/userdefined.rb +15 -35
  53. data/lib/sisimai/lhost/v5sendmail.rb +59 -89
  54. data/lib/sisimai/lhost/verizon.rb +75 -124
  55. data/lib/sisimai/lhost/x1.rb +30 -54
  56. data/lib/sisimai/lhost/x2.rb +28 -52
  57. data/lib/sisimai/lhost/x3.rb +44 -68
  58. data/lib/sisimai/lhost/x4.rb +34 -58
  59. data/lib/sisimai/lhost/x5.rb +42 -70
  60. data/lib/sisimai/lhost/yahoo.rb +44 -68
  61. data/lib/sisimai/lhost/yandex.rb +59 -85
  62. data/lib/sisimai/lhost/zoho.rb +54 -78
  63. data/lib/sisimai/mail.rb +5 -9
  64. data/lib/sisimai/mail/maildir.rb +10 -14
  65. data/lib/sisimai/mail/mbox.rb +8 -12
  66. data/lib/sisimai/mail/memory.rb +5 -9
  67. data/lib/sisimai/mail/stdin.rb +7 -11
  68. data/lib/sisimai/mda.rb +2 -2
  69. data/lib/sisimai/message.rb +51 -154
  70. data/lib/sisimai/mime.rb +2 -2
  71. data/lib/sisimai/order.rb +2 -27
  72. data/lib/sisimai/reason.rb +3 -5
  73. data/lib/sisimai/rfc1894.rb +1 -1
  74. data/lib/sisimai/rfc3464.rb +29 -29
  75. data/lib/sisimai/rfc3834.rb +7 -6
  76. data/lib/sisimai/rfc5322.rb +20 -31
  77. data/lib/sisimai/rhost/franceptt.rb +120 -24
  78. data/lib/sisimai/rhost/iua.rb +1 -1
  79. data/lib/sisimai/smtp/error.rb +7 -7
  80. data/lib/sisimai/version.rb +1 -1
  81. data/set-of-emails/maildir/bsd/email-einsundeins-03.eml +66 -0
  82. data/set-of-emails/maildir/bsd/email-exchange2007-05.eml +1469 -0
  83. data/set-of-emails/maildir/bsd/email-exchange2007-06.eml +764 -0
  84. data/set-of-emails/maildir/bsd/email-postfix-64.eml +96 -0
  85. data/set-of-emails/maildir/bsd/rfc3834-03.eml +26 -0
  86. data/set-of-emails/maildir/bsd/rhost-franceptt-04.eml +66 -0
  87. data/set-of-emails/maildir/bsd/rhost-franceptt-05.eml +96 -0
  88. data/set-of-emails/maildir/bsd/rhost-franceptt-06.eml +100 -0
  89. data/set-of-emails/maildir/bsd/rhost-franceptt-07.eml +97 -0
  90. data/set-of-emails/maildir/bsd/rhost-franceptt-08.eml +78 -0
  91. data/set-of-emails/maildir/bsd/rhost-franceptt-10.eml +79 -0
  92. data/set-of-emails/maildir/bsd/rhost-franceptt-11.eml +96 -0
  93. metadata +14 -9
  94. data/lib/sisimai/bite.rb +0 -42
  95. data/lib/sisimai/bite/email.rb +0 -18
  96. data/lib/sisimai/bite/json.rb +0 -16
  97. data/lib/sisimai/message/email.rb +0 -26
  98. data/lib/sisimai/message/json.rb +0 -24
  99. data/lib/sisimai/order/email.rb +0 -20
  100. data/lib/sisimai/order/json.rb +0 -16
@@ -35,7 +35,7 @@ module Sisimai
35
35
  :deliverystatus, # [String] Delivery Status(DSN)
36
36
  :timezoneoffset, # [Integer] Time zone offset(seconds)
37
37
  ]
38
- @@rwaccessors.each { |e| attr_accessor e }
38
+ attr_accessor(*@@rwaccessors)
39
39
 
40
40
  EndOfEmail = Sisimai::String.EOM
41
41
  RetryIndex = Sisimai::Reason.retry
@@ -153,7 +153,6 @@ module Sisimai
153
153
  # Detect email address from message/rfc822 part
154
154
  fieldorder[:addresser].each do |f|
155
155
  # Check each header in message/rfc822 part
156
- next unless rfc822data.key?(f)
157
156
  next unless rfc822data[f]
158
157
  next if rfc822data[f].empty?
159
158
 
@@ -312,7 +311,7 @@ module Sisimai
312
311
  o = Sisimai::Data.new(p)
313
312
  next unless o.recipient
314
313
 
315
- if o.reason.empty? || RetryIndex.key?(o.reason)
314
+ if o.reason.empty? || RetryIndex[o.reason]
316
315
  # Decide the reason of email bounce
317
316
  r = ''
318
317
  r = Sisimai::Rhost.get(o) if Sisimai::Rhost.match(o.rhost) # Remote host dependent error
@@ -348,9 +347,8 @@ module Sisimai
348
347
  textasargv = (o.replycode + ' ' + p['diagnosticcode']).lstrip
349
348
  getchecked = Sisimai::SMTP::Error.is_permanent(textasargv)
350
349
  tmpfailure = getchecked.nil? ? false : (getchecked ? false : true)
351
- pseudocode = Sisimai::SMTP::Status.code(o.reason, tmpfailure)
352
350
 
353
- if pseudocode
351
+ if pseudocode = Sisimai::SMTP::Status.code(o.reason, tmpfailure)
354
352
  # Set the value of "deliverystatus" and "softbounce"
355
353
  o.deliverystatus = pseudocode
356
354
 
@@ -81,20 +81,6 @@ module Sisimai
81
81
  # part or nil if it failed to parse or
82
82
  # the arguments are missing
83
83
  def make; return nil; end
84
-
85
- # @abstract Print warnings about an obsoleted method. This method will be
86
- # removed at the future release of Sisimai
87
- # @until v4.25.5
88
- def warn(whois = '', useit = nil)
89
- label = ' ***warning:'
90
- methodname = caller[0][/`.*'/][1..-2]
91
- messageset = sprintf("%s %s.%s is marked as obsoleted", label, whois, methodname)
92
-
93
- useit ||= methodname
94
- messageset << sprintf(" and will be removed at %s.", removedat)
95
- messageset << sprintf(" Use %s.%s instead.\n", self.name, useit) if useit != 'gone'
96
- Kernel.warn messageset
97
- end
98
84
  end
99
85
  end
100
86
  end
@@ -8,10 +8,8 @@ module Sisimai::Lhost
8
8
  require 'sisimai/lhost'
9
9
 
10
10
  Indicators = Sisimai::Lhost.INDICATORS
11
- StartingOf = {
12
- message: [' ----- The following addresses had permanent fatal errors -----'],
13
- rfc822: ['Content-type: message/rfc822'],
14
- }.freeze
11
+ ReBackbone = %r|^Content-type:[ ]message/rfc822|.freeze
12
+ StartingOf = { message: [' ----- The following addresses had permanent fatal errors -----'] }.freeze
15
13
 
16
14
  def description; return 'TransWARE Active!hunter'; end
17
15
  def smtpagent; return Sisimai::Lhost.smtpagent(self); end
@@ -34,68 +32,47 @@ module Sisimai::Lhost
34
32
  return nil unless mhead['x-ahmailid']
35
33
 
36
34
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
37
- hasdivided = mbody.split("\n")
38
- rfc822list = [] # (Array) Each line in message/rfc822 part string
39
- blanklines = 0 # (Integer) The number of blank lines
35
+ emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
36
+ bodyslices = emailsteak[0].split("\n")
40
37
  readcursor = 0 # (Integer) Points the current cursor position
41
38
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
42
39
  v = nil
43
40
 
44
- while e = hasdivided.shift do
41
+ while e = bodyslices.shift do
42
+ # Read error messages and delivery status lines from the head of the email
43
+ # to the previous line of the beginning of the original message.
45
44
  if readcursor == 0
46
45
  # Beginning of the bounce message or delivery status part
47
- if e == StartingOf[:message][0]
48
- readcursor |= Indicators[:deliverystatus]
49
- next
50
- end
46
+ readcursor |= Indicators[:deliverystatus] if e == StartingOf[:message][0]
47
+ next
51
48
  end
49
+ next if (readcursor & Indicators[:deliverystatus]) == 0
50
+ next if e.empty?
52
51
 
53
- if (readcursor & Indicators[:'message-rfc822']) == 0
54
- # Beginning of the original message part
55
- if e == StartingOf[:rfc822][0]
56
- readcursor |= Indicators[:'message-rfc822']
57
- next
58
- end
59
- end
52
+ # ----- The following addresses had permanent fatal errors -----
53
+ #
54
+ # >>> kijitora@example.org <kijitora@example.org>
55
+ #
56
+ # ----- Transcript of session follows -----
57
+ # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
58
+ v = dscontents[-1]
60
59
 
61
- if readcursor & Indicators[:'message-rfc822'] > 0
62
- # Inside of the original message part
63
- if e.empty?
64
- blanklines += 1
65
- break if blanklines > 1
66
- next
60
+ if cv = e.match(/\A[>]{3}[ \t]+.+[<]([^ ]+?[@][^ ]+?)[>]\z/)
61
+ # >>> kijitora@example.org <kijitora@example.org>
62
+ if v['recipient']
63
+ # There are multiple recipient addresses in the message body.
64
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
65
+ v = dscontents[-1]
67
66
  end
68
- rfc822list << e
67
+ v['recipient'] = cv[1]
68
+ v['diagnosis'] = ''
69
+ recipients += 1
69
70
  else
70
- # Error message part
71
- next if (readcursor & Indicators[:deliverystatus]) == 0
72
- next if e.empty?
73
-
74
- # ----- The following addresses had permanent fatal errors -----
75
- #
76
- # >>> kijitora@example.org <kijitora@example.org>
77
- #
78
71
  # ----- Transcript of session follows -----
79
72
  # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
80
- v = dscontents[-1]
81
-
82
- if cv = e.match(/\A[>]{3}[ \t]+.+[<]([^ ]+?[@][^ ]+?)[>]\z/)
83
- # >>> kijitora@example.org <kijitora@example.org>
84
- if v['recipient']
85
- # There are multiple recipient addresses in the message body.
86
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
87
- v = dscontents[-1]
88
- end
89
- v['recipient'] = cv[1]
90
- v['diagnosis'] = ''
91
- recipients += 1
92
- else
93
- # ----- Transcript of session follows -----
94
- # 550 sorry, no mailbox here by that name (#5.1.1 - chkusr)
95
- next unless e =~ /\A[0-9A-Za-z]+/
96
- next unless v['diagnosis'].empty?
97
- v['diagnosis'] = e
98
- end
73
+ next unless e =~ /\A[0-9A-Za-z]+/
74
+ next unless v['diagnosis'].empty?
75
+ v['diagnosis'] = e
99
76
  end
100
77
  end
101
78
  return nil unless recipients > 0
@@ -107,8 +84,7 @@ module Sisimai::Lhost
107
84
  e.each_key { |a| e[a] ||= '' }
108
85
  end
109
86
 
110
- rfc822part = Sisimai::RFC5322.weedout(rfc822list)
111
- return { 'ds' => dscontents, 'rfc822' => rfc822part }
87
+ return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
112
88
  end
113
89
 
114
90
  end
@@ -7,6 +7,7 @@ module Sisimai::Lhost
7
7
  require 'sisimai/lhost'
8
8
 
9
9
  Indicators = Sisimai::Lhost.INDICATORS
10
+ ReBackbone = %r|^Content-Type:[ ]text/rfc822-headers|.freeze
10
11
  StartingOf = {
11
12
  message: ['The message '],
12
13
  rfc822: ['Content-Type: text/rfc822-headers'],
@@ -36,82 +37,57 @@ module Sisimai::Lhost
36
37
  permessage = {} # (Hash) Store values of each Per-Message field
37
38
 
38
39
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
39
- hasdivided = mbody.split("\n")
40
- havepassed = ['']
41
- rfc822list = [] # (Array) Each line in message/rfc822 part string
42
- blanklines = 0 # (Integer) The number of blank lines
40
+ emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
41
+ bodyslices = emailsteak[0].split("\n")
43
42
  readcursor = 0 # (Integer) Points the current cursor position
44
43
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
45
44
  v = nil
46
45
 
47
- while e = hasdivided.shift do
48
- # Save the current line for the next loop
49
- havepassed << e
50
- p = havepassed[-2]
46
+ while e = bodyslices.shift do
47
+ # Read error messages and delivery status lines from the head of the email
48
+ # to the previous line of the beginning of the original message.
51
49
 
52
50
  if readcursor == 0
53
51
  # Beginning of the bounce message or message/delivery-status part
54
- if e.start_with?(StartingOf[:message][0])
55
- readcursor |= Indicators[:deliverystatus]
56
- next
57
- end
58
- end
59
-
60
- if (readcursor & Indicators[:'message-rfc822']) == 0
61
- # Beginning of the original message part(message/rfc822)
62
- if e == StartingOf[:rfc822][0]
63
- readcursor |= Indicators[:'message-rfc822']
64
- next
65
- end
52
+ readcursor |= Indicators[:deliverystatus] if e.start_with?(StartingOf[:message][0])
53
+ next
66
54
  end
67
-
68
- if readcursor & Indicators[:'message-rfc822'] > 0
69
- # message/rfc822 OR text/rfc822-headers part
70
- if e.empty?
71
- blanklines += 1
72
- break if blanklines > 1
73
- next
74
- end
75
- rfc822list << e
76
- else
77
- # message/delivery-status part
78
- next if (readcursor & Indicators[:deliverystatus]) == 0
79
- next if e.empty?
80
- next unless f = Sisimai::RFC1894.match(e)
81
-
82
- # "e" matched with any field defined in RFC3464
83
- next unless o = Sisimai::RFC1894.field(e)
84
- v = dscontents[-1]
85
-
86
- if o[-1] == 'addr'
55
+ next if (readcursor & Indicators[:deliverystatus]) == 0
56
+ next if e.empty?
57
+ next unless f = Sisimai::RFC1894.match(e)
58
+
59
+ # "e" matched with any field defined in RFC3464
60
+ next unless o = Sisimai::RFC1894.field(e)
61
+ v = dscontents[-1]
62
+
63
+ if o[-1] == 'addr'
64
+ # Final-Recipient: rfc822; kijitora@example.jp
65
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
66
+ if o[0] == 'final-recipient'
87
67
  # Final-Recipient: rfc822; kijitora@example.jp
88
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
89
- if o[0] == 'final-recipient'
90
- # Final-Recipient: rfc822; kijitora@example.jp
91
- if v['recipient']
92
- # There are multiple recipient addresses in the message body.
93
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
94
- v = dscontents[-1]
95
- end
96
- v['recipient'] = o[2]
97
- recipients += 1
98
- else
99
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
100
- v['alias'] = o[2]
68
+ if v['recipient']
69
+ # There are multiple recipient addresses in the message body.
70
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
71
+ v = dscontents[-1]
101
72
  end
102
- elsif o[-1] == 'code'
103
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
104
- v['spec'] = o[1]
105
- v['diagnosis'] = o[2]
73
+ v['recipient'] = o[2]
74
+ recipients += 1
106
75
  else
107
- # Other DSN fields defined in RFC3464
108
- next unless fieldtable.key?(o[0])
109
- v[fieldtable[o[0]]] = o[2]
110
-
111
- next unless f == 1
112
- permessage[fieldtable[o[0]]] = o[2]
76
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
77
+ v['alias'] = o[2]
113
78
  end
114
- end # End of message/delivery-status
79
+ elsif o[-1] == 'code'
80
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
81
+ v['spec'] = o[1]
82
+ v['diagnosis'] = o[2]
83
+ else
84
+ # Other DSN fields defined in RFC3464
85
+ next unless fieldtable[o[0]]
86
+ v[fieldtable[o[0]]] = o[2]
87
+
88
+ next unless f == 1
89
+ permessage[fieldtable[o[0]]] = o[2]
90
+ end
115
91
  end
116
92
  return nil unless recipients > 0
117
93
 
@@ -123,8 +99,7 @@ module Sisimai::Lhost
123
99
  e['agent'] = self.smtpagent
124
100
  end
125
101
 
126
- rfc822part = Sisimai::RFC5322.weedout(rfc822list)
127
- return { 'ds' => dscontents, 'rfc822' => rfc822part }
102
+ return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
128
103
  end
129
104
 
130
105
  end
@@ -9,9 +9,9 @@ module Sisimai::Lhost
9
9
 
10
10
  # https://aws.amazon.com/ses/
11
11
  Indicators = Sisimai::Lhost.INDICATORS
12
+ ReBackbone = %r|^content-type:[ ]message/rfc822|.freeze
12
13
  StartingOf = {
13
14
  message: ['The following message to <', 'An error occurred while trying to deliver the mail'],
14
- rfc822: ['content-type: message/rfc822'],
15
15
  }.freeze
16
16
  MessagesOf = { 'expired' => ['Delivery expired'] }.freeze
17
17
 
@@ -36,17 +36,31 @@ module Sisimai::Lhost
36
36
  # part or nil if it failed to parse or
37
37
  # the arguments are missing
38
38
  def make(mhead, mbody)
39
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
40
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
41
+
39
42
  if mbody.start_with?('{')
40
43
  # The message body is JSON string
41
44
  return nil unless mhead['x-amz-sns-message-id']
42
45
  return nil if mhead['x-amz-sns-message-id'].empty?
43
46
 
44
- hasdivided = mbody.split("\n")
47
+ # https://docs.aws.amazon.com/en_us/ses/latest/DeveloperGuide/notification-contents.html
48
+ bouncetype = {
49
+ 'Permanent' => { 'General' => '', 'NoEmail' => '', 'Suppressed' => '' },
50
+ 'Transient' => {
51
+ 'General' => '',
52
+ 'MailboxFull' => 'mailboxfull',
53
+ 'MessageTooLarge' => 'mesgtoobig',
54
+ 'ContentRejected' => '',
55
+ 'AttachmentRejected' => '',
56
+ },
57
+ }.freeze
45
58
  jsonstring = ''
46
59
  sespayload = nil
47
60
  foldedline = false
61
+ bodyslices = mbody.split("\n")
48
62
 
49
- while e = hasdivided.shift do
63
+ while e = bodyslices.shift do
50
64
  # Find JSON string from the message body
51
65
  next if e.empty?
52
66
  break if e == '--'
@@ -88,10 +102,136 @@ module Sisimai::Lhost
88
102
  return nil
89
103
  end
90
104
 
91
- return json(sespayload)
105
+ rfc822head = {} # (Hash) Check flags for headers in RFC822 part
106
+ labeltable = {
107
+ 'Bounce' => 'bouncedRecipients',
108
+ 'Complaint' => 'complainedRecipients',
109
+ }
110
+ p = sespayload
111
+ v = nil
112
+
113
+ if %w[Bounce Complaint].index(p['notificationType'])
114
+ # { "notificationType":"Bounce", "bounce": { "bounceType":"Permanent",...
115
+ o = p[p['notificationType'].downcase].dup
116
+ r = o[labeltable[p['notificationType']]] || []
117
+
118
+ while e = r.shift do
119
+ # 'bouncedRecipients' => [ { 'emailAddress' => 'bounce@si...' }, ... ]
120
+ # 'complainedRecipients' => [ { 'emailAddress' => 'complaint@si...' }, ... ]
121
+ next unless Sisimai::RFC5322.is_emailaddress(e['emailAddress'])
122
+
123
+ v = dscontents[-1]
124
+ if v['recipient']
125
+ # There are multiple recipient addresses in the message body.
126
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
127
+ v = dscontents[-1]
128
+ end
129
+ recipients += 1
130
+ v['recipient'] = e['emailAddress']
131
+
132
+ if p['notificationType'] == 'Bounce'
133
+ # 'bouncedRecipients => [ {
134
+ # 'emailAddress' => 'bounce@simulator.amazonses.com',
135
+ # 'action' => 'failed',
136
+ # 'status' => '5.1.1',
137
+ # 'diagnosticCode' => 'smtp; 550 5.1.1 user unknown'
138
+ # }, ... ]
139
+ v['action'] = e['action']
140
+ v['status'] = e['status']
141
+
142
+ if cv = e['diagnosticCode'].match(/\A(.+?);[ ]*(.+)\z/)
143
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
144
+ v['spec'] = cv[1].upcase
145
+ v['diagnosis'] = cv[2]
146
+ else
147
+ v['diagnosis'] = e['diagnosticCode']
148
+ end
149
+
150
+ # 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
151
+ if cv = o['reportingMTA'].match(/\Adsn;[ ](.+)\z/) then v['lhost'] = cv[1] end
152
+
153
+ if bouncetype[o['bounceType']] &&
154
+ bouncetype[o['bounceType']][o['bounceSubType']]
155
+ # 'bounce' => {
156
+ # 'bounceType' => 'Permanent',
157
+ # 'bounceSubType' => 'General'
158
+ # },
159
+ v['reason'] = bouncetype[o['bounceType']][o['bounceSubType']]
160
+ end
161
+ else
162
+ # 'complainedRecipients' => [ {
163
+ # 'emailAddress' => 'complaint@simulator.amazonses.com' }, ... ],
164
+ v['reason'] = 'feedback'
165
+ v['feedbacktype'] = o['complaintFeedbackType'] || ''
166
+ end
167
+
168
+ v['date'] = o['timestamp'] || p['mail']['timestamp']
169
+ v['date'].sub!(/[.]\d+Z\z/, '')
170
+ end
171
+ elsif p['notificationType'] == 'Delivery'
172
+ # { "notificationType":"Delivery", "delivery": { ...
173
+ o = p['delivery'].dup
174
+ r = o['recipients'] || []
175
+
176
+ while e = r.shift do
177
+ # 'delivery' => {
178
+ # 'timestamp' => '2016-11-23T12:01:03.512Z',
179
+ # 'processingTimeMillis' => 3982,
180
+ # 'reportingMTA' => 'a27-29.smtp-out.us-west-2.amazonses.com',
181
+ # 'recipients' => [
182
+ # 'success@simulator.amazonses.com'
183
+ # ],
184
+ # 'smtpResponse' => '250 2.6.0 Message received'
185
+ # },
186
+ next unless Sisimai::RFC5322.is_emailaddress(e)
187
+
188
+ v = dscontents[-1]
189
+ if v['recipient']
190
+ # There are multiple recipient addresses in the message body.
191
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
192
+ v = dscontents[-1]
193
+ end
194
+ recipients += 1
195
+ v['recipient'] = e
196
+ v['lhost'] = o['reportingMTA'] || ''
197
+ v['diagnosis'] = o['smtpResponse'] || ''
198
+ v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || ''
199
+ v['replycode'] = Sisimai::SMTP::Reply.find(v['diagnosis']) || ''
200
+ v['reason'] = 'delivered'
201
+ v['action'] = 'deliverable'
202
+
203
+ v['date'] = o['timestamp'] || p['mail']['timestamp']
204
+ v['date'].sub!(/[.]\d+Z\z/, '')
205
+ end
206
+ else
207
+ # The value of "notificationType" is not any of "Bounce", "Complaint",
208
+ # or "Delivery".
209
+ return nil
210
+ end
211
+ return nil unless recipients > 0
212
+
213
+ dscontents.each { |e| e['agent'] = self.smtpagent }
214
+ if p['mail']['headers']
215
+ # "headersTruncated":false,
216
+ # "headers":[ { ...
217
+ p['mail']['headers'].each do |e|
218
+ # 'headers' => [ { 'name' => 'From', 'value' => 'neko@nyaan.jp' }, ... ],
219
+ next unless %w[From To Subject Message-ID Date].index(e['name'])
220
+ rfc822head[e['name'].downcase] = e['value']
221
+ end
222
+ end
223
+
224
+ unless rfc822head['message-id']
225
+ # Try to get the value of "Message-Id".
226
+ if p['mail']['messageId']
227
+ # 'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
228
+ rfc822head['message-id'] = p['mail']['messageId']
229
+ end
230
+ end
231
+ return { 'ds' => dscontents, 'rfc822' => rfc822head }
232
+
92
233
  else
93
234
  # The message body is an email
94
-
95
235
  # :from => %r/\AMAILER-DAEMON[@]email[-]bounces[.]amazonses[.]com\z/,
96
236
  # :subject => %r/\ADelivery Status Notification [(]Failure[)]\z/,
97
237
  return nil if mhead['x-mailer'].to_s.start_with?('Amazon WorkMail')
@@ -105,19 +245,16 @@ module Sisimai::Lhost
105
245
  fieldtable = Sisimai::RFC1894.FIELDTABLE
106
246
  permessage = {} # (Hash) Store values of each Per-Message field
107
247
 
108
- dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
109
- hasdivided = mbody.split("\n")
110
- havepassed = ['']
111
- rfc822list = [] # (Array) Each line in message/rfc822 part string
112
- blanklines = 0 # (Integer) The number of blank lines
248
+ emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone)
249
+ bodyslices = emailsteak[0].split("\n")
250
+ readslices = ['']
113
251
  readcursor = 0 # (Integer) Points the current cursor position
114
- recipients = 0 # (Integer) The number of 'Final-Recipient' header
115
252
  v = nil
116
253
 
117
- while e = hasdivided.shift do
118
- # Save the current line for the next loop
119
- havepassed << e
120
- p = havepassed[-2]
254
+ while e = bodyslices.shift do
255
+ # Read error messages and delivery status lines from the head of the email
256
+ # to the previous line of the beginning of the original message.
257
+ readslices << e # Save the current line for the next loop
121
258
 
122
259
  if readcursor == 0
123
260
  # Beginning of the bounce message or message/delivery-status part
@@ -126,69 +263,49 @@ module Sisimai::Lhost
126
263
  next
127
264
  end
128
265
  end
266
+ next if (readcursor & Indicators[:deliverystatus]) == 0
267
+ next if e.empty?
129
268
 
130
- if (readcursor & Indicators[:'message-rfc822']) == 0
131
- # Beginning of the original message part(message/rfc822)
132
- if e == StartingOf[:rfc822][0]
133
- readcursor |= Indicators[:'message-rfc822']
134
- next
135
- end
136
- end
137
-
138
- if readcursor & Indicators[:'message-rfc822'] > 0
139
- # message/rfc822 OR text/rfc822-headers part
140
- if e.empty?
141
- blanklines += 1
142
- break if blanklines > 1
143
- next
144
- end
145
- rfc822list << e
146
- else
147
- # message/delivery-status part
148
- next if (readcursor & Indicators[:deliverystatus]) == 0
149
- next if e.empty?
150
-
151
- if f = Sisimai::RFC1894.match(e)
152
- # "e" matched with any field defined in RFC3464
153
- next unless o = Sisimai::RFC1894.field(e)
154
- v = dscontents[-1]
269
+ if f = Sisimai::RFC1894.match(e)
270
+ # "e" matched with any field defined in RFC3464
271
+ next unless o = Sisimai::RFC1894.field(e)
272
+ v = dscontents[-1]
155
273
 
156
- if o[-1] == 'addr'
274
+ if o[-1] == 'addr'
275
+ # Final-Recipient: rfc822; kijitora@example.jp
276
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
277
+ if o[0] == 'final-recipient'
157
278
  # Final-Recipient: rfc822; kijitora@example.jp
158
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
159
- if o[0] == 'final-recipient'
160
- # Final-Recipient: rfc822; kijitora@example.jp
161
- if v['recipient']
162
- # There are multiple recipient addresses in the message body.
163
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
164
- v = dscontents[-1]
165
- end
166
- v['recipient'] = o[2]
167
- recipients += 1
168
- else
169
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
170
- v['alias'] = o[2]
279
+ if v['recipient']
280
+ # There are multiple recipient addresses in the message body.
281
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
282
+ v = dscontents[-1]
171
283
  end
172
- elsif o[-1] == 'code'
173
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
174
- v['spec'] = o[1]
175
- v['diagnosis'] = o[2]
284
+ v['recipient'] = o[2]
285
+ recipients += 1
176
286
  else
177
- # Other DSN fields defined in RFC3464
178
- next unless fieldtable.key?(o[0])
179
- v[fieldtable[o[0]]] = o[2]
180
-
181
- next unless f == 1
182
- permessage[fieldtable[o[0]]] = o[2]
287
+ # X-Actual-Recipient: rfc822; kijitora@example.co.jp
288
+ v['alias'] = o[2]
183
289
  end
290
+ elsif o[-1] == 'code'
291
+ # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
292
+ v['spec'] = o[1]
293
+ v['diagnosis'] = o[2]
184
294
  else
185
- # Continued line of the value of Diagnostic-Code field
186
- next unless p.start_with?('Diagnostic-Code:')
187
- next unless cv = e.match(/\A[ \t]+(.+)\z/)
188
- v['diagnosis'] << ' ' << cv[1]
189
- havepassed[-1] = 'Diagnostic-Code: ' << e
295
+ # Other DSN fields defined in RFC3464
296
+ next unless fieldtable[o[0]]
297
+ v[fieldtable[o[0]]] = o[2]
298
+
299
+ next unless f == 1
300
+ permessage[fieldtable[o[0]]] = o[2]
190
301
  end
191
- end # End of message/delivery-status
302
+ else
303
+ # Continued line of the value of Diagnostic-Code field
304
+ next unless readslices[-2].start_with?('Diagnostic-Code:')
305
+ next unless cv = e.match(/\A[ \t]+(.+)\z/)
306
+ v['diagnosis'] << ' ' << cv[1]
307
+ readslices[-1] = 'Diagnostic-Code: ' << e
308
+ end
192
309
  end
193
310
 
194
311
  if recipients == 0 && mbody =~ /notificationType/
@@ -228,175 +345,12 @@ module Sisimai::Lhost
228
345
  e['reason'] = r
229
346
  break
230
347
  end
231
-
232
348
  end
233
349
 
234
- rfc822part = Sisimai::RFC5322.weedout(rfc822list)
235
- return { 'ds' => dscontents, 'rfc822' => rfc822part }
350
+ return { 'ds' => dscontents, 'rfc822' => emailsteak[1] }
236
351
  end # END of a parser for email message
237
- end # END of def make()
238
-
239
- # @abstract Adapt Amazon SES bounce object for Sisimai::Message format
240
- # @param [Hash] argvs bounce object(JSON) retrieved from Amazon SNS
241
- # @return [Hash, Nil] Bounce data list and message/rfc822 part or
242
- # nil if it failed to parse or the
243
- # arguments are missing
244
- # @since v4.20.0
245
- # @until v4.25.5
246
- def json(argvs)
247
- return nil unless argvs.is_a? Hash
248
- return nil if argvs.empty?
249
- return nil unless argvs.key?('notificationType')
250
-
251
- # https://docs.aws.amazon.com/en_us/ses/latest/DeveloperGuide/notification-contents.html
252
- bouncetype = {
253
- 'Permanent' => {
254
- 'General' => '',
255
- 'NoEmail' => '',
256
- 'Suppressed' => '',
257
- },
258
- 'Transient' => {
259
- 'General' => '',
260
- 'MailboxFull' => 'mailboxfull',
261
- 'MessageTooLarge' => 'mesgtoobig',
262
- 'ContentRejected' => '',
263
- 'AttachmentRejected' => '',
264
- },
265
- }.freeze
266
-
267
- dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
268
- rfc822head = {} # (Hash) Check flags for headers in RFC822 part
269
- recipients = 0 # (Integer) The number of 'Final-Recipient' header
270
- labeltable = {
271
- 'Bounce' => 'bouncedRecipients',
272
- 'Complaint' => 'complainedRecipients',
273
- }
274
- v = nil
275
-
276
- if %w[Bounce Complaint].index(argvs['notificationType'])
277
- # { "notificationType":"Bounce", "bounce": { "bounceType":"Permanent",...
278
- o = argvs[argvs['notificationType'].downcase].dup
279
- r = o[labeltable[argvs['notificationType']]] || []
280
-
281
- while e = r.shift do
282
- # 'bouncedRecipients' => [ { 'emailAddress' => 'bounce@si...' }, ... ]
283
- # 'complainedRecipients' => [ { 'emailAddress' => 'complaint@si...' }, ... ]
284
- next unless Sisimai::RFC5322.is_emailaddress(e['emailAddress'])
285
-
286
- v = dscontents[-1]
287
- if v['recipient']
288
- # There are multiple recipient addresses in the message body.
289
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
290
- v = dscontents[-1]
291
- end
292
- recipients += 1
293
- v['recipient'] = e['emailAddress']
294
-
295
- if argvs['notificationType'] == 'Bounce'
296
- # 'bouncedRecipients => [ {
297
- # 'emailAddress' => 'bounce@simulator.amazonses.com',
298
- # 'action' => 'failed',
299
- # 'status' => '5.1.1',
300
- # 'diagnosticCode' => 'smtp; 550 5.1.1 user unknown'
301
- # }, ... ]
302
- v['action'] = e['action']
303
- v['status'] = e['status']
304
-
305
- if cv = e['diagnosticCode'].match(/\A(.+?);[ ]*(.+)\z/)
306
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
307
- v['spec'] = cv[1].upcase
308
- v['diagnosis'] = cv[2]
309
- else
310
- v['diagnosis'] = e['diagnosticCode']
311
- end
312
-
313
- # 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
314
- if cv = o['reportingMTA'].match(/\Adsn;[ ](.+)\z/) then v['lhost'] = cv[1] end
315
-
316
- if bouncetype.key?(o['bounceType']) &&
317
- bouncetype[o['bounceType']].key?(o['bounceSubType'])
318
- # 'bounce' => {
319
- # 'bounceType' => 'Permanent',
320
- # 'bounceSubType' => 'General'
321
- # },
322
- v['reason'] = bouncetype[o['bounceType']][o['bounceSubType']]
323
- end
324
- else
325
- # 'complainedRecipients' => [ {
326
- # 'emailAddress' => 'complaint@simulator.amazonses.com' }, ... ],
327
- v['reason'] = 'feedback'
328
- v['feedbacktype'] = o['complaintFeedbackType'] || ''
329
- end
330
-
331
- v['date'] = o['timestamp'] || argvs['mail']['timestamp']
332
- v['date'].sub!(/[.]\d+Z\z/, '')
333
- end
334
- elsif argvs['notificationType'] == 'Delivery'
335
- # { "notificationType":"Delivery", "delivery": { ...
336
- o = argvs['delivery'].dup
337
- r = o['recipients'] || []
338
-
339
- while e = r.shift do
340
- # 'delivery' => {
341
- # 'timestamp' => '2016-11-23T12:01:03.512Z',
342
- # 'processingTimeMillis' => 3982,
343
- # 'reportingMTA' => 'a27-29.smtp-out.us-west-2.amazonses.com',
344
- # 'recipients' => [
345
- # 'success@simulator.amazonses.com'
346
- # ],
347
- # 'smtpResponse' => '250 2.6.0 Message received'
348
- # },
349
- next unless Sisimai::RFC5322.is_emailaddress(e)
350
-
351
- v = dscontents[-1]
352
- if v['recipient']
353
- # There are multiple recipient addresses in the message body.
354
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
355
- v = dscontents[-1]
356
- end
357
- recipients += 1
358
- v['recipient'] = e
359
- v['lhost'] = o['reportingMTA'] || ''
360
- v['diagnosis'] = o['smtpResponse'] || ''
361
- v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || ''
362
- v['replycode'] = Sisimai::SMTP::Reply.find(v['diagnosis']) || ''
363
- v['reason'] = 'delivered'
364
- v['action'] = 'deliverable'
365
-
366
- v['date'] = o['timestamp'] || argvs['mail']['timestamp']
367
- v['date'].sub!(/[.]\d+Z\z/, '')
368
- end
369
- else
370
- # The value of "notificationType" is not any of "Bounce", "Complaint",
371
- # or "Delivery".
372
- return nil
373
- end
374
- return nil unless recipients > 0
375
-
376
- dscontents.each do |e|
377
- e['agent'] = self.smtpagent
378
- end
379
-
380
- if argvs['mail']['headers']
381
- # "headersTruncated":false,
382
- # "headers":[ { ...
383
- argvs['mail']['headers'].each do |e|
384
- # 'headers' => [ { 'name' => 'From', 'value' => 'neko@nyaan.jp' }, ... ],
385
- next unless %w[From To Subject Message-ID Date].index(e['name'])
386
- rfc822head[e['name'].downcase] = e['value']
387
- end
388
- end
389
-
390
- unless rfc822head['message-id']
391
- # Try to get the value of "Message-Id".
392
- if argvs['mail']['messageId']
393
- # 'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
394
- rfc822head['message-id'] = argvs['mail']['messageId']
395
- end
396
- end
397
- return { 'ds' => dscontents, 'rfc822' => rfc822head }
398
- end
399
352
 
353
+ end # END of def make()
400
354
  end
401
355
  end
402
356
  end