<commit_before>using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using N2.Engine;
using log4net;

namespace N2.Web.Mvc
{
	/// <summary>
	/// An ASP.NET MVC route that gets route data for content item paths.
	/// </summary>
	public class ContentRoute : RouteBase
	{
		private readonly ILog logger = LogManager.GetLogger(typeof (ContentRoute));

		/// <summary>Used to reference the currently executing content item in the route value dictionary.</summary>
		public static string ContentItemKey
		{
			get { return PathData.ItemQueryKey; }
		}
		/// <summary>Used to reference the content page in the route value dictionary.</summary>
		public static string ContentPageKey
		{
			get { return PathData.PageQueryKey; }
		}
		/// <summary>Used to reference the content part in the route value dictionary.</summary>
		public static string ContentPartKey
		{
			get { return PathData.PartQueryKey; }
		}

		/// <summary>Used to reference the N2 content engine.</summary>
		public const string ContentEngineKey = "engine";
		/// <summary>Convenience reference to the MVC controller.</summary>
		public const string ControllerKey = "controller";
		/// <summary>Convenience reference to the MVC area.</summary>
		public const string AreaKey = "area";
		/// <summary>Convenience reference to the MVC action.</summary>
		public const string ActionKey = "action";
		
		readonly IEngine engine;
		readonly IRouteHandler routeHandler;
		readonly IControllerMapper controllerMapper;
		readonly Route innerRoute;
		string managementPath;

		public ContentRoute(IEngine engine)
			: this(engine, null, null, null)
		{
		}

		public ContentRoute(IEngine engine, IRouteHandler routeHandler, IControllerMapper controllerMapper, Route innerRoute)
		{
			managementPath = Url.ToRelative(Url.ResolveTokens(Url.ManagementUrlToken + "/"));
			this.engine = engine;
			this.routeHandler = routeHandler ?? new MvcRouteHandler();
			this.controllerMapper = controllerMapper ?? engine.Resolve<IControllerMapper>();
			this.innerRoute = innerRoute ?? new Route("{controller}/{action}", 
				new RouteValueDictionary(new { action = "Index" }), 
				new RouteValueDictionary(), 
				new RouteValueDictionary(new { this.engine }), 
				this.routeHandler);
		}

		/// <summary>Gets route data for for items this route handles.</summary>
		/// <param name="item">The item whose route to get.</param>
		/// <param name="routeValues">The route values to apply to the route data.</param>
		/// <returns>A route data object or null.</returns>
		public virtual RouteValueDictionary GetRouteValues(ContentItem item, RouteValueDictionary routeValues)
		{
			string actionName = "Index";
			if (routeValues.ContainsKey(ActionKey))
				actionName = (string)routeValues[ActionKey];

			string controllerName = controllerMapper.GetControllerName(item.GetContentType());
			if (controllerName == null || !controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var values = new RouteValueDictionary(routeValues);

			foreach (var kvp in innerRoute.Defaults)
				if(!values.ContainsKey(kvp.Key))
					values[kvp.Key] = kvp.Value;

			values[ControllerKey] = controllerName;
			values[ActionKey] = actionName;
			values[ContentItemKey] = item.ID;
			values[AreaKey] = innerRoute.DataTokens["area"];

			return values;
		}

		public override RouteData GetRouteData(HttpContextBase httpContext)
		{
			string path = httpContext.Request.AppRelativeCurrentExecutionFilePath;
			if (path.StartsWith(managementPath, StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());
			if (path.EndsWith(".axd", StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());
			if (path.EndsWith(".ashx", StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());

			RouteData routeData = null;

			if(httpContext.Request.QueryString[ContentPartKey] != null)
				// part in query string, this is an indicator of a request to a part, takes precendence over friendly urls
				routeData = CheckForContentController(httpContext);

			if(routeData == null)
				// this might be a friendly url
				routeData = GetRouteDataForPath(httpContext.Request);

			if(routeData == null)
				// fallback to route to controller/action
				routeData = CheckForContentController(httpContext);

			if (logger.IsDebugEnabled)
				logger.Debug("GetRouteData for '" + path + "' got values: " + (routeData != null ? routeData.Values.ToQueryString() : "(null)"));

			return routeData;
		}

		private RouteData GetRouteDataForPath(HttpRequestBase request)
		{
			//On a multi-lingual site with separate domains per language,
			//the full url (with host) should be passed to UrlParser.ResolvePath():
			string host = (request.Url.IsDefaultPort) ? request.Url.Host : request.Url.Authority;
			var url = new Url(request.Url.Scheme, host, request.RawUrl);
			PathData path = engine.Resolve<RequestPathProvider>().ResolveUrl(url);

			if (!path.IsEmpty() && path.IsRewritable && StopRewritableItems)
				return new RouteData(this, new StopRoutingHandler());

			var page = path.CurrentPage;

			var actionName = path.Action;
			if (string.IsNullOrEmpty(actionName))
				actionName = request.QueryString["action"] ?? "Index";

			if (!string.IsNullOrEmpty(request.QueryString[PathData.PageQueryKey]))
			{
				int pageId;
				if (int.TryParse(request.QueryString[PathData.PageQueryKey], out pageId))
				{
					path.CurrentPage = page = engine.Persister.Get(pageId);
				}
			}

			ContentItem part = null;
			if (!string.IsNullOrEmpty(request.QueryString[PathData.PartQueryKey]))
			{
				// part in query string is used to render a part
				int partId;
				if (int.TryParse(request.QueryString[PathData.PartQueryKey], out partId))
					path.CurrentItem = part = engine.Persister.Get(partId);
			}

			if (!string.IsNullOrEmpty(request.QueryString[PathData.ItemQueryKey]))
			{
				// this is a discrepancy between mvc and the legacy
				// in mvc the item query key doesn't route to the item, it's a marker
				// the urlparser however parses the item query key and passes the item as current in pathdata
				// hence this somewhat strange sidestepping
				int itemId;
				if (int.TryParse(request.QueryString[PathData.ItemQueryKey], out itemId))
				{
					if (itemId == path.ID)
					{
						// we have an item id and it matches the path data we found via url parser
						if (part == null || part.ID != itemId)
						{
							// it hasn't been changed by a specific part query string so we reset it
							path.CurrentItem = path.CurrentPage;
						}
					}
				}
			}

			if (page == null && part == null)
				return null;
			
			path.CurrentPage = page ?? part.ClosestPage();

			var controllerName = controllerMapper.GetControllerName((part ?? page).GetContentType());

			if (controllerName == null)
				return null;

			if (actionName == null || !controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var data = new RouteData(this, routeHandler);

			foreach (var defaultPair in innerRoute.Defaults)
				data.Values[defaultPair.Key] = defaultPair.Value;
			foreach (var tokenPair in innerRoute.DataTokens)
				data.DataTokens[tokenPair.Key] = tokenPair.Value;

			RouteExtensions.ApplyCurrentPath(data, controllerName, actionName, path);
			data.DataTokens[ContentEngineKey] = engine;

			return data;
		}

		/// <summary>Responds to the path /{controller}/{action}/?page=123&item=234</summary>
		private RouteData CheckForContentController(HttpContextBase context)
		{
			var routeData = innerRoute.GetRouteData(context);

			if (routeData == null)
				return null;
			routeData.Route = this;
			routeData.RouteHandler = routeHandler;

			var controllerName = Convert.ToString(routeData.Values[ControllerKey]);
			var actionName = Convert.ToString(routeData.Values[ActionKey]);

			if (!controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var part = ResolveContent(context.Request.QueryString, ContentPartKey)
				?? ResolveContent(context.Request.QueryString, ContentItemKey);
			var page = ResolveContent(context.Request.QueryString, ContentPageKey);

			routeData.ApplyCurrentPath(new PathData(page ?? Find.ClosestPage(part), part));
			routeData.DataTokens[ContentEngineKey] = engine;

			return routeData;
		}

		private ContentItem ResolveContent(NameValueCollection query, string key)
		{
			int id;
			if (int.TryParse(query[key], out id))
			{
				var item = engine.Persister.Get(id);
				return item;
			}
			return null;
		}



		public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
		{
			// here we deal with people linking to non-n2 controllers but we have merged route values containing n2 stuff
			// scenarios:
			// controller: other -> not our business
			// controller: self -> link to same acton and page
			// acton: other -> link to action on same page
			// item: other -> link to other page
			// nuthin' -> link to same acton and page
			// x: y -> add parameter x
			// page: other -> not our business<commit_msg>don't fallback to default controller\action if no content item resolved<commit_after>using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using N2.Engine;
using log4net;

namespace N2.Web.Mvc
{
	/// <summary>
	/// An ASP.NET MVC route that gets route data for content item paths.
	/// </summary>
	public class ContentRoute : RouteBase
	{
		private readonly ILog logger = LogManager.GetLogger(typeof (ContentRoute));

		/// <summary>Used to reference the currently executing content item in the route value dictionary.</summary>
		public static string ContentItemKey
		{
			get { return PathData.ItemQueryKey; }
		}
		/// <summary>Used to reference the content page in the route value dictionary.</summary>
		public static string ContentPageKey
		{
			get { return PathData.PageQueryKey; }
		}
		/// <summary>Used to reference the content part in the route value dictionary.</summary>
		public static string ContentPartKey
		{
			get { return PathData.PartQueryKey; }
		}

		/// <summary>Used to reference the N2 content engine.</summary>
		public const string ContentEngineKey = "engine";
		/// <summary>Convenience reference to the MVC controller.</summary>
		public const string ControllerKey = "controller";
		/// <summary>Convenience reference to the MVC area.</summary>
		public const string AreaKey = "area";
		/// <summary>Convenience reference to the MVC action.</summary>
		public const string ActionKey = "action";
		
		readonly IEngine engine;
		readonly IRouteHandler routeHandler;
		readonly IControllerMapper controllerMapper;
		readonly Route innerRoute;
		string managementPath;

		public ContentRoute(IEngine engine)
			: this(engine, null, null, null)
		{
		}

		public ContentRoute(IEngine engine, IRouteHandler routeHandler, IControllerMapper controllerMapper, Route innerRoute)
		{
			managementPath = Url.ToRelative(Url.ResolveTokens(Url.ManagementUrlToken + "/"));
			this.engine = engine;
			this.routeHandler = routeHandler ?? new MvcRouteHandler();
			this.controllerMapper = controllerMapper ?? engine.Resolve<IControllerMapper>();
			this.innerRoute = innerRoute ?? new Route("{controller}/{action}", 
				new RouteValueDictionary(new { action = "Index" }), 
				new RouteValueDictionary(), 
				new RouteValueDictionary(new { this.engine }), 
				this.routeHandler);
		}

		/// <summary>Gets route data for for items this route handles.</summary>
		/// <param name="item">The item whose route to get.</param>
		/// <param name="routeValues">The route values to apply to the route data.</param>
		/// <returns>A route data object or null.</returns>
		public virtual RouteValueDictionary GetRouteValues(ContentItem item, RouteValueDictionary routeValues)
		{
			string actionName = "Index";
			if (routeValues.ContainsKey(ActionKey))
				actionName = (string)routeValues[ActionKey];

			string controllerName = controllerMapper.GetControllerName(item.GetContentType());
			if (controllerName == null || !controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var values = new RouteValueDictionary(routeValues);

			foreach (var kvp in innerRoute.Defaults)
				if(!values.ContainsKey(kvp.Key))
					values[kvp.Key] = kvp.Value;

			values[ControllerKey] = controllerName;
			values[ActionKey] = actionName;
			values[ContentItemKey] = item.ID;
			values[AreaKey] = innerRoute.DataTokens["area"];

			return values;
		}

		public override RouteData GetRouteData(HttpContextBase httpContext)
		{
			string path = httpContext.Request.AppRelativeCurrentExecutionFilePath;
			if (path.StartsWith(managementPath, StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());
			if (path.EndsWith(".axd", StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());
			if (path.EndsWith(".ashx", StringComparison.InvariantCultureIgnoreCase))
				return new RouteData(this, new StopRoutingHandler());

			RouteData routeData = null;

			if(httpContext.Request.QueryString[ContentPartKey] != null)
				// part in query string, this is an indicator of a request to a part, takes precendence over friendly urls
				routeData = CheckForContentController(httpContext);

			if(routeData == null)
				// this might be a friendly url
				routeData = GetRouteDataForPath(httpContext.Request);

			if(routeData == null)
				// fallback to route to controller/action
				routeData = CheckForContentController(httpContext);

			if (logger.IsDebugEnabled)
				logger.Debug("GetRouteData for '" + path + "' got values: " + (routeData != null ? routeData.Values.ToQueryString() : "(null)"));

			return routeData;
		}

		private RouteData GetRouteDataForPath(HttpRequestBase request)
		{
			//On a multi-lingual site with separate domains per language,
			//the full url (with host) should be passed to UrlParser.ResolvePath():
			string host = (request.Url.IsDefaultPort) ? request.Url.Host : request.Url.Authority;
			var url = new Url(request.Url.Scheme, host, request.RawUrl);
			PathData path = engine.Resolve<RequestPathProvider>().ResolveUrl(url);

			if (!path.IsEmpty() && path.IsRewritable && StopRewritableItems)
				return new RouteData(this, new StopRoutingHandler());

			var page = path.CurrentPage;

			var actionName = path.Action;
			if (string.IsNullOrEmpty(actionName))
				actionName = request.QueryString["action"] ?? "Index";

			if (!string.IsNullOrEmpty(request.QueryString[PathData.PageQueryKey]))
			{
				int pageId;
				if (int.TryParse(request.QueryString[PathData.PageQueryKey], out pageId))
				{
					path.CurrentPage = page = engine.Persister.Get(pageId);
				}
			}

			ContentItem part = null;
			if (!string.IsNullOrEmpty(request.QueryString[PathData.PartQueryKey]))
			{
				// part in query string is used to render a part
				int partId;
				if (int.TryParse(request.QueryString[PathData.PartQueryKey], out partId))
					path.CurrentItem = part = engine.Persister.Get(partId);
			}

			if (!string.IsNullOrEmpty(request.QueryString[PathData.ItemQueryKey]))
			{
				// this is a discrepancy between mvc and the legacy
				// in mvc the item query key doesn't route to the item, it's a marker
				// the urlparser however parses the item query key and passes the item as current in pathdata
				// hence this somewhat strange sidestepping
				int itemId;
				if (int.TryParse(request.QueryString[PathData.ItemQueryKey], out itemId))
				{
					if (itemId == path.ID)
					{
						// we have an item id and it matches the path data we found via url parser
						if (part == null || part.ID != itemId)
						{
							// it hasn't been changed by a specific part query string so we reset it
							path.CurrentItem = path.CurrentPage;
						}
					}
				}
			}

			if (page == null && part == null)
				return null;
			
			path.CurrentPage = page ?? part.ClosestPage();

			var controllerName = controllerMapper.GetControllerName((part ?? page).GetContentType());

			if (controllerName == null)
				return null;

			if (actionName == null || !controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var data = new RouteData(this, routeHandler);

			foreach (var defaultPair in innerRoute.Defaults)
				data.Values[defaultPair.Key] = defaultPair.Value;
			foreach (var tokenPair in innerRoute.DataTokens)
				data.DataTokens[tokenPair.Key] = tokenPair.Value;

			RouteExtensions.ApplyCurrentPath(data, controllerName, actionName, path);
			data.DataTokens[ContentEngineKey] = engine;

			return data;
		}

		/// <summary>Responds to the path /{controller}/{action}/?page=123&item=234</summary>
		private RouteData CheckForContentController(HttpContextBase context)
		{
			var routeData = innerRoute.GetRouteData(context);

			if (routeData == null)
				return null;
			routeData.Route = this;
			routeData.RouteHandler = routeHandler;

			var controllerName = Convert.ToString(routeData.Values[ControllerKey]);
			var actionName = Convert.ToString(routeData.Values[ActionKey]);

			if (!controllerMapper.ControllerHasAction(controllerName, actionName))
				return null;

			var part = ResolveContent(context.Request.QueryString, ContentPartKey)
				?? ResolveContent(context.Request.QueryString, ContentItemKey);
			var page = ResolveContent(context.Request.QueryString, ContentPageKey);

			if (part == null && page == null)
				return null;

			routeData.ApplyCurrentPath(new PathData(page ?? Find.ClosestPage(part), part));
			routeData.DataTokens[ContentEngineKey] = engine;

			return routeData;
		}

		private ContentItem ResolveContent(NameValueCollection query, string key)
		{
			int id;
			if (int.TryParse(query[key], out id))
			{
				var item = engine.Persister.Get(id);
				return item;
			}
			return null;
		}



		public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
		{
			// here we deal with people linking to non-n2 controllers but we have merged route values containing n2 stuff
			// scenarios:
			// controller: other -> not our business
			// controller: self -> link to same acton and page
			// acton: other -> link to action on same page
			// item: other -> link to other page
			// nuthin' -> link to same acton and page
			// x: y -> add parameter x
			// page: other -> not our business
{'fraction_non_alphanumeric': 0.10335180502501748, 'fraction_numerical': 0.0012912250497659654, 'mean_word_length': 3.9687249398556537, 'pattern_counts': {'":': 0, '<': 59, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '919491', 'n_tokens_mistral': 6114, 'n_tokens_neox': 5188, 'n_words': 1499}