今天在處理Google網站管理員中的500錯誤時發現這樣一些URL:
http://www.cnblogs.com/Garnai/tag/3D%3F%96%CA/ http://www.cnblogs.com/henryfan/tag/%3F%3F%3F%90%B6%90%AC%3F%8C%8F/ http://www.cnblogs.com/zhangpengshou/tag/%3F%96%DA%3F%97%9D%94V%8FC%3F/ http://www.cnblogs.com/henryfan/tag/%3F%3F%3F%90%B6%90%AC%3F%8C%8F/ ...
這些URL不僅出現500錯誤,而且不顯示自定義錯誤,只顯示ASP.NET的默認錯誤頁面:
服務器日志中記錄具體的錯誤信息是:
[ArgumentOutOfRangeException: 在多字節的目標代碼頁中,沒有此 Unicode 字符可以映射到的字符。 (異常來自 HRESULT:0x80070459)] System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo) +0 System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode) +13563503 System.Web.Hosting.IIS7WorkerRequest.GetServerVariableInternal(String name) +50 System.Web.Hosting.IIS7WorkerRequest.ReadRequestHeaders() +144 System.Web.Hosting.IIS7WorkerRequest.GetKnownRequestHeader(Int32 index) +109 System.Web.HttpWorkerRequest.HasEntityBody() +27 System.Web.HttpRequest.GetEncodingFromHeaders() +126 System.Web.HttpRequest.get_ContentEncoding() +162 System.Web.HttpRequest.get_QueryStringEncoding() +10 System.Web.HttpRequest.get_QueryStringText() +209 System.Web.HttpRequest.ValidateInputIfRequiredByConfig() +87 System.Web.PipelineStepManager.ValidateHelper(HttpContext context) +55
對應的英文錯誤信息是:
No mapping for the Unicode character exists in the target multi-byte code page.
從這些出錯的URL中觀察到了一個規律:都包含%3F這個編碼,解碼出來對應的字符是?。
從錯誤信息的代碼執行堆棧信息 System.Web.HttpRequest.get_QueryStringText() 中,可以看出錯誤發生在從URL中讀取查詢字符串的時候。
可是出錯的URL中並沒有查詢字符串。。。
后來突然想到,ASP.NET是先進行UrlDecode,然后再進行get_QueryStringText()的。
比如將 http://www.cnblogs.com/Garnai/tag/3D%3F%96%CA/ 進行URLDecode之后得到的URL是:
http://www.cnblogs.com/Garnai/tag/3D?柺/
看到沒有,出現了問號,變成有查詢字符串的URL。於是,ASP.NET將問號之后的字符作為key進行讀取,由於key是不支持中文的,於是引發“在多字節的目標代碼頁中,沒有此Unicode字符可以映射到的字符”。如果ASP.NET先進行get_QueryStringText(),再進行UrlDecode就不會觸發這個問題,可是ASP.NET偏偏不這么干。
那如何解決這個問題?
雖然問題出在ASP.NET,但我們無法改變ASP.NET,只能另辟蹊徑。
既然是ASP.NET處理上的問題,那我們別無選擇,只能搶在ASP.NET之前攔截這樣的URL請求,而進行這樣的攔截最簡單的工具就是IIS的Url Rewrite module。
根據我們的應用場景,在rewriteRules.config中添加一條規則——在URL中如果/tag/之后出現問號就直接返回404,規則定義如下:
<rule name="block_invalid_tag_url" stopProcessing="true"> <match url="^[^/]+/tag/.*?\?.*$" /> <action type="CustomResponse" statusCode="404" statusReason="The request URL is invalid" statusDescription="The request URL is invalid" /> </rule>
然后就搞定了這個問題,寫了這篇博客。