1 //------------------------------------------------------------------------------
2 // <copyright file="ScriptModule.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web.Handlers {
8     using System;
9     using System.Collections.Generic;
10     using System.Globalization;
11     using System.Linq;
12     using System.Net;
13     using System.Threading;
14     using System.Web;
15     using System.Web.ApplicationServices;
16     using System.Web.UI;
17     using System.Web.Resources;
18     using System.Web.Script.Services;
19     using System.Web.Security;
20 
21     public class ScriptModule : IHttpModule {
22         private static readonly object _contextKey = new Object();
23 
24         private static Type _authenticationServiceType = typeof(System.Web.Security.AuthenticationService); // disambiguation required
25         private static int _isHandlerRegistered;
26 
ShouldSkipAuthorization(HttpContext context)27         private static bool ShouldSkipAuthorization(HttpContext context) {
28             if (context == null || context.Request == null) {
29                 return false;
30             }
31 
32             string path = context.Request.FilePath;
33             if (ScriptResourceHandler.IsScriptResourceRequest(path)) {
34                 return true;
35             }
36 
37             // if auth service is disabled, dont bother checking.
38             // (NOTE: if a custom webservice is used, it will be up to them to enable anon access to it)
39             // if it isn't a rest request dont bother checking.
40             if(!ApplicationServiceHelper.AuthenticationServiceEnabled || !RestHandlerFactory.IsRestRequest(context)) {
41                 return false;
42             }
43 
44             if(context.SkipAuthorization) {
45                 return true;
46             }
47 
48             // it may be a rest request to a webservice. It must end in axd if it is an app service.
49             if((path == null) || !path.EndsWith(".axd", StringComparison.OrdinalIgnoreCase)) {
50                 return false;
51             }
52 
53             // WebServiceData caches the object in cache, so this should be a quick lookup.
54             // If it hasnt been cached yet, this will cause it to be cached, so later in the request
55             // it will be a cache-hit anyway.
56             WebServiceData wsd = WebServiceData.GetWebServiceData(context, path, false, false);
57             if((wsd != null) && (_authenticationServiceType == wsd.TypeData.Type)) {
58                 return true;
59             }
60 
61             return false;
62         }
63 
Dispose()64         protected virtual void Dispose() {
65         }
66 
AuthenticateRequestHandler(object sender, EventArgs e)67         private void AuthenticateRequestHandler(object sender, EventArgs e) {
68             // flags the request with SkipAuthorization if it is a request for
69             // the script Authentication webservice.
70             HttpApplication app = (HttpApplication)sender;
71             if (app != null && ShouldSkipAuthorization(app.Context)) {
72                 app.Context.SetSkipAuthorizationNoDemand(true, false);
73             }
74         }
75 
EndRequestHandler(object sender, EventArgs e)76         private void EndRequestHandler(object sender, EventArgs e){
77             // DevDiv 100198: Send error response from EndRequest so Page and Application error handlers still fire on async posts
78             // DevDiv 118737: Call Response.Clear as well as Response.ClearHeaders to force status code reset in integrated mode and
79             // to ensure there are no errant headers such as a caching policy. Do not call Response.End or app.CompleteRequest as they
80             // are pointless from the EndRequest event.
81             HttpApplication app = (HttpApplication)sender;
82             HttpContext context = app.Context;
83             object o = context.Items[PageRequestManager.AsyncPostBackErrorKey];
84 
85             if ((o != null) && ((bool)o == true)) {
86                 context.ClearError();
87                 context.Response.ClearHeaders();
88                 context.Response.Clear();
89                 context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
90                 context.Response.ContentType = "text/plain";
91 
92                 string errorMessage = (string)context.Items[PageRequestManager.AsyncPostBackErrorMessageKey];
93                 o = context.Items[PageRequestManager.AsyncPostBackErrorHttpCodeKey];
94                 // o should definitely be an int, but user code could overwrite it
95                 int httpCode = (o is int) ? (int)o : 500;
96 
97                 PageRequestManager.EncodeString(context.Response.Output,
98                     PageRequestManager.ErrorToken,
99                     httpCode.ToString(CultureInfo.InvariantCulture),
100                     errorMessage);
101             }
102         }
103 
Init(HttpApplication app)104         protected virtual void Init(HttpApplication app) {
105 
106             //////////////////////////////////////////////////////////////////
107             // Check if this module has been already addded
108             if (app.Context.Items[_contextKey] != null) {
109                 return; // already added to the pipeline
110             }
111             app.Context.Items[_contextKey] = _contextKey;
112 
113             // use the static HttpResponse.Redirecting event to hook all Response.Redirects.
114             // Only hook the event once. Multiple pipelines may cause multiple instances of this
115             // module to be created.
116             if (Interlocked.Exchange(ref _isHandlerRegistered, 1) == 0) {
117                 HttpResponse.Redirecting += new EventHandler(HttpResponse_Redirecting);
118             }
119             app.PostAcquireRequestState += new EventHandler(OnPostAcquireRequestState);
120             app.AuthenticateRequest += new EventHandler(AuthenticateRequestHandler);
121             // DevDiv 100198: Send error response from EndRequest so Page and Application error handlers still fire on async posts
122             app.EndRequest += new EventHandler(EndRequestHandler);
123         }
124 
HttpResponse_Redirecting(object sender, EventArgs e)125         private static void HttpResponse_Redirecting(object sender, EventArgs e) {
126             HttpResponse response = (HttpResponse)sender;
127             HttpContext context = response.Context;
128             // Is in async postback, get status code and check for 302
129             if (PageRequestManager.IsAsyncPostBackRequest(new HttpRequestWrapper(context.Request))) {
130                 // Save the redirect location and other data before we clear it
131                 string redirectLocation = response.RedirectLocation;
132                 List<HttpCookie> cookies = new List<HttpCookie>(response.Cookies.Count);
133                 for (int i = 0; i < response.Cookies.Count; i++) {
134                     cookies.Add(response.Cookies[i]);
135                 }
136 
137                 // Clear the entire response and send a custom response that the client script can process
138                 response.ClearContent();
139                 response.ClearHeaders();
140                 for (int i = 0; i < cookies.Count; i++) {
141                     response.AppendCookie(cookies[i]);
142                 }
143                 response.Cache.SetCacheability(HttpCacheability.NoCache);
144                 response.ContentType = "text/plain";
145 
146                 // DevDiv#961281
147                 // Allow apps to access to the redirect location
148                 context.Items[PageRequestManager.AsyncPostBackRedirectLocationKey] = redirectLocation;
149 
150                 // Preserve redirected state: TFS#882879
151                 response.IsRequestBeingRedirected = true;
152 
153                 PageRequestManager.EncodeString(response.Output, PageRequestManager.UpdatePanelVersionToken, String.Empty, PageRequestManager.UpdatePanelVersionNumber);
154                 // url encode the location in a way that javascript unescape() will be able to reverse
155                 redirectLocation = String.Join(" ", redirectLocation.Split(' ').Select(part => HttpUtility.UrlEncode(part)));
156                 PageRequestManager.EncodeString(response.Output, PageRequestManager.PageRedirectToken, String.Empty, redirectLocation);
157             }
158             else if (RestHandlerFactory.IsRestRequest(context)) {
159                 // We need to special case webservice redirects, as we want them to fail (always are auth failures)
160                 RestHandler.WriteExceptionJsonString(context, new InvalidOperationException(AtlasWeb.WebService_RedirectError), (int)HttpStatusCode.Unauthorized);
161             }
162         }
163 
OnPostAcquireRequestState(object sender, EventArgs eventArgs)164         private void OnPostAcquireRequestState(object sender, EventArgs eventArgs) {
165             HttpApplication app = (HttpApplication)sender;
166             HttpRequest request = app.Context.Request;
167 
168             if (app.Context.Handler is Page && RestHandlerFactory.IsRestMethodCall(request)) {
169                 // Get the data about the web service being invoked
170                 WebServiceData webServiceData = WebServiceData.GetWebServiceData(HttpContext.Current, request.FilePath, false, true);
171 
172                 // Get the method name
173                 string methodName = request.PathInfo.Substring(1);
174 
175                 // Get the data about the specific method being called
176                 WebServiceMethodData methodData = webServiceData.GetMethodData(methodName);
177                 RestHandler.ExecuteWebServiceCall(HttpContext.Current, methodData);
178 
179                 // Skip the rest of the page lifecycle
180                 app.CompleteRequest();
181             }
182         }
183 
184         #region IHttpModule Members
IHttpModule.Dispose()185         void IHttpModule.Dispose() {
186             Dispose();
187         }
188 
IHttpModule.Init(HttpApplication context)189         void IHttpModule.Init(HttpApplication context) {
190             Init(context);
191         }
192         #endregion
193     }
194 }
195