Friday, July 13, 2007

MS KB928365, ASP.NET Request.Headers.Add() Hack No Longer Works

So a project that I am currently a part of is using an ASP.NET 2.0 HttpModule to add some additional values to the incoming HTTP request's headers in the DEV environment (i.e., our local disconnected laptops) to simulate what an enterprise single-sign-on solution is performing in the production environment. It has worked like a charm. That is until I installed the new security update for the .NET Framework 2.0 release this past Wednesday, July 10, MS KB928365.

Apparently this "hack"
has been disabled with the release of this security update.

When attempting to call Headers.Add(), with or without the above hack in place, you will now receive a PlatformNotSupported exception.

All in all, this post is by no means a rant against the security update, but simply an attempt to add a quick answer to the "Google answer machine" for those searching. I am also already aware of a number of other potentially better solutions than the one currently in place for simulating those user accounts, but for now, simply uninstalling the above referenced security update gets the team back working again.

11 comments:

Anonymous said...

I go have the same problem.

You said you have better ideas. Can you share those...

I have SSO type app and I used to pass values using header.

Do you know any other way to add values to header to pass to other pagge....

Anonymous said...

Hi,

I managed to add a custom header to the Request.Headers collection by using the following code:

Imports System.Reflection

Public Class CustomHttpModule
Implements IHttpModule

Public Sub New()
' Class constructor.
End Sub


' Classes that inherit IHttpModule
' must implement the Init and Dispose methods.
Public Sub Init(ByVal app As HttpApplication) Implements IHttpModule.Init
AddHandler app.BeginRequest, AddressOf app_BeginRequest
End Sub


Public Sub Dispose() Implements IHttpModule.Dispose
' Add code to clean up the
' instance variables of a module.
End Sub

Public Sub app_BeginRequest(ByVal o As Object, ByVal ea As EventArgs)
Dim user As New ArrayList
Dim headers As NameValueCollection = HttpContext.Current.Request.Headers
Dim t As Type = headers.GetType()
t.InvokeMember("MakeReadWrite", System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance, Nothing, headers, Nothing)
t.InvokeMember("InvalidateCachedArrays", System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance, Nothing, headers, Nothing)
user.Add("user@domain.com")
t.InvokeMember("BaseAdd", System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance, Nothing, headers, New Object() {"SM_USER", user})
t.InvokeMember("MakeReadOnly", System.Reflection.BindingFlags.InvokeMethod Or System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance, Nothing, headers, Nothing)
End Sub

End Class

Hope this helps.

Daniel M.

Anonymous said...

I've had a similar problem trying to simulate the passing of an SSO token to one of our web applications.

After banging my head against a brick wall for a coupple of hours I tried the above soloution with great success.
Thanks a million.

Im posting the c# version of the code below as it may help someone out in future :-)

HttpApplication objApp = (HttpApplication)r_objSender;
HttpRequest Request = (HttpContext)objApp.Context.Request;


//get a reference
NameValueCollection headers = Request.Headers;

//get a type
Type t = headers.GetType();
System.Collections.ArrayList item = new System.Collections.ArrayList();

t.InvokeMember("MakeReadWrite",BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,null,headers,null);
t.InvokeMember("InvalidateCachedArrays",BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,null,headers,null);
item.Add("CUSTOM_HEADER_VALUE");
t.InvokeMember("BaseAdd",BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,null,headers, new object[]{"CUSTOM_HEADER_NAME",item});
t.InvokeMember("MakeReadOnly",BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,null,headers,null);

Anonymous said...

FANTASTIC - THIS SOLVED MY PROBLEM!!!!

Thanks so much for this, I have been searching around the Google all day for a solution and have been unable to find anything that worked. I plugged in your C# Code which only needed a small modification:


HttpRequest Request = (HttpContext)objApp.Context.Request;

needed to be cast to:

HttpRequest Request = (HttpRequest)objApp.Context.Request;

AS I said I just plugged it in and VOILA!!! You turned my terrible and unproductive day into PROGRESS!!! thanks so much...

*Passes an internet Beer*

Anonymous said...

May gently someone post here a complete c# project? Thank you.

Jameel M said...

But where do you place this code?

Victor said...

I am trying to set the User-Agent header with a value if is not in the request. This code works great but I also need a way to set the Request.UserAgent property at the same time. I am not too hot on reflection so here is what I have been trying to do with no luck:

Dim u As Type = req.GetType()
u.InvokeMember("UserAgent", System.Reflection.BindingFlags.Default Or System.Reflection.BindingFlags.SetProperty Or System.Reflection.BindingFlags.NonPublic, Nothing, req, New Object() {"none"})

This fails with Method Not found. Oh and you can see that it is just converted to VB since the web app I am working in was written in VB.Net.

BigJimInDC said...

Victor,

If you're trying to do this and then pass the request off to another web server, then you should be looking into writing a proxy server, which is the correct way to deal with this.

On the other hand, if you know that you're not creating a proxy server, and you're just injecting this value into the headers for processing further down the line within your own code (within the same process even), then a better solution exists. Whether you use a DI/IOC container to do it, or you roll your own solution, you need to change your code so that the code that is trying to read this value from the HTTP headers is not directly relying on the values from the HTTP headers. One very easy solution is to simply create another class that sits in between the consuming code and the code that is reading from the headers. If you're running in a PRODuction environment, where the headers "are what they are", have the new class read from the value from the HTTP headers, and if you're running in a TEST environment, have the new class simply return the value that you're trying so hard to injection into the HTTP headers. Via DI/IOC, you would have two separate classes, one for each of those scenarios, that both implement the same interface, and then use one or the other depending on the environment. Simple as that!

Anonymous said...

Thanks a lot for the solution...

Anonymous said...

Thanks for the pattern to add a header value. I have used this example and see my Header value in the AllKeys collection. The problem I now have is that reading back the value throws an Invalid Cast Exception. Rather than recreate the entire explanation see my post at http://forums.asp.net/p/1638759/4236389.aspx#4236389

Thanks!

Nathan Rose said...

This worked really well for me as well. I was creating unit tests to test handling the HttpRequest headers via WCF data services. I created a method that added my custom headers to the request object and turned it into a utility for other future unit tests.

Method is as follows:

private void AddHeaderToRequest(HttpRequest request, string key, string value) {

NameValueCollection headers = request.Headers;

Type t = headers.GetType();
ArrayList item = new ArrayList();

// Remove read-only limitation on headers
t.InvokeMember("MakeReadWrite", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
t.InvokeMember("InvalidateCachedArrays", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
item.Add(value);
t.InvokeMember("BaseAdd", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, new object[] { key, item });
t.InvokeMember("MakeReadOnly", BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance, null, headers, null);
}