New test.
[mono.git] / mcs / class / System.Web.Mvc2 / System.Web.Mvc / AntiForgeryDataSerializer.cs
1 /* ****************************************************************************\r
2  *\r
3  * Copyright (c) Microsoft Corporation. All rights reserved.\r
4  *\r
5  * This software is subject to the Microsoft Public License (Ms-PL). \r
6  * A copy of the license can be found in the license.htm file included \r
7  * in this distribution.\r
8  *\r
9  * You must not remove this notice, or any other, from this software.\r
10  *\r
11  * ***************************************************************************/\r
12 \r
13 namespace System.Web.Mvc {\r
14     using System;\r
15     using System.Diagnostics.CodeAnalysis;\r
16     using System.IO;\r
17     using System.Web;\r
18     using System.Web.Mvc.Resources;\r
19     using System.Web.UI;\r
20 \r
21     internal class AntiForgeryDataSerializer {\r
22 \r
23         private IStateFormatter _formatter;\r
24 \r
25         protected internal IStateFormatter Formatter {\r
26             get {\r
27                 if (_formatter == null) {\r
28                     _formatter = FormatterGenerator.GetFormatter();\r
29                 }\r
30                 return _formatter;\r
31             }\r
32             set {\r
33                 _formatter = value;\r
34             }\r
35         }\r
36 \r
37         private static HttpAntiForgeryException CreateValidationException(Exception innerException) {\r
38             return new HttpAntiForgeryException(MvcResources.AntiForgeryToken_ValidationFailed, innerException);\r
39         }\r
40 \r
41         public virtual AntiForgeryData Deserialize(string serializedToken) {\r
42             if (String.IsNullOrEmpty(serializedToken)) {\r
43                 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "serializedToken");\r
44             }\r
45 \r
46             // call property getter outside try { } block so that exceptions bubble up for debugging\r
47             IStateFormatter formatter = Formatter;\r
48 \r
49             try {\r
50                 object[] deserializedObj = (object[])formatter.Deserialize(serializedToken);\r
51                 return new AntiForgeryData() {\r
52                     Salt = (string)deserializedObj[0],\r
53                     Value = (string)deserializedObj[1],\r
54                     CreationDate = (DateTime)deserializedObj[2],\r
55                     Username = (string)deserializedObj[3]\r
56                 };\r
57             }\r
58             catch (Exception ex) {\r
59                 throw CreateValidationException(ex);\r
60             }\r
61         }\r
62 \r
63         public virtual string Serialize(AntiForgeryData token) {\r
64             if (token == null) {\r
65                 throw new ArgumentNullException("token");\r
66             }\r
67 \r
68             object[] objToSerialize = new object[] {\r
69                 token.Salt,\r
70                 token.Value,\r
71                 token.CreationDate,\r
72                 token.Username\r
73             };\r
74 \r
75             string serializedValue = Formatter.Serialize(objToSerialize);\r
76             return serializedValue;\r
77         }\r
78 \r
79         // See http://www.yoda.arachsys.com/csharp/singleton.html (fifth version - fully lazy) for the singleton pattern\r
80         // used here. We need to defer the call to TokenPersister.CreateFormatterGenerator() until we're actually\r
81         // servicing a request, else HttpContext.Current might be invalid in TokenPersister.CreateFormatterGenerator().\r
82         private static class FormatterGenerator {\r
83 \r
84             public static readonly Func<IStateFormatter> GetFormatter = TokenPersister.CreateFormatterGenerator();\r
85 \r
86             [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline",\r
87                 Justification = "This type must not be marked 'beforefieldinit'.")]\r
88             static FormatterGenerator() {\r
89             }\r
90 \r
91             // This type is very difficult to unit-test because Page.ProcessRequest() requires mocking\r
92             // much of the hosting environment. For now, we can perform functional tests of this feature.\r
93             private sealed class TokenPersister : PageStatePersister {\r
94                 private TokenPersister(Page page)\r
95                     : base(page) {\r
96                 }\r
97 \r
98                 public static Func<IStateFormatter> CreateFormatterGenerator() {\r
99                     // This code instantiates a page and tricks it into thinking that it's servicing\r
100                     // a postback scenario with encrypted ViewState, which is required to make the\r
101                     // StateFormatter properly decrypt data. Specifically, this code sets the\r
102                     // internal Page.ContainsEncryptedViewState flag.\r
103                     TextWriter writer = TextWriter.Null;\r
104                     HttpResponse response = new HttpResponse(writer);\r
105                     HttpRequest request = new HttpRequest("DummyFile.aspx", HttpContext.Current.Request.Url.ToString(), "__EVENTTARGET=true&__VIEWSTATEENCRYPTED=true");\r
106                     HttpContext context = new HttpContext(request, response);\r
107 \r
108                     Page page = new Page() {\r
109                         EnableViewStateMac = true,\r
110                         ViewStateEncryptionMode = ViewStateEncryptionMode.Always\r
111                     };\r
112                     page.ProcessRequest(context);\r
113 \r
114                     return () => new TokenPersister(page).StateFormatter;\r
115                 }\r
116 \r
117                 public override void Load() {\r
118                     throw new NotImplementedException();\r
119                 }\r
120 \r
121                 public override void Save() {\r
122                     throw new NotImplementedException();\r
123                 }\r
124             }\r
125         }\r
126 \r
127     }\r
128 }\r