/** * Parse attachments * @return array Returns array with failed or success data * (See parser-common/src/Parser.php) for more info. */ public function parse() { if ($this->arfMail !== true) { $this->feedName = 'default'; // As this is a generic FBL parser we need to see which was the source and add the name // to the report, so its origin is clearly shown. $source = $this->parsedMail->getHeader('from'); foreach (config("{$this->configBase}.parser.aliases") as $from => $alias) { if (preg_match($from, $source)) { // If there is an alias, prefer that name instead of the from address $source = $alias; // If there is an more specific feed configuration prefer that config over the default if (!empty(config("{$this->configBase}.feeds.{$source}"))) { $this->feedName = $source; } } } // If feed is known and enabled, validate data and save report if ($this->isKnownFeed() && $this->isEnabledFeed()) { // To get some more consitency, remove "\r" from the report. $this->arfMail['report'] = str_replace("\r", "", $this->arfMail['report']); // Build up the report preg_match_all("/([\\w\\-]+): (.*)[ ]*\n/m", $this->arfMail['report'], $matches); $report = array_combine($matches[1], $matches[2]); if (empty($report['Received-Date'])) { if (!empty($report['Arrival-Date'])) { $report['Received-Date'] = $report['Arrival-Date']; unset($report['Arrival-Date']); } } // Now parse the headers from the spam messages and add it to the report if (!empty($this->arfMail['evidence'])) { $spamMessage = new MimeParser(); $spamMessage->setText($this->arfMail['evidence']); $report['headers'] = $spamMessage->getHeaders(); } else { $this->failed('The e-mail received at the parser is not RFC822 compliant, and therefor not a FBL message'); } // Also add the spam message body to the report $report['body'] = $spamMessage->getMessageBody(); // Sanity check if ($this->hasRequiredFields($report) === true) { // incident has all requirements met, filter and add! $report = $this->applyFilters($report); $incident = new Incident(); $incident->source = $source; // FeedName $incident->source_id = false; $incident->ip = $report['Source-IP']; $incident->domain = false; $incident->class = config("{$this->configBase}.feeds.{$this->feedName}.class"); $incident->type = config("{$this->configBase}.feeds.{$this->feedName}.type"); $incident->timestamp = strtotime($report['Received-Date']); $incident->information = json_encode($report); $this->incidents[] = $incident; } } } return $this->success(); }
/** * @param $rawMessage * * @return \MailChecker\Models\Message */ protected function getMessage($rawMessage) { $parser = new MailParser(); $parser->setText($rawMessage); $headers = array_change_key_case($parser->getHeaders(), CASE_LOWER); $message = new Message(); try { $message->setDate(new \DateTime($headers['date'])); } catch (\Exception $e) { // Can't recognize date time format // TODO add config option for date time format parsing $message->setDate(new \DateTime()); } $message->setSubject($headers['subject']); $message->setFrom($headers['from']); $message->setTo($headers['to']); if (isset($headers['cc'])) { $message->setCc($headers['cc']); } foreach ($parser->getParts() as $part) { if (in_array($part['content-type'], ['text/plain', 'text/html'])) { $body = new Body(); $body->setContentType($part['content-type']); $body->setCharset(isset($part['content-charset']) ? $part['content-charset'] : null); $body->setEncoding(isset($part['transfer-encoding']) ? $part['transfer-encoding'] : null); $start = $part['starting-pos-body']; $end = $part['ending-pos-body']; $body->setBody(substr($rawMessage, $start, $end - $start)); $message->addBody($body); } } /** @var \PhpMimeMailParser\Attachment $messageAttachment */ foreach ($parser->getAttachments() as $messageAttachment) { $attachment = new Attachment(); $attachment->setType($messageAttachment->getContentType()); $attachment->setFilename($messageAttachment->getFilename()); $message->addAttachment($attachment); } return $message; }
/** * @dataProvider provideData */ public function testFromStream($mid, $subjectExpected, $fromExpected, $toExpected, $textExpected, $htmlExpected, $attachmentsExpected, $countEmbeddedExpected) { //Init $file = __DIR__ . '/mails/' . $mid; $attach_dir = __DIR__ . '/mails/attach_' . $mid . '/'; //Load From Path $Parser = new Parser(); $Parser->setStream(fopen($file, 'r')); //Test Header : subject $this->assertEquals($subjectExpected, $Parser->getHeader('subject')); $this->assertArrayHasKey('subject', $Parser->getHeaders()); //Test Header : from $this->assertEquals($fromExpected, $Parser->getHeader('from')); $this->assertArrayHasKey('from', $Parser->getHeaders()); //Test Header : to $this->assertEquals($toExpected, $Parser->getHeader('to')); $this->assertArrayHasKey('to', $Parser->getHeaders()); //Test Invalid Header $this->assertFalse($Parser->getHeader('azerty')); $this->assertArrayNotHasKey('azerty', $Parser->getHeaders()); //Test Body : text if ($textExpected[0] == 'COUNT') { $this->assertEquals($textExpected[1], substr_count($Parser->getMessageBody('text'), $textExpected[2])); } elseif ($textExpected[0] == 'MATCH') { $this->assertEquals($textExpected[1], $Parser->getMessageBody('text')); } //Test Body : html if ($htmlExpected[0] == 'COUNT') { $this->assertEquals($htmlExpected[1], substr_count($Parser->getMessageBody('html'), $htmlExpected[2])); } elseif ($htmlExpected[0] == 'MATCH') { $this->assertEquals($htmlExpected[1], $Parser->getMessageBody('html')); } //Test Nb Attachments $attachments = $Parser->getAttachments(); $this->assertEquals(count($attachmentsExpected), count($attachments)); $iterAttachments = 0; //Test Attachments $attachmentsEmbeddedToCheck = array(); if (count($attachmentsExpected) > 0) { //Save attachments $Parser->saveAttachments($attach_dir); foreach ($attachmentsExpected as $attachmentExpected) { //Test Exist Attachment $this->assertTrue(file_exists($attach_dir . $attachmentExpected[0])); //Test Filename Attachment $this->assertEquals($attachmentExpected[0], $attachments[$iterAttachments]->getFilename()); //Test Size Attachment $this->assertEquals($attachmentExpected[1], filesize($attach_dir . $attachments[$iterAttachments]->getFilename())); //Test Inside Attachment if ($attachmentExpected[2] != '' && $attachmentExpected[3] > 0) { $fileContent = file_get_contents($attach_dir . $attachments[$iterAttachments]->getFilename(), FILE_USE_INCLUDE_PATH); $this->assertEquals($attachmentExpected[3], substr_count($fileContent, $attachmentExpected[2])); $this->assertEquals($attachmentExpected[3], substr_count($attachments[$iterAttachments]->getContent(), $attachmentExpected[2])); } //Test ContentType Attachment $this->assertEquals($attachmentExpected[4], $attachments[$iterAttachments]->getContentType()); //Test ContentDisposition Attachment $this->assertEquals($attachmentExpected[5], $attachments[$iterAttachments]->getContentDisposition()); //Test md5 of Headers Attachment $this->assertEquals($attachmentExpected[6], md5(serialize($attachments[$iterAttachments]->getHeaders()))); //Save embedded Attachments to check if ($attachmentExpected[7] != '') { array_push($attachmentsEmbeddedToCheck, $attachmentExpected[7]); } //Remove Attachment unlink($attach_dir . $attachments[$iterAttachments]->getFilename()); $iterAttachments++; } //Remove Attachment Directory rmdir($attach_dir); } else { $this->assertFalse($Parser->saveAttachments($attach_dir)); } //Test embedded Attachments $htmlEmbedded = $Parser->getMessageBody('htmlEmbedded'); $this->assertEquals($countEmbeddedExpected, substr_count($htmlEmbedded, "data:")); if (!empty($attachmentsEmbeddedToCheck)) { foreach ($attachmentsEmbeddedToCheck as $itemExpected) { $this->assertEquals(1, substr_count($htmlEmbedded, $itemExpected)); } } }
/** * This is a ARF mail with a single incident * * @return array $reports */ public function parseSpamReportArf() { $reports = []; //Seriously spamcop? Newlines arent in the CL specifications $this->arfMail['report'] = str_replace("\r", "", $this->arfMail['report']); preg_match_all('/([\\w\\-]+): (.*)[ ]*\\r?\\n/', $this->arfMail['report'], $regs); $report = array_combine($regs[1], $regs[2]); //Valueable information put in the body instead of the report, thnx for that Spamcop if (strpos($this->arfMail['message'], 'Comments from recipient') !== false) { preg_match("/Comments from recipient.*\\s]\n(.*)\n\n\nThis/s", str_replace(array("\r", "> "), "", $this->arfMail['message']), $match); $report['recipient_comment'] = str_replace("\n", " ", $match[1]); } // Add the headers from evidence into infoblob $parsedEvidence = new MimeParser(); $parsedEvidence->setText($this->arfMail['evidence']); $headers = $parsedEvidence->getHeaders(); foreach ($headers as $key => $value) { if (is_array($value) || is_object($value)) { foreach ($value as $index => $subvalue) { $report['headers']["{$key}{$index}"] = "{$subvalue}"; } } else { $report['headers']["{$key}"] = $value; } } /* * Sometimes Spamcop has a trouble adding the correct fields. The IP is pretty * normal to add. In a last attempt we will try to fetch the IP from the body ourselves */ if (empty($report['Source-IP'])) { preg_match("/Email from (?<ip>[a-f0-9:\\.]+) \\/ " . preg_quote($report['Received-Date']) . "/s", $this->arfMail['message'], $regs); if (!empty($regs['ip']) && !filter_var($regs['ip'], FILTER_VALIDATE_IP) === false) { $report['Source-IP'] = $regs['ip']; } preg_match("/from: (?<ip>[a-f0-9:\\.]+)\r?\n?\r\n/s", $this->parsedMail->getMessageBody(), $regs); if (!empty($regs['ip']) && !filter_var($regs['ip'], FILTER_VALIDATE_IP) === false) { $report['Source-IP'] = $regs['ip']; } } $reports[] = $report; return $reports; }