Many PHP applications are distributed with configuration files for the Apache Web Server. These configuration files (usually called .htaccess) contain a number of settings for integrating the application with the Web Server’s capabilities. IIS 7 uses a file called web.config to contain settings for integration with applications. The sections below describe the most common uses of the .htaccess file by PHP applications, along with instructions how to achieve the results you seek using the web.config file for IIS.
Application Example
The .htaccess file for our test Application is included below. We’ll go through each of the sections of this file, and examine how you would establish the same functionality with IIS.
Request Filtering
Application uses the FilesMatch directive to limit browser access to files that are components of the application.
<FilesMatch "\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template)$">
Order allow,deny
</FilesMatch>
IIS uses the Request Filtering module to limit browser access to files like this. For example, for Application, the section could look like:
<security>
<requestFiltering>
<denyUrlSequences>
<add sequence="engine" />
<add sequence="inc" />
<add sequence="info" />
<add sequence="install" />
<add sequence="module" />
<add sequence="profile" />
<add sequence="po" />
<add sequence="sh" />
<add sequence="theme" />
<add sequence="tpl(\.php" />
<add sequence="Root" />
<add sequence="Tag" />
<add sequence="Template" />
<add sequence="Repository" />
<add sequence="code-style" />
</denyUrlSequences>
<fileExtensions>
<add fileExtension=".sql" allowed="false" />
<add fileExtension=".pl" allowed="false" />
</fileExtensions>
</requestFiltering>
</security>
NOTE: I leave this section commented out for installation, as the installation scripts are blocked by this filter.
An alternative to using the request filtering is to use the URL Rewriter (described below) to return a 403 error for any of the matching file types. This might be preferable, as it uses a regular expression for the match.
<rule name="Protect files and directories from prying eyes" stopProcessing="true">
<match url="\.(engine|inc|info|install|module|profile|test|po|sh|.*sql|theme|tpl(\.php)?|xtmpl|svn-base)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template|all-wcprops|entries|format)$" />
<action type="CustomResponse" statusCode="403" subStatusCode="0"
statusReason="Forbidden"
statusDescription="Access is forbidden." />
</rule>
Default Document
The DirectoryIndex directive tells the web server which file to load if no filename is included with the URL.
# Set the default handler.
DirectoryIndex index.php
For IIS, the default document should be set up as high in the web site hierarchy as the Module Handler. For example, with PHP, the Module Handler is usually set at the Web Server level. The Default Document should be set at that level as well, rather than locally within a Web Site’s context. To make sure that it is set for your application, you can use the following snippet within your web.config file:
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
URL Rewriting
IIS7 has an extension called “URL Rewrite”. You can use this extension to provide rules for IIS to rewrite incoming URL requests. The most common use of URL Rewriting is for applications that want to have shorter, easy to remember URLs. Many PHP applications currently ship with rewrite rules as part of their .htaccess file. These rules tell Apache’s mod_rewrite how and when to rewrite incoming requests. The IIS7 URL Rewrite module can read these rules, and translate them into URL Rewrite rules. The document linked below gives an overview of this procedure.
http://learn.iis.net/page.aspx/470/importing-apache-modrewrite-rules/
For Application, the relevant mod_rewrite rules are:
RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
The IIS URL Rewriter can read these rules and translate them. The translated URL Rewriter rules are:
<rewrite>
<rules>
<rule name="Imported Rule 1" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions>
<add input="{HTTP_HOST}" pattern="^example\.com$" />
</conditions>
<action type="Redirect" redirectType="Permanent" url="http://www.example.com/{R:1}" />
</rule>
<rule name="Imported Rule 2" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
<add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php?q={R:1}" appendQueryString="true" />
</rule>
</rules>
</rewrite>
Error page redirects / handling
Standard “Page Not Found” error messages from web servers usually don’t look integrated into the application. Some applications, like Application, choose to handle those errors within the scope of the application. The ErrorDocument directive below tells the web server to load the home page for any 404 or File Not Found errors.
# Make Application handle any 404 errors.
ErrorDocument 404 /index.php
IIS uses the httpErrors directive for this functionality. However, as the capability to set this at the application level is turned off by default for IIS, this section should be shipped commented out.
<!-- HTTP Errors section should only be enabled if the "Error Pages"
feature has been delegated as "Read/Write" at the Web Server level.
<httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/index.php" responseMode="ExecuteURL" />
</httpErrors>
-->
Directory Browsing
Another application security / integrity measure often implemented is to disable directory browsing from the clients. Many web server configurations will allow users to see a listing of files in a directory that doesn’t contain one of the Default Document files. Application disables this using the Options directive:
# Don't show directory listings for URLs which map to a directory.
Options -Indexes
IIS limits this access using the directoryBrowse directive:
<directoryBrowse enabled="false" />
Cache Aging
Caching directives are used to ensure that static content is cached for a while, and dynamic content isn’t cached at all. Application uses the ExpiresBy directives provided by mod_expires module.
# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
# Enable expirations.
ExpiresActive On
# Cache all files for 2 weeks after access (A).
ExpiresDefault A1209600
# Do not cache dynamically generated pages.
ExpiresByType text/html A1
</IfModule>
IIS uses the Output Caching module and the caching directive to control caching. For Application, we would enable caching for .html files with a maximum life of 14 days. For .php files, we would make sure that no caching was performed at all:
<caching>
<profiles>
<add extension=".php" policy="DisableCache" kernelCachePolicy="DisableCache" />
<add extension=".html" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod" duration="14:00:00:00" />
</profiles>
</caching>
Example Application .htaccess file
#
# Apache/PHP/Application settings:
#
# Protect files and directories from prying eyes.
<FilesMatch "\.(engine|inc|info|install|module|profile|po|sh|.*sql|theme|tpl(\.php)?|xtmpl)$|^(code-style\.pl|Entries.*|Repository|Root|Tag|Template)$">
Order allow,deny
</FilesMatch>
# Don't show directory listings for URLs which map to a directory.
Options -Indexes
# Follow symbolic links in this directory.
Options +FollowSymLinks
# Make Application handle any 404 errors.
ErrorDocument 404 /index.php
# Force simple error message for requests for non-existent favicon.ico.
<Files favicon.ico>
ErrorDocument 404 "The requested file favicon.ico was not found.
</Files>
# Set the default handler.
DirectoryIndex index.php
# Override PHP settings. More in sites/default/settings.php
# but the following cannot be changed at runtime.
# PHP 4, Apache 1.
<IfModule mod_php4.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# PHP 4, Apache 2.
<IfModule sapi_apache2.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# PHP 5, Apache 1 and 2.
<IfModule mod_php5.c>
php_value magic_quotes_gpc 0
php_value register_globals 0
php_value session.auto_start 0
php_value mbstring.http_input pass
php_value mbstring.http_output pass
php_value mbstring.encoding_translation 0
</IfModule>
# Requires mod_expires to be enabled.
<IfModule mod_expires.c>
# Enable expirations.
ExpiresActive On
# Cache all files for 2 weeks after access (A).
ExpiresDefault A1209600
# Do not cache dynamically generated pages.
ExpiresByType text/html A1
</IfModule>
# Various rewrite rules.
<IfModule mod_rewrite.c>
RewriteEngine on
# If your site can be accessed both with and without the 'www.' prefix, you
# can use one of the following settings to redirect users to your preferred
# URL, either WITH or WITHOUT the 'www.' prefix. Choose ONLY one option:
#
# To redirect all users to access the site WITH the 'www.' prefix,
# (http://example.com/... will be redirected to http://www.example.com/...)
# adapt and uncomment the following:
# RewriteCond %{HTTP_HOST} ^example\.com$ [NC]
# RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#
# To redirect all users to access the site WITHOUT the 'www.' prefix,
# (http://www.example.com/... will be redirected to http://example.com/...)
# uncomment and adapt the following:
# RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC]
# RewriteRule ^(.*)$ http://example.com/$1 [L,R=301]
# Modify the RewriteBase if you are using Application in a subdirectory or in a
# VirtualDocumentRoot and the rewrite rules are not working properly.
# For example if your site is at http://example.com/application uncomment and
# modify the following line:
# RewriteBase /application
#
# If your site is running in a VirtualDocumentRoot at http://example.com/,
# uncomment the following line:
# RewriteBase /
# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
</IfModule>
# $Id: .htaccess,v 1.90.2.1 2008/07/08 09:33:14 goba Exp $
Application Example Web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<configSections>
<sectionGroup name="system.webServer">
<sectionGroup name="rewrite">
<section name="rewriteMaps" overrideModeDefault="Allow" />
<section name="rules" overrideModeDefault="Allow" />
</sectionGroup>
</sectionGroup>
</configSections>
<system.webServer>
<security>
<!-- This section should be uncommented after
installation to secure the installation. -->
<!--
<requestFiltering>
<denyUrlSequences>
<add sequence="engine" />
<add sequence="inc" />
<add sequence="info" />
<add sequence="module" />
<add sequence="profile" />
<add sequence="po" />
<add sequence="sh" />
<add sequence="theme" />
<add sequence="tpl(\.php" />
<add sequence="Root" />
<add sequence="Tag" />
<add sequence="Template" />
<add sequence="Repository" />
<add sequence="code-style" />
</denyUrlSequences>
<fileExtensions>
<add fileExtension=".sql" allowed="false" />
<add fileExtension=".pl" allowed="false" />
</fileExtensions>
</requestFiltering>
-->
</security>
<directoryBrowse enabled="true" />
<caching>
<profiles>
<add extension=".php" policy="DisableCache" kernelCachePolicy="DisableCache" />
<add extension=".html" policy="CacheForTimePeriod" kernelCachePolicy="CacheForTimePeriod" duration="14:00:00:00" />
</profiles>
</caching>
<rewrite>
<rules>
<rule name="block favicon" stopProcessing="true">
<match url="favicon\.ico" />
<action type="CustomResponse" statusCode="404" subStatusCode="1"
statusReason="The requested file favicon.ico was not found"
statusDescription="The requested file favicon.ico was not found" />
</rule>
<rule name="Imported Rule 1" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions>
<add input="{HTTP_HOST}" pattern="^example\.com$" />
</conditions>
<action type="Redirect" redirectType="Permanent" url="http://www.example.com/{R:1}" />
</rule>
<rule name="Imported Rule 2" stopProcessing="true">
<match url="^(.*)$" ignoreCase="false" />
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
<add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" />
</conditions>
<action type="Rewrite" url="index.php?q={R:1}" appendQueryString="true" />
</rule>
</rules>
</rewrite>
<defaultDocument>
<files>
<remove value="index.php" />
<add value="index.php" />
</files>
</defaultDocument>
<!-- HTTP Errors section should only be enabled if the "Error Pages"
feature has been delegated as "Read/Write" at the Web Server level.
<httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/index.php" responseMode="ExecuteURL" />
</httpErrors>
-->
</system.webServer>
</configuration>
Comments