<?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 ReflectionProperty;

class DFIShield 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('dfishield', 1) == 1);
	}

	/**
	 * Simple Direct Files Inclusion block.
	 */
	public function onAfterRoute(): void
	{
		$input = $this->input;

		$fallbackView = $input->getCmd('controller', '');
		$option       = $input->getCmd('option', '');
		$view         = $input->getCmd('view', $fallbackView);
		$layout       = $input->getCmd('layout', '');

		// Special case: JCE
		if (($option == 'com_jce') && ($view == 'editor') && ($layout == 'plugin'))
		{
			return;
		}

		$hashes = ['get', 'post'];

		foreach ($hashes as $hash)
		{
			$input = $this->input->$hash;
			$ref   = new ReflectionProperty($input, 'data');

			if (version_compare(PHP_VERSION, '8.1.0', 'lt'))
			{
				$ref->setAccessible(true);
			}

			$allVars = $ref->getValue($input);

			if (empty($allVars))
			{
				continue;
			}

			if ($this->match_array_dfi($allVars))
			{
				$extraInfo = "Hash      : $hash\n";
				$extraInfo .= "Variables :\n";
				$extraInfo .= print_r($allVars, true);
				$extraInfo .= "\n";
				$this->exceptionsHandler->blockRequest('dfishield', null, $extraInfo);
			}
		}
	}

	private function match_array_dfi($array)
	{
		$result = false;

		if (is_array($array))
		{
			foreach ($array as $key => $value)
			{
				if (!empty($this->exceptions) && in_array($key, $this->exceptions))
				{
					continue;
				}

				// If there's a null byte in the key, break
				if (strstr($key, "\u0000"))
				{
					$result = true;
					break;
				}

				// If there's no value, treat the key as a value
				if (empty($value))
				{
					$value = $key;
				}

				// Scan the value
				if (is_array($value))
				{
					$result = $this->match_array_dfi($value);
				}
				else
				{
					// If there's a null byte, break
					if (strstr($value, "\u0000"))
					{
						$result = true;
						break;
					}

					// If the value starts with a /, ../ or [a-z]{1,2}:, block
					$value = str_replace('\\', '/', $value);
					if (preg_match('#^(/|\.\.|[a-z]{1,2}:\\\)#i', $value))
					{
						// Fix 2.0.1: Check that the file exists
						$result = @file_exists($value);

						if (!$result)
						{
							$sillyParts = explode('../', $value);
							$realParts  = [];

							foreach ($sillyParts as $p)
							{
								if (!empty($p))
								{
									$realParts[] = $p;
								}
							}

							$path   = implode('/', $realParts);
							$result = @file_exists($path);
						}
						break;
					}

					if ($result)
					{
						break;
					}
				}
			}
		}

		return $result;
	}
}
