UPDATE: Sorry for the repost everyone - but when reading over my feed in my aggregator this morning I noticed some bad typos that anyone who CARED about this post would have a hard time with (i.e. some of my code samples had bloopers in them). (Note to self: Proof THEN publish...)
Yesterday I blogged about some upcoming changes to ReverseDOS. One of the changes involved letting users create <rule> entries based on 'verbs' describing the 'actions' of the current request being evaluated.
Using the [Flags] Enum worked out well here - as it provides me with the ability to specify multiple verbs, such that users can create 'combinations' of various activities that they would either like to prohibit or allow against a given directory. The enum is nothing special, and looks like so:
[Flags]
internal enum Verbs
{
Head = 1,
Get = 2,
Post = 4,
Query = 8,
Refer = 16,
Proxy = 32
}
The fun, however, began when I realized that I needed to give people the EASY option to create OR-ing combinations as well as AND combinations. For example: You want to block against a Proxy-ed request, and don't care if the request is HEAD-ed, GET-ed, or POST-ed. If you wanted to set that up, you'd technically need to create three rules:
<rules>
<deny verbs="head,proxy">/pathHere/</deny>
<deny verbs="get, proxy">/pathHere/</deny>
<deny verbs="post,proxy">/pathHere/</deny>
</rules>
That, frankly, seemed quite ridiculous - who would want to have to do that - especially when the whole goal is to easily 'fight' spam.
So, I figured the following syntax (while spooky to a grandma) wouldn't be too PERL-ish to the average developer:
<rules>
<deny verbs="head|get|post,proxy">/pathHere/</deny>
</rules>
As you can see, it's not perfect - but much simpler than the alternative. (And, it's technically a VERY lame example - cuz all you'd be worried about here is the Proxy action/verb - so bear with me and my lame examples).
In the end, however, I just wanted to let people easily specify OR-ing and AND-ing with a simple text attribute, and let ReverseDOS figure out (behind the scenes) what people meant. Which lead me on an amusing journey to the dark and evil land of recursion. (Actually, it was quite fun.)
In the end, my solution was REALLY over-built - as there just aren't enough combinations for the types of verbs logically available and associable, but the alternative to over-building was some lame type of hard-coding, so I decided to share my solution for anyone who's interested. Especially since this code should be pretty easy to 'steal' for any other operations involving enums and various AND-ing and OR-ing combinations expressed as text.
For example, suppose we were dealing with PizzaToppings, expressed as a [Flags]-ed enum. With this logic if you specified: "TomatoSauce|PestoSauce, Cheese, Anchovies, Squid|Pepperoni", you'd be specifying that you REALLY wanted an adventure, not a pizza. The results for such a 'choice' of options would be:
TomatoSauce, Cheese, Anchovies, Squid OR
TomatoSauce, Cheese, Anchovies, Pepperoni OR
PestoSauce, Cheese, Anchovies, Squid OR
PestoSauce, Cheese, Anchovies, Pepperoni
The logic I used to calculate this (as an Array of Verbs[]) is as follows:
1) Pass in a string value indicating the serialzed 'choice' (where we break apart all of the 'AND-ed' options and size an output array of Verbs[], etc.) :
private Verbs[] LoadVerbs(string verbInfo)
{
Verbs[] output;
if (verbInfo.IndexOf("|") > 0)
{
string[] segments =
verbInfo.Replace(" ", "").Split(new char[] { ',' });
string[][] iterations = new string[segments.Length][];
int bounds = 1;
for (int i = 0; i < segments.Length; i++)
{
if (segments[i].IndexOf("|") > 0)
{
iterations[i] = segments[i].Split(new char[] { '|' });
bounds *= iterations[i].Length;
}
else
iterations[i] = new string[] { segments[i] };
}
output = new Verbs[bounds];
int location = 0;
output = this.RecurseVerbOptions(
iterations,
0,
string.Empty,
ref location,
output);
}
else
{
output = new Verbs[1];
output[0] =
(Verbs)Enum.Parse(typeof(Verbs), verbInfo, true);
}
return output;
}
2) Use a bit of evil recursion to traverse down the 'list' of AND-ed options, and then as I do so, account for each OR-ed option as needed:
private Verbs[] RecurseVerbOptions(string[][] iterations,
int currentLevel, string currentOption,
ref int outputLocation, Verbs[] results)
{
string currentValue;
string verbsOption;
for (int i=0; i < iterations[currentLevel].GetUpperBound(0)+1;i++)
{
currentValue = iterations[currentLevel][i];
if (currentOption.Length > 0)
verbsOption = string.Format(
"{0},{1}",
currentOption,
currentValue);
else
verbsOption = currentValue; // start a new line...
if (currentLevel < iterations.GetUpperBound(0))
this.RecurseVerbOptions(
iterations,
currentLevel + 1,
verbsOption,
ref outputLocation,
results);
else
{
results[outputLocation] =
(Verbs)Enum.Parse(typeof(Verbs), verbsOption, true);
outputLocation++; // increment for the next position
}
}
return results;
}
In the end, this works perfectly - but, as I said, it's a bit over-built. (Still, it was fun.)