mwrock / RequestReduce

Instantly makes your .net website faster by reducing the number and size of requests with almost no effort.
www.requestreduce.org
Apache License 2.0
228 stars 48 forks source link

Regression in 1.8.49: Access to the path 'C:\inetpub\wwwroot\RequestReduceContent' is denied. #191

Closed candrews closed 12 years ago

candrews commented 12 years ago

I just upgraded from 1.832 to 1.8.49. I did not change the Request Reduce configuration.

The web.config configuration is:

<RequestReduce spriteVirtualPath="~/RequestReduceContent"  />

My application runs in the virtual path "/app" - the physical directory is c:\inetpub\wwwroot\app. So RequestReduceContent's physical path is: c:\inetpub\wwwroot\app\RequestReduceContent

Once I upgraded to 1.8.49, I got this error reported by ASP.NET: Access to the path 'C:\inetpub\wwwroot\RequestReduceContent' is denied.

The stack trace:

[UnauthorizedAccessException: Access to the path 'C:\inetpub\wwwroot\RequestReduceContent' is denied.]
   System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) +12898791
   System.IO.Directory.InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj) +1594
   System.IO.Directory.CreateDirectory(String path) +311
   RequestReduce.Configuration.RRConfiguration.CreatePhysicalPath() in c:\RequestReduce\RequestReduce\Configuration\RRConfiguration.cs:152
   RequestReduce.Configuration.RRConfiguration.set_SpritePhysicalPath(String value) in c:\RequestReduce\RequestReduce\Configuration\RRConfiguration.cs:130
   RequestReduce.Module.ResponseFilter.InstallFilter(HttpContextBase context) in c:\RequestReduce\RequestReduce\Module\ResponseFilter.cs:452
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +80
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +270

It appears that RR isn't performing the virtual to physical path mapping correctly.

mwrock commented 12 years ago

Hi Craig,

I have not been able to reproduce this. I created a new IIS site "Site" and a virtual application off of that "app". I use the same config as you which is actually the default path ~/RequestReduceContent and /app/RequestReduceContent gets created as expected.

Is it possible that a requestreduce config setting got added to a web.config of the parent app?

candrews commented 12 years ago

I do not have a web.config in the parent directory. I just reconfirmed that using the 1.8.32 RR DLL works fine, and simply replacing the DLL with 1.8.49 causes this problem.

I suppose I need to figure out how to attach the VS debugger in such a way that I can step through the RR code to track down the problem.

candrews commented 12 years ago

The problem is that RRConfiguration.SpriteVirtualPath is being set to "/RequestReduceContent" - not "/appName/RequestReduceContent".

I set a breakpoint at RRConfiguration.cs line 96. GetAbsolutePath is called, and inside this method, HttpContext.Current is null, so the method returns "/RequestReduceContent". So that's the problem.

Here's the stack trace when the breakpoint at RRConfiguration.cs line 96 is hit:

    RequestReduce.dll!RequestReduce.Configuration.RRConfiguration.RRConfiguration() Line 64 + 0x5 bytes
    [External Code] 
    RequestReduce.dll!RequestReduce.IOC.RRContainer.LoadAppropriateStoreRegistry(StructureMap.IContainer initContainer = {StructureMap.Container}) Line 97 + 0x3a bytes C#
    RequestReduce.dll!RequestReduce.IOC.RRContainer.InitContainer() Line 59 + 0xa bytes
    RequestReduce.dll!RequestReduce.IOC.RRContainer.RRContainer() Line 21 + 0x5 bytes
    [External Code] 
    RequestReduce.dll!RequestReduce.Api.Registry.HandlerFactory.get() Line 23 + 0x6 bytes
    [External Code] 

I'm not sure which change caused GetAbsolutePath to run when HttpContext.Current is null.

Using HttpRuntime.AppDomainAppVirtualPath instead of VirtualPathUtility.ToAbsolute solves the problem. What do you think of that fix?

Proposed new GetAbsolutePath:

private string GetAbsolutePath(string spriteVirtualPath)
{
    string tildeReplacement;
    if (HttpRuntime.AppDomainAppVirtualPath == "/")
    {
        tildeReplacement = String.Empty;
    }
    else
    {
        tildeReplacement = HttpRuntime.AppDomainAppVirtualPath;
    }
    return spriteVirtualPath.Replace("~", tildeReplacement);
}
mwrock commented 12 years ago

Hi Craig,

I think the GetAbsolutePath issue is a bit of a Red Herring here. There was another problem which I believe is unrelated that was causing the Context to be null. Running this in a web server (ie not a unit test) should never produce a null context.This was introduced in 1.7.94 released on 2/11 and is now fixed.

So the basic flow here is that when the response filter is attached, it makes sure that the physical path has been resolved. If not it maps it using the virtual path set in the config. This happens in line 450 of the ResponseFilter and should most likely happen only on the very first time that the filter is installed (ie the first request handled by RequestReduce).

This is where the code path ende up in GetAbsolutePath but I dont think it was this path where you were seeing a null context. You were likely breaking in that method on the background thread. With the fix just submitted, the background thread should never enter GetAbsolutePath. Using AppDomainAppVirtualPath seems to do the same thing but i'm changing the code to use that just in case.All my integration tests continue to pass.

I have not released a new nuget package but I have pushed the fix. Could you try pulling down the latest and building? Then let me know if this fixes your issue.

candrews commented 12 years ago

I just built from the trunk - and the problem still exists in the same way. :(

candrews commented 12 years ago

I set a breakpoint in GetAbsolutePath, and HttpContext.Current is null. Here's the stack trace when this happens:

>   RequestReduce.DLL!RequestReduce.Configuration.RRConfiguration.GetAbsolutePath(string resourceVirtualPath = "~/RequestReduceContent") Line 118 + 0x5 bytes   C#
    RequestReduce.DLL!RequestReduce.Configuration.RRConfiguration.ResourceVirtualPath.set(string value = "~/RequestReduceContent") Line 145 + 0x19 bytes    C#
    RequestReduce.DLL!RequestReduce.Configuration.RRConfiguration.RRConfiguration() Line 89 + 0xc2 bytes    C#
    [External Code] 
    RequestReduce.DLL!RequestReduce.IOC.RRContainer.LoadAppropriateStoreRegistry(StructureMap.IContainer initContainer = {StructureMap.Container}) Line 97 + 0x3a bytes C#
    RequestReduce.DLL!RequestReduce.IOC.RRContainer.InitContainer() Line 59 + 0xa bytes C#
    RequestReduce.DLL!RequestReduce.IOC.RRContainer.RRContainer() Line 21 + 0x5 bytes   C#
    [External Code] 
    RequestReduce.DLL!RequestReduce.Api.Registry.HandlerFactory.get() Line 23 + 0x6 bytes   C#
    [External Code] 

Therefore, ResourceVirtualPath gets set to "/RequestReduceContent" instead of "/appName/RequestReduceContent" and the same problem as before occurs.

I think RR is being initialized by IIS before the current request is made available. You either need to have ResourceVirtualPath's getter resolve ~ (instead of its setter), or use the version of GetAbsolutePath that I posted that does not rely on there being an current request.

If you do keep your version of GetAbsolutePath that relies on there being a current request, instead of replacing ~ with the empty string if there is no current request, you should probably throw an exception. You really require a current request - and if there isn't one, things aren't going to work and your attempted workaround doesn't work and leads to confusion.

mwrock commented 12 years ago

Thanks Craig. Do you use sasslesscoffee? I'd like to know what is calling the handlerfactory API and sasslesscoffee is the only thing that would call it unless your code calls it directly. This would also support your theory since it calls it in a preappsrartup method.

candrews commented 12 years ago

I have sasslesscoffee installed, yes.

In my global.asax's Application_Start, I call a few RR methods: set RequestReduce.Api.Registry.CaptureErrorAction RequestReduce.Api.Registry.AddFilter set RequestReduce.Api.Registry.UrlTransformer

mwrock commented 12 years ago

Thanks. It all makes sense now and ill have a fix by tomorrow morning.