I am using the approach below to handle 404 errors on my sites. This has worked for a long time but suddenly within the last month I am getting a "Handle is not initialized" exception with a number of sites on our dedicated server (some still work, and on development machine it works). Anyone have any thoughts?
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<httpErrors existingResponse="Replace">
<remove statusCode="500" subStatusCode="-1"/>
<remove statusCode="404" subStatusCode="-1"/>
<error statusCode="404" prefixLanguageFilePath="" path="/default.aspx" responseMode="ExecuteURL"/>
<error statusCode="500" prefixLanguageFilePath="" path="/error.aspx" responseMode="ExecuteURL"/>
</httpErrors>
</system.webServer>
On default.aspx page:
protected void Page_PreRender(object sender, EventArgs e)
{
if (!Page.IsPostBack && Request.Url.ToString().Contains("?404;"))
{
HttpContext.Current.RewritePath("~/");
Page.Header.Controls.AddAt(0, new LiteralControl("<base href='" + Request.Url.Scheme + "://" + Request.Url.Authority + "'/>"));
Response.StatusCode = 404;
Util.DisplayAlert("The page you are looking for no longer exists. If you navigated to this page by clicking a link within this site please <a href='/contact.aspx'>contact us</a> to let us know.");
}
}
Exception details:
Exception information:
Exception type: InvalidOperationException
Exception message: Handle is not initialized.
at System.Runtime.InteropServices.GCHandle.FromIntPtr(IntPtr value)
at System.Web.Hosting.PipelineRuntime.GetManagedPrincipalHandler(IntPtr pRootedObjects)
at System.Web.Hosting.UnsafeIISMethods.MgdGetPrincipal(IntPtr pHandler, IntPtr& pToken, IntPtr& ppAuthType, Int32& pcchAuthType, IntPtr& ppUserName, Int32& pcchUserName, IntPtr& pManagedPrincipal)
at System.Web.Hosting.IIS7WorkerRequest.GetUserPrincipal()
at System.Web.Hosting.IIS7WorkerRequest.SynchronizeVariables(HttpContext context)
at System.Web.HttpRuntime.ProcessRequestNotificationPrivate(IIS7WorkerRequest wr, HttpContext context)
I am having the same problem but haven't found a solution yet. I have confirmed that the problem is caused by setting the StatusCode or Status properties on the response, it affects IIS 7 and IIS Express but not Cassini which makes sense given the stack trace.
I'll keep investigating.
Edit: No luck in finding a solution. I posted the same question on the IIS forums: http://forums.iis.net/p/1187959/2016914.aspx#2016914
Edit 2: Confirmed as fixed in .NET 4.5 RC.
Levi: We have tracked down the root cause and are queuing a fix.
Andrew McLachlan Confirmed as fixed in .NET 4.5 RC
Related
I avoid the default ASP.NET approach of redirecting on errors (as many people do). Clean AJAX code and SEO are among the reasons.
However, I'm using the following method to do it, and it seems that I may lose HttpContext.Current.Items in the transfer?
<httpErrors errorMode="Custom" existingResponse="Replace">
<remove statusCode="401" />
<remove statusCode="403" />
<remove statusCode="404" />
<remove statusCode="500" />
<error statusCode="401" responseMode="ExecuteURL" path="/Account/SignIn" />
<error statusCode="403" responseMode="ExecuteURL" path="/Site/Forbidden" />
<error statusCode="404" responseMode="ExecuteURL" path="/Site/NotFound" />
<error statusCode="500" responseMode="ExecuteURL" path="/Site/Error" />
</httpErrors>
I assumed it just performed a Server.Transfer() under the covers, which I understand preserves Items. (See: Scope of HttpContext.Current.Items and http://weblog.west-wind.com/posts/2010/Jan/20/HttpContextItems-and-ServerTransferExecute )
But I'm also capturing something in Items before the "ExecuteURL", and retrieving/outputting it after the transfer (or whatever it is), and it seems to disappear. I've watched it go into the Items collection, I see the Count raise to 5, and then when the value is retrieved there are only 2 items in the collection.
What is going on?
If you'd like to understand more about what I'm doing and recommend an alternate implementation, I'm open to it. I'm using this to push the ELMAH Error Id into a ViewModel in a way that is free from race conditions. (i.e. a common workaround for this that I'm replacing is to merely display the most recent error.) Here's my code:
Global.asax
protected void ErrorLog_Logged(object sender, ErrorLoggedEventArgs args) {
ElmahSupplement.CurrentId = args.Entry.Id;
}
void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e) {
if (ElmahSupplement.IsNotFound(e.Exception)) {
ElmahSupplement.LogNotFound((e.Context as HttpContext).Request);
e.Dismiss();
}
}
SiteController.cs
public virtual ActionResult Error() {
Response.StatusCode = 500;
return View(MVC.Site.Views.Error, ElmahSupplement.CurrentId);
}
ElmahSupplement.cs
public class ElmahSupplement {
// TODO: This is a rather fragile way to access this info
private static readonly Guid contextId = new Guid("A41A67AA-8966-4205-B6C1-14128A653F21");
public static string CurrentId {
get {
return
// Elmah 1.2 will fail to log when enumerating form values that raise RequestValidationException (angle brackets)
// https://code.google.com/p/elmah/issues/detail?id=217
// So this id could technically be empty here
(HttpContext.Current.Items[contextId] as string);
}
set {
HttpContext.Current.Items[contextId] = value;
}
}
public static void LogNotFound(HttpRequest request) {
var context = RepositoryProxy.Context;
context.NotFoundErrors.Add(new NotFoundError {
RecordedOn = DateTime.UtcNow,
Url = request.Url.ToString(),
ClientAddress = request.UserHostAddress,
Referrer = request.UrlReferrer == null ? "" : request.UrlReferrer.ToString()
});
context.SaveChanges();
}
public static bool IsNotFound(Exception e) {
HttpException he = e as HttpException;
return he != null && he.GetHttpCode() == 404;
}
}
As explained here, the ExecuteURL generates two requests: the first one throws the exception and the second one generates the error response.
Since Context.Items is cleared between requests, your code always see the 2nd request generated hence the diff between the Items.
Try the sugestion in the post: use system.web > customErrors with redirectMode="ResponseRewrite" instead.
I've followed a trace and determined the following. Some is loosely inferred.
The CustomErrorModule (in the IIS module stack) receives the SEND_RESPONSE notification.
The HttpStatus is 500, so it clones the context, sets a new URL (according the the matching custom error rule), and executes the request on this context (see ExecuteRequest).
The purpose of HttpContext.Items per documentation is:
Gets a key/value collection that can be used to organize and share
data between an IHttpModule interface and an IHttpHandler interface
during an HTTP request.
Viewing this function definition critically, of course, there is only "HTTP request". However, it seems likely that the Items dictionary is itself an item in a dictionary keyed on the HttpContext, which is a unique (cloned) reference in this executing child request. The trace shows the full pipeline (all modules, e.g. duplicate authentication) being run for this ExecuteURL, so this isolated context is of course required.
From unmanaged code, it is trivial to GetParentContext. However, from managed code this hierarchy is not available. So, I'm left without a way to retrieve the original Items.
As an alternate solution, it might be functional to leverage a Global.asax variable, since my tests showed the child request sharing an ApplicationInstance, but I'm not certain client access to this is necessarily sequential.
Another, possibly better approach, would be to avoid re-running the entire pipeline; to never exit the MVC handler (e.g. Controller.OnException and TransferToAction). However, this prevents implementing a Single-Point-of-Truth for error page configuration, since errors can also be raised outside of MVC's awareness.
I have taken over a domain that had was running an old version of Community Server. Needless to say the bots are spamming me trying to find holes.
I'd like to block entire IP blocks before System.Web.HttpRequest.ValidateInputIfRequiredByConfig() is fired. I have a IHttpModule that I have tried but I assume it's getting called after because HealthMonitoring is catching the Exceptions. Here is the module:
public class IpBlockerModule : IHttpModule
{
private static readonly string[] Hacks = new[]
{
"60.169.73.",
"60.169.75.",
"61.160.232.",
"61.160.207.",
"92.85.161."
};
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += (Application_BeginRequest);
}
private void Application_BeginRequest(object source, EventArgs e)
{
var context = ((HttpApplication) source).Context;
var ipAddress = context.Request.UserHostAddress;
if (!IsHackIpAddress(ipAddress))
{
context.Response.StatusCode = 403; // (Forbidden)
}
}
private static bool IsHackIpAddress(string ip)
{
if (ip == null) return true;
return Hacks.Any(x => x.StartsWith(ip));
}
}
And the relevent web.config sections:
<system.web>
<httpModules>
<add name="IpBlockerModule" type="MyNameSpace.IpBlockerModule" />
</httpModules>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules runAllManagedModulesForAllRequests="true" >
<add name="IpBlockerModule" type="MyNameSpace.IpBlockerModule" preCondition="" />
</modules>
</system.webServer>
The reasoning behind this is my inbox is getting spammed from all the
A potentially dangerous Request.Path value was detected from the
client
and
A potentially dangerous Request.Form value was detected from the client
notifications. Is something wrong with my Module, or am I correct in assuming modules don't get fired until after the fact?
As an alternative solution have you considered letting IIS do the work for you? This way the request never makes it to your application. You can do this via the web.config and there's an article detailing the process located here. The following example is copied directly from that article and would be placed inside the <system.webServer> section of your web.config:
<security>
<ipSecurity allowUnlisted="true"> <!-- this line allows everybody, except those listed below -->
<clear/> <!-- removes all upstream restrictions -->
<add ipAddress="83.116.19.53"/> <!-- blocks the specific IP of 83.116.19.53 -->
<add ipAddress="83.116.119.0" subnetMask="255.255.255.0"/> <!--blocks network 83.116.119.0 to 83.116.119.255-->
<add ipAddress="83.116.0.0" subnetMask="255.255.0.0"/> <!--blocks network 83.116.0.0 to 83.116.255.255-->
<add ipAddress="83.0.0.0" subnetMask="255.0.0.0"/> <!--blocks entire /8 network of 83.0.0.0 to 83.255.255.255-->
</ipSecurity>
</security>
You can also add the ability to get and log IP addresses so as to identify and block only the spammy ones.
Here's C# code to get IP addresses
string ipadd;
ipadd = Request.ServerVariables["HTTP_X_FORWARDED_FOR"];
if (ipadd == "" || ipaddress == null)
ipadd = Request.ServerVariables["REMOTE_ADDR"];
I noticed that the link in the answer above is dead, so use this well-detailed article here
I am using the Application_Error in the Global class to handle 404s.
Once a 404 is caught I send an email for the purpose of logging it. To do this I call a static method to write the error.
ErrorHandler.WriteError(Exception ex)
Within this method I create a new instance of my EmailHandler in order to initialise the System.Net.Mail class. Within my EmailHandler object I have a private member
private MailMessage m_mail = null;
In the constructor of the EmailHandler class I create a new MailMesage instance in order to construct the email.
public EmailHandler()
{
m_mail = new MailMessage();
}
At this point I get the following error.
System.Security.Cryptography.CryptographicException
"The handle is invalid."
I need to point out that this only happens when the request is to a specific file only (such as a www.mysite.com/random-incorrect-image.jpg).
It doesn't happen when I am requesting a page like www.mysite.com/random-incorrect-page.aspx
Maybe you should try the following for static file 404 :
Configure custom 404 in your web.config
<system.webServer>
<httpErrors>
<remove statusCode="404" subStatusCode="-1" />
<error statusCode="404" prefixLanguageFilePath="" path="/yourpath/yourcustom404.aspx" responseMode="ExecuteURL" />
</httpErrors>
404 Redirection for static files will then occur to http://www.mysite.com/yourpath/yourcustom404.aspx?404;http://www.mysite.com/random-incorrect-image.jpg
You can then process the queryString to extract the url in the pageLoad of custom404.aspx, send your email, and display a friendly message
You can do the same for aspx pages.
In your web.config :
<customErrors mode="On">
<error statusCode="404" redirect="/yourpath/yourcustom404.aspx" />
</customErrors>
the redirection will be slightly different though :
http://www.mysite.com/yourpath/yourcustom404.aspx?aspxerrorpath=/random-incorrect-page.aspx
How can I replace the standard error page in the case the request length exceeds the one specified in maxQueryStringLength and show the user something more friendly?
Note: Although as an HttpException this falls into generic 400th error, I want to separate the QueryLength condition and show a very specific page for this specific error. So, I cannot use "customErrors" section to indicate a page, but rather need to filter this programmatically. The problem with the below is it doesn't work.
protected virtual void Application_Error(Object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (logs != null) logs.ExceptionMsg("Application exception", this, ex);
var httpex = ex as System.Web.HttpException;
if (httpex != null && httpex.ErrorCode == -2147467259)
{
Server.ClearError();
Server.TransferRequest(#"~/main/maxlengthexceeded", false);
}
}
The problem is that Server.TransferRequest does not work. Is there an alternative to how I can tell ASP.NET which page to load?
if you are able to catch the error type/number you are getting, then you can configure a different error/redirect page only for that, here an example of configuration in the web.config:
<configuration>
<system.web>
<customErrors defaultRedirect="GenericError.htm"
mode="RemoteOnly">
<error statusCode="500"
redirect="InternalError.htm"/>
</customErrors>
</system.web>
</configuration>
you can see the full article here: Displaying a Custom Error Page
I have an aspx page where I’m allowing a user to upload a file and I want to cap the max file upload size to be 10MB. IIS7, .NET 3.5. I have the following configured in my web.config file:
<location path="foo.aspx">
<system.web>
<!-- maxRequestLength: kbytes, executionTimeout:seconds -->
<httpRuntime maxRequestLength="10240" executionTimeout="120" />
<authorization>
<allow roles="customRole"/>
<!-- Deny everyone else -->
<deny users="*"/>
</authorization>
</system.web>
<system.webServer>
<security>
<requestFiltering>
<!-- maxAllowedContentLength: bytes -->
<requestLimits maxAllowedContentLength="10240000"/>
</requestFiltering>
</security>
<handlers accessPolicy="Read, Script">
<add name="foo" path="foo.aspx" verb="POST"
type="System.Web.UI.PageHandlerFactory"
preCondition="integratedMode" />
</handlers>
</system.webServer>
</location>
I have a custom error handling module that implements IHttpModule. I’ve found that when maxRequestLength is exceeded, HttpApplication.Error does indeed get raised. However when I play with maxAllowedContentLength, the HttpApplication.Error event isn’t being raised and the user gets redirected to a 404.13 page. I've attached with Visual Studio with first chance exceptions turned on nothing is being thrown.
My first thought is to check the header content length in an earlier event – are there recommendations/best practices of where I do this? PostLogRequest? EndRequest?
After looking at the ASP.NET Application Life Cycle Overview for IIS 7.0 and doing my own experimentation, I'm assuming that the request validation is done internally by IIS before any of the events are raised.
It looks like only LogRequest, PostLogRequest, EndRequest, PreSendRequestContent, and PreSendRequestHeaders are raised after the internal validation with this error.
I've decided to attach an event handler to the HttpApplication.EndRequest event in my custom error handler and check for the 404.13 status code on POST and handle as I need it to be handled, which in my case is to redirect to the calling page which will check Server.GetLastError() and display a friendly error to the end user.
private void application_EndRequest(object sender, EventArgs e)
{
HttpRequest request = HttpContext.Current.Request;
HttpResponse response = HttpContext.Current.Response;
if ((request.HttpMethod == "POST") &&
(response.StatusCode == 404 && response.SubStatusCode == 13))
{
// Clear the response header but do not clear errors and
// transfer back to requesting page to handle error
response.ClearHeaders();
HttpContext.Current.Server.Transfer(
request.AppRelativeCurrentExecutionFilePath);
}
}
I'd welcome feedback on this approach and alternatives.
The simplest method is to handle it in the OnError method of page itself.
I think this works only in .NET 4.0, since the WebEventCode property is documented as NEW in .NET 4.0.
protected override void OnError(EventArgs e)
{
Exception err = Server.GetLastError();
if (err is HttpException)
{
if ((err as HttpException).WebEventCode == 3004)
{
Context.Items["error"] = "File exceeded maximum allowed length.";
Server.Transfer( Context.Request.Url.LocalPath );
return;
}
}
base.OnError(e);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!IsPostBack)
{
string error = Context.Items["error"] as string;
if (!string.IsNullOrEmpty( error ))
showErrorMessage( error );
}
}
What I did was:
get the last error with Server.GetLastError
check that it's a "Maximum request length exceeded." error (WebEventCode == 3004).
added a value to Context.Items collections to flag the request as an error
transfer the request back to the page itself with Server.Transfer(Context.Request.Url.LocalPath)
the page's OnLoad method checks for the error flag and displays a message if present
This ensures the error is handled entirely on the requested page, and the page is capable of reporting errors.
Also note that while the browser will eventually receive a proper response, the browser may take its time uploading the entire request before it handles the server's response and displays it. This behavior is probably defined as part of the server/browser interaction in the HTTP protocol, so probably not much can be done about that.