“路径中有非法字符。”仅当从移动浏览器查看时

“Illegal characters in path.” only when viewing from mobile browser

我正在使用传统的 WebAPI 控制器:

    [Route("api/results/{query}")]
    [AcceptVerbs("GET")]
    public HttpResponseMessage GetQueryResults(string query)
    {
            var userAgent = Request.Headers.UserAgent;
            var result = _fooService.GetResults(GetUsername(), query);
            var response = Request.CreateResponse(HttpStatusCode.OK, result);
            return response;
    }

GetResults returns 元素数组,如下所示:

[{
"resultId":2039016,
"text":null,
"dateCreated":"2020-09-10T02:24:36.003",
"targetPlatform":"FooBar"
}]

在大多数浏览器上,这都可以正常工作。我的用户代理 header 看起来像这样:

{Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36}

然而,当我使用设备工具栏从 Chrome 进行调试时,或者当我在 iPhone 上从 Safari 访问我的站点时,我的用户代理发生了变化。在 Chrome 的设备工具栏(移动模拟器)中,它看起来像这样:

{Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Mobile Safari/537.36}

并且在这种情况下,CheckInvalidPathChars 被调用并且针对 JSON:

失败
"type": "System.ArgumentException",
"message": "Illegal characters in path.",
"stackTrace": "   at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional)
   at System.IO.Path.GetExtension(String path)
   at System.Web.WebPages.DefaultDisplayMode.TransformPath(String virtualPath, String suffix)
   at System.Web.WebPages.DefaultDisplayMode.GetDisplayInfo(HttpContextBase httpContext, String virtualPath, Func`2 virtualPathExists)
   at System.Web.WebPages.DisplayModeProvider.GetDisplayInfoForVirtualPath(String virtualPath, HttpContextBase httpContext, Func`2 virtualPathExists, IDisplayMode currentDisplayMode, Boolean requireConsistentDisplayMode)
   at System.Web.WebPages.WebPageRoute.GetRouteLevelMatch(String pathValue, String[] supportedExtensions, Func`2 virtualPathExists, HttpContextBase context, DisplayModeProvider displayModeProvider)
   at System.Web.WebPages.WebPageRoute.MatchRequest(String pathValue, String[] supportedExtensions, Func`2 virtualPathExists, HttpContextBase context, DisplayModeProvider displayModes)
   at System.Web.WebPages.WebPageRoute.DoPostResolveRequestCache(HttpContextBase context)
   at System.Web.WebPages.WebPageHttpModule.OnApplicationPostResolveRequestCache(Object sender, EventArgs e)
   at System.Web.HttpApplication.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)"

我可以通过尝试将序列化 JSON 解析为文件路径来手动重现:

        try
        {
            var isValid = System.IO.Path.GetExtension(jsonString);
        }
        catch (Exception e)
        {
            throw e;
        }

当然,尝试将序列化的 JSON object 解析为文件会引发错误。为什么 ASP.NET 修改基于用户代理 headers 的解析行为?

我能否以某种方式覆盖 Request 的入站 header 以强制框架实现功能行为?

要清楚 - 我可以从 Chrome(标准)调用控制器,没有任何问题。当我使用来自 Chrome 的相同请求调用控制器时(打开 devtools 并激活移动模拟器),抛出异常。同样,当我在 iPhone 上使用来自 Safari 的完全相同的请求调用它时,抛出异常。在这些情况下,用户代理 header 是自变量 - 因此必须遵循此 header 以某种方式 导致 调用不同的执行路径。对吗?

在执行所有控制器逻辑后抛出异常 - response 在抛出时由控制器返回。

我使用了 Asp.net 核心请找到下面的代码和屏幕截图,假设我完全理解你的问题。

 [Route("api/results/{query}")]
    [HttpGet]
    public IActionResult GetQueryResults(string query)
    {
        var userAgent = Request.Headers["User-Agent"].ToString();
        var result = @"[{\'resultId\':2039016,\'text\':null,\'dateCreated\':\'2020-09-10T02:24:36.003\',\'targetPlatform\':\'FooBar\'}]";
        // _fooService.GetResults(GetUsername(), query);
        return Ok(new { result = result, userAgent = userAgent });
    }
  • 这是来自 vscode
  • 的图像截图

  • 这是使用 chrome 移动响应
  • 的屏幕截图

  • 这是使用 chrome 网络或桌面视图
  • 的屏幕截图

正如@Dipen Shah 指出的那样,解决方案在另一个 post


Why is ASP.NET modifying parsing behavior based on user agent headers?

在您的 ASP.NET 应用程序 (reference) 中 自动 注册了一个 WebPageHttpModule。在内部,WebPageHttpModule 尝试根据 显示模式 .

路由请求
// NOTE: Excluded unnecessary code
public sealed class DisplayModeProvider
{
    public static readonly string MobileDisplayModeId = "Mobile";

    private readonly List<IDisplayMode> _displayModes = new List<IDisplayMode>()
    {
        (IDisplayMode) new DefaultDisplayMode(DisplayModeProvider.MobileDisplayModeId)
        {
            ContextCondition = (Func<HttpContextBase, bool>) (context => context.GetOverriddenBrowser().IsMobileDevice)
        },
        (IDisplayMode) new DefaultDisplayMode()
    };

    public IList<IDisplayMode> Modes
    {
        get
        {
            return (IList<IDisplayMode>) this._displayModes;
        }
    }
}

您可以在那里看到字符串 Mobile 并根据 IsMobileDevice 检查条件(reference)。这就是为什么上述 post 中的解决方案有意义。了解其内部工作原理后,您还可以执行以下 hack

public static void RegisterDisplayModes()
{
    var displayModes = DisplayModeProvider.Instance.Modes;
    var mobileDisplayMode = displayModes.FirstOrDefault(d => d.DisplayModeId == DisplayModeProvider.MobileDisplayModeId);
    if (mobileDisplayMode != null)
    {
        displayModes.Remove(mobileDisplayMode);
    }
}

注意,这是基于位于 C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v2.0\Assemblies\.

的反编译 System.Web.WebPages.dll