module Sisimai::Lhost::Office365
Sisimai::Lhost::Office365
parses a bounce email which created by Microsoft Office 365. Methods in the module are called from only Sisimai::Message
.
Constants
- Headers365
- Indicators
- MarkingsOf
- ReBackbone
- ReCommands
- StartingOf
- StatusList
Public Class Methods
description()
click to toggle source
# File lib/sisimai/lhost/office365.rb, line 212 def description; return 'Microsoft Office 365: https://office.microsoft.com/'; end
make(mhead, mbody)
click to toggle source
Parse bounce messages from Microsoft Office 365 @param [Hash] mhead Message
headers of a bounce email @param [String] mbody Message
body of a bounce email @return [Hash] Bounce data list and message/rfc822 part @return [Nil] it failed to parse or the arguments are missing
# File lib/sisimai/lhost/office365.rb, line 78 def make(mhead, mbody) # X-MS-Exchange-Message-Is-Ndr: # X-Microsoft-Antispam-PRVS: <....@...outlook.com> # X-Exchange-Antispam-Report-Test: UriScan:; # X-Exchange-Antispam-Report-CFA-Test: # X-MS-Exchange-CrossTenant-OriginalArrivalTime: 29 Apr 2015 23:34:45.6789 (JST) # X-MS-Exchange-CrossTenant-FromEntityHeader: Hosted # X-MS-Exchange-Transport-CrossTenantHeadersStamped: ... tryto = %r/.+[.](?:outbound[.]protection|prod)[.]outlook[.]com\b/ match = 0 match += 1 if mhead['subject'].include?('Undeliverable:') Headers365.each do |e| next if mhead[e].nil? next if mhead[e].empty? match += 1 end match += 1 if mhead['received'].any? { |a| a =~ tryto } if mhead['message-id'] # Message-ID: <00000000-0000-0000-0000-000000000000@*.*.prod.outlook.com> match += 1 if mhead['message-id'] =~ tryto end return nil if match < 2 require 'sisimai/rfc1894' fieldtable = Sisimai::RFC1894.FIELDTABLE permessage = {} # (Hash) Store values of each Per-Message field dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone) bodyslices = emailsteak[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header connheader = {} endoferror = false # (Boolean) Flag for the end of error messages htmlbegins = false # (Boolean) Flag for HTML part v = nil while e = bodyslices.shift do # Read error messages and delivery status lines from the head of the email # to the previous line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part readcursor |= Indicators[:deliverystatus] if e =~ MarkingsOf[:message] next end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? # kijitora@example.com<mailto:kijitora@example.com> # The email address wasn't found at the destination domain. It might # be misspelled or it might not exist any longer. Try retyping the # address and resending the message. # # Original Message Details # Created Date: 4/29/2017 6:40:30 AM # Sender Address: neko@example.jp # Recipient Address: kijitora@example.org # Subject: Nyaan# v = dscontents[-1] if cv = e.match(/\A.+[@].+[<]mailto:(.+[@].+)[>]\z/) || e.match(/\ARecipient[ ]Address:[ ]+(.+)\z/) # kijitora@example.com<mailto:kijitora@example.com> if v['recipient'] # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end v['recipient'] = cv[1] recipients += 1 elsif cv = e.match(/\AGenerating server: (.+)\z/) # Generating server: FFFFFFFFFFFF.e0.prod.outlook.com connheader['lhost'] = cv[1].downcase else if endoferror # After "Original message headers:" next unless f = Sisimai::RFC1894.match(e) next unless o = Sisimai::RFC1894.field(e) next unless fieldtable[o[0]] next if o[0] =~ /\A(?:diagnostic-code|final-recipient)\z/ v[fieldtable[o[0]]] = o[2] next unless f == 1 permessage[fieldtable[o[0]]] = o[2] else if e =~ MarkingsOf[:error] # Diagnostic information for administrators: v['diagnosis'] = e else # kijitora@example.com # Remote Server returned '550 5.1.10 RESOLVER.ADR.RecipientNotFound; Recipien= # t not found by SMTP address lookup' next unless v['diagnosis'] if e =~ MarkingsOf[:eoe] # Original message headers: endoferror = true next end v['diagnosis'] << ' ' << e end end end end return nil unless recipients > 0 dscontents.each do |e| # Set default values if each value is empty. permessage.each_key { |a| e[a] ||= permessage[a] || '' } e['status'] ||= '' e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' if e['status'].empty? || e['status'].end_with?('.0.0') # There is no value of Status header or the value is 5.0.0, 4.0.0 e['status'] = Sisimai::SMTP::Status.find(e['diagnosis']) || e['status'] end ReCommands.each_key do |p| # Try to match with regular expressions defined in ReCommands next unless e['diagnosis'] =~ ReCommands[p] e['command'] = p.to_s break end next unless e['status'] StatusList.each_key do |f| # Try to match with each key as a regular expression next unless e['status'] =~ f e['reason'] = StatusList[f] break end end return { 'ds' => dscontents, 'rfc822' => emailsteak[1] } end