ASP.NET request validation causes: is there a list? - c#

is anybody aware of a list of exactly what triggers ASP.NET's HttpRequestValidationException? [This is behind the common error: "A potentially dangerous Request.Form value was detected," etc.]
I've checked here, around the Web, and MSDN Library but can't find this documented. I'm aware of some ways to generate the error, but would like to have a complete list so I can guard against and selectively circumvent it (I know how to disable request validation for a page, but this isn't an option in this case).
Is it a case of "security through obscurity"?
Thanks.
[Note: Scripts won't load for me in IE8 (as described frequently in the Meta forum) so I won't be able to "Add comment."]
EDIT 1: Hi Oded, are you aware of a list that documents the conditions used to determine a "potentially malicious input string"? That's what I'm looking for.
EDIT 2: #Chris Pebble: Yeah, what you said. :)

I couldn't find a document outlining a conclusive list, but looking through Reflector and doing some analysis on use of HttpRequestValidationException, it looks like validation errors on the following can cause the request validation to fail:
A filename in one of the files POSTed to an upload.
The incoming request raw URL.
The value portion of the name/value pair from any of the incoming cookies.
The value portion of the name/value pair from any of the fields coming in through GET/POST.
The question, then, is "what qualifies one of these things as a dangerous input?" That seems to happen during an internal method System.Web.CrossSiteScriptingValidation.IsDangerousString(string, out int) which looks like it decides this way:
Look for < or & in the value. If it's not there, or if it's the last character in the value, then the value is OK.
If the & character is in a &# sequence (e.g.,   for a non-breaking space), it's a "dangerous string."
If the < character is part of <x (where "x" is any alphabetic character a-z), <!, </, or <?, it's a "dangerous string."
Failing all of that, the value is OK.
The System.Web.CrossSiteScriptingValidation type seems to have other methods in it for determining if things are dangerous URLs or valid JavaScript IDs, but those don't appear, at least through Reflector analysis, to result in throwing HttpRequestValidationExceptions.

Update:
Warning: Some parts of the code in the original answer (below) were removed and marked as OBSOLETE.
Latest source code in Microsoft site (has syntax highlighting):
http://referencesource.microsoft.com/#System.Web/CrossSiteScriptingValidation.cs
After checking the newest code you will probably agree that what Travis Illig explained are the only validations used now in 2018 (and seems to have no changes since 2014 when the source was released in GitHub). But the old code below may still be relevant if you use an older version of the framework.
Original Answer:
Using Reflector, I did some browsing. Here's the raw code. When I have time I will translate this into some meaningful rules:
The HttpRequestValidationException is thrown by only a single method in the System.Web namespace, so it's rather isolated. Here is the method:
private void ValidateString(string s, string valueName, string collectionName)
{
int matchIndex = 0;
if (CrossSiteScriptingValidation.IsDangerousString(s, out matchIndex))
{
string str = valueName + "=\"";
int startIndex = matchIndex - 10;
if (startIndex <= 0)
{
startIndex = 0;
}
else
{
str = str + "...";
}
int length = matchIndex + 20;
if (length >= s.Length)
{
length = s.Length;
str = str + s.Substring(startIndex, length - startIndex) + "\"";
}
else
{
str = str + s.Substring(startIndex, length - startIndex) + "...\"";
}
throw new HttpRequestValidationException(HttpRuntime.FormatResourceString("Dangerous_input_detected", collectionName, str));
}
}
That method above makes a call to the IsDangerousString method in the CrossSiteScriptingValidation class, which validates the string against a series of rules. It looks like the following:
internal static bool IsDangerousString(string s, out int matchIndex)
{
matchIndex = 0;
int startIndex = 0;
while (true)
{
int index = s.IndexOfAny(startingChars, startIndex);
if (index < 0)
{
return false;
}
if (index == (s.Length - 1))
{
return false;
}
matchIndex = index;
switch (s[index])
{
case 'E':
case 'e':
if (IsDangerousExpressionString(s, index))
{
return true;
}
break;
case 'O':
case 'o':
if (!IsDangerousOnString(s, index))
{
break;
}
return true;
case '&':
if (s[index + 1] != '#')
{
break;
}
return true;
case '<':
if (!IsAtoZ(s[index + 1]) && (s[index + 1] != '!'))
{
break;
}
return true;
case 'S':
case 's':
if (!IsDangerousScriptString(s, index))
{
break;
}
return true;
}
startIndex = index + 1;
}
}
That IsDangerousString method appears to be referencing a series of validation rules, which are outlined below:
private static bool IsDangerousExpressionString(string s, int index)
{
if ((index + 10) >= s.Length)
{
return false;
}
if ((s[index + 1] != 'x') && (s[index + 1] != 'X'))
{
return false;
}
return (string.Compare(s, index + 2, "pression(", 0, 9, true, CultureInfo.InvariantCulture) == 0);
}
-
private static bool IsDangerousOnString(string s, int index)
{
if ((s[index + 1] != 'n') && (s[index + 1] != 'N'))
{
return false;
}
if ((index > 0) && IsAtoZ(s[index - 1]))
{
return false;
}
int length = s.Length;
index += 2;
while ((index < length) && IsAtoZ(s[index]))
{
index++;
}
while ((index < length) && char.IsWhiteSpace(s[index]))
{
index++;
}
return ((index < length) && (s[index] == '='));
}
-
private static bool IsAtoZ(char c)
{
return (((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')));
}
-
private static bool IsDangerousScriptString(string s, int index)
{
int length = s.Length;
if ((index + 6) >= length)
{
return false;
}
if ((((s[index + 1] != 'c') && (s[index + 1] != 'C')) || ((s[index + 2] != 'r') && (s[index + 2] != 'R'))) || ((((s[index + 3] != 'i') && (s[index + 3] != 'I')) || ((s[index + 4] != 'p') && (s[index + 4] != 'P'))) || ((s[index + 5] != 't') && (s[index + 5] != 'T'))))
{
return false;
}
index += 6;
while ((index < length) && char.IsWhiteSpace(s[index]))
{
index++;
}
return ((index < length) && (s[index] == ':'));
}
So there you have it. It's not pretty to decipher, but it's all there.

How about this script? Your code can not detect this script, right?
";}alert(1);function%20a(){//

Try this regular expresson pattern.
You may need to ecape the \ for javascript ex \\
var regExpPattern = '[eE][xX][pP][rR][eE][sS][sS][iI][oO][nN]\\(|\\b[oO][nN][a-zA-Z]*\\b\\s*=|&#|<[!/a-zA-Z]|[sS][cC][rR][iI][pP][tT]\\s*:';
var re = new RegExp("","gi");
re.compile(regExpPattern,"gi");
var outString = null;
outString = re.exec(text);

Following on from Travis' answer, the list of 'dangerous' character sequences can be simplified as follows;
&#
<A through to <Z (upper and lower case)
<!
</
<?
Based on this, in an ASP.Net MVC web app the following Regex validation attribute can be used on a model field to trigger client side validation before an HttpRequestValidationException is thrown when the form is submitted;
[RegularExpression(#"^(?![\s\S]*(&#|<[a-zA-Z!\/?]))[\s\S]*$", ErrorMessage = "This field does not support HTML or allow any of the following character sequences; "&#", "<A" through to "<Z" (upper and lower case), "<!", "</" or "<?".")]
Note that validation attribute error messages are HTML encoded when output by server side validation, but not when used in client side validation, so this one is already encoded as we only intend to see it with client side validation.

From MSDN:
'The exception that is thrown when a potentially malicious input string is received from the client as part of the request data. '
Many times this happens when JavaScript changes the values of a server side control in a way that causes the ViewState to not agree with the posted data.

Related

Detecting multiple keys in Unity

I'm following along with a tutorial about creating a mini-RTS in Unity, but I've hit something of a roadblock when it comes to the selection feature for assigning selection groups for multiple units.
The pertinent parts are below:
In the Update() method of my UnitsSelection class
//manage selection groups with alphanumeric keys
if (Input.anyKeyDown)
{
int alphaKey = Utils.GetAlphaKeyValue(Input.inputString);
if (alphaKey != -1)
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
_CreateSelectionGroup(alphaKey);
}
else
{
_ReselectGroup(alphaKey);
}
}
}
And the GetAlphaKeyValue method from Utils:
public static int GetAlphaKeyValue(string inputString)
{
if (inputString == "0") return 0;
if (inputString == "1") return 1;
if (inputString == "2") return 2;
if (inputString == "3") return 3;
if (inputString == "4") return 4;
if (inputString == "5") return 5;
if (inputString == "6") return 6;
if (inputString == "7") return 7;
if (inputString == "8") return 8;
if (inputString == "9") return 9;
return -1;
}
This is the code that is used in the tutorial, but to my understanding there is no way that _CreateSelectionGroup() would ever be called.
I've seen the tutorial demonstrate this functionality working, but whenever I try to run it GetAlphaKeyValue turns the Left and Right control keys into a -1 value so the if statement that checks for them never runs.
Am I missing something here? How does Unity normally handle things like Ctrl+1?
If you use the inputString I would always rather check for Contains instead of an exact string match. However, I tried to use the inputString in the past and I found it too unpredictable for most usecases ^^
While holding control keys your Keyboard most likely simply won't generate any inputString.
Only ASCII characters are contained in the inputString.
But e.g. CTRL+1 will not generate the ASCII symbol 1 but rather a "non-printing character", a control symbol - or simply none at all.
You should probably rather use e.g.
public static bool GetAlphaKeyValue(out int alphaKey)
{
alphaKey = -1;
if (Input.GetKeyDown(KeyCode.Alpha0) alphaKey = 0;
else if (Input.GetKeyDown(KeyCode.Alpha1) alphaKey = 1;
else if (Input.GetKeyDown(KeyCode.Alpha2) alphaKey = 2;
else if (Input.GetKeyDown(KeyCode.Alpha3) alphaKey = 3;
else if (Input.GetKeyDown(KeyCode.Alpha4) alphaKey = 4;
else if (Input.GetKeyDown(KeyCode.Alpha5) alphaKey = 5;
else if (Input.GetKeyDown(KeyCode.Alpha6) alphaKey = 6;
else if (Input.GetKeyDown(KeyCode.Alpha7) alphaKey = 7;
else if (Input.GetKeyDown(KeyCode.Alpha8) alphaKey = 8;
else if (Input.GetKeyDown(KeyCode.Alpha9) alphaKey = 9;
return alphaKey >= 0;
}
And then use it like
//manage selection groups with alphanumeric keys
if(Utils.GetAlphaKeyValue(out var alphaKey)
{
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl))
{
_CreateSelectionGroup(alphaKey);
}
else
{
_ReselectGroup(alphaKey);
}
}
As it turns out it may just be an issue with my keyboard. Different keyboards handle key presses in different ways. Mine just refuses to tell Unity that control + (some other key) are being pressed together. Changed the code to respond to Shift + (some other key) and it works fine.

HttpUtility.HtmlDecode fails to decode &#39

I am using .net 4.5 and
HttpUtility.HtmlDecode fails to decode &#39 which is single quote character
Any idea why ?
Using C# .net 4.5 WPF on windows 8.1
Here the text that is failed
Apple 13&#39&#39 Z0RA30256 MacBook Pro Retina
Below is framework version
#region Assembly System.Web.dll, v4.0.0.0
// C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Web.dll
#endregion
It's not possible to handle this with the built it HtmlDecode method, you would have to find/replace it or otherwise work around.
Below is the source code for HtmlDecode - you can see from the comment explicitly that your scenario is considered and not supported - HTML entities have to be bounded with a ;, otherwise they are simply not HTML entities. Browsers are forgiving of the incorrect markup, and compensate accordingly.
// We found a '&'. Now look for the next ';' or '&'. The idea is that
// if we find another '&' before finding a ';', then this is not an entity,
// and the next '&' might start a real entity (VSWhidbey 275184)
Here is the full source of the .NET HtmlDecode in HttpUtility, if you want to adapt the behaviour.
http://referencesource.microsoft.com/#System/net/System/Net/WebUtility.cs,44d08941e6aeb00d
public static void HtmlDecode(string value, TextWriter output)
{
if (value == null)
{
return;
}
if (output == null)
{
throw new ArgumentNullException("output");
}
if (value.IndexOf('&') < 0)
{
output.Write(value); // good as is
return;
}
int l = value.Length;
for (int i = 0; i < l; i++)
{
char ch = value[i];
if (ch == '&')
{
// We found a '&'. Now look for the next ';' or '&'. The idea is that
// if we find another '&' before finding a ';', then this is not an entity,
// and the next '&' might start a real entity (VSWhidbey 275184)
int index = value.IndexOfAny(_htmlEntityEndingChars, i + 1);
if (index > 0 && value[index] == ';')
{
string entity = value.Substring(i + 1, index - i - 1);
if (entity.Length > 1 && entity[0] == '#')
{
// The # syntax can be in decimal or hex, e.g.
// å --> decimal
// å --> same char in hex
// See http://www.w3.org/TR/REC-html40/charset.html#entities
ushort parsed;
if (entity[1] == 'x' || entity[1] == 'X')
{
UInt16.TryParse(entity.Substring(2), NumberStyles.AllowHexSpecifier, NumberFormatInfo.InvariantInfo, out parsed);
}
else
{
UInt16.TryParse(entity.Substring(1), NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out parsed);
}
if (parsed != 0)
{
ch = (char)parsed;
i = index; // already looked at everything until semicolon
}
}
else
{
i = index; // already looked at everything until semicolon
char entityChar = HtmlEntities.Lookup(entity);
if (entityChar != (char)0)
{
ch = entityChar;
}
else
{
output.Write('&');
output.Write(entity);
output.Write(';');
continue;
}
}
}
}
output.Write(ch);
}
}

PhysicalAddress.Parse() won't parse lower cased string, is this a bug?

Note: Using .Net 4.0
Consider the following piece of code.
String ad = "FE23658978541236";
String ad2 = "00FABE002563447E".ToLower();
try
{
PhysicalAddress.Parse(ad);
}
catch (Exception)
{
//We dont get here, all went well
}
try
{
PhysicalAddress.Parse(ad2);
}
catch (Exception)
{
//we arrive here for what reason?
}
try
{
//Ok, I do it myself then.
ulong dad2 = ulong.Parse(ad2, System.Globalization.NumberStyles.HexNumber);
byte[] bad2 = BitConverter.GetBytes(dad2);
if (BitConverter.IsLittleEndian)
{
bad2 = bad2.Reverse().ToArray<byte>();
}
PhysicalAddress pa = new PhysicalAddress(bad2);
}
catch (Exception ex)
{
//We don't get here as all went well
}
So an exception is thrown in PhysicalAddress.Parse method when trying to parse an address with lower case. When I look at the source code of .Net its totally clear to me why.
Its because of the following piece of code.
if (value >= 0x30 && value <=0x39){
value -= 0x30;
}
else if (value >= 0x41 && value <= 0x46) {
value -= 0x37;
}
That is found within the Parse method.
public static PhysicalAddress Parse(string address) {
int validCount = 0;
bool hasDashes = false;
byte[] buffer = null;
if(address == null)
{
return PhysicalAddress.None;
}
//has dashes?
if (address.IndexOf('-') >= 0 ){
hasDashes = true;
buffer = new byte[(address.Length+1)/3];
}
else{
if(address.Length % 2 > 0){ //should be even
throw new FormatException(SR.GetString(SR.net_bad_mac_address));
}
buffer = new byte[address.Length/2];
}
int j = 0;
for (int i = 0; i < address.Length; i++ ) {
int value = (int)address[i];
if (value >= 0x30 && value <=0x39){
value -= 0x30;
}
else if (value >= 0x41 && value <= 0x46) {
value -= 0x37;
}
else if (value == (int)'-'){
if (validCount == 2) {
validCount = 0;
continue;
}
else{
throw new FormatException(SR.GetString(SR.net_bad_mac_address));
}
}
else{
throw new FormatException(SR.GetString(SR.net_bad_mac_address));
}
//we had too many characters after the last dash
if(hasDashes && validCount >= 2){
throw new FormatException(SR.GetString(SR.net_bad_mac_address));
}
if (validCount%2 == 0) {
buffer[j] = (byte) (value << 4);
}
else{
buffer[j++] |= (byte) value;
}
validCount++;
}
//we too few characters after the last dash
if(validCount < 2){
throw new FormatException(SR.GetString(SR.net_bad_mac_address));
}
return new PhysicalAddress(buffer);
}
Can this be considered a bug? Or is it so very wrong to use a lower cased hex values in a string? Or is there some convention I am unaware of.
Personally, I consider this programmer unfriendly.
From MSDN:
The address parameter must contain a string that can only consist of
numbers and upper-case letters as hexadecimal digits. Some examples of
string formats that are acceptable are as follows .... Note that an address that contains f0-e1-d2-c3-b4-a5 will fail to parse and throw an exception.
So you could simply do: PhysicalAddress.Parse(ad.ToUpper());
No, it's only a bug if it doesn't do something the documentation states that it does, or it does something the documentation states that it doesn't. The mere fact that it doesn't behave as you expect doesn't make it a bug. You could of course consider it a bad design decision (or, as you put it so eloquently, programmer-unfriendly) but that's not the same thing.
I tend to agree with you there since I like to follow the "be liberal in what you expect, consistent in what you deliver" philosophy and the code could probably be easily fixed with something like:
if (value >= 0x30 && value <=0x39) {
value -= 0x30;
}
else if (value >= 0x41 && value <= 0x46) {
value -= 0x37;
}
else if (value >= 0x61 && value <= 0x66) { // added
value -= 0x57; // added
} // added
else if ...
though, of course, you'd have to change the doco as well, and run vast numbers of tests to ensure you hadn't stuffed things up.
Regarding the doco, it can be found here and the important bit is repeated below (with my bold):
The address parameter must contain a string that can only consist of numbers and upper-case letters as hexadecimal digits. Some examples of string formats that are acceptable are as follows:
001122334455
00-11-22-33-44-55
F0-E1-D2-C3-B4-A5
Note that an address that contains f0-e1-d2-c3-b4-a5 will fail to parse and throw an exception.

C# annoyance - access data outside of sting

I have made a email validation program in C#, but how do I check data outside of the string?
Here is my C# code:
private bool CheckEmail()
{
string email1 = email.Text;
//calculating the length of the email
int EmailLen = email1.Length;
int num = 0;
//the first character of the email must not be the "#"
if (email1.Substring(0, 1) != "#")
{
//checking the email entered after the first character as it is not a "#" so i will start from 1.
for (int i = 1; i < EmailLen; i++)
{
//prevents there from being two "#"next to each other
if (email1[i] == '#' && (i + 1) < email1.Length && email1[i + 1] != '#')
{
//if there is an "#" in the email then num will increase by one
num = num + 1;
//now the stored value of i is the position where the "#" is placed. j will be i+2 as there should be at least one character after the "#"
int j = i + 2;
if (j < EmailLen)
{
for (int k = j; k < EmailLen; k++)
{
//when it finds a "." In the email, the character after the "." Should not be empty or have a space, e.g. it should be something like ".com"
if (email1[k] == '.' && k + 1 < email1.Length && email1[k + 1] != ' ')
{
num = num + 1;
}
}
}
else
{
break;
}
}
}
}
else
{
num = 0;
}
//if the num is 2, then the email is valid, otherwise it is invalid. If the email had more than one "#" for example, the num will be greater than 2.
if (num == 2)
{
return true;
}
else
{
return false;
}
}
When I try typing in “aa#”, I get this error: “Index and length must refer to a location within the string.”
When I ty typing in aa#a. , I get this error: “Index and length must refer to a location within the string.”
You cannot access data outside of the string. This is for a very good reason - doing so would violate the type safety that is a major attraction of a virtual machine like the .NET CLR.
You just want to check your bounds to make sure you're not trying to access a part of the string that doesn't exist. BTW, for checking single characters, you totally want to be doing email1[i], not email1.Substring(i, 1), so you're not constructing new string objects left, right and center.
Your first test should be:
if (email1[i] == '#' && i + 1 < email1.Length && email1[i + 1] != '#')
Your problem is
email1.Substring(i + 1, 1)
On the last iteration of the for loop, i == EmailLen -1.
So i + 1 == EmailLen, which is one past the end of the string.

Parsing strings recursively

I am trying to extract information out of a string - a fortran formatting string to be specific. The string is formatted like:
F8.3, I5, 3(5X, 2(A20,F10.3)), 'XXX'
with formatting fields delimited by "," and formatting groups inside brackets, with the number in front of the brackets indicating how many consecutive times the formatting pattern is repeated. So, the string above expands to:
F8.3, I5, 5X, A20,F10.3, A20,F10.3, 5X, A20,F10.3, A20,F10.3, 5X, A20,F10.3, A20,F10.3, 'XXX'
I am trying to make something in C# that will expand a string that conforms to that pattern. I have started going about it with lots of switch and if statements, but am wondering if I am not going about it the wrong way?
I was basically wondering if some Regex wizzard thinks that Regular expressions can do this in one neat-fell swoop? I know nothing about regular expressions, but if this could solve my problem I am considering putting in some time to learn how to use them... on the other hand if regular expressions can't sort this out then I'd rather spend my time looking at another method.
This has to be doable with Regex :)
I've expanded my previous example and it test nicely with your example.
// regex to match the inner most patterns of n(X) and capture the values of n and X.
private static readonly Regex matcher = new Regex(#"(\d+)\(([^(]*?)\)", RegexOptions.None);
// create new string by repeating X n times, separated with ','
private static string Join(Match m)
{
var n = Convert.ToInt32(m.Groups[1].Value); // get value of n
var x = m.Groups[2].Value; // get value of X
return String.Join(",", Enumerable.Repeat(x, n));
}
// expand the string by recursively replacing the innermost values of n(X).
private static string Expand(string text)
{
var s = matcher.Replace(text, Join);
return (matcher.IsMatch(s)) ? Expand(s) : s;
}
// parse a string for occurenses of n(X) pattern and expand then.
// return the string as a tokenized array.
public static string[] Parse(string text)
{
// Check that the number of parantheses is even.
if (text.Sum(c => (c == '(' || c == ')') ? 1 : 0) % 2 == 1)
throw new ArgumentException("The string contains an odd number of parantheses.");
return Expand(text).Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
}
I would suggest using a recusive method like the example below( not tested ):
ResultData Parse(String value, ref Int32 index)
{
ResultData result = new ResultData();
Index startIndex = index; // Used to get substrings
while (index < value.Length)
{
Char current = value[index];
if (current == '(')
{
index++;
result.Add(Parse(value, ref index));
startIndex = index;
continue;
}
if (current == ')')
{
// Push last result
index++;
return result;
}
// Process all other chars here
}
// We can't find the closing bracket
throw new Exception("String is not valid");
}
You maybe need to modify some parts of the code, but this method have i used when writing a simple compiler. Although it's not completed, just a example.
Personally, I would suggest using a recursive function instead. Every time you hit an opening parenthesis, call the function again to parse that part. I'm not sure if you can use a regex to match a recursive data structure.
(Edit: Removed incorrect regex)
Ended up rewriting this today. It turns out that this can be done in one single method:
private static string ExpandBrackets(string Format)
{
int maxLevel = CountNesting(Format);
for (int currentLevel = maxLevel; currentLevel > 0; currentLevel--)
{
int level = 0;
int start = 0;
int end = 0;
for (int i = 0; i < Format.Length; i++)
{
char thisChar = Format[i];
switch (Format[i])
{
case '(':
level++;
if (level == currentLevel)
{
string group = string.Empty;
int repeat = 0;
/// Isolate the number of repeats if any
/// If there are 0 repeats the set to 1 so group will be replaced by itself with the brackets removed
for (int j = i - 1; j >= 0; j--)
{
char c = Format[j];
if (c == ',')
{
start = j + 1;
break;
}
if (char.IsDigit(c))
repeat = int.Parse(c + (repeat != 0 ? repeat.ToString() : string.Empty));
else
throw new Exception("Non-numeric character " + c + " found in front of the brackets");
}
if (repeat == 0)
repeat = 1;
/// Isolate the format group
/// Parse until the first closing bracket. Level is decremented as this effectively takes us down one level
for (int j = i + 1; j < Format.Length; j++)
{
char c = Format[j];
if (c == ')')
{
level--;
end = j;
break;
}
group += c;
}
/// Substitute the expanded group for the original group in the format string
/// If the group is empty then just remove it from the string
if (string.IsNullOrEmpty(group))
{
Format = Format.Remove(start - 1, end - start + 2);
i = start;
}
else
{
string repeatedGroup = RepeatString(group, repeat);
Format = Format.Remove(start, end - start + 1).Insert(start, repeatedGroup);
i = start + repeatedGroup.Length - 1;
}
}
break;
case ')':
level--;
break;
}
}
}
return Format;
}
CountNesting() returns the highest level of bracket nesting in the format statement, but could be passed in as a parameter to the method. RepeatString() just repeats a string the specified number of times and substitutes it for the bracketed group in the format string.

Categories