Files
fossbilling-mailgateway/InboundPolicy.php
T

102 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
namespace Box\Mod\Mailgateway;
use FOSSBilling\InformationException;
class InboundPolicy
{
private const FIELDS = [
'auto-submitted', 'precedence', 'return-path', 'content-type',
'x-auto-response-suppress', 'x-autoreply', 'x-autorespond', 'list-id',
];
/** Normalize parsed MIME headers and supported flat header fields. */
public function headers(array $data): array
{
$raw = $data['headers'] ?? [];
if (!is_array($raw)) {
throw new InformationException('headers must be an array of header values');
}
foreach (self::FIELDS as $field) {
$flat = str_replace('-', '_', $field);
if (array_key_exists($flat, $data)) {
$raw[$field] = array_merge((array) ($raw[$field] ?? []), (array) $data[$flat]);
}
}
$headers = [];
foreach ($raw as $name => $values) {
if (!is_string($name)) {
throw new InformationException('Header names must be strings');
}
$name = strtolower(str_replace('_', '-', $name));
if (!in_array($name, self::FIELDS, true)) {
continue;
}
$values = is_array($values) ? $values : [$values];
if (count($values) > 20) {
throw new InformationException('Too many repeated header values');
}
foreach ($values as $value) {
if (!is_string($value) || strlen($value) > 16384) {
throw new InformationException('Header values must be strings of at most 16384 bytes');
}
$value = preg_replace('/\r?\n[ \t]+/', ' ', $value);
if (preg_match('/[\x00-\x08\x0A-\x1F\x7F]/', $value)) {
throw new InformationException('Invalid control character in header value');
}
$headers[$name][] = strtolower(trim($value));
}
}
ksort($headers);
return $headers;
}
public function ignoreReason(array $headers): ?string
{
foreach ($headers['auto-submitted'] ?? [] as $value) {
if (trim(explode(';', $value, 2)[0]) !== 'no') {
return 'auto_submitted';
}
}
foreach ($headers['return-path'] ?? [] as $value) {
if (preg_match('/^<\s*>$/D', $value)) {
return 'null_return_path';
}
}
foreach ($headers['content-type'] ?? [] as $value) {
if (preg_match('/^multipart\/report\s*(?:;|$)/', $value)
|| preg_match('/^message\/(?:delivery-status|disposition-notification|global-delivery-status)\s*(?:;|$)/', $value)) {
return 'delivery_report';
}
}
foreach ($headers['precedence'] ?? [] as $value) {
if (in_array(trim(explode(';', $value, 2)[0]), ['bulk', 'list', 'junk'], true)) {
return 'bulk_mail';
}
}
foreach ($headers['list-id'] ?? [] as $value) {
if ($value !== '') {
return 'mailing_list';
}
}
foreach ($headers['x-auto-response-suppress'] ?? [] as $value) {
if (preg_match('/(?:^|[\s,;])(?:all|autoreply|oof|dr|ndr|rn|nrn)(?:$|[\s,;])/', $value)) {
return 'response_suppressed';
}
}
foreach (['x-autoreply', 'x-autorespond'] as $field) {
foreach ($headers[$field] ?? [] as $value) {
if (!in_array($value, ['', 'no', 'false', '0'], true)) {
return 'legacy_autoresponder';
}
}
}
return null;
}
}