<?php
/**
 * @package   admintools
 * @copyright Copyright (c)2010-2026 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace Akeeba\Plugin\System\AdminTools\Feature;

defined('_JEXEC') || die;

use Exception;

class MUAShield extends Base
{
	/**
	 * Is this feature enabled?
	 *
	 * @return bool
	 */
	public function isEnabled()
	{
		if (!$this->app->isClient('site'))
		{
			return false;
		}

		if ($this->skipFiltering)
		{
			return false;
		}

		return ($this->wafParams->getValue('muashield', 0) == 1);
	}

	/**
	 * Protects against a malicious User Agent string
	 */
	public function onAfterInitialise(): void
	{
		// Some PHP binaries don't set the $_SERVER array under all platforms
		if (!isset($_SERVER))
		{
			return;
		}

		if (!is_array($_SERVER))
		{
			return;
		}

		$this->blockUserAgent();

		$this->blockForwardHeader();
	}

	private function blockUserAgent()
	{
		// Some user agents don't set a UA string at all
		if (!array_key_exists('HTTP_USER_AGENT', $_SERVER))
		{
			return;
		}

		$mua = $_SERVER['HTTP_USER_AGENT'];
		$mua = trim($mua);

		if (strstr($mua, '<?'))
		{
			$this->exceptionsHandler->blockRequest('muashield');
		}

		// Serialised data in the MUA string?
		$patterns = [
			'@"feed_url@', // feed_url isn't your typical UA but it sure as hell is part of an exploit
			'@}__(.*)|O:@', // Typical start of serialised data
			'@J?Simple(p|P)ie(Factory)?@', // If SimplePie or JSimplepieFactory is referenced
		];

		foreach ($patterns as $pattern)
		{
			if (preg_match($pattern, $mua) == 1)
			{
				// Neuter the attack
				$neuterMUA = 'HACKING ATTEMPT DETECTED';
				// 1. Reset the User Agent string reported by the server
				$_SERVER['HTTP_USER_AGENT'] = $neuterMUA;
				// 2. Replace the saved User Agent in the session storage to something non-malicious
				$this->app->getSession()->set('session.client.browser', $neuterMUA);
				// 3. KILL THE SESSION (may not work, depends on the session handler)
				$this->app->getSession()->destroy();

				// Immediately block the scumbag
				$this->exceptionsHandler->blockRequest('muashield');
			}
		}
	}

	private function blockForwardHeader()
	{
		// Do I have a HTTP_X_FORWARDED_FOR header?
		if (!isset($_SERVER['HTTP_X_FORWARDED_FOR']) && !isset($_SERVER['HTTP_X_SUCURI_CLIENTIP']))
		{
			return;
		}

		// The same attack could be performed using the HTTP_X_FORWARDED_FOR / HTTP_X_SUCURI_CLIENTIP headers
		if (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
		{
			$header = $_SERVER['HTTP_X_FORWARDED_FOR'];
			$header = trim($header);

			$this->neuterMUA($header, 'HTTP_X_FORWARDED_FOR');
		}

		if (isset($_SERVER['HTTP_X_SUCURI_CLIENTIP']))
		{
			$header = $_SERVER['HTTP_X_SUCURI_CLIENTIP'];
			$header = trim($header);

			$this->neuterMUA($header, 'HTTP_X_SUCURI_CLIENTIP');
		}
	}

	/**
	 * @param $header
	 *
	 *
	 * @throws Exception
	 */
	private function neuterMUA($header, $headerName)
	{
		$patterns = [
			'@"feed_url@', // feed_url isn't your typical UA but it sure as hell is part of an exploit
			'@}__(.*)|O:@', // Typical start of serialised data
			'@"J?Simple(p|P)ie(Factory)?"@', // If SimplePie or JSimplepieFactory is referenced
		];

		foreach ($patterns as $pattern)
		{
			if (preg_match($pattern, $header))
			{
				// Neuter the attack
				$neuterMUA = 'HACKING ATTEMPT DETECTED';
				// 1. Reset the Forwarded header reported by the server
				$_SERVER[$headerName] = $neuterMUA;
				// 2. Replace the saved Forwarded header in the session storage to something non-malicious
				$this->app->getSession()->set('session.client.forwarded', $neuterMUA);
				// 3. KILL THE SESSION (may not work, depends on the session handler)
				$this->app->getSession()->destroy();

				// Immediately block the scumbag
				$this->exceptionsHandler->blockRequest('muashield');
			}
		}
	}
}
