. */ namespace Liuch\DmarcSrg\Report; use Liuch\DmarcSrg\DateTime; use Liuch\DmarcSrg\Exception\RuntimeException; class ReportData { public static $rep_data = null; public static $tag_id = null; public static function fromXmlFile($fd) { self::$tag_id = ''; self::$rep_data = [ 'records' => [] ]; $parser = xml_parser_create(); xml_set_element_handler( $parser, 'Liuch\DmarcSrg\Report\ReportData::xmlStartTag', 'Liuch\DmarcSrg\Report\ReportData::xmlEndTag' ); xml_set_character_data_handler($parser, 'Liuch\DmarcSrg\Report\ReportData::xmlTagData'); xml_set_external_entity_ref_handler($parser, function () { throw new RuntimeException('The XML document has an external entity!'); }); try { while ($file_data = fread($fd, 4096)) { if (!xml_parse($parser, $file_data, feof($fd))) { throw new RuntimeException('XML error!'); } } } finally { xml_parser_free($parser); unset($parser); } return self::$rep_data; } public static function xmlStartTag($parser, $name, $attrs) { self::xmlEnterTag($name); switch (self::$tag_id) { case 'rec': self::$rep_data['records'][] = []; break; case 'error_string': if (!isset(self::$rep_data['error_string'])) { self::$rep_data['error_string'] = []; } break; case 'reason': $idx = count(self::$rep_data['records']) - 1; if (!isset(self::$rep_data['records'][$idx]['reason'])) { self::$rep_data['records'][$idx]['reason'] = []; } self::$report_tags['reason']['tmp_data'] = []; break; case 'dkim_auth': $idx = count(self::$rep_data['records']) - 1; if (!isset(self::$rep_data['records'][$idx]['dkim_auth'])) { self::$rep_data['records'][$idx]['dkim_auth'] = []; } self::$report_tags['dkim_auth']['tmp_data'] = []; break; case 'spf_auth': $idx = count(self::$rep_data['records']) - 1; if (!isset(self::$rep_data['records'][$idx]['spf_auth'])) { self::$rep_data['records'][$idx]['spf_auth'] = []; } self::$report_tags['spf_auth']['tmp_data'] = []; break; } } public static function xmlEndTag($parser, $name) { switch (self::$tag_id) { case 'reason': $idx = count(self::$rep_data['records']) - 1; self::$rep_data['records'][$idx]['reason'][] = self::$report_tags['reason']['tmp_data']; unset(self::$report_tags['reason']['tmp_data']); break; case 'dkim_auth': $idx = count(self::$rep_data['records']) - 1; self::$rep_data['records'][$idx]['dkim_auth'][] = self::$report_tags['dkim_auth']['tmp_data']; unset(self::$report_tags['dkim_auth']['tmp_data']); break; case 'spf_auth': $idx = count(self::$rep_data['records']) - 1; self::$rep_data['records'][$idx]['spf_auth'][] = self::$report_tags['spf_auth']['tmp_data']; unset(self::$report_tags['spf_auth']['tmp_data']); break; case 'feedback': // Set the default value if it's necessary and there is no data foreach (self::$report_tags as $tag_id => &$tag_data) { if (array_key_exists('default', $tag_data)) { // not isset() because of null values if (isset($tag_data['header']) && $tag_data['header']) { if (!isset(self::$rep_data[$tag_id])) { self::$rep_data[$tag_id] = $tag_data['default']; } } else { foreach (self::$rep_data['records'] as $idx => &$rec_val) { if (!isset($rec_val[$tag_id])) { $rec_val[$tag_id] = $tag_data['default']; } } unset($rec_val); } } } unset($tag_data); $b_ts = intval(self::$rep_data['begin_time']); $e_ts = intval(self::$rep_data['end_time']); self::$rep_data['begin_time'] = new DateTime('@' . ($b_ts < 0 ? 0 : $b_ts)); self::$rep_data['end_time'] = new DateTime('@' . ($e_ts < 0 ? 0 : $e_ts)); foreach (self::$rep_data['records'] as &$rec_data) { $rec_data['rcount'] = intval($rec_data['rcount']); } unset($rec_data); break; } self::xmlLeaveTag(); } public static function xmlTagData($parser, $data) { switch (self::$tag_id) { case 'error_string': if (self::$tag_id === 'error_string') { self::$rep_data['error_string'][] = $data; } break; case 'reason_type': self::$report_tags['reason']['tmp_data']['type'] = $data; break; case 'reason_comment': self::$report_tags['reason']['tmp_data']['comment'] = $data; break; case 'dkim_domain': self::$report_tags['dkim_auth']['tmp_data']['domain'] = $data; break; case 'dkim_selector': self::$report_tags['dkim_auth']['tmp_data']['selector'] = $data; break; case 'dkim_result': self::$report_tags['dkim_auth']['tmp_data']['result'] = $data; break; case 'dkim_human_result': self::$report_tags['dkim_auth']['tmp_data']['human_result'] = $data; break; case 'spf_domain': self::$report_tags['spf_auth']['tmp_data']['domain'] = $data; break; case 'spf_scope': self::$report_tags['spf_auth']['tmp_data']['scope'] = $data; break; case 'spf_result': self::$report_tags['spf_auth']['tmp_data']['result'] = $data; break; default: if (!isset(self::$report_tags[self::$tag_id]['children'])) { if (isset(self::$report_tags[self::$tag_id]['header']) && self::$report_tags[self::$tag_id]['header'] ) { if (!isset(self::$rep_data[self::$tag_id])) { self::$rep_data[self::$tag_id] = $data; } else { self::$rep_data[self::$tag_id] .= $data; } } else { $last_idx = count(self::$rep_data['records']) - 1; $last_rec =& self::$rep_data['records'][$last_idx]; if (!isset($last_rec[self::$tag_id])) { $last_rec[self::$tag_id] = $data; } else { $last_rec[self::$tag_id] .= $data; } unset($last_rec); } } } } public static function xmlEnterTag($name) { if (!isset(self::$report_tags[self::$tag_id]['children']) || !isset(self::$report_tags[self::$tag_id]['children'][$name]) ) { throw new RuntimeException("Unknown tag: {$name}"); } self::$tag_id = self::$report_tags[self::$tag_id]['children'][$name]; } public static function xmlLeaveTag() { self::$tag_id = self::$report_tags[self::$tag_id]['parent']; } public static $report_tags = [ '' => [ 'children' => [ 'FEEDBACK' => 'feedback' ] ], 'feedback' => [ 'parent' => '', 'children' => [ 'VERSION' => 'ver', 'REPORT_METADATA' => 'rmd', 'POLICY_PUBLISHED' => 'p_p', 'RECORD' => 'rec' ] ], 'ver' => [ 'parent' => 'feedback', 'header' => true, 'default' => null ], 'rmd' => [ 'parent' => 'feedback', 'children' => [ 'ORG_NAME' => 'org', 'EMAIL' => 'email', 'EXTRA_CONTACT_INFO' => 'extra_contact_info', 'REPORT_ID' => 'external_id', 'DATE_RANGE' => 'd_range', 'ERROR' => 'error_string' ] ], 'p_p' => [ 'parent' => 'feedback', 'children' => [ 'DOMAIN' => 'domain', 'ADKIM' => 'policy_adkim', 'ASPF' => 'policy_aspf', 'P' => 'policy_p', 'SP' => 'policy_sp', 'NP' => 'policy_np', 'PCT' => 'policy_pct', 'FO' => 'policy_fo' ] ], 'rec' => [ 'parent' => 'feedback', 'children' => [ 'ROW' => 'row', 'IDENTIFIERS' => 'ident', 'AUTH_RESULTS' => 'au_res' ] ], 'org' => [ 'parent' => 'rmd', 'header' => true ], 'email' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ], 'extra_contact_info' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ], 'external_id' => [ 'parent' => 'rmd', 'header' => true ], 'd_range' => [ 'parent' => 'rmd', 'children' => [ 'BEGIN' => 'begin_time', 'END' => 'end_time' ] ], 'error_string' => [ 'parent' => 'rmd', 'header' => true, 'default' => null ], 'begin_time' => [ 'parent' => 'd_range', 'header' => true ], 'end_time' => [ 'parent' => 'd_range', 'header' => true ], 'domain' => [ 'parent' => 'p_p', 'header' => true ], 'policy_adkim' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_aspf' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_p' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_sp' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_np' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_pct' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'policy_fo' => [ 'parent' => 'p_p', 'header' => true, 'default' => null ], 'row' => [ 'parent' => 'rec', 'children' => [ 'SOURCE_IP' => 'ip', 'COUNT' => 'rcount', 'POLICY_EVALUATED' => 'p_e' ] ], 'ident' => [ 'parent' => 'rec', 'children' => [ 'ENVELOPE_TO' => 'envelope_to', 'ENVELOPE_FROM' => 'envelope_from', 'HEADER_FROM' => 'header_from' ] ], 'au_res' => [ 'parent' => 'rec', 'children' => [ 'DKIM' => 'dkim_auth', 'SPF' => 'spf_auth' ] ], 'ip' => [ 'parent' => 'row' ], 'rcount' => [ 'parent' => 'row' ], 'p_e' => [ 'parent' => 'row', 'children' => [ 'DISPOSITION' => 'disposition', 'DKIM' => 'dkim_align', 'SPF' => 'spf_align', 'REASON' => 'reason' ] ], 'disposition' => [ 'parent' => 'p_e' ], 'dkim_align' => [ 'parent' => 'p_e' ], 'spf_align' => [ 'parent' => 'p_e' ], 'reason' => [ 'parent' => 'p_e', 'default' => null, 'children' => [ 'TYPE' => 'reason_type', 'COMMENT' => 'reason_comment' ] ], 'envelope_to' => [ 'parent' => 'ident', 'default' => null ], 'envelope_from' => [ 'parent' => 'ident', 'default' => null ], 'header_from' => [ 'parent' => 'ident', 'default' => null ], 'dkim_auth' => [ 'parent' => 'au_res', 'default' => null, 'children' => [ 'DOMAIN' => 'dkim_domain', 'SELECTOR' => 'dkim_selector', 'RESULT' => 'dkim_result', 'HUMAN_RESULT' => 'dkim_human_result' ] ], 'spf_auth' => [ 'parent' => 'au_res', 'default' => null, 'children' => [ 'DOMAIN' => 'spf_domain', 'SCOPE' => 'spf_scope', 'RESULT' => 'spf_result' ] ], 'reason_type' => [ 'parent' => 'reason' ], 'reason_comment' => [ 'parent' => 'reason' ], 'dkim_domain' => [ 'parent' => 'dkim_auth' ], 'dkim_selector' => [ 'parent' => 'dkim_auth' ], 'dkim_result' => [ 'parent' => 'dkim_auth' ], 'dkim_human_result' => [ 'parent' => 'dkim_auth' ], 'spf_domain' => [ 'parent' => 'spf_auth' ], 'spf_scope' => [ 'parent' => 'spf_auth' ], 'spf_result' => [ 'parent' => 'spf_auth' ] ]; }