In a Web site, the proper handling of address modifications (renaming, new structure, new technology etc.) is an important aspect of the life cycle.
To do so, there is for instance a broadly used HTTP redirection system under Apache, but it has some shortcomings such as not being portable to other Web servers such as IIS, with a risk to slow down a Web site, and to be difficult to test or migrate to a server with a different address or root (typical for a development server).
In the case of IIS, the handling of redirections can be fastidious, and not always available in the case of shared hosting.
In response to the problem, I propose on this page a redirection management system based on personalised 404 error pages. That is to say this redirection system is only activated when a resource is not found, thus supporting a higher load on the server. The system is compatible with Web servers such as Apache, Microsoft IIS, and others. An implementation is already available in PHP, and in ASP.NET. Finally, porting this system into another language is simple (JSP is planned).
When a Web page is not found, instead of displaying a 404 error message right away, the script will search in a list of rules to see if a redirection is available. If such a rule is found, an HTTP redirection will be sent to the client; otherwise, a 404 error page will be displayed.
Admitting that you will place the redirection script in a sub-folder /errors/ of your Web site (recommended), it will search by default for some redirections rules in /errors/404//404.txt, that is to say 404.txt files located in the folder /errors/404/ or any sub-folder, as illustrated in the following file tree:
The name of the folders and sub-folders must match existing or former folders of the Web site to redirect.
There is no limit on the deepness and breath of the tree structure.
Making use of sub-folders is not required and all redirection rules can be in the same file /errors/404/404.txt.
However, this possibility of tree structure is interesting to avoid having one single large 404.txt file, to better manage related rules,
and to improve the performances by avoiding the need to read many rules.
A sub-folder is typically created when there is a number of rules targeting it or its child.
In the name of the folders, special characters that are not acceptable in a URL (more or less everything except a-z_.0-9-
) have to be %-encoded.
In particular, a space will be encoded %20 (see above example: folder%20with%20spaces/).
Warning: the redirection script is set up by default to be in a sub-folder (level 1) of the Web site, such as /errors/ like in the example above. This can be modified in the constant distanceToRoot at the beginning of the script.
When a sub-folder does exist (which is not mandatory), all the redirections rules targeting it must be stored into
its 404.txt file.
In the end, for a given redirection request, only one single 404.txt file will be read:
the most precise one in the tree structure of /errors/404//.
The syntax is explained just after. Have a look already to the following redirection rule:
404.txt permanent /old-folder/sub-folder/file\.html /new-map/file.html
This rule can be placed in the file /errors/404/404.txt, or /errors/404/old-folder/404.txt, or even /errors/404/old-folder/sub-folder/404.txt. However, if the folder /errors/404/old-folder/ does exist, the rule will not be read if it is stored into the parent folder /errors/404/404.txt.
Note that by default, as opposed to Apache’s .htaccess files, the 404.txt files are not mixed with the rest of the site (they are in /errors/404/), and this is in order to avoid having to maintain old folder (or to have one large file at the root). Nevertheless, it is possible to customize the location where these 404.txt files are searched, thanks to the path404 variable at the beginning of the script.
The syntax of a redirection rule is the following (one rule per line, and the three parts of the rules are separated by tabulations or spaces):
404.txt <redirection type> <regular expression of the old address> <new address>
The syntax used for the redirection rules is similar to the one used by the Apache’s instruction RedirectMatch
;
here are the main differences:
RedirectMatch
style (i.e. based on regular expressions), and not simply Redirect
.^
at the beginning and the code $
at the end of the pattern are implied and must not be written..*
at the beginning and/or at the end of the expression.The different types of redirections are:
permanent
HTTP/1.1 301 Moved Permanently
)temp
or found
HTTP/1.1 302 Found
)seeother
HTTP/1.1 303 See Other
)temporary
HTTP/1.1 307 Temporary Redirect
)gone
HTTP/1.1 410 Gone
)The old addresses are written as regular expressions. If you are not familiar with this syntax, just write the old address by taking care to treat some special characters are follow:
/:a-z_.0-9-
) have to be %-encoded.
\
.
^
of the beginning and the $
of the end of the pattern are implied and must not be written.
The old addresses are understood from the root of the Web site; that is to say they neither contain http:// nor the name of the server.
For instance, to redirect http://example.net/old-folder/, the old address will be written /old-folder/ simply.
In particular, if a Web site is not at the root of is Web server, like http://example.net/~myWebSite/,
the redirections are understood from the root of the Web site and not from the root of the server
(this can be set up in the distanceToRoot constant at the beginning of the script).
For instance, to redirect http://example.net/~myWebSite/old-folder/, the old address will be written /old-folder/ only.
The former address can optionally be abbreviated according to the location of the 404.txt file (such as abbreviating /old-folder/file\.html as file\.html if the instruction is located in /errors/404/old-folder/404.txt). However, the full syntax given relatively to the root is recommended for its flexibility and robustness.
The new addresses are not regular expressions; anti-slash escaping must therefore not be used.
It is possible to use $1
, $2
etc. to reference a captured ()
coming from the regular expression of the former address.
The redirection addresses can be absolute (http://example.net/new-folder/filer.html), or also relative to the root of the site (/new-folder/file.html) [recommended], or relative to the former location (new-file.html)
/errors/404/404.txt #Redirect a precise file permanent /old-folder/sub-folder/file\.html /new-folder/file.html #Same example, but with a folder name containing a space (coded %20) permanent /old%20folder/my%20file\.html /new%20folder/my%20file.html #Redirect a full Web site permanent /(.*) http://new-site.net/$1 #Redirect only the root of the site permanent / /folder/ #Redirect a folder and its sub-folders and files permanent /old-folder/sub-folder2/(.*) /new-folder2/$1 #Same effect, allowing also the redirection when the trailing / is omitted permanent /old-folder/sub-folder2(.*) /new-folder2$1 #Redirect a folder but neither its sub-folder nor files permanent /old-folder/sub-folder2/ /new-folder2/ #Redirect a folder and its files but not its sub-folders permanent /old-folder/sub-folder2/([^/\\]*) /new-folder2/$1 #Mass redirection of all the files with a given pattern, #and reuse a part of their name (with $n) to make the new address permanent /images/image([0-9]+)\.(gif|jpg) /images/image$1.png #Same effect, with a abbreviated destination address permanent /images/image([0-9]+)\.(gif|jpg) image$1.png #Temporary redirection #(the original address must still be kept as the one used and referenced) temp /shortcut /longer-address/more-complicated/ #Indicates that a resource is no longer available gone /folder/old\.pdf
A number of options can be customised via the constants at the beginning of the script:
distanceToRoot = 1
path = "./404/"
customRedirect = "/"
customRedirectTimeOut = 5
defaultNewServer = ""
"http://example.net:80"
allowASPmode = true
Under Apache, this personalised error page is added via the ErrorDocument
directive,
which can in particular be used in the general configuration file ./conf/httpd.conf
located in Apache’s installation directory,
or in a /.htaccess file at the root of the Web site of interest, as exemplified below:
/.htaccess ErrorDocument 404 /errors/redirection-404.php
The redirections appear in Apache’s access logs, (codes 301, 302, 410…), as well as in the error logs:
access.log 127.0.0.1 - - [05/Jan/2008:18:50:39 +0100] "GET /old-folder/ HTTP/1.1" 301 651 "-" "Mozilla" 127.0.0.1 - - [05/Jan/2008:18:50:40 +0100] "GET /new-folder/ HTTP/1.1" 200 8397 "-" "Mozilla"
error.log [Sat Jan 05 18:50:39 2008] [error] [client 127.0.0.1] File does not exist: E:/www/old-folder
(Optional and rare): In the case when you do not want or cannot treat the redirections on a given server, the following method gives a possibility to delegates this task to another server, after having enabled this feature in the allowASPmode variable, at the beginning of the redirection script.
/.htaccess <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)$ http://example.net/errors/redirection-404.php?aspxerrorpath=$1 [R=301,L] </IfModule>
In IIS, this personalised error page is added from the administration panel.
The setup is then done in the properties of the Web site, in the tab “Custom errors” (IIS 5 & 6) or from the icon “Error pages” (IIS 7). After having selected the line for the 404 error code, click on the button or the link “Edit”, and then enter an address of the type URL indicating the path for the script redirection-404 relatively to the root of the Web site like for instance /errors/redirection-404.aspx or /errors/redirection-404.php.
In IIS 7, the link “Edit Feature Settings…” (on the right) allows among other things to specify if this personalised error page should be served also for local connections (localhost), which is disabled by default.
In IIS 6 and newer, the above setup in graphic mode can also be done manually, with the directive httpErrors as follow:
/Web.config <configuration> <system.webServer> <httpErrors errorMode="Custom"><!-- For non-managed files --> <remove statusCode="404" subStatusCode="-1" /> <error statusCode="404" path="/errors/redirection-404.aspx" responseMode="ExecuteURL" /> </httpErrors> </system.webServer> … </configuration>
Note that in order to be allowed to put this instruction at the local level in a Web.config at the root of your site, instead of in the glogal applicationHost.config, it is required to enable the following line in applicationHost.config:
%windir%\System32\inetsrv\config\applicationHost.config <section name="httpErrors" overrideModeDefault="Allow" />
Redirecting .aspx files requires an additional configuration.
Until IIS 6, there was a possibility to check for the existence of .aspx files before sending them to the ASP.NET engine, in [Properties / Home Directory / Configuration… / Mappings / .aspx / Edit] (screenshot of IIS 5):
If IIS 7.5+ is used, or if the above option cannot be used, then an additional instruction customErrors must be used for ASP.NET files such as .aspx. Furthermore, the redirection script must have the allowASPmode variable enabled at the beginning of the redirection script.
/Web.config <configuration> … <system.web> <customErrors mode="On" redirectMode="ResponseRewrite"> <error statusCode="404" redirect="/errors/redirection-404.aspx" /> </customErrors> </system.web> </configuration>
Note that the attribute redirectMode with the value ResponseRewrite removes the problematic intermediary 302 redirection that occurs by default, but this is only available from ASP.NET 3.5 SP1 and IIS 7.5 (Windows 7).
If you cannot use the above methods to ensure the redirection of .aspx files, there is still the possibility to write a custom module to handle errors, which has the advantage of working with IIS 6 and 7.0+:
/App_Code/Redirection404Helper.cs using System; using System.Web; /// <summary>Error 404 handler helper. 2008-10-20</summary> /// <see cref="https://alexandre.alapetite.fr/doc-alex/redirection-404/"/> public class Redirection404Helper : IHttpModule { public void Dispose() {} public void Init(HttpApplication context) { context.Error += new EventHandler(On404Error); } void On404Error(object sender, EventArgs e) { HttpApplication httpApplication = sender as HttpApplication; if (httpApplication == null) return; HttpContext httpContext = httpApplication.Context; HttpException httpException = httpContext.Error as HttpException; if (httpException == null) return; if (httpException.GetHttpCode() == 404) httpContext.Server.Transfer(httpContext.Request.ApplicationPath + @"/errors/Redirection-404.aspx?aspxerrorpath=" + httpContext.Request.RawUrl); } }
Place the C# file above in the folder /App_Code/ of your Web site (paying attention to the address of the redirection script on the last line of the module), then reference it as follow (can also be setup graphically via the “Modules” icon, then the link “Add Managed Module…”):
/Web.config <configuration> <system.webServer> <httpErrors errorMode="Custom"><!-- For non-managed files --> <remove statusCode="404" subStatusCode="-1" /> <error statusCode="404" path="/errors/redirection-404.aspx" responseMode="ExecuteURL" /> </httpErrors> <!-- To have both section for IIS 6 and 7+ at the same time --> <validation validateIntegratedModeConfiguration="false" /> <modules><!-- For IIS 7+ --> <add name="Redirection404Helper" type="Redirection404Helper" preCondition="managedHandler" /> </modules> </system.webServer> <system.web> <httpModules><!-- For IIS 6 --> <add name="Redirection404Helper" type="Redirection404Helper" /> </httpModules> </system.web> </configuration>
Under construction…
./conf/web.xml <error-page> <error-code>404</error-code> <location>/errors/redirection-404.jsp</location> </error-page>
redirection-404.php <?php /* PHP script handling HTTP redirections. To be used as a custom HTTP 404 error page. https://alexandre.alapetite.fr/doc-alex/redirection-404/ */ //--<Constants>-- //Number of levels from the root of the Web site, to this script, for automatic handling of Web sites which root is in a sub-folder of the Web server. //Set to -1 to disable this functionality. //For example, set to 1 if this script in http://example.com/errors/ or http://example.net/~myWebSite/errors/ $distanceToRoot=1; //Root of the folder containing the redirection (404.txt files) $path404=(empty($_SERVER['SCRIPT_FILENAME']) ? '404/' : dirname($_SERVER['SCRIPT_FILENAME'])).'/404/'; $customRedirect='/'; //To specify an optional custom HTML redirection after the error message $customRedirectTimeOut=5; //Delay in second before redirection to the optional above address $defaultNewServer=''; //Change the server for redirections using relative addresses: for example 'http://example.net:80' $allowASPmode=true; //Allow old addresses to be given as a parameter (ASP.NET mode, or to receive 404 errors from an external server) //--</Constants>-- //Get old address $oldUrl=''; if (!empty($_SERVER['REQUEST_URI'])) $oldUrl=substr($_SERVER['REQUEST_URI'],0,1024); //Apache, IIS6 elseif (!empty($_SERVER['QUERY_STRING'])) $oldUrl=substr($_SERVER['QUERY_STRING'],0,1024); //IIS5 (defined but empty under Apache) else $oldUrl='/_unknown_'; if (($sc=strpos($oldUrl,';'))!==false) $oldUrl=trim(substr($oldUrl,++$sc)); //IIS $oldUrlParsed=strpos($oldUrl,'://')===false ? @parse_url($oldUrl) : ''; if (empty($oldUrlParsed)) { $oldUrl='/_unknown_'; $oldUrlParsed=parse_url($oldUrl); } if ($allowASPmode&&(!empty($oldUrlParsed['query']))) //Special ASP.NET { parse_str($oldUrlParsed['query'],$oldquery); if (!empty($oldquery['aspxerrorpath'])) $oldUrlParsed=parse_url($oldquery['aspxerrorpath']); } $oldPath=$oldUrlParsed['path']; $siteRoot=''; //For the case when the Web site is not at the root of the Web server, such as http://example.net/~myWebSite/ if (($distanceToRoot>=0)&&(!empty($_SERVER['SCRIPT_NAME']))) { $map404=$_SERVER['SCRIPT_NAME']; if (substr($map404,-1)!=='/') $map404=dirname($map404); $map404=trim($map404,'/\\'); $dirs=explode('/',$map404); $nbSubLevels=count($dirs)-$distanceToRoot; for ($i=0;$i<$nbSubLevels;$i++) $siteRoot.='/'.$dirs[$i]; if (!empty($siteRoot)) { if (strcasecmp(substr($oldPath,0,strlen($siteRoot)),$siteRoot)===0) $oldPath=substr($oldPath,strlen($siteRoot)); if ((!empty($customRedirect))&&($customRedirect[0]==='/')) $customRedirect=$siteRoot.$customRedirect; } } //Search the best 404.txt mapping file in the file-tree structure $absolute='/'; $dirs=explode('/',$oldPath); //We do not do urldecode(), so special characters (e.g. space) of local folders must be %-encoded: ./404/Hello%20World/404.txt foreach ($dirs as $dir) if (strlen($dir)>0) { if (($dir[0]!=='.')&&is_dir($path404.$dir)) { $path404.=$dir.'/'; $absolute.=$dir.'/'; } else break; } $path404.='404.txt'; //Search in the 404.txt file for the first matching for $oldPath $newPath=''; $httpStatus=302; $found=false; if (is_file($path404)&&($handle=@fopen($path404,'r'))) { while (!feof($handle)) { $line=trim(fgets($handle,4096)); if ((strlen($line)<3)||($line[0]=='#')) continue; //comment or invalid $map=preg_split('"\s+"',$line,4); if (count($map)<2) continue; //invalid $mapOld=$map[1]; if ($mapOld[0]!='/') $mapOld=$absolute.$mapOld; if (@preg_match('"^'.$mapOld.'$"iD',$oldPath)&& ((($status=$map[0])==='gone')|| ((count($map)>2)&& (strlen($newPath=@preg_replace('"^'.$mapOld.'$"iD',$map[2],$oldPath))>0)))) { switch ($status) { case 'permanent': $httpStatus=301; break; case 'found': case 'temp': $httpStatus=302; break; case 'seeother': $httpStatus=303; break; case 'temporary': $httpStatus=307; break; case 'gone': $httpStatus=410; break; default: continue 2; } $found=true; if ($httpStatus!==410) { if (!empty($siteRoot)) $newPath=$siteRoot.$newPath; if (!preg_match('"^(?:(?:[a-z]{3,6}:)|(?:\.\./))"i',$newPath)) //No URI Scheme, and no ../ in front {//When it is possible and not already the case, make the redirection an absolute URL if (empty($defaultNewServer)&&isset($_SERVER['HTTP_HOST'])) { if (!empty($_SERVER['SERVER_PORT'])) { if (empty($_SERVER['HTTPS'])) { if ($_SERVER['SERVER_PORT']!='80') $defaultNewServer.=':'.$_SERVER['SERVER_PORT']; } elseif ($_SERVER['SERVER_PORT']!='443') $defaultNewServer.=':'.$_SERVER['SERVER_PORT']; } if (empty($newPath)||($newPath[0]!=='/')) //relative address $newPath=rtrim(substr($oldPath,-1)==='/' ? $oldPath : dirname($oldPath),'/\\').'/'.$newPath; } $newPath=$defaultNewServer.$newPath; } } break; } } fclose($handle); } if ($found) //Redirect if new address is found { if ($httpStatus===410) { header('HTTP/1.1 410 Gone'); header('Status: 410 Gone'); echo '<!DOCTYPE html>'."\n", '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n", '<head>'."\n", '<meta charset="UTF-8" />'."\n", empty($customRedirect) ? '' : '<meta http-equiv="Refresh" content="'.$customRedirectTimeOut.'; url='.$customRedirect.'" />'."\n", '<title>410 Gone</title>'."\n", '<meta name="robots" content="noindex,follow" />'."\n", '</head>'."\n", '<body>'."\n", '<h1>Gone</h1>'."\n", '<p>The requested resource <kbd>'.$oldPath.'</kbd> is no longer available on this server and there is no forwarding address. ', 'Please remove all references to this resource.</p>'."\n", '</body>'."\n", '</html>'."\n"; } else { if (isset($oldUrlParsed['query'])) $newPath.='?'.$oldUrlParsed['query']; $status=array(301=>'Moved Permanently',302=>'Found',303=>'See Other',307=>'Temporary Redirect'); header('Location: '.$newPath); header('HTTP/1.1 '.$httpStatus.' '.$status[$httpStatus]); header('Status: '.$httpStatus.' '.$status[$httpStatus]); echo '<!DOCTYPE html>'."\n", '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n", '<head>'."\n", '<meta charset="UTF-8" />'."\n", '<meta http-equiv="Refresh" content="0; url='.$newPath.'" />'."\n", '<title>'.$httpStatus.' '.$status[$httpStatus].'</title>'."\n", '<meta name="robots" content="noindex,follow" />'."\n", '</head>'."\n", '<body>'."\n", '<h1>'.$status[$httpStatus].'</h1>'."\n", '<p>The document has moved <a href="'.$newPath.'">here</a>.</p>'."\n", '</body>'."\n", '</html>'."\n"; } } else //404 error message { header('HTTP/1.1 404 Not Found'); header('Status: 404 Not Found'); echo '<!DOCTYPE html>'."\n", '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">'."\n", '<head>'."\n", '<meta charset="UTF-8" />'."\n", empty($customRedirect) ? '' : '<meta http-equiv="Refresh" content="'.$customRedirectTimeOut.'; url='.$customRedirect.'" />'."\n", '<title>404 Not Found</title>'."\n", '<meta name="robots" content="noindex,follow" />'."\n", '</head>'."\n", '<body>'."\n", '<h1>Not Found</h1>'."\n", '<p>The requested <abbr title="Uniform Resource Locator">URL</abbr> <kbd>'.$oldPath.'</kbd> was not found on this server.</p>'."\n", '</body>'."\n", '</html>'."\n"; } ?>
continue
in PHP7.3+Redirection-404.aspx <%-- ASP.NET script handling HTTP redirections. To be used as a custom HTTP 404 error page. https://alexandre.alapetite.fr/doc-alex/redirection-404/ --%> <%@ Page Language="C#" %> <%@ Import Namespace="System.IO" %> <script runat="server"> protected void Page_Load(object sender, EventArgs e) { #region --Constants-- //Number of levels from the root of the Web site, to this script, for automatic handling of Web sites which root is in a sub-folder of the Web server. //Set to -1 to disable this functionality. //For example, set to 1 if this script in http://example.com/errors/ or http://example.net/~myWebSite/errors/ int distanceToRoot = 1; //Root of the redirection structure (404.txt files) string path = Path.GetDirectoryName(Server.MapPath(Request.CurrentExecutionFilePath)) + @"/404/"; string customRedirect = @"/"; //To specify an optional custom HTML redirection after the error message int customRedirectTimeOut = 5; //Time-out in second before redirection to the optional above address string defaultNewServer = @""; //Change the server of the new relative addresses: 'http://example.net:80' bool allowASPmode = true; //Allow old addresses to be given as a parameter (ASP.NET mode, or to receive 404 errors from an external server) #endregion #region Get old address string oldUrl = "http://localhost" + Request.RawUrl; int sc = oldUrl.IndexOf("404;"); if (sc >= 0) oldUrl = oldUrl.Substring(sc + 4).Trim(); //IIS Uri oldUrlParsed = null; try { oldUrlParsed = new Uri(oldUrl); if (allowASPmode && (!String.IsNullOrEmpty(oldUrlParsed.Query))) //Special ASP.NET { NameValueCollection oldquery = HttpUtility.ParseQueryString(oldUrlParsed.Query); if (!String.IsNullOrEmpty(oldquery["aspxerrorpath"])) oldUrlParsed = new Uri("http://localhost" + oldquery["aspxerrorpath"]); } } catch { oldUrlParsed = new Uri(@"http://localhost/_unknown_"); } string oldPath = oldUrlParsed.AbsolutePath; string siteRoot = ""; //For the case when the Web site is not at the root of the Web server, such as http://example.net/~myWebSite/ string[] dirs; if (distanceToRoot >= 0) { string map404 = Request.CurrentExecutionFilePath; if (map404[map404.Length - 1] != '/') map404 = Path.GetDirectoryName(map404); map404 = map404.Trim(new char[] { '/', '\\' }).Replace('\\', '/'); dirs = map404.Split('/'); int nbSubLevels = dirs.Length - distanceToRoot; for (int i = 0; i < nbSubLevels; i++) siteRoot += '/' + dirs[i]; if (!String.IsNullOrEmpty(siteRoot)) { if (oldPath.StartsWith(siteRoot, StringComparison.InvariantCultureIgnoreCase)) oldPath = oldPath.Substring(siteRoot.Length); if ((!String.IsNullOrEmpty(customRedirect)) && (customRedirect[0] == '/')) customRedirect = siteRoot + customRedirect; } } #endregion #region Search the best 404.txt mapping file in the file-tree structure string absolute = "/"; dirs = oldPath.Split('/'); //We do not do Server.UrlDecode(), so special characters (e.g. space) of local folders must be %-encoded: ./404/Hello%20World/404.txt foreach (string dir in dirs) if (dir.Length > 0) { if ((dir[0] != '.') && Directory.Exists(path + dir)) { path += dir + '/'; absolute += dir + '/'; } else break; } path += "404.txt"; #endregion #region Search in the 404.txt file for the first matching for oldPath string newPath = ""; int httpStatus = 302; bool found = false; FileInfo fileInfo = new FileInfo(path); StreamReader streamReader = null; try { if (fileInfo.Exists && ((streamReader = fileInfo.OpenText()) != null)) { string line; while ((line = streamReader.ReadLine()) != null) { if (line.Length > 4096) line = line.Substring(0, 4096); line = line.Trim(); if ((line.Length < 3) || (line[0] == '#')) continue; //comment or invalid string[] map = Regex.Split(line, @"\s+"); if (map.Length < 2) continue; //invalid string status = map[0]; string mapOld = map[1]; if (mapOld[0] != '/') mapOld = absolute + mapOld; if (Regex.Match(oldPath, "^" + mapOld + '$', RegexOptions.IgnoreCase | RegexOptions.CultureInvariant).Success && ((status == "gone") || ((map.Length > 2) && ((newPath = Regex.Replace(oldPath, "^" + mapOld + '$', map[2], RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)).Length > 0)) )) { switch (status) { case "permanent": httpStatus=301; break; case "found": case "temp": httpStatus=302; break; case "seeother": httpStatus=303; break; case "temporary": httpStatus=307; break; case "gone": httpStatus=410; break; default: continue; } found = true; if (found && (httpStatus != 410)) { if (!String.IsNullOrEmpty(siteRoot)) newPath = siteRoot + newPath; if (!Regex.Match(newPath, @"^(?:(?:[a-z]{3,6}:)|(?:\.\./))", RegexOptions.IgnoreCase).Success) //No URI Scheme, and no ../ in front {//When it is possible and not already the case, make the redirection an absolute URL if (defaultNewServer.Length < 8) { defaultNewServer = "http://" + Request.ServerVariables["HTTP_HOST"]; if (String.IsNullOrEmpty(newPath) || (newPath[0] != '/')) //relative address newPath = (oldPath[oldPath.Length - 1] == '/' ? oldPath : Path.GetDirectoryName(oldPath).Replace('\\', '/')).TrimEnd(new char[] { '/', '\\' }) + '/' + newPath; } newPath = defaultNewServer + newPath; } } break; } } } } catch (Exception ex) { Trace.Write("Redirection-404", "Error while reading a 404.txt file", ex); } finally { if (streamReader != null) streamReader.Close(); fileInfo = null; } #endregion #region Response if (found) //Redirect if new address is found { if (httpStatus == 410) { Response.Status = "410 Gone"; Response.Write(@"<!DOCTYPE html> <html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en-GB"" lang=""en-GB""> <head> <meta charset=""UTF-8"" /> "); if (customRedirect.Length > 0) Response.Write(string.Format("<meta http-equiv=\"Refresh\" content=\"{0}; url={1}\" />\n", customRedirectTimeOut, customRedirect)); Response.Write(@"<title>410 Gone</title> <meta name=""robots"" content=""noindex,follow"" /> </head> <body> <h1>Gone</h1> <p>The requested resource <kbd>" + oldPath + @"</kbd> is no longer available on this server and there is no forwarding address. Please remove all references to this resource.</p> </body> </html> "); } else { if (!String.IsNullOrEmpty(oldUrlParsed.Query)) newPath += oldUrlParsed.Query; Response.RedirectLocation = newPath; switch (httpStatus) { case 301: Response.Status = "301 Moved Permanently"; break; case 302: Response.Status = "302 Found"; break; case 303: Response.Status = "303 See Other"; break; case 307: Response.Status = "307 Temporary Redirect"; break; default: Response.Status = "302 Found"; break; } Response.Write(@"<!DOCTYPE html> <html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en-GB"" lang=""en-GB""> <head> <meta charset=""UTF-8"" /> <meta http-equiv=""Refresh"" content=""0; url=" + newPath + @""" /> <title>" + Response.Status + @"</title> <meta name=""robots"" content=""noindex,follow"" /> </head> <body> <h1>" + Response.StatusDescription + @"</h1> <p>The document has moved <a href=""" + newPath + @""">here</a>.</p> </body> </html> "); } } else //404 error message { Response.Status = "404 Not Found"; Response.Write(@"<!DOCTYPE html> <html xmlns=""http://www.w3.org/1999/xhtml"" xml:lang=""en-GB"" lang=""en-GB""> <head> <meta charset=""UTF-8"" /> "); if (customRedirect.Length > 0) Response.Write(string.Format("<meta http-equiv=\"Refresh\" content=\"{0}; url={1}\" />\n", customRedirectTimeOut, customRedirect)); Response.Write(@"<title>404 Not Found</title> <meta name=""robots"" content=""noindex,follow"" /> </head> <body> <h1>Not Found</h1> <p>The requested <abbr title=""Uniform Resource Locator"">URL</abbr> <kbd>" + oldPath + @"</kbd> was not found on this server.</p> </body> </html> "); } #endregion } </script>
This content is protected by a licence
Creative Commons Attribution-ShareAlike 2.0 France "BY-SA (FR)"
If you use and like this library (especially for professional purposes), please consider doing a donation.
If you expect an answer or if it is to report a problem, please contact me by e-mail.