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