Исключение ASP.NET MVC UrlHelper.GenerateUrl: "Невозможно использовать ведущий.. для выхода из верхнего каталога"

Я использую модуль перезаписи IIS 7 для перезаписи входящего URL-адреса, например:

http://server/year/all

к

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

Все работает отлично, за исключением случаев, когда при обработке переписанного запроса я использую метод MVC UrlHelper.GenerateUrl():

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

Вызов этого метода приводит к исключению 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)

Глядя на RequestContext, кажется, что все пути запроса верны (т.е. имеют переписанные значения). Я не могу понять, почему он пытается выйти из каталога верхнего уровня... Там нигде мы не используем... в пути.

Я также убедился, что RewriteModule находится над модулем UrlRouting в IIS.

Пока я могу входить в методы framework, я не могу проверить ни одну из локальных переменных (либо в VS или WinDbg), поскольку он был оптимизирован компилятором.

Любые мысли?

Ответы

Ответ 1

Это гротескное решение, связанное с частными деталями реализации, но добавьте это:

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

Это позволяет избежать внутренней проверки, сделанной в PathHelper.GenerateClientUrlInternal, чтобы проверить, был ли запрос переписан. Весьма вероятно, что это нарушит некоторые сценарии, о чем свидетельствует этот комментарий в справочных источниках:

// 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.

Ответ 2

Не уверен, что это помогает, но вот код, бросающий исключение:

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 ".";
}

Ответ 3

Рабочим решением является вставка строки перед Url.Content/UrlHelper.GenerateContentUrl (лучшее место в Application_BeginRequest):

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

Мой ответ - результат двух вышеупомянутых ответов (Рик Шотт и Том). Оба были совершенно правы, но это не помогло. Я узнал исходный код на https://github.com/aspnet/AspNetWebStack/blob/master/src/ двух классов (System.Web.WebPages.Utils.UrlRewriterHelper.cs и System.Web.WebPages.Utils.UrlUtil.cs. ) которые находятся в моей трассировке стека:

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) 

Есть код в System.Web.WebPages.Utils.UrlUtil.cs - метод GenerateClientUrlInternal:

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;

Вы могли видеть странные строки с комментарием автора для переписанных путей URL. Кроме того, исходный путь клиента находится в HttpContext.Request.RawUrl, но в URL-адресе он переписан. Посмотрите вперед на 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;
            }

Если мы записываем фиктивное значение в HttpContext.Items [UrlWasRewrittenServerVar] со значением "false", мы делаем пропущенный httpWorkerRequest.GetServerVariable(UrlWasRewrittenServerVar)! = Проверка нуля. Итак, Url.Content сейчас работает.