After spending entirely too much time researching this issue today here is how you can use any characters you want for URLs in ASP.NET 4 and IIS 7. A bit of background: I am writing a web application that has a custom HttpModule and HttpHandler that should handle all requests and not limit the syntax of those requests at all. I could not find the information on how to do this in one place anywhere, and there are a reasonable amount of misleading, unanswered and naive responses on various forums that will likely lead you astray if you have an advanced configuration like mine. There are also a lot of completely out of date posts centered on .NET 1.1 and .NET 2.0.
The first thing I was trying to do was make a POST-ed form value containing a forward slash into something that could be used as a component in a RESTful URL. I tried to accomplish that by implementing a handler for AuthenticateRequest in my HttpModule (you can’t do it in BeginRequest unless you want to read the form data manually because Request.Form is not intialized yet) that would encode the value and call TransferRequest. First that made this happen:
A potentially dangerous Request.Path value was detected from the client (%).
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.
Exception Details: System.Web.HttpException: A potentially dangerous Request.Path value was detected from the client (%).
[HttpException (0x80004005): A potentially dangerous Request.Path value was detected from the client (%).] System.Web.HttpRequest.ValidateInputIfRequiredByConfig() +8815985 System.Web.PipelineStepManager.ValidateHelper(HttpContext context) +59
Okay. ASP.NET, for security reasons, normally protects your web applications from potentially harmful content being sent to them, which is probably a good thing. But what if we want or need potentially harmful content? Should we be limited? I hoped the answer was “no”, but the almost complete lack of information on the subject sure wasn’t making it seem that way. After some not too helpful reading and some digging with Reflector, I came up with this:
<httpRuntime requestPathInvalidCharacters="" />
<pages validateRequest="false" />
Now no characters are invalid and requests shouldn’t even BE validated right? WRONG. While this did clear up the first exception, I faced a new one. And, while I was not sure if the
validateRequest setting would even apply to my case since it is on the
pages element, I assure you that for some reason that setting is an integral part of the above and following changes to work properly together. Here was my second roadblock:
Great, now it didn’t even look like it was getting from IIS to ASP.NET! However, deciding I “fully understood” the change, this problem was much easier to find a solution to and get past. Once again, it was a simple matter of adding the right magic words to the Web.config:
<requestFiltering allowDoubleEscaping="true" />
Finally my Frankenstein was coming to life and I thought I was in the clear. But, then I realized that when other clients (not form POST-ers who would not realize my HttpModule was secretly processing their inputs) wanted to send me a URL with a forward slash in a path component they would have to double encode or use some other strange method to identify it as a different kind of forward slash then the normal path component delimiter.
So, I decided to add a feature to my framework that would allow a syntax to specify that “the rest” of a URL is a single component and to do that I wanted to use “/*”. Convinced I could use any character I wanted now, I went ahead and ran the debugger with my test path, and to my chagrin ran into this little beauty:
Message=Illegal characters in path.
at System.Security.Permissions.FileIOPermission.HasIllegalCharacters(String str)
mscorlib.dll!System.Security.Permissions.FileIOPermission.HasIllegalCharacters(string str) + 0x117 bytes
mscorlib.dll!System.Security.Permissions.FileIOPermission.AddPathList(System.Security.Permissions.FileIOPermissionAccess access, System.Security.AccessControl.AccessControlActions control, string pathListOrig, bool checkForDuplicates, bool needFullPath, bool copyPathList) + 0x4a bytes
mscorlib.dll!System.Security.Permissions.FileIOPermission.FileIOPermission(System.Security.Permissions.FileIOPermissionAccess access, string pathList, bool checkForDuplicates, bool needFullPath) + 0x2c bytes
mscorlib.dll!System.IO.Path.GetFullPath(string path) + 0x5c bytes
System.Web.dll!System.Web.Util.FileUtil.IsSuspiciousPhysicalPath(string physicalPath, out bool pathTooLong) + 0x42 bytes
System.Web.dll!System.Web.Util.FileUtil.IsSuspiciousPhysicalPath(string physicalPath) + 0x18 bytes
System.Web.dll!System.Web.Util.FileUtil.CheckSuspiciousPhysicalPath(string physicalPath) + 0x9 bytes
System.Web.dll!System.Web.CachedPathData.GetPhysicalPath(System.Web.VirtualPath virtualPath) + 0x77 bytes
System.Web.dll!System.Web.CachedPathData.GetConfigPathData(string configPath) + 0x190 bytes
System.Web.dll!System.Web.CachedPathData.GetVirtualPathData(System.Web.VirtualPath virtualPath, bool permitPathsOutsideApp) + 0x6f bytes
System.Web.dll!System.Web.HttpContext.GetFilePathData() + 0x25 bytes
System.Web.dll!System.Web.HttpContext.GetConfigurationPathData() + 0x1b bytes
System.Web.dll!System.Web.Configuration.RuntimeConfig.GetConfig(System.Web.HttpContext context) + 0x2c bytes
System.Web.dll!System.Web.HttpContext.SetImpersonationEnabled() + 0xd bytes
System.Web.dll!System.Web.HttpApplication.AssignContext(System.Web.HttpContext context) + 0x5c bytes
System.Web.dll!System.Web.HttpRuntime.ProcessRequestNotificationPrivate(System.Web.Hosting.IIS7WorkerRequest wr, System.Web.HttpContext context) + 0x22f bytes
System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotificationHelper(System.IntPtr managedHttpContext, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) + 0x1fc bytes
System.Web.dll!System.Web.Hosting.PipelineRuntime.ProcessRequestNotification(System.IntPtr managedHttpContext, System.IntPtr nativeRequestContext, System.IntPtr moduleData, int flags) + 0x29 bytes
So, in a last desperate attempt to not have to give up, I circled back to a solution that had not worked for any of the other problems I ran into before and to my surprise, it all worked out. Unfortunately, it requires adding a registry value, apparently making your entire server less secure and not an option except for in the most “all access” hosting environments. Anyway, you need to set the following to get the last few “illegal” characters to be allowed:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ASP.NET\VerificationCompatibility = 1 (32-bit DWORD)
Before I get accused of hacking my way out of this, this is actually a recommended fix from Microsoft written when this feature was first added as a service pack for .NET 1.1 but it is apparently still in there and for guys like me who like to push the limits, I thank them for trusting us a tiny bit:
I did find one posting that sort of talks about this problem (on Scott Hanselman’s blog) but it appears rather than finding a solution they opted to change their URLs to workaround the problem. Yes, maybe that was a better idea, but not nearly as FUN:
Hopefully this will help someone get past the issue themselves without all the forumining and experiments, or at least convince them it’s a bad idea and give up on the special characters.