2013-09-26 10 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과 사용자 지정 구문 분석의 조합을 사용합니다. 왜냐하면 해당 라이브러리가 현저하게 과보호 적이기 때문입니다.