Add in software files and templates

This commit is contained in:
2023-06-21 14:19:40 +01:00
parent f42fdb947c
commit 5228fc5e9f
143 changed files with 23175 additions and 2 deletions

View File

@@ -0,0 +1,194 @@
<?php
/**
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
* Copyright (C) 2020 Aleksey Andreev (liuch)
*
* Available at:
* https://github.com/liuch/dmarc-srg
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Liuch\DmarcSrg\ReportFile;
use Liuch\DmarcSrg\Exception\SoftException;
class ReportFile
{
private $fd = null;
private $zip = null;
private $type = null;
private $remove = false;
private $filename = null;
private $filepath = null;
private $gzcutfilter = null;
private $gzinflatefilter = null;
private static $filters = [];
private static $ext_mime_map = [
'xml' => 'text/xml',
'gz' => 'application/gzip',
'zip' => 'application/zip'
];
private function __construct($filename, $type = null, $fd = null, $remove = false, $filepath = null)
{
$this->filename = $filename;
$this->type = $type ?? self::getMimeType($filename, $fd, $filepath);
$this->remove = $remove;
$this->filepath = $filepath;
switch ($this->type) {
case 'application/gzip':
case 'application/x-gzip':
$this->fd = $fd;
break;
case 'application/zip':
if ($fd) {
$tmpfname = tempnam(sys_get_temp_dir(), 'dmarc_');
if ($tmpfname === false) {
throw new SoftException('Failed to create a temporary file');
}
rewind($fd);
if (file_put_contents($tmpfname, $fd) === false) {
throw new SoftException('Failed to copy data to a temporary file');
}
$this->filepath = $tmpfname;
$this->remove = true;
}
break;
}
}
public function __destruct()
{
if ($this->fd) {
if ($this->isGzipStream()) {
$this->enableGzFilter(false);
}
gzclose($this->fd);
}
if ($this->zip) {
$this->zip->close();
}
if ($this->remove && $this->filepath) {
unlink($this->filepath);
}
}
public static function getMimeType($filename, $fd = null, $filepath = null)
{
if (function_exists('mime_content_type')) {
if ($fd && ($res = mime_content_type($fd))) {
return $res;
}
if ($filepath && ($res = mime_content_type($filepath))) {
return $res;
}
}
$ext = pathinfo(basename($filename), PATHINFO_EXTENSION);
return self::$ext_mime_map[$ext] ?? 'application/octet-stream';
}
public static function fromFile($filepath, $filename = null, $remove = false)
{
if (!is_file($filepath)) {
throw new SoftException('ReportFile: it is not a file');
}
return new ReportFile(
$filename ? basename($filename) : basename($filepath),
null,
null,
$remove,
$filepath
);
}
public static function fromStream($fd, $filename, $type)
{
return new ReportFile($filename, $type, $fd);
}
public function filename()
{
return $this->filename;
}
public function datastream()
{
if (!$this->fd) {
$fd = null;
switch ($this->type) {
case 'application/zip':
$this->zip = new \ZipArchive();
$this->zip->open($this->filepath);
if ($this->zip->count() !== 1) {
throw new SoftException('The archive must have only one file in it');
}
$zfn = $this->zip->getNameIndex(0);
if ($zfn !== pathinfo($zfn, PATHINFO_BASENAME)) {
throw new SoftException('There must not be any directories in the archive');
}
$fd = $this->zip->getStream($zfn);
break;
default:
// gzopen() can be used to read a file which is not in gzip format;
// in this case gzread() will directly read from the file without decompression.
$fd = gzopen($this->filepath, 'r');
break;
}
if (!$fd) {
throw new SoftException('Failed to open a report file');
}
$this->fd = $fd;
}
if ($this->isGzipStream()) {
ReportFile::ensureRegisterFilter(
'report_gzfile_cut_filter',
'Liuch\DmarcSrg\ReportFile\ReportGZFileCutFilter'
);
$this->enableGzFilter(true);
}
return $this->fd;
}
private static function ensureRegisterFilter($filtername, $classname)
{
if (!isset(ReportFile::$filters[$filtername])) {
stream_filter_register($filtername, $classname);
ReportFile::$filters[$filtername] = true;
}
}
private function enableGzFilter($enable)
{
if ($enable) {
if (!$this->gzcutfilter) {
$this->gzcutfilter = stream_filter_append($this->fd, 'report_gzfile_cut_filter', STREAM_FILTER_READ);
$this->gzinflatefilter = stream_filter_append($this->fd, 'zlib.inflate', STREAM_FILTER_READ);
}
} else {
if ($this->gzcutfilter) {
stream_filter_remove($this->gzinflatefilter);
stream_filter_remove($this->gzcutfilter);
}
}
}
private function isGzipStream(): bool
{
return (in_array($this->type, [ 'application/gzip', 'application/x-gzip' ]) && !$this->filepath);
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* dmarc-srg - A php parser, viewer and summary report generator for incoming DMARC reports.
* Copyright (C) 2020 Aleksey Andreev (liuch)
*
* Available at:
* https://github.com/liuch/dmarc-srg
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace Liuch\DmarcSrg\ReportFile;
use php_user_filter;
class ReportGZFileCutFilter extends php_user_filter
{
private $head = true;
private $header_data = '';
private $tail_data = '';
public function filter($in, $out, &$consumed, $closing): int
{
$b_cnt = 0;
while ($bucket = stream_bucket_make_writeable($in)) {
$consumed += $bucket->datalen;
$data = null;
if ($this->head) {
$data = $this->skipGzHeader($bucket->data);
} else {
$data = $bucket->data;
}
$data = $this->cutGzTail($data);
if (strlen($data) > 0) {
$bucket->data = $data;
stream_bucket_append($out, $bucket);
$b_cnt += 1;
}
}
return ($b_cnt > 0) ? PSFS_PASS_ON : PSFS_FEED_ME;
}
private function skipGzHeader($data)
{
// https://tools.ietf.org/html/rfc1952
$this->header_data .= $data;
$len = strlen($this->header_data);
if ($len < 10) { // minimal gz header
return '';
}
$pos = 10;
$flags = ord($this->header_data[3]);
if ($flags & 4) { // FLG.FEXTRA
$pos += (ord($this->header_data[$pos + 1]) | (ord($this->header_data[$pos + 2]) << 8)) + 2;
if ($pos > $len) {
return '';
}
}
if ($flags & 8) { // FLG.FNAME
$pos = $this->skipZeroTerminatedString($this->header_data, $len, $pos);
if ($pos > $len) {
return '';
}
}
if ($flags & 16) { // FLG.FCOMMENT
$pos = $this->skipZeroTerminatedString($this->header_data, $len, $pos);
if ($pos > $len) {
return '';
}
}
if ($flags & 2) { // FLG.FHCRC
$pos += 2;
if ($pos > $len) {
return '';
}
}
$res = substr($this->header_data, $pos);
$this->head = false;
$this->header_data = '';
return $res;
}
private function cutGzTail($data)
{
$res = $this->tail_data . $data;
$this->tail_data = substr($res, -8);
if (strlen($res) <= 8) {
return '';
}
return substr($res, 0, -8);
}
private function skipZeroTerminatedString($str, $len, $pos)
{
for ($i = $pos; $i < $len; ++$i) {
if ($str[$i] === "\0") {
return $i + 1;
}
}
return $len + 1;
}
}