18

I am using the IIS 7 Rewrite module to rewrite an incoming url like:

http://server/year/all

to

http://server/application/controller/year/all

Everything works fine, except when, while processing the rewritten request, I use MVC's UrlHelper.GenerateUrl() method:

UrlHelper.GenerateUrl(
   "Assets",
   "Css",
   "Asset",
   new RouteValueDictionary(new { site = site.Name, assetPath = assetPath }),
   RouteTable.Routes,
   controllerContext.RequestContext,
   false);

Calling this method results in an HttpException:

System.Web.HttpException: Cannot use a leading .. to exit above the top directory.
   at System.Web.Util.UrlPath.ReduceVirtualPath(String path)
   at System.Web.Util.UrlPath.Reduce(String path)
   at System.Web.VirtualPath.Combine(VirtualPath relativePath)
   at System.Web.VirtualPathUtility.Combine(String basePath, String relativePath)
   at System.Web.Mvc.PathHelpers.GenerateClientUrlInternal(HttpContextBase httpContext, String contentPath)
   at System.Web.Mvc.PathHelpers.GenerateClientUrl(HttpContextBase httpContext, String contentPath)
   at System.Web.Mvc.UrlHelper.GenerateUrl(String routeName, String actionName, String controllerName, RouteValueDictionary routeValues, RouteCollection routeCollection, RequestContext requestContext, Boolean includeImplicitMvcValues)

Looking at the RequestContext, it seems that all of the request paths are correct (ie, have the rewritten values). I can't seem to figure out why it's trying to exit out of the top level directory... There's nowhere we are using .... in a path.

I've also made sure the RewriteModule is in above the UrlRouting module in IIS.

While I can step into the framework methods, I can't examine any of the local variables (either in VS or WinDbg) because it's been compiler optimized.

Any thoughts?

Paul Kearney - pk
  • 5,435
  • 26
  • 28
  • @Rob: we didn't find a solution. I was able to track it down to the presence of the X_ORIGINAL_URL header in the request. IIS puts that header in there whenever it rewrites a url. It uses that header to try to undo the rewriting of the url, since you probably don't want the generated url to follow the rewritten format. It gets hung up for us when it takes the original url into consideration. It doesn't make a whole lot of sense to *always* use the original url - for us, we just want it to generate the url and ignore the original one. There is no way we've found to override this behavior. – Paul Kearney - pk Jan 06 '11 at 17:48
  • I ended up switching from a rewrite to a redirect and that fixed the problem. our URLs now contain the area, which isn't ideal, but it's more important that the site works without screwing with MVC's routing too much. – Rob Volk Jan 10 '11 at 17:32
  • Not sure if this helps but I had this problem as well and ended up calling the overloaded [UrlHelper.GenerateUrl() method](http://msdn.microsoft.com/en-us/library/ee703653.aspx) that also takes the hostname as a parameter. It did mean I had to keep the hostname in config and pass it around, but it seemed to help with url rewriting... :) – codefrenzy Sep 29 '11 at 14:02

3 Answers3

9

This is a grotesque workaround involving private implementation details, but add this:

HttpContext.Current.Request.ServerVariables.Remove("IIS_WasUrlRewritten");

This avoids the internal check done in PathHelper.GenerateClientUrlInternal to see if the request was rewritten. It's quite likely that this will break some scenarios, as hinted at by this comment in the reference sources:

// Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base 
// of our absolute paths. For example, consider mysite.example.com/foo, which is internally 
// rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
// base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar, 
// which is incorrect.
Thom
  • 2,643
  • 2
  • 30
  • 33
  • Could you tell where one should run this? In my case there is actually no IIS_WasUrlRewritten server variable... But tried to remove the X-Original-URL from the headers and server variables that is supposed to have to do something with Url Rewrite too, but neither removing that worked from an Http Module (although the value is there, but calling Remove() doesn't do anything with it). – Piedone Apr 29 '14 at 21:33
  • 1
    Unfortunately this did not solve the problem for us. – Krisztián Balla Oct 08 '14 at 07:35
  • You are quite right but you need to make HttpContext.Items "dirty" instead of ServerVariables: System.Web.HttpContext.Current.Items.Add("IIS_WasUrlRewritten", "false"); – Pavel Nazarov Dec 19 '18 at 13:50
3

Working solution is to insert the line before Url.Content/UrlHelper.GenerateContentUrl (best place is in Application_BeginRequest):

System.Web.HttpContext.Current.Items.Add("IIS_WasUrlRewritten", "false");

My answer is the result of 2 above answers (Rick Schott and Thom). Both was quite right but that didn't help. I learned source code at https://github.com/aspnet/AspNetWebStack/blob/master/src/ of two classes (System.Web.WebPages.Utils.UrlRewriterHelper.cs and System.Web.WebPages.Utils.UrlUtil.cs) that are in my stack trace:

System.Web.HttpException (0x80004005): Cannot use a leading .. to exit above the top directory. 
at System.Web.Util.UrlPath.ReduceVirtualPath(String path) 
at System.Web.Util.UrlPath.Reduce(String path) 
at System.Web.VirtualPath.Combine(VirtualPath relativePath) 
at System.Web.VirtualPathUtility.Combine(String basePath, String relativePath) 
at System.Web.WebPages.UrlUtil.GenerateClientUrlInternal(HttpContextBase httpContext, String contentPath) 
at System.Web.WebPages.UrlUtil.GenerateClientUrlInternal(HttpContextBase httpContext, String contentPath) 
at System.Web.WebPages.UrlUtil.GenerateClientUrl(HttpContextBase httpContext, String basePath, String path, Object[] pathParts) 

There is code in System.Web.WebPages.Utils.UrlUtil.cs - GenerateClientUrlInternal method:

if (!wasRequestRewritten)
            {
                return contentPath;
            }

            // Since the rawUrl represents what the user sees in his browser, it is what we want to use as the base
            // of our absolute paths. For example, consider mysite.example.com/foo, which is internally
            // rewritten to content.example.com/mysite/foo. When we want to generate a link to ~/bar, we want to
            // base it from / instead of /foo, otherwise the user ends up seeing mysite.example.com/foo/bar,
            // which is incorrect.
            string relativeUrlToDestination = MakeRelative(httpContext.Request.Path, contentPath);
            string absoluteUrlToDestination = MakeAbsolute(httpContext.Request.RawUrl, relativeUrlToDestination);
            return absoluteUrlToDestination;

You could see strange lines with author's comment for url rewritten paths. Also, original client path is in HttpContext.Request.RawUrl but in Url it is rewritten. Look forward at System.Web.WebPages.Utils.UrlRewriterHelper.cs:

 if (httpContext.Items.Contains(UrlWasRewrittenServerVar))
            {
                return Object.Equals(httpContext.Items[UrlWasRewrittenServerVar], UrlWasRequestRewrittenTrueValue);
            }
            else
            {
                HttpWorkerRequest httpWorkerRequest = (HttpWorkerRequest)httpContext.GetService(typeof(HttpWorkerRequest));
                bool requestWasRewritten = (httpWorkerRequest != null && httpWorkerRequest.GetServerVariable(UrlWasRewrittenServerVar) != null);

                if (requestWasRewritten)
                {
                    httpContext.Items.Add(UrlWasRewrittenServerVar, UrlWasRequestRewrittenTrueValue);
                }
                else
                {
                    httpContext.Items.Add(UrlWasRewrittenServerVar, UrlWasRequestRewrittenFalseValue);
                }

                return requestWasRewritten;
            }

If we write dummy value to HttpContext.Items[UrlWasRewrittenServerVar] with "false" value we make skipped httpWorkerRequest.GetServerVariable(UrlWasRewrittenServerVar) != null check. So Url.Content is working now.

Pavel Nazarov
  • 723
  • 6
  • 10
0

Not sure if it helps but here is the code throwing the exception:

internal static string ReduceVirtualPath(string path)
{
    int length = path.Length;
    int startIndex = 0;
    while (true)
    {
        startIndex = path.IndexOf('.', startIndex);
        if (startIndex < 0)
        {
            return path;
        }
        if (((startIndex == 0) || (path[startIndex - 1] == '/')) && ((((startIndex + 1) == length) || (path[startIndex + 1] == '/')) || ((path[startIndex + 1] == '.') && (((startIndex + 2) == length) || (path[startIndex + 2] == '/')))))
        {
            break;
        }
        startIndex++;
    }
    ArrayList list = new ArrayList();
    StringBuilder builder = new StringBuilder();
    startIndex = 0;
    do
    {
        int num3 = startIndex;
        startIndex = path.IndexOf('/', num3 + 1);
        if (startIndex < 0)
        {
            startIndex = length;
        }
        if ((((startIndex - num3) <= 3) && ((startIndex < 1) || (path[startIndex - 1] == '.'))) && (((num3 + 1) >= length) || (path[num3 + 1] == '.')))
        {
            if ((startIndex - num3) == 3)
            {
                if (list.Count == 0)
                {
                    throw new HttpException(SR.GetString("Cannot_exit_up_top_directory"));
                }
                if ((list.Count == 1) && IsAppRelativePath(path))
                {
                    return ReduceVirtualPath(MakeVirtualPathAppAbsolute(path));
                }
                builder.Length = (int) list[list.Count - 1];
                list.RemoveRange(list.Count - 1, 1);
            }
        }
        else
        {
            list.Add(builder.Length);
            builder.Append(path, num3, startIndex - num3);
        }
    }
    while (startIndex != length);
    string str = builder.ToString();
    if (str.Length != 0)
    {
        return str;
    }
    if ((length > 0) && (path[0] == '/'))
    {
        return "/";
    }
    return ".";
}
rick schott
  • 21,012
  • 5
  • 52
  • 81