Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Web / ThreadContext.cs
1 namespace System.Web {
2     using System;
3     using System.ComponentModel;
4     using System.Globalization;
5     using System.Runtime.Remoting.Messaging;
6     using System.Security.Principal;
7     using System.Text;
8     using System.Threading;
9     using System.Web.Configuration;
10     using System.Web.UI;
11     using System.Web.Util;
12
13     // Contains information about any modifications ASP.NET has made to the current
14     // thread and how to undo them. See also the comments on
15     // HttpApplication.OnThreadEnterPrivate.
16
17     internal sealed class ThreadContext : ISyncContextLock {
18
19         // This is a marker holding the current ThreadContext for the current
20         // thread. Uses TLS so that it's not wiped away by ExecutionContext.Run.
21         [ThreadStatic]
22         private static ThreadContext _currentThreadContext;
23
24         private ImpersonationContext _newImpersonationContext;
25         private HttpContext _originalHttpContext;
26         private SynchronizationContext _originalSynchronizationContext;
27         private ThreadContext _originalThreadContextCurrent;
28         private CultureInfo _originalThreadCurrentCulture;
29         private CultureInfo _originalThreadCurrentUICulture;
30         private IPrincipal _originalThreadPrincipal;
31         private bool _setCurrentThreadOnHttpContext;
32
33         internal ThreadContext(HttpContext httpContext) {
34             HttpContext = httpContext;
35         }
36
37         internal static ThreadContext Current {
38             get { return _currentThreadContext; }
39             private set { _currentThreadContext = value; }
40         }
41
42         internal bool HasBeenDisassociatedFromThread {
43             get;
44             private set;
45         }
46
47         internal HttpContext HttpContext {
48             get;
49             private set;
50         }
51
52         // Associates this ThreadContext with the current thread. This restores certain
53         // ambient values associated with the current HttpContext, such as the current
54         // user and cultures. It also sets HttpContext.Current.
55         internal void AssociateWithCurrentThread(bool setImpersonationContext) {
56             Debug.Assert(HttpContext != null); // only to be used when context is available
57             Debug.Assert(Current != this, "This ThreadContext is already associated with this thread.");
58             Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
59
60             Debug.Trace("OnThread", GetTraceMessage("Enter1"));
61
62             /*
63              * !! IMPORTANT !!
64              * Keep this logic in [....] with DisassociateFromCurrentThread and EnterExecutionContext.
65              */
66
67             // attach http context to the call context
68             _originalHttpContext = DisposableHttpContextWrapper.SwitchContext(HttpContext);
69
70             // set impersonation on the current thread
71             if (setImpersonationContext) {
72                 SetImpersonationContext();
73             }
74
75             // set synchronization context for the current thread to support the async pattern
76             _originalSynchronizationContext = AsyncOperationManager.SynchronizationContext;
77             AspNetSynchronizationContextBase aspNetSynchronizationContext = HttpContext.SyncContext;
78             AsyncOperationManager.SynchronizationContext = aspNetSynchronizationContext;
79
80             // set ETW trace ID
81             Guid g = HttpContext.WorkerRequest.RequestTraceIdentifier;
82             if (g != Guid.Empty) {
83                 CallContext.LogicalSetData("E2ETrace.ActivityID", g);
84             }
85
86             // set SqlDependecyCookie
87             HttpContext.ResetSqlDependencyCookie();
88
89             // set principal on the current thread
90             _originalThreadPrincipal = Thread.CurrentPrincipal;
91             HttpApplication.SetCurrentPrincipalWithAssert(HttpContext.User);
92
93             // only set culture on the current thread if it is not initialized
94             SetRequestLevelCulture();
95
96             // DevDivBugs 75042
97             // set current thread in context if there is not there
98             // the timeout manager  uses this to abort the correct thread
99             if (HttpContext.CurrentThread == null) {
100                 _setCurrentThreadOnHttpContext = true;
101                 HttpContext.CurrentThread = Thread.CurrentThread;
102             }
103
104             // Store a reference to the original ThreadContext.Current. It is possible that a parent
105             // ThreadContext might already be associated with the current thread, e.g. if the current
106             // stack contains a call to MgdIndicateCompletion (via
107             // PipelineRuntime.ProcessRequestNotificationHelper). If this is the case, the child
108             // ThreadContext will temporarily take over.
109             _originalThreadContextCurrent = Current;
110             Current = this;
111
112             Debug.Trace("OnThread", GetTraceMessage("Enter2"));
113         }
114
115         private ClientImpersonationContext CreateNewClientImpersonationContext() {
116             // impersonation is set in the ClientImpersonationContext ctor
117             return new ClientImpersonationContext(HttpContext);
118         }
119
120         // Disassociates this ThreadContext from the current thread. Any ambient values (e.g., culture)
121         // associated with the current request are stored in the HttpContext object so that they
122         // can be restored the next time a ThreadContext associated with this HttpContext is active.
123         // Impersonation and other similar modifications to the current thread are undone.
124         internal void DisassociateFromCurrentThread() {
125             Debug.Trace("OnThread", GetTraceMessage("Leave1"));
126             Debug.Assert(Current == this, "This ThreadContext isn't associated with current thread.");
127             Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
128
129             /*
130              * !! IMPORTANT !!
131              * Keep this logic in [....] with AssociateWithCurrentThread and EnterExecutionContext.
132              */
133
134             Current = _originalThreadContextCurrent;
135             HasBeenDisassociatedFromThread = true;
136
137             // remove thread if set
138             if (_setCurrentThreadOnHttpContext) {
139                 HttpContext.CurrentThread = null;
140             }
141
142             // this thread should not be locking app state
143             HttpApplicationFactory.ApplicationState.EnsureUnLock();
144
145             // stop impersonation
146             UndoImpersonationContext();
147
148             // restore culture
149             RestoreRequestLevelCulture();
150
151             // restrore synchronization context
152             AsyncOperationManager.SynchronizationContext = _originalSynchronizationContext;
153
154             // restore thread principal
155             HttpApplication.SetCurrentPrincipalWithAssert(_originalThreadPrincipal);
156
157             // Remove SqlCacheDependency cookie from call context if necessary
158             HttpContext.RemoveSqlDependencyCookie();
159
160             // remove http context from the call context
161             DisposableHttpContextWrapper.SwitchContext(_originalHttpContext);
162             _originalHttpContext = null;
163
164             Debug.Trace("OnThread", GetTraceMessage("Leave2"));
165         }
166
167         // Called by AspNetHostExecutionContextManager to signal that ExecutionContext.Run
168         // is being called on a thread currently associated with our ThreadContext. Since
169         // ExecutionContext.Run destroys some of our ambient state (HttpContext.Current, etc.),
170         // we need to restore it. This method returns an Action which should be called when
171         // the call to ExecutionContext.Run is concluding.
172         internal Action EnterExecutionContext() {
173             Debug.Trace("OnThread", GetTraceMessage("EnterExecutionContext1"));
174             Debug.Assert(Current == this, "This ThreadContext isn't associated with current thread.");
175             Debug.Assert(!HasBeenDisassociatedFromThread, "This ThreadContext has already been disassociated from a thread.");
176
177             /*
178              * !! IMPORTANT !!
179              * Keep this logic in [....] with AssociateWithCurrentThread and DisassociateFromCurrentThread.
180              */
181
182             // ExecutionContext.Run replaces the current impersonation token, so we need to impersonate
183             // if AssociateWithCurrentThread also did so.
184
185             ClientImpersonationContext executionContextClientImpersonationContext = null;
186             if (_newImpersonationContext != null) {
187                 executionContextClientImpersonationContext = CreateNewClientImpersonationContext();
188             }
189
190             // ExecutionContext.Run resets the LogicalCallContext / IllogicalCallContext (which contains HttpContext.Current),
191             // so we need to restore both of them.
192
193             DisposableHttpContextWrapper.SwitchContext(HttpContext);
194
195             Guid g = HttpContext.WorkerRequest.RequestTraceIdentifier;
196             if (g != Guid.Empty) {
197                 CallContext.LogicalSetData("E2ETrace.ActivityID", g);
198             }
199
200             HttpContext.ResetSqlDependencyCookie();
201
202             // ExecutionContext.Run resets the thread's CurrentPrincipal, so we need to restore it.
203
204             HttpApplication.SetCurrentPrincipalWithAssert(HttpContext.User);
205
206             // Other items like [ThreadStatic] fields, culture, etc. are untouched by ExecutionContext.Run,
207             // so we don't need to worry about them.
208
209             Debug.Trace("OnThread", GetTraceMessage("EnterExecutionContext2"));
210
211             // This delegate is the cleanup routine.
212             return () => {
213                 Debug.Trace("OnThread", GetTraceMessage("LeaveExecutionContext1"));
214
215                 // Undo any impersonation that we performed.
216                 if (executionContextClientImpersonationContext != null) {
217                     executionContextClientImpersonationContext.Undo();
218                 }
219
220                 // Other things, e.g. changes to the logical/illogical call contexts, changes
221                 // to CurrentPrincipal, etc., will automatically be reverted anyway when
222                 // the call to ExecutionContext.Run concludes, so we don't need to clean up
223                 // here.
224
225                 Debug.Trace("OnThread", GetTraceMessage("LeaveExecutionContext2"));
226             };
227         }
228
229         private static string GetTraceMessage(string tag) {
230 #if DBG
231             StringBuilder sb = new StringBuilder(256);
232             sb.Append(tag);
233             sb.AppendFormat(" Thread={0}", SafeNativeMethods.GetCurrentThreadId().ToString(CultureInfo.InvariantCulture));
234             sb.AppendFormat(" Context={0}", (HttpContext.Current != null) ? HttpContext.Current.GetHashCode().ToString(CultureInfo.InvariantCulture) : "NULL_CTX");
235             sb.AppendFormat(" Principal={0}", (Thread.CurrentPrincipal != null) ? Thread.CurrentPrincipal.GetHashCode().ToString(CultureInfo.InvariantCulture) : "NULL_PRIN");
236             sb.AppendFormat(" Culture={0}", Thread.CurrentThread.CurrentCulture.LCID.ToString(CultureInfo.InvariantCulture));
237             sb.AppendFormat(" UICulture={0}", Thread.CurrentThread.CurrentUICulture.LCID.ToString(CultureInfo.InvariantCulture));
238             sb.AppendFormat(" ActivityID={0}", CallContext.LogicalGetData("E2ETrace.ActivityID"));
239             return sb.ToString();
240 #else
241             // This method should never be called in release mode.
242             throw new NotImplementedException();
243 #endif
244         }
245
246
247         // Restores the thread's CurrentCulture and CurrentUICulture back to what
248         // they were before this ThreadContext was associated with the thread. If
249         // any culture has changed from its original value, we squirrel the new
250         // culture away in HttpContext so that we can restore it the next time any
251         // ThreadContext associated with this HttpContext is active.
252         private void RestoreRequestLevelCulture() {
253             CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
254             CultureInfo currentUICulture = Thread.CurrentThread.CurrentUICulture;
255
256             if (_originalThreadCurrentCulture != null) {
257                 // Avoid the cost of the Demand when setting the culture by comparing the cultures first
258                 if (currentCulture != _originalThreadCurrentCulture) {
259                     HttpRuntime.SetCurrentThreadCultureWithAssert(_originalThreadCurrentCulture);
260                     if (HttpContext != null) {
261                         // remember changed culture for the rest of the request
262                         HttpContext.DynamicCulture = currentCulture;
263                     }
264                 }
265
266                 _originalThreadCurrentCulture = null;
267             }
268
269             if (_originalThreadCurrentUICulture != null) {
270                 // Avoid the cost of the Demand when setting the culture by comparing the cultures first
271                 if (currentUICulture != _originalThreadCurrentUICulture) {
272                     Thread.CurrentThread.CurrentUICulture = _originalThreadCurrentUICulture;
273                     if (HttpContext != null) {
274                         // remember changed culture for the rest of the request
275                         HttpContext.DynamicUICulture = currentUICulture;
276                     }
277                 }
278
279                 _originalThreadCurrentUICulture = null;
280             }
281         }
282
283         // Sets impersonation on the current thread.
284         internal void SetImpersonationContext() {
285             if (_newImpersonationContext == null) {
286                 _newImpersonationContext = CreateNewClientImpersonationContext();
287             }
288         }
289
290         // Sets the thread's CurrentCulture and CurrentUICulture to those associated
291         // with the current HttpContext. We do this since the culture of a request can
292         // change over its lifetime and isn't necessarily the default for the AppDomain,
293         // e.g. if the culture was read from the request headers.
294         private void SetRequestLevelCulture() {
295             CultureInfo culture = null;
296             CultureInfo uiculture = null;
297
298             GlobalizationSection globConfig = RuntimeConfig.GetConfig(HttpContext).Globalization;
299             if (!String.IsNullOrEmpty(globConfig.Culture))
300                 culture = HttpContext.CultureFromConfig(globConfig.Culture, true);
301
302             if (!String.IsNullOrEmpty(globConfig.UICulture))
303                 uiculture = HttpContext.CultureFromConfig(globConfig.UICulture, false);
304
305             if (HttpContext.DynamicCulture != null)
306                 culture = HttpContext.DynamicCulture;
307
308             if (HttpContext.DynamicUICulture != null)
309                 uiculture = HttpContext.DynamicUICulture;
310
311             // Page also could have its own culture settings
312             Page page = HttpContext.CurrentHandler as Page;
313
314             if (page != null) {
315                 if (page.DynamicCulture != null)
316                     culture = page.DynamicCulture;
317
318                 if (page.DynamicUICulture != null)
319                     uiculture = page.DynamicUICulture;
320             }
321
322             _originalThreadCurrentCulture = Thread.CurrentThread.CurrentCulture;
323             _originalThreadCurrentUICulture = Thread.CurrentThread.CurrentUICulture;
324
325             if (culture != null && culture != Thread.CurrentThread.CurrentCulture) {
326                 HttpRuntime.SetCurrentThreadCultureWithAssert(culture);
327             }
328
329             if (uiculture != null && uiculture != Thread.CurrentThread.CurrentUICulture) {
330                 Thread.CurrentThread.CurrentUICulture = uiculture;
331             }
332         }
333
334         // Use of IndicateCompletion requires that we synchronize the cultures
335         // with what may have been set by user code during execution of the
336         // notification.
337         internal void Synchronize() {
338             HttpContext.DynamicCulture = Thread.CurrentThread.CurrentCulture;
339             HttpContext.DynamicUICulture = Thread.CurrentThread.CurrentUICulture;
340         }
341
342         // Undoes any impersonation that we did when associating this ThreadContext
343         // with the current thread.
344         internal void UndoImpersonationContext() {
345             // remove impersonation on the current thread
346             if (_newImpersonationContext != null) {
347                 _newImpersonationContext.Undo();
348                 _newImpersonationContext = null;
349             }
350         }
351
352         // Called by AspNetSynchronizationContext to signal that it is finished
353         // processing on the current thread.
354         void ISyncContextLock.Leave() {
355             DisassociateFromCurrentThread();
356         }
357
358     }
359 }