/* **************************************************************************** * * Copyright (c) Microsoft Corporation. All rights reserved. * * This software is subject to the Microsoft Public License (Ms-PL). * A copy of the license can be found in the license.htm file included * in this distribution. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ namespace System.Web.Mvc { using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Web; using System.Web.Mvc.Resources; using System.Web.UI; internal class AntiForgeryDataSerializer { private IStateFormatter _formatter; protected internal IStateFormatter Formatter { get { if (_formatter == null) { _formatter = FormatterGenerator.GetFormatter(); } return _formatter; } set { _formatter = value; } } private static HttpAntiForgeryException CreateValidationException(Exception innerException) { return new HttpAntiForgeryException(MvcResources.AntiForgeryToken_ValidationFailed, innerException); } public virtual AntiForgeryData Deserialize(string serializedToken) { if (String.IsNullOrEmpty(serializedToken)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "serializedToken"); } // call property getter outside try { } block so that exceptions bubble up for debugging IStateFormatter formatter = Formatter; try { object[] deserializedObj = (object[])formatter.Deserialize(serializedToken); return new AntiForgeryData() { Salt = (string)deserializedObj[0], Value = (string)deserializedObj[1], CreationDate = (DateTime)deserializedObj[2], Username = (string)deserializedObj[3] }; } catch (Exception ex) { throw CreateValidationException(ex); } } public virtual string Serialize(AntiForgeryData token) { if (token == null) { throw new ArgumentNullException("token"); } object[] objToSerialize = new object[] { token.Salt, token.Value, token.CreationDate, token.Username }; string serializedValue = Formatter.Serialize(objToSerialize); return serializedValue; } // See http://www.yoda.arachsys.com/csharp/singleton.html (fifth version - fully lazy) for the singleton pattern // used here. We need to defer the call to TokenPersister.CreateFormatterGenerator() until we're actually // servicing a request, else HttpContext.Current might be invalid in TokenPersister.CreateFormatterGenerator(). private static class FormatterGenerator { public static readonly Func GetFormatter = TokenPersister.CreateFormatterGenerator(); [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "This type must not be marked 'beforefieldinit'.")] static FormatterGenerator() { } // This type is very difficult to unit-test because Page.ProcessRequest() requires mocking // much of the hosting environment. For now, we can perform functional tests of this feature. private sealed class TokenPersister : PageStatePersister { private TokenPersister(Page page) : base(page) { } public static Func CreateFormatterGenerator() { // This code instantiates a page and tricks it into thinking that it's servicing // a postback scenario with encrypted ViewState, which is required to make the // StateFormatter properly decrypt data. Specifically, this code sets the // internal Page.ContainsEncryptedViewState flag. TextWriter writer = TextWriter.Null; HttpResponse response = new HttpResponse(writer); HttpRequest request = new HttpRequest("DummyFile.aspx", HttpContext.Current.Request.Url.ToString(), "__EVENTTARGET=true&__VIEWSTATEENCRYPTED=true"); HttpContext context = new HttpContext(request, response); Page page = new Page() { EnableViewStateMac = true, ViewStateEncryptionMode = ViewStateEncryptionMode.Always }; page.ProcessRequest(context); return () => new TokenPersister(page).StateFormatter; } public override void Load() { throw new NotImplementedException(); } public override void Save() { throw new NotImplementedException(); } } } } }