PageRenderTime 57ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Umbraco.Web/umbraco.presentation/requestModule.cs

http://umbraco.codeplex.com
C# | 475 lines | 312 code | 76 blank | 87 comment | 40 complexity | 4454cf4a60c5ecc1566ca3edfc23fd0f MD5 | raw file
Possible License(s): CC-BY-SA-3.0, MIT, BSD-3-Clause, LGPL-2.1
  1. using System;
  2. using System.ComponentModel;
  3. using System.Diagnostics;
  4. using System.Threading;
  5. using System.Web;
  6. using Umbraco.Core.Configuration;
  7. using Umbraco.Core.Logging;
  8. using umbraco.BusinessLogic;
  9. using System.Collections.Generic;
  10. using umbraco.BusinessLogic.Utils;
  11. using umbraco.businesslogic;
  12. using umbraco.cms.businesslogic.cache;
  13. using System.Web.Caching;
  14. using umbraco.IO;
  15. using umbraco.interfaces;
  16. namespace umbraco.presentation
  17. {
  18. /// <summary>
  19. /// Summary description for requestModule.
  20. /// </summary>
  21. public class requestModule : IHttpModule
  22. {
  23. protected static Timer publishingTimer;
  24. protected static Timer pingTimer;
  25. private HttpApplication mApp;
  26. private IContainer components = null;
  27. private readonly IList<IApplicationStartupHandler> startupHandlers = new List<IApplicationStartupHandler>();
  28. /// <summary>True if the module is currently handling an error.</summary>
  29. private static object handlingError = false;
  30. /// <summary>List of errors that occurred since the last error was being handled.</summary>
  31. private List<Exception> unhandledErrors = new List<Exception>();
  32. public const string ORIGINAL_URL_CXT_KEY = "umbOriginalUrl";
  33. private static string LOG_SCRUBBER_TASK_NAME = "ScrubLogs";
  34. private static CacheItemRemovedCallback OnCacheRemove = null;
  35. protected void ApplicationStart(HttpApplication HttpApp)
  36. {
  37. //starting the application. Application doesn't support HttpContext in integrated mode (standard mode on IIS7)
  38. //So everything is moved to beginRequest.
  39. }
  40. protected void Application_PostResolveRequestCache(object sender, EventArgs e)
  41. {
  42. // process rewrite here so forms authentication can Authorize based on url before the original url is discarded
  43. this.UmbracoRewrite(sender, e);
  44. }
  45. protected void Application_AuthorizeRequest(object sender, EventArgs e)
  46. {
  47. // nothing needs to be done here
  48. }
  49. protected void Application_PreRequestHandlerExecute(object sender, EventArgs e)
  50. {
  51. HttpContext context = mApp.Context;
  52. //httpContext.RewritePath( (string) httpContext.Items[ORIGINAL_URL_CXT_KEY] + "?" + httpContext.Request.QueryString );
  53. }
  54. /// <summary>
  55. /// Handles the BeginRequest event of the Application control.
  56. /// </summary>
  57. /// <param name="sender">The source of the event.</param>
  58. /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  59. protected void Application_BeginRequest(Object sender, EventArgs e)
  60. {
  61. HttpApplication app = (HttpApplication)sender;
  62. //first time init, starts timers, and sets httpContext
  63. InitializeApplication(app);
  64. // grab the original url before anything modifies it
  65. HttpContext httpContext = mApp.Context;
  66. httpContext.Items[ORIGINAL_URL_CXT_KEY] = rewrite404Url(httpContext.Request.Url.AbsolutePath, httpContext.Request.Url.Query, false);
  67. // create the Umbraco context
  68. UmbracoContext.Current = new UmbracoContext(httpContext);
  69. // rewrite will happen after authorization
  70. }
  71. protected string rewrite404Url(string url, string querystring, bool returnQuery)
  72. {
  73. // adding endswith and contains checks to ensure support for custom 404 messages (only 404 parse directory and aspx requests)
  74. if (querystring.StartsWith("?404") && (!querystring.Contains(".") || querystring.EndsWith(".aspx") || querystring.Contains(".aspx&")))
  75. {
  76. Uri u = new Uri(querystring.Substring(5, querystring.Length - 5));
  77. string path = u.AbsolutePath;
  78. if (returnQuery)
  79. {
  80. return u.Query;
  81. }
  82. else
  83. {
  84. return path;
  85. }
  86. }
  87. if (returnQuery)
  88. {
  89. return querystring;
  90. }
  91. else
  92. {
  93. return url;
  94. }
  95. }
  96. /// <summary>
  97. /// Performs path rewriting.
  98. /// </summary>
  99. /// <param name="sender">The source of the event.</param>
  100. /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  101. protected void UmbracoRewrite(Object sender, EventArgs e)
  102. {
  103. HttpContext context = mApp.Context;
  104. string path = rewrite404Url(context.Request.Path.ToLower(), context.Request.Url.Query, false);
  105. string query = String.Empty;
  106. if (GlobalSettings.UseDirectoryUrls)
  107. {
  108. // zb-00017 #29930 : do not change url casing when rewriting
  109. string requestPath = context.Request.Path; // not lowercased
  110. int asmxPos = path.IndexOf(".asmx/");
  111. if (asmxPos >= 0)
  112. context.RewritePath(path.Substring(0, asmxPos + 5),
  113. requestPath.Substring(asmxPos + 5),
  114. context.Request.QueryString.ToString());
  115. }
  116. if (path.IndexOf(".aspx") > -1 || path.IndexOf('.') == -1)
  117. {
  118. // validate configuration
  119. if (mApp.Application["umbracoNeedConfiguration"] == null)
  120. mApp.Application["umbracoNeedConfiguration"] = !GlobalSettings.Configured;
  121. if (!GlobalSettings.IsReservedPathOrUrl(path))
  122. {
  123. // redirect if Umbraco needs configuration
  124. Nullable<bool> needsConfiguration = (Nullable<bool>)mApp.Application["umbracoNeedConfiguration"];
  125. if (needsConfiguration.HasValue && needsConfiguration.Value)
  126. {
  127. string url = SystemDirectories.Install;
  128. string meh = IOHelper.ResolveUrl(url);
  129. string installUrl = string.Format("{0}/default.aspx?redir=true&url={1}", IOHelper.ResolveUrl( SystemDirectories.Install ), context.Request.Path.ToLower());
  130. context.Response.Redirect(installUrl, true);
  131. }
  132. // show splash?
  133. else if (UmbracoSettings.EnableSplashWhileLoading && content.Instance.isInitializing)
  134. context.RewritePath(string.Format("{0}/splashes/booting.aspx", SystemDirectories.Config));
  135. // rewrite page path
  136. else
  137. {
  138. string receivedQuery = rewrite404Url(context.Request.Path.ToLower(), context.Request.Url.Query, true);
  139. if (receivedQuery.Length > 0)
  140. {
  141. // Clean umbPage from querystring, caused by .NET 2.0 default Auth Controls
  142. if (receivedQuery.IndexOf("umbPage") > 0)
  143. {
  144. int ampPos = receivedQuery.IndexOf('&');
  145. // query contains no ampersand?
  146. if (ampPos < 0)
  147. {
  148. // no ampersand means no original query string
  149. query = String.Empty;
  150. // ampersand would occur past then end the of received query
  151. ampPos = receivedQuery.Length;
  152. }
  153. else
  154. {
  155. // original query string past ampersand
  156. query = receivedQuery.Substring(ampPos + 1,
  157. receivedQuery.Length - ampPos - 1);
  158. }
  159. // get umbPage out of query string (9 = "&umbPage".Length() + 1)
  160. path = receivedQuery.Substring(9, ampPos - 9); //this will fail if there are < 9 characters before the &umbPage query string
  161. }
  162. else
  163. {
  164. // strip off question mark
  165. query = receivedQuery.Substring(1);
  166. }
  167. }
  168. // Add questionmark to query string if it's not empty
  169. if (!String.IsNullOrEmpty(query))
  170. query = "?" + query;
  171. // save original URL
  172. context.Items["UmbPage"] = path;
  173. context.Items["VirtualUrl"] = String.Format("{0}{1}", path, query);
  174. // rewrite to the new URL
  175. context.RewritePath(string.Format("{0}/default.aspx{2}",
  176. SystemDirectories.Root, path, query));
  177. }
  178. }
  179. }
  180. }
  181. /// <summary>
  182. /// Handles the Error event of the Application control.
  183. /// </summary>
  184. /// <param name="sender">The source of the event.</param>
  185. /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
  186. protected void Application_Error(Object sender, EventArgs e)
  187. {
  188. // return immediately if an error is already been handled, to avoid infinite recursion
  189. if ((bool)handlingError)
  190. {
  191. lock (unhandledErrors)
  192. {
  193. unhandledErrors.Add(mApp.Server.GetLastError());
  194. }
  195. return;
  196. }
  197. // make sure only one thread at a time can handle an error
  198. lock (handlingError)
  199. {
  200. Debug.Assert(!(bool)handlingError, "Two errors are being handled at the same time.");
  201. handlingError = true;
  202. // make sure handlingError always gets set to false
  203. try
  204. {
  205. if (GlobalSettings.Configured)
  206. {
  207. // log the error
  208. // zb-00017 #29930 : could have been cleared, though: take care, .GetLastError() may return null
  209. Exception ex = mApp.Server.GetLastError();
  210. if (ex != null)
  211. ex = ex.InnerException;
  212. string error;
  213. if (mApp.Context.Request != null)
  214. error = string.Format("At {0} (Referred by: {1}): {2}",
  215. mApp.Context.Request.RawUrl,
  216. mApp.Context.Request.UrlReferrer,
  217. ex);
  218. else
  219. error = "No Context available -> "
  220. + ex;
  221. // Hide error if getting the user throws an error (e.g. corrupt / blank db)
  222. User staticUser = null;
  223. try
  224. {
  225. User.GetCurrent();
  226. }
  227. catch
  228. {
  229. }
  230. LogHelper.Debug<requestModule>(error);
  231. Trace.TraceError(error);
  232. lock (unhandledErrors)
  233. {
  234. if (unhandledErrors.Count > 0)
  235. Trace.TraceError("New errors occurred while an error was being handled. The error handler Application_Error possibly raised another error, but was protected against an infinite loop.");
  236. foreach (Exception unhandledError in unhandledErrors)
  237. Trace.TraceError(unhandledError.StackTrace);
  238. }
  239. }
  240. }
  241. finally
  242. {
  243. // clear unhandled errors
  244. lock (unhandledErrors)
  245. {
  246. unhandledErrors.Clear();
  247. }
  248. // flag we're done with the error handling
  249. handlingError = false;
  250. }
  251. }
  252. }
  253. #region IHttpModule Members
  254. ///<summary>
  255. ///Initializes a module and prepares it to handle requests.
  256. ///</summary>
  257. ///
  258. ///<param name="httpContext">An <see cref="T:System.Web.HttpApplication"></see> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application </param>
  259. public void Init(HttpApplication context)
  260. {
  261. InitializeComponent();
  262. ApplicationStart(context);
  263. context.BeginRequest += new EventHandler(Application_BeginRequest);
  264. context.AuthorizeRequest += new EventHandler(Application_AuthorizeRequest);
  265. // Alex Norcliffe - 2010 02 - Changed this behaviour as it disables OutputCaching due to Rewrite happening too early in the chain
  266. // context.PostAuthorizeRequest += new EventHandler(Application_PostAuthorizeRequest);
  267. context.PostResolveRequestCache += new EventHandler(Application_PostResolveRequestCache);
  268. context.PreRequestHandlerExecute += new EventHandler(Application_PreRequestHandlerExecute);
  269. // Alex Norcliffe - 2010 06 - Added a check at the end of the page lifecycle to see if we should persist Xml cache to disk
  270. // (a replacement for all those parallel Async methods launching ThreadPool threads)
  271. context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
  272. context.Error += new EventHandler(Application_Error);
  273. mApp = context;
  274. }
  275. void context_PostRequestHandlerExecute(object sender, EventArgs e)
  276. {
  277. if (content.Instance.IsXmlQueuedForPersistenceToFile)
  278. {
  279. content.Instance.PersistXmlToFile();
  280. }
  281. }
  282. private void InitializeComponent()
  283. {
  284. components = new Container();
  285. }
  286. ///<summary>
  287. ///Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"></see>.
  288. ///</summary>
  289. ///
  290. public void Dispose()
  291. {
  292. }
  293. //this makes sure that times and other stuff is started on the first request, instead of depending on
  294. // application_start, which was inteded for being a httpContext-agnostic state.
  295. private static bool s_InitializedAlready = false;
  296. private static Object s_lock = new Object();
  297. // Initialize only on the first request
  298. public void InitializeApplication(HttpApplication HttpApp)
  299. {
  300. if (s_InitializedAlready)
  301. return;
  302. lock (s_lock)
  303. {
  304. if (s_InitializedAlready)
  305. return;
  306. // Perform first-request initialization here ...
  307. try
  308. {
  309. LogHelper.Info<requestModule>(string.Format("Application started at {0}", DateTime.Now));
  310. if (UmbracoSettings.AutoCleanLogs)
  311. {
  312. AddTask(LOG_SCRUBBER_TASK_NAME, GetLogScrubbingInterval());
  313. }
  314. }
  315. catch
  316. {
  317. }
  318. // Trigger startup handlers
  319. ApplicationStartupHandler.RegisterHandlers();
  320. // Check for configured key, checking for currentversion to ensure that a request with
  321. // no httpcontext don't set the whole app in configure mode
  322. if (UmbracoVersion.Current != null && !GlobalSettings.Configured)
  323. {
  324. HttpApp.Application["umbracoNeedConfiguration"] = true;
  325. }
  326. /* This section is needed on start-up because timer objects
  327. * might initialize before these are initialized without a traditional
  328. * request, and therefore lacks information on application paths */
  329. /* Initialize SECTION END */
  330. // add current default url
  331. HttpApp.Application["umbracoUrl"] = string.Format("{0}:{1}{2}", HttpApp.Context.Request.ServerVariables["SERVER_NAME"], HttpApp.Context.Request.ServerVariables["SERVER_PORT"], IOHelper.ResolveUrl( SystemDirectories.Umbraco ));
  332. // Start ping / keepalive timer
  333. pingTimer = new Timer(new TimerCallback(keepAliveService.PingUmbraco), HttpApp.Context, 60000, 300000);
  334. // Start publishingservice
  335. publishingTimer = new Timer(new TimerCallback(publishingService.CheckPublishing), HttpApp.Context, 30000, 60000);
  336. //Find Applications and event handlers and hook-up the events
  337. //BusinessLogic.Application.RegisterIApplications();
  338. //define the base settings for the dependency loader to use the global path settings
  339. //if (!CompositeDependencyHandler.HandlerFileName.StartsWith(GlobalSettings_Path))
  340. // CompositeDependencyHandler.HandlerFileName = GlobalSettings_Path + "/" + CompositeDependencyHandler.HandlerFileName;
  341. // Backwards compatibility - set the path and URL type for ClientDependency 1.5.1 [LK]
  342. ClientDependency.Core.CompositeFiles.Providers.XmlFileMapper.FileMapVirtualFolder = "~/App_Data/TEMP/ClientDependency";
  343. ClientDependency.Core.CompositeFiles.Providers.BaseCompositeFileProcessingProvider.UrlTypeDefault = ClientDependency.Core.CompositeFiles.Providers.CompositeUrlType.Base64QueryStrings;
  344. // init done...
  345. s_InitializedAlready = true;
  346. }
  347. }
  348. #endregion
  349. #region Inteval tasks
  350. private static int GetLogScrubbingInterval()
  351. {
  352. int interval = 24 * 60 * 60; //24 hours
  353. try
  354. {
  355. if (UmbracoSettings.CleaningMiliseconds > -1)
  356. interval = UmbracoSettings.CleaningMiliseconds;
  357. }
  358. catch (Exception)
  359. {
  360. LogHelper.Info<requestModule>("Unable to locate a log scrubbing interval. Defaulting to 24 hours");
  361. }
  362. return interval;
  363. }
  364. private static int GetLogScrubbingMaximumAge()
  365. {
  366. int maximumAge = 24 * 60 * 60;
  367. try
  368. {
  369. if (UmbracoSettings.MaxLogAge > -1)
  370. maximumAge = UmbracoSettings.MaxLogAge;
  371. }
  372. catch (Exception)
  373. {
  374. LogHelper.Info<requestModule>("Unable to locate a log scrubbing maximum age. Defaulting to 24 hours");
  375. }
  376. return maximumAge;
  377. }
  378. private void AddTask(string name, int seconds)
  379. {
  380. OnCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
  381. HttpRuntime.Cache.Insert(name, seconds, null,
  382. DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
  383. CacheItemPriority.NotRemovable, OnCacheRemove);
  384. }
  385. public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
  386. {
  387. if (k.Equals(LOG_SCRUBBER_TASK_NAME))
  388. {
  389. ScrubLogs();
  390. }
  391. AddTask(k, Convert.ToInt32(v));
  392. }
  393. private static void ScrubLogs()
  394. {
  395. Log.CleanLogs(GetLogScrubbingMaximumAge());
  396. }
  397. #endregion
  398. }
  399. }