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

namespace Akeeba\Component\AdminTools\Administrator\Model;

defined('_JEXEC') or die;

use Akeeba\Component\AdminTools\Administrator\Helper\CloudIPRanges;
use Akeeba\Component\AdminTools\Administrator\Helper\ServerTechnology;
use DateTimeZone;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;

#[\AllowDynamicProperties]
class WebconfigmakerModel extends ServerconfigmakerModel
{

	/**
	 * The current configuration of this feature
	 *
	 * @var  object
	 */
	protected $configKey = 'wcconfig';

	/**
	 * The base name of the configuration file being saved by this feature, e.g. ".htaccess". The file is always saved
	 * in the site's root. Any old files under that name are renamed with a .admintools suffix.
	 *
	 * @var string
	 */
	protected $configFileName = 'web.config';

	/** @inheritdoc  */
	public function isSupported(): int
	{
		return ServerTechnology::isWebConfigSupported();
	}

	/**
	 * Compile and return the contents of the web.config configuration file
	 *
	 * @return string
	 */
	public function makeConfigFile()
	{
		// Make sure we are called by an expected caller
		ServerTechnology::checkCaller($this->allowedCallersForMake);

		$app = Factory::getApplication();

		$timezone = 'UTC';

		// Fetch the timezone from the user only if we're not in CLI
		if (!$app->isClient('cli'))
		{
			$timezone = $app->getIdentity()->getParam('timezone', $app->get('offset', 'UTC'));
		}

		$date = clone Factory::getDate();
		$tz   = new DateTimeZone($timezone);
		$date->setTimezone($tz);
		$d       = $date->format('Y-m-d H:i:s T', true);
		$version = ADMINTOOLS_VERSION;

		$webConfig = <<< XML
<?xml version="1.0" encoding="utf-8"?>
<!--
	Security Enhanced & Highly Optimized .web.config File for Joomla!
	automatically generated by Admin Tools $version on $d

	Admin Tools is Free Software, distributed under the terms of the GNU
	General Public License version 3 or, at your option, any later version
	published by the Free Software Foundation.

	!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! IMPORTANT !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	!!                                                                       !!
	!!  If you get an Internal Server Error 500 or a blank page when trying  !!
	!!  to access your site, remove this file and try tweaking its settings  !!
	!!  in the back-end of the Admin Tools component.                        !!
	!!                                                                       !!
	!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-->
<configuration>
	<system.webServer>

XML;

		$config = (object) $this->loadConfiguration();

		// Let's start with IP restriction
		$restrictIP = $config->restrictip ?? 'none';

		if ($restrictIP !== 'none')
		{
			$restrictIPList = CloudIPRanges::getIPRanges($restrictIP);

			if ($restrictIPList === null)
			{
				throw new \RuntimeException(Text::_('COM_ADMINTOOLS_ERR_INVALID_RESTRICTIP'));
			}

			$ipRules = '';
			foreach ($restrictIPList as $ipRange)
			{
				[$ip, $subNet] = $this->ipRangeToIPandNetmask($ipRange);
				$ipRules .= "\t\t\t\t<add ipAddress=\"$ip\" subnetMask=\"$subNet\" allowed=\"true\" />\n";
			}

			$requestFilteringRules = '';

			if ($config->notracetrack == 1)
			{
				$requestFilteringRules = <<<XML
			<!-- Disable HTTP methods TRACE and TRACK (protect against XST) -->
			<requestFiltering>
				<verbs>
					<add verb="TRACE" allowed="false" />
					<add verb="TRACK" allowed="false" />
				</verbs>
			</requestFiltering>
XML;
			}

			$webConfig .= <<< END
		<security>
			<!-- Restricted access by IP address -->
			<ipSecurity allowUnlisted="false" denyAction="Forbidden">
$ipRules
			</ipSecurity>
			$requestFilteringRules
		</security>
END;

		}


		if ($config->fileorder == 1)
		{
			$webConfig .= <<< XML
		<!-- File execution order -->
		<defaultDocument enabled="true">
			<files>
				<clear />
				<add value="index.php" />
				<add value="index.html" />
				<add value="index.htm" />
			</files>
		</defaultDocument>

XML;
		}

		if ($config->nodirlists == 1)
		{
			$webConfig .= <<< XML
		<!-- No directory listings -->
		<directoryBrowse enabled="false" />

XML;
		}

		if ($config->exptime != 0)
		{
			$setEtag  = ($config->etagtype == 'none') ? 'setEtag="false"' : '';
			$eTagInfo = ($config->etagtype == 'none') ? '// Send ETag: false (IIS only supports true/false for ETags)' : '';

			// Expiration time of 1 week or 1 year (based on selection)
			$expTime = ($config->exptime == 1) ? 604800 : 31708800;

			$webConfig .= <<< XML
		<!-- Optimal default expiration time $eTagInfo -->
		<staticContent>
			<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="$expTime" $setEtag />
		</staticContent>

XML;
		}

		if ($config->autocompress == 1)
		{
			$webConfig .= <<<XML
		<urlCompression doStaticCompression="false" doDynamicCompression="true" />
		<httpCompression>
			<dynamicTypes>
				<clear />
				<add mimeType="text/*" enabled="true" />
				<add mimeType="message/*" enabled="true" />
				<add mimeType="application/xml" enabled="true" />
				<add mimeType="application/xhtml+xml" enabled="true" />
				<add mimeType="application/rss+xml" enabled="true" />
				<add mimeType="application/javascript" enabled="true" />
				<add mimeType="text/javascript" enabled="true" />
				<add mimeType="application/x-javascript" enabled="true" />
				<add mimeType="image/svg+xml" enabled="true" />
				<add mimeType="*/*" enabled="false" />
			</dynamicTypes>
		</httpCompression>

XML;
		}


		$webConfig .= <<< XML
		<rewrite>
			<rules>
				<clear />

XML;

		if (!empty($config->hoggeragents) && ($config->nohoggers == 1))
		{
			$conditions   = '';
			$patternCache = [];

			foreach ($config->hoggeragents as $agent)
			{
				$patternCache[] = $agent;

				if ((is_array($agent) || $agent instanceof \Countable ? count($agent) : 0) < 10)
				{
					continue;
				}

				$newPattern   = implode('|', $patternCache);
				$conditions   .= <<< XML
<add input="{HTTP_USER_AGENT}" pattern="$newPattern" />

XML;
				$patternCache = [];
			}

			if (count($patternCache))
			{
				$newPattern = implode('|', $patternCache);
				$conditions .= <<< XML
						<add input="{HTTP_USER_AGENT}" pattern="$newPattern" />

XML;
			}

			$webConfig .= <<< XML
				<rule name="Common hacking tools and bandwidth hoggers block" stopProcessing="true">
					<match url=".*" />
					<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
$conditions
					</conditions>
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden: Access is denied." statusDescription="You do not have permission to view this directory or page using the credentials that you supplied." />
				</rule>

XML;
		}

		if ($config->autoroot)
		{
			$webConfig .= <<<XML
				<rule name="Redirect index.php to /" stopProcessing="true">
					<match url="^index\.php$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll">
						<add input="{THE_REQUEST}" pattern="^POST" ignoreCase="false" negate="true" />
						<add input="{THE_REQUEST}" pattern="^[A-Z]{3,9}\ /index\.php\ HTTP/" ignoreCase="false" />
						<add input="{HTTPS}>s" pattern="^(1>(s)|0>s)$" ignoreCase="false" />
					</conditions>
					<action type="Redirect" url="http{C:2}://{HTTP_HOST}:{SERVER_PORT }/" redirectType="Permanent" />
				</rule>

XML;
		}

		switch ($config->wwwredir)
		{
			case 1:
				// If I have a rewriteBase condition, I have to append it here
				$subfolder = trim($config->rewritebase, '/') ? trim($config->rewritebase, '/') . '/' : '';

				// non-www to www
				$webConfig .= <<<END
				<rule name="Redirect non-www to www" stopProcessing="true">
					<match url="^(.*)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll">
						<add input="{HTTP_HOST}" pattern="^www\." negate="true" />
					</conditions>
					<action type="Redirect" url="http://www.{HTTP_HOST}/$subfolder{R:1}" redirectType="Found" />
				</rule>

END;
				break;

			case 2:
				// www to non-www
				$webConfig .= <<<END
				<rule name="Redirect www to non-www" stopProcessing="true">
					<match url="^(.*)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll">
						<add input="{HTTP_HOST}" pattern="^www\.(.+)$" />
					</conditions>
					<action type="Redirect" url="http://{C:1}/{R:1}" redirectType="Found" />
				</rule>

END;
				break;
		}

		if (!empty($config->olddomain))
		{
			$domains   = trim($config->olddomain);
			$domains   = explode(',', $domains);
			$newdomain = $config->httphost;

			foreach ($domains as $olddomain)
			{
				$olddomain         = trim($olddomain);
				$originalOldDomain = $olddomain;

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

				$olddomain = $this->escape_string_for_regex($olddomain);

				$webConfig .= <<<END
				<rule name="Redirect old to new domain ($originalOldDomain)" stopProcessing="true">
					<match url="(.*)" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll">
						<add input="{HTTP_HOST}" pattern="^$olddomain" />
					</conditions>
					<action type="Redirect" url="http://$newdomain/{R:1}" redirectType="Found" />
				</rule>

END;
			}
		}

		if (!empty($config->httpsurls))
		{
			$webConfig .= "<!-- Force HTTPS for certain pages -->\n";
			foreach ($config->httpsurls as $url)
			{
				$urlesc    = '^' . $this->escape_string_for_regex($url) . '$';
				$webConfig .= <<<END
				<rule name="Force HTTPS for $url" stopProcessing="true">
					<match url="^$urlesc$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAny">
						<add input="{HTTPS}" pattern="0" />
					</conditions>
					<action type="Redirect" url="https://{$config->httpshost}/$url" redirectType="Found" />
				</rule>

END;
			}
		}

		$webConfig .= <<<END
				<rule name="Block out some common exploits">
					<match url=".*" ignoreCase="false" />
					<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
						<add input="{QUERY_STRING}" pattern="proc/self/environ" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="mosConfig_[a-zA-Z_]{1,21}(=|\%3D)" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="base64_(en|de)code\(.*\)" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="(&lt;|%3C).*script.*(>|%3E)" />
						<add input="{QUERY_STRING}" pattern="GLOBALS(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="_REQUEST(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
					</conditions>
					<action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

END;

		if ($config->fileinj == 1)
		{
			$webConfig .= <<<END
				<rule name="File injection protection" stopProcessing="true">
					<match url=".*" ignoreCase="false" />
					<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
						<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=http://" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=(\.\.//?)+" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="[a-zA-Z0-9_]=/([a-z0-9_.]//?)+" />
					</conditions>
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

END;
		}

		$webConfig .= "                <!-- Advanced server protection rules exceptions -->\n";

		if (!empty($config->exceptionfiles))
		{
			$ruleCounter = 0;

			foreach ($config->exceptionfiles as $file)
			{
				$ruleCounter++;
				$file      = '^' . $this->escape_string_for_regex($file) . '$';
				$webConfig .= <<<END
				<rule name="Advanced server protection rules exception #$ruleCounter" stopProcessing="true">
					<match url="$file" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>

END;
			}
		}

		if (!empty($config->exceptiondirs))
		{
			$ruleCounter = 0;

			foreach ($config->exceptiondirs as $dir)
			{
				$ruleCounter++;
				$dir       = trim($dir, '/');
				$dir       = $this->escape_string_for_regex($dir);
				$webConfig .= <<<END
				<rule name="Allow access to folders except .php files #$ruleCounter" stopProcessing="true">
					<match url="^$dir/" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
						<add input="{URL}" pattern="(\.php)$" ignoreCase="false" negate="true" />
						<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
					</conditions>
					<action type="None" />
				</rule>

END;
			}
		}

		if (!empty($config->fullaccessdirs))
		{
			$ruleCounter = 0;

			foreach ($config->fullaccessdirs as $dir)
			{
				$ruleCounter++;
				$dir       = trim($dir, '/');
				$dir       = $this->escape_string_for_regex($dir);
				$webConfig .= <<<END
				<rule name="Allow access to folders, including .php files #$ruleCounter" stopProcessing="true">
					<match url="^$dir/" ignoreCase="false" />
					<action type="None" />
				</rule>

END;
			}
		}

		if ($config->backendprot == 1)
		{
			$directories = $this->bugfixBackendProtectionExclusionDirectories($config->bepexdirs ?: []);
			$bedirs      = implode('|', $directories);
			$betypes     = implode('|', $config->bepextypes);
			$webConfig   .= <<<END
				<rule name="Back-end protection - allow administrator login" stopProcessing="true">
					<match url="^administrator/?$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Back-end protection - allow administrator login, alternate" stopProcessing="true">
					<match url="^administrator/index\.(php|html?)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Back-end protection - allow access to static media files" stopProcessing="true">
					<match url="^administrator/($bedirs)/.*\.($betypes)$" ignoreCase="true" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Back-end protection - Catch all">
					<match url="^administrator/" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

END;
		}

		if ($config->frontendprot == 1)
		{
			$fedirs    = implode('|', $config->fepexdirs);
			$fetypes   = implode('|', $config->fepextypes);
			$webConfig .= <<<END
				<rule name="Front-end protection - allow access to additional TinyMCE plugins' HTML files" stopProcessing="true">
					<match url="^media/plg_editors_tinymce/js/plugins/.*\.(htm|html)$" ignoreCase="true" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Front-end protection - allow access to static media files" stopProcessing="true">
					<match url="^($fedirs)/.*\.($fetypes)$" ignoreCase="true" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Front-end protection - Do not block includes/js" stopProcessing="true">
					<match url="^includes/js/" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="None" />
				</rule>
				<rule name="Front-end protection - Block access to certain folders">
					<match url="^($fedirs)/" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>
				<rule name="Front-end protection - Block access to certain folders, part 2">
					<match url="^(cache|includes|language|logs|log|tmp)/" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>
				<rule name="Front-end protection - Forbid access to leftover Joomla! files">
					<match url="^(configuration\.php|CONTRIBUTING\.md|htaccess\.txt|joomla\.xml|LICENSE\.txt|phpunit\.xml|README\.txt|web\.config\.txt)" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>
				<rule name="Front-end protection - Block access to all PHP files except index.php">
					<match url="(.*\.php)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
						<add input="{URL}" pattern="(\.php)$" ignoreCase="false" />
						<add input="{URL}" pattern="(/index?\.php)$" ignoreCase="false" negate="true" />
						<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" />
					</conditions>
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

END;
		}

		if ($config->leftovers == 1)
		{
			$webConfig .= <<<END
				<rule name="Front-end protection - Block access to common server configuration files">
					<match url="^(htaccess\.txt|configuration\.php-dist|php\.ini|.user\.ini|web\.config|web\.config\.txt)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false" />
					<action type="CustomResponse" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

END;
		}

		$webConfig .= <<< XML
				<rule name="Joomla! Common Exploits Prevention" stopProcessing="true">
					<match url="^(.*)$" ignoreCase="false" />
					<conditions logicalGrouping="MatchAny" trackAllCaptures="false">
						<add input="{QUERY_STRING}" pattern="base64_encode[^(]*\([^)]*\)" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="(>|%3C)([^s]*s)+cript.*(&lt;|%3E)" />
						<add input="{QUERY_STRING}" pattern="GLOBALS(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
						<add input="{QUERY_STRING}" pattern="_REQUEST(=|\[|\%[0-9A-Z]{0,2})" ignoreCase="false" />
					</conditions>
					<action type="CustomResponse" url="index.php" statusCode="403" statusReason="Forbidden" statusDescription="Forbidden" />
				</rule>

XML;

		$webConfig .= <<< XML
			<rule name="Joomla! API Application SEF URLs">
               <match url="^api/(.*)" ignoreCase="false" />
               <conditions logicalGrouping="MatchAll">
                 <add input="{URL}" pattern="^/api/index.php" ignoreCase="true" negate="true" />
                 <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
                 <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
               </conditions>
               <action type="Rewrite" url="api/index.php" />
           </rule>

XML;

		$webConfig .= <<< XML
				<rule name="Joomla! SEF Rule 2">
					<match url="(.*)" ignoreCase="false" />
					<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
						<add input="{URL}" pattern="^/index.php" ignoreCase="true" negate="true" />
						<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
						<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
					</conditions>
					<action type="Rewrite" url="index.php" />
				</rule>

			</rules>

XML;

		if (($config->noserversignature == 1) || ($config->svgneutralize == 1))
		{
			$webConfig .= <<< XML
		<outboundRules>

XML;

			if ($config->noserversignature == 1)
			{
				$webConfig .= <<< XML
		  <rule name="Remove IIS version siganture">
			<match serverVariable="RESPONSE_Server" pattern=".+" />
			<action type="Rewrite" value="MYOB" />
		  </rule>

XML;
			}

			if ($config->svgneutralize == 1)
			{
				$webConfig .= <<< XML
		  <rule name="Neutralize SVG scritp execution">
			<match serverVariable="RESPONSE_Content_Security_Policy" pattern="^$" />
			<conditions>
				<add input="{REQUEST_FILENAME}" pattern="\.svg$" />
			</conditions>
			<action type="Rewrite" value="script-src 'none'"/>
		  </rule>

XML;
			}

			$webConfig .= <<< XML
		</outboundRules>

XML;
		}

		$webConfig .= <<< XML
		</rewrite>
		<httpProtocol>
			<customHeaders>

XML;

		if ($config->clickjacking == 1)
		{
			$webConfig .= <<< ENDCONF
				<!-- Protect against clickjacking / Forbid displaying in FRAME -->
				<add name="X-Frame-Options" value="SAMEORIGIN" />

ENDCONF;
		}

		if ($config->reducemimetyperisks == 1)
		{
			$webConfig .= <<< XML
				<!-- Reduce MIME type security risks -->
				<add name="X-Content-Type-Options" value="nosniff" />

XML;
		}

		if ($config->reflectedxss == 1)
		{
			$webConfig .= <<< XML
				<!-- Reflected XSS prevention -->
				<add name="X-XSS-Protection" value="1; mode=block" />

XML;
		}

		if ($config->noserversignature == 1)
		{
			$webConfig .= <<< XML
				<!-- Remove IIS and PHP version signature -->
				<remove name="X-Powered-By" />
				<add name="X-Powered-By" value="MYOB" />
				<remove name="X-Content-Powered-By" />
				<add name="X-Content-Powered-By" value="MYOB" />

XML;

		}

		if ($config->notransform == 1)
		{
			$webConfig .= <<< XML
				<!-- Prevent content transformation -->
				<add name="Cache-Control" value="no-transform" />

XML;
		}

		if ($config->hstsheader == 1)
		{
			$webConfig .= <<<XML
				<!-- HSTS Header - See http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security -->
				<add name="Strict-Transport-Security" value="max-age=31536000" />

XML;
		}
		elseif ($config->hstsheader == 2)
		{
			$webConfig .= <<<XML
				<!-- HSTS Header - See http://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security -->
				<add name="Strict-Transport-Security" value="max-age=31536000; includeSubDomains; preload" />

XML;
		}

		if ($config->cors == 1)
		{
			$webConfig .= <<<XML
				<!-- Explicitly enable Cross-Origin Resource Sharing (CORS) - See http://enable-cors.org/ -->
				<add name="Access-Control-Allow-Origin" value="*" />
				<add name="Timing-Allow-Origin" value="*" />

XML;
		}
		elseif ($config->cors == -1)
		{
			$webConfig .= <<<XML
				<!-- Explicitly disable Cross-Origin Resource Sharing (CORS) - See http://enable-cors.org/ -->
				<add name="Cross-Origin-Resource-Policy" value="same-origin" />

XML;
		}

		if ($config->referrerpolicy !== '-1')
		{
			$webConfig .= <<<XML
				<!-- Referrer-policy -->
				<add name="Referrer-Policy" value="{$config->referrerpolicy}" />

XML;
		}

		$webConfig .= <<< XML
			</customHeaders>
		</httpProtocol>

XML;

		// If IP restriction is turned on, this block is moved towards the top of the web.config file.
		if ($config->notracetrack == 1 && $restrictIP === 'none')
		{
			$webConfig .= <<<XML
		<!-- Disable HTTP methods TRACE and TRACK (protect against XST) -->
		<security>
			<requestFiltering>
				<verbs>
					<add verb="TRACE" allowed="false" />
					<add verb="TRACK" allowed="false" />
				</verbs>
			</requestFiltering>
		</security>

XML;
		}

		$webConfig .= <<< XML
	</system.webServer>
</configuration>

XML;

		return $webConfig;
	}

	private function ipRangeToIPandNetmask(string $ipRange): array
	{
		$ipRange = explode('/', $ipRange);
		$ip      = $ipRange[0];
		$cidr    = $ipRange[1] ?? null;
		$isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
		$cidr ??= $isIPv4 ? 32 : 128;
		$isNumeric = preg_match('/^\d+$/', $cidr);

		if (!$isNumeric)
		{
			$netmask = $cidr;
		}
		elseif ($isIPv4)
		{
			$cidr = min(max($cidr, 0), 32);
			$netmask = long2ip(~((1 << (32 - $cidr)) - 1));
		}
		else
		{
			$cidr     = min(max($cidr, 0), 128);
			$mask     = str_repeat('1', $cidr) . str_repeat('0', 128 - $cidr);
			$segments = str_split($mask, 16);
			$hextet   = array_map(
				function ($segment) {
					return str_pad(dechex(bindec($segment)), 4, '0', STR_PAD_LEFT);
				}, $segments
			);

			$netmask = implode(':', $hextet);
		}

		return [$ip, $netmask];
	}
}

© 2025 Cubjrnet7