Skip to content

Commit

Permalink
Added validation for Controller and Area fields to ensure invalid cha…
Browse files Browse the repository at this point in the history
…racters are not supplied in the configuration.
  • Loading branch information
NightOwl888 committed Jan 4, 2014
1 parent 55f1d60 commit 04d29da
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
<Compile Include="Reflection\PluginInstantiator.cs" />
<Compile Include="DI\SiteMapFactoryContainer.cs" />
<Compile Include="DI\SiteMapNodeFactoryContainer.cs" />
<Compile Include="Text\StringExtensions.cs" />
<Compile Include="TrimEmptyGroupingNodesVisibilityProvider.cs" />
<Compile Include="Visitor\NullSiteMapNodeVisitor.cs" />
<Compile Include="Web\Mvc\ControllerExtensions.cs" />
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/MvcSiteMapProvider/MvcSiteMapProvider/Resources/Messages.resx
Original file line number Diff line number Diff line change
Expand Up @@ -341,4 +341,18 @@ The available values for HttpMethod are:

{3}</value>
</data>
<data name="SiteMapNodeAreaNameInvalid" xml:space="preserve">
<value>The node with key '{0}' and title '{1}' does not have a valid value for Area. The current value is '{2}'. The Area field must be a valid C# identifier or be set to an empty string to indicate a non-area controller.

A C# identifier must start with a Unicode letter, underscore, or ampersand and may be followed by zero or more Unicode letters, digits, or underscores.

Please use the same value that is returned from the AreaName property of your AreaRegistration class.</value>
</data>
<data name="SiteMapNodeControllerNameInvalid" xml:space="preserve">
<value>The node with key '{0}' and title '{1}' does not have a valid value for Controller. The current value is '{2}'. The Controller field must be a valid C# identifier and not end with the suffix 'Controller'.

A C# identifier must start with a Unicode letter, underscore, or ampersand and may be followed by zero or more Unicode letters, digits, or underscores.

If you are attempting to add an area to the controller field, do note that the 'AreaName/ControllerName' syntax is not supported by MvcSiteMapProvider. To set the area, use the 'Area' property or 'area' attribute.</value>
</data>
</root>
25 changes: 25 additions & 0 deletions src/MvcSiteMapProvider/MvcSiteMapProvider/SiteMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Web.UI;
using System.Web.Mvc;
using System.Web.Routing;
using MvcSiteMapProvider.Text;
using MvcSiteMapProvider.Web;
using MvcSiteMapProvider.Web.Mvc;
using MvcSiteMapProvider.Collections.Specialized;
Expand Down Expand Up @@ -772,6 +773,8 @@ protected virtual ISiteMapNode ReturnNodeIfAccessible(ISiteMapNode node)
protected virtual void AssertSiteMapNodeConfigurationIsValid(ISiteMapNode node)
{
ThrowIfTitleNotSet(node);
ThrowIfControllerNameInvalid(node);
ThrowIfAreaNameInvalid(node);
ThrowIfActionAndUrlNotSet(node);
ThrowIfHttpMethodInvalid(node);
ThrowIfRouteValueIsPreservedRouteParameter(node);
Expand Down Expand Up @@ -818,6 +821,28 @@ protected virtual void ThrowIfHttpMethodInvalid(ISiteMapNode node)
}
}

protected virtual void ThrowIfControllerNameInvalid(ISiteMapNode node)
{
if (!String.IsNullOrEmpty(node.Controller))
{
if (!node.Controller.IsValidIdentifier() || node.Controller.EndsWith("Controller"))
{
throw new MvcSiteMapException(String.Format(Resources.Messages.SiteMapNodeControllerNameInvalid, node.Key, node.Title, node.Controller));
}
}
}

protected virtual void ThrowIfAreaNameInvalid(ISiteMapNode node)
{
if (!String.IsNullOrEmpty(node.Area))
{
if (!node.Area.IsValidIdentifier())
{
throw new MvcSiteMapException(String.Format(Resources.Messages.SiteMapNodeAreaNameInvalid, node.Key, node.Title, node.Area));
}
}
}

#endregion

}
Expand Down
79 changes: 79 additions & 0 deletions src/MvcSiteMapProvider/MvcSiteMapProvider/Text/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace MvcSiteMapProvider.Text
{
public static class StringExtensions
{
// C# keywords: http://msdn.microsoft.com/en-us/library/x53a06bb(v=vs.71).aspx
private static string[] keywords = new[]
{
"abstract", "event", "new", "struct",
"as", "explicit", "null", "switch",
"base", "extern", "object", "this",
"bool", "false", "operator", "throw",
"breal", "finally", "out", "true",
"byte", "fixed", "override", "try",
"case", "float", "params", "typeof",
"catch", "for", "private", "uint",
"char", "foreach", "protected", "ulong",
"checked", "goto", "public", "unchekeced",
"class", "if", "readonly", "unsafe",
"const", "implicit", "ref", "ushort",
"continue", "in", "return", "using",
"decimal", "int", "sbyte", "virtual",
"default", "interface", "sealed", "volatile",
"delegate", "internal", "short", "void",
"do", "is", "sizeof", "while",
"double", "lock", "stackalloc",
"else", "long", "static",
"enum", "namespace", "string"
};

// definition of a valid C# identifier: http://msdn.microsoft.com/en-us/library/aa664670(v=vs.71).aspx
private const string formattingCharacter = @"\p{Cf}";
private const string connectingCharacter = @"\p{Pc}";
private const string decimalDigitCharacter = @"\p{Nd}";
private const string combiningCharacter = @"\p{Mn}|\p{Mc}";
private const string letterCharacter = @"\p{Lu}|\p{Ll}|\p{Lt}|\p{Lm}|\p{Lo}|\p{Nl}";
private const string identifierPartCharacter = letterCharacter + "|" +
decimalDigitCharacter + "|" +
connectingCharacter + "|" +
combiningCharacter + "|" +
formattingCharacter;
private const string identifierPartCharacters = "(" + identifierPartCharacter + ")+";
private const string identifierStartCharacter = "(" + letterCharacter + "|_)";
private const string identifierOrKeyword = identifierStartCharacter + "(" +
identifierPartCharacters + ")*";
private static Regex validIdentifierRegex = new Regex("^" + identifierOrKeyword + "$", RegexOptions.Compiled);


/// <summary>
/// Determines if a string matches a valid C# identifier according to the C# language specification (including Unicode support).
/// </summary>
/// <param name="identifier">The identifier being analyzed.</param>
/// <returns><b>true</b> if the identifier is valid, otherwise <b>false</b>.</returns>
/// <remarks>Source: https://gist.github.com/LordDawnhunter/5245476 </remarks>
public static bool IsValidIdentifier(this string identifier)
{
if (String.IsNullOrEmpty(identifier)) return false;
var normalizedIdentifier = identifier.Normalize();

// 1. check that the identifier match the validIdentifer regular expression and it's not a C# keyword
if (validIdentifierRegex.IsMatch(normalizedIdentifier) && !keywords.Contains(normalizedIdentifier))
{
return true;
}

// 2. check if the identifier starts with @
if (normalizedIdentifier.StartsWith("@") && validIdentifierRegex.IsMatch(normalizedIdentifier.Substring(1)))
{
return true;
}

// 3. it's not a valid identifier
return false;
}
}
}

0 comments on commit 04d29da

Please sign in to comment.