2013-09-26 55 views
2

我已經構建了一個基於AntiXSS的HTML清理程序,通過覆蓋默認的模型聯編程序來自動清理用戶輸入字符串,該聯編程序在標準的發佈請求中工作正常。但是,當使用新的ApiController時,默認的模型綁定器永遠不會被調用,我認爲這是因爲這個新的MVC控制器使用JSON格式器來綁定來自請求主體的輸入數據。如何在使用MVC4 ApiController時清理JSON輸入參數?

那麼如何去擴展格式化程序,以便在JSON綁定後修改字符串屬性?我寧願不必在控制器級別實現這一點,並且在它進入控制器之前應該有辦法做到這一點。

回答

1

我通過創建一個修改的Json格式化程序解決了我的問題,但是大多數關於如何做到這一點的文檔都是基於.NET 4.0的預發佈代碼。

using System; 
using System.Collections.Concurrent; 
using System.Collections.Generic; 
using System.Diagnostics.Contracts; 
using System.Linq; 
using System.IO; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Net.Http.Formatting; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Json; 
using System.Text; 
using System.Reflection; 
using System.Threading.Tasks; 
using System.Web; 
using System.Web.Script.Serialization; 
using Newtonsoft.Json; 
using Newtonsoft.Json.Serialization; 

public class JsonNetFormatterAntiXss : JsonMediaTypeFormatter 
{ 
    public override bool CanReadType(Type type) 
    { 
     return base.CanReadType(type); 
    } 
    public override bool CanWriteType(Type type) 
    { 
     return base.CanWriteType(type); 
    } 

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger) 
    { 
     HttpContentHeaders contentHeaders = content == null ? null : content.Headers; 
     // If content length is 0 then return default value for this type 
     if (contentHeaders != null && contentHeaders.ContentLength == 0) 
     { 
      return Task.FromResult(MediaTypeFormatter.GetDefaultValueForType(type)); 
     } 

     // Get the character encoding for the content 
     Encoding effectiveEncoding = SelectCharacterEncoding(contentHeaders); 

     try 
     { 
      using (JsonTextReader jsonTextReader = new JsonTextReader(new StreamReader(readStream, effectiveEncoding)) { CloseInput = false, MaxDepth = _maxDepth }) 
      { 
       JsonSerializer jsonSerializer = JsonSerializer.Create(_jsonSerializerSettings); 
       if (formatterLogger != null) 
       { 
        // Error must always be marked as handled 
        // Failure to do so can cause the exception to be rethrown at every recursive level and overflow the stack for x64 CLR processes 
        jsonSerializer.Error += (sender, e) => 
        { 
         Exception exception = e.ErrorContext.Error; 
          formatterLogger.LogError(e.ErrorContext.Path, exception); 
         e.ErrorContext.Handled = true; 
        }; 
       } 

       return Task.FromResult(DeserializeJsonString(jsonTextReader, jsonSerializer, type)); 
      } 

     } 
     catch (Exception e) 
     { 
      if (formatterLogger == null) 
      { 
       throw; 
      } 
      formatterLogger.LogError(String.Empty, e); 
      return Task.FromResult(MediaTypeFormatter.GetDefaultValueForType(type)); 
     } 
    } 

    private object DeserializeJsonString(JsonTextReader jsonTextReader, JsonSerializer jsonSerializer, Type type) 
    { 
     object data = jsonSerializer.Deserialize(jsonTextReader, type); 

     // sanitize strings if we are told to do so 
     if(_antiXssOptions != AntiXssOption.None) 
      data = CleanAntiXssStrings(data); // call your custom XSS cleaner 

     return data; 
    } 

    /// <summary> 
    /// Clean all strings using internal AntiXss sanitize operation 
    /// </summary> 
    /// <param name="data"></param> 
    /// <returns></returns> 
    private object CleanAntiXssStrings(object data) 
    { 
     PropertyInfo[] properties = data.GetType().GetProperties(); 
     foreach (PropertyInfo property in properties) 
     { 
      Type ptype = property.PropertyType; 
      if (ptype == typeof(string) && ptype != null) 
      { 
       // sanitize the value using the preferences set 
       property.SetValue(data, DO_MY_SANITIZE(property.GetValue(data).ToString())); 
       } 
      } 
      return data; 
     } 

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext) 
    { 
     return base.WriteToStreamAsync(type, value, writeStream, content, transportContext); 
    } 

    private DataContractJsonSerializer GetDataContractSerializer(Type type) 
    { 
     Contract.Assert(type != null, "Type cannot be null"); 
     DataContractJsonSerializer serializer = _dataContractSerializerCache.GetOrAdd(type, (t) => CreateDataContractSerializer(type, throwOnError: true)); 

     if (serializer == null) 
     { 
      // A null serializer means the type cannot be serialized 
      throw new InvalidOperationException(String.Format("Cannot serialize '{0}'", type.Name)); 
     } 

     return serializer; 
    } 

    private static DataContractJsonSerializer CreateDataContractSerializer(Type type, bool throwOnError) 
    { 
     if (type == null) 
     { 
      throw new ArgumentNullException("type"); 
     } 

     DataContractJsonSerializer serializer = null; 
     Exception exception = null; 

     try 
     { 
      // Verify that type is a valid data contract by forcing the serializer to try to create a data contract 
      XsdDataContractExporter xsdDataContractExporter = new XsdDataContractExporter(); 
      xsdDataContractExporter.GetRootElementName(type); 
      serializer = new DataContractJsonSerializer(type); 
     } 
     catch (InvalidDataContractException invalidDataContractException) 
     { 
      exception = invalidDataContractException; 
     } 

     if (exception != null) 
     { 
      if (throwOnError) 
      { 
       throw new InvalidOperationException(String.Format("Can not serialize type '{0}'.", type.Name), exception); 
      } 
     } 

     return serializer; 
    } 

} 

我基於基於JsonMediaTypeFormatter的Json.NET implementation以及本article此代碼。至於我的AntiXSS實現,我沒有打擾發佈它,它使用了AntiXSS 4.2.1以及自定義分析的組合,因爲該庫瘋狂過度保護。