Hosts File Utility Part III
Thursday, May 28 2009 - development
In part II here I showed the unit tests for my host file utility. I will note that this is not “best practices” code. It is simply a quick utility that has more than paid for itself in time saved.
I have one outstanding bug/feature. When you attempt to uncomment something: hostu –u 127.0.0.1 www.codeverity.com if the host entry in question doesn’t exist the utility doesn’t give you any feedback that the requested action failed. There is no way to know where you are pointed without following up the above with: hostu –l www.codeverity.com.
The project structure looks like this:
HostEntries:
public class HostEntries
{
private IList<HostEntry> _hosts = new List<HostEntry>();
public HostEntries()
{
ReadHostsFile();
}
private void ReadHostsFile()
{
var path = FindHostFile();
using(var reader = new StreamReader(path))
{
while (!reader.EndOfStream)
{
var entry = new HostEntry(reader.ReadLine());
_hosts.Add(entry);
}
}
}
public void WriteHostEntries()
{
var path = FindHostFile();
var sb = new StringBuilder();
foreach (var host in _hosts)
{
sb.Append(host.WriteLine(true));
sb.Append(Environment.NewLine);
}
using (var writer = new StreamWriter(path, false))
{
writer.WriteLine(sb.ToString());
writer.Flush();
}
}
public int EntryCount
{
get { return _hosts.Count; }
}
public IList<HostEntry> HostEntrys
{
get { return _hosts;}
}
private static string FindHostFile()
{
var tempPath = Environment.SystemDirectory;
tempPath += "\\drivers\\etc\\hosts";
return tempPath;
}
}
HostEntry:
public class HostEntry
{
private bool _isCommented = false;
private string _ipAddress;
private string _domain;
public HostEntry(string entry)
{
ReadHostEntry(entry);
}
public string WriteLine(bool includeCommented)
{
if(includeCommented && IsCommented )
{
return string.Format("{0}{1} {2}","#", IPAddress, Domain );
}
return string.Format("{0} {1}", IPAddress, Domain);
}
private void ReadHostEntry(string entry)
{
var items = entry.Trim().Split('\t', ' ');
foreach (var item in items)
{
if (IsComment(item))
{
IsCommented = true;
if (item.Length > 1)
SetHostEntry(item.Substring(1));
}
SetHostEntry(item);
}
}
private void SetHostEntry(string item)
{
if (IsIPParam(item))
{
_ipAddress = item;
}
else
{
_domain = item;
}
}
private bool IsComment(string item)
{
if(item.Length == 1 && item =="#")
return true;
if(item.IndexOf('#')==0)
return true;
return false;
}
public bool IsCommented
{
get { return _isCommented; }
set { _isCommented = value; }
}
public string IPAddress
{
get { return _ipAddress; }
private set { _ipAddress = value;}
}
public string Domain
{
get { return _domain; }
private set { _domain = value;}
}
public static bool IsIPParam(string param)
{
var testString = param.Split('.')[0];
var ipTemp = 0;
return Int32.TryParse(testString, out ipTemp);
}
}
HostEntryParam:
public class HostEntryParam
{
private string _matchString;
public HostEntryParam(string hostParam)
{
CreateMatchString(hostParam);
}
public string MatchString
{
get { return _matchString; }
private set { _matchString = value; }
}
private void CreateMatchString(string hostParam)
{
//IP or domain
if(hostParam.Length<4)
{
throw new ArgumentException("supplied param is not an IP address or domain name: " + hostParam);
}
if(HostEntry.IsIPParam(hostParam))
{
BuildIPMask(hostParam);
}
else
{
BuildDomainMask(hostParam);
}
}
private void BuildDomainMask(string param)
{
_matchString = param;
}
private void BuildIPMask(string param)
{
var items = param.Split(".".ToCharArray(0,1));
_matchString = items[0];
for(var i =1;i<items.Length;i++)
{
_matchString += ".";
if (items[i] != null)
{
_matchString += items[i];
}
else
{
_matchString += "*";
}
}
_matchString = AddTrailingDot(_matchString);
}
public bool IsMatch(string testString)
{
testString = AddTrailingDot(testString);
if(testString.IndexOf(_matchString)>=0)
return true;
return false;
}
private string AddTrailingDot(string testString)
{
if (testString.Split('.').Length!=4 && testString.LastIndexOf(".") < testString.Length + 1)
{
testString += ".";
}
return testString;
}
}
HostProcessor:
public class HostProcessor
{
private HostEntries _hostEntries;
private Params _hostParams;
private IList<HostEntryParam> _hostEntryParams;
public HostProcessor(Params hostParams, HostEntries hostEntries, IList<HostEntryParam> hostEntryParams)
{
_hostEntries = hostEntries;
_hostParams = hostParams;
_hostEntryParams = hostEntryParams;
}
public void ProcessAction()
{
Params.HostsAction current = _hostParams.CurrentAction;
switch(current)
{
case Params.HostsAction.ShowHelp:
Console.WriteLine(String.Format(" {0} HostU Help *************************** {0} {0} Help -h {0} List -l {0} CommentOut -c {0} Uncomment -u {0} Add -a 127.0.0.1 www.codeverity.com {0} {0} IPAddress and domain partial matching:{0} 127.0 or 127.0.0 or 127.0.0.1 {0} or www.code or www.codeverity or www.codeverity.com", Environment.NewLine));
Console.WriteLine(String.Format(" {0}{0} Samples {0} -l www.codeverity.com {0} -l 127.0 {0} -l 127.0.0.1 www.codeverity.com {0} -l 127.0 www.code {0} partial matching applies to comment and uncomment as well. {0} Add requires full IP and domain", Environment.NewLine));
break;
case Params.HostsAction.List:
ListCurrentEntries();
break;
case Params.HostsAction.CommentOut:
CommentOutMatchingEntries();
WriteEntries();
break;
case Params.HostsAction.Add:
AddNewEntry();
WriteEntries();
break;
case Params.HostsAction.Uncomment:
UncommentMatchingEntries();
WriteEntries();
break;
default:
ListCurrentEntries();
break;
}
}
private void WriteEntries()
{
_hostEntries.WriteHostEntries();
}
private void UncommentMatchingEntries()
{
foreach (HostEntry entry in _hostEntries.HostEntrys)
{
if (_hostEntryParams[0].IsMatch(entry.WriteLine(true)) && _hostEntryParams.Count == 1)
{
entry.IsCommented=false;
continue;
}
if (_hostEntryParams[0].IsMatch(entry.WriteLine(true)) && _hostEntryParams.Count == 2)
{
entry.IsCommented = false;
continue;
}
}
}
private void AddNewEntry()
{
if(_hostEntryParams.Count!=2)
{
throw new ArgumentOutOfRangeException("Can not add an entry unless the full domain and IP address is given. Only param found was: " + _hostEntryParams[0].MatchString);
}
var entry = new HostEntry(_hostEntryParams[0].MatchString + "\t" + _hostEntryParams[1].MatchString);
_hostEntries.HostEntrys.Add(entry);
}
private void CommentOutMatchingEntries()
{
foreach (HostEntry entry in _hostEntries.HostEntrys)
{
if (_hostEntryParams[0].IsMatch(entry.WriteLine(false)) && _hostEntryParams.Count == 1)
{
entry.IsCommented = true;
continue;
}
if (_hostEntryParams[0].IsMatch(entry.WriteLine(false)) && _hostEntryParams.Count == 2)
{
entry.IsCommented = true;
continue;
}
}
}
private void ListCurrentEntries()
{
foreach(HostEntry entry in _hostEntries.HostEntrys)
{
if (_hostEntryParams.Count > 0)
{
if (_hostEntryParams[0].IsMatch(entry.WriteLine(true)) && _hostEntryParams.Count == 1)
{
Console.WriteLine(entry.WriteLine(true));
continue;
}
if (_hostEntryParams[0].IsMatch(entry.WriteLine(true )) && _hostEntryParams.Count == 2)
{
if (_hostEntryParams[1].IsMatch(entry.WriteLine(false)))
{
Console.WriteLine(entry.WriteLine(true));
}
continue;
}
}
else
{
Console.WriteLine(entry.WriteLine(true));
}
}
}
}
Params:
public class Params
{
public enum HostsAction
{
Add = 0,
List = 1,
Uncomment = 2,
CommentOut = 3,
ShowHelp = 4
}
private int _count;
private const int FirstParam = 0;
private const int SecondParam = 1;
private const int ThirdParam = 2;
private List<HostEntryParam> _hostEntryParams = new List<HostEntryParam>();
public Params(string [] hostparams)
{
Count = hostparams.Count();
if(Count>3)
{
throw new ArgumentException(String.Format("App was supplied {0} arguments. Only 3 are supported" , Count));
}
var paramValue = "-l";
if(Count>0)
{
paramValue = hostparams[FirstParam];
}
switch(paramValue)
{
case "-a":
CurrentAction = HostsAction.Add;
break;
case "-l":
CurrentAction = HostsAction.List;
break;
case "-u":
CurrentAction = HostsAction.Uncomment;
break;
case "-c":
CurrentAction = HostsAction.CommentOut;
break;
case "-h":
CurrentAction = HostsAction.ShowHelp;
break;
default:
CurrentAction = HostsAction.List;
break;
}
if (Count > 1)
{
var hostEntry = new HostEntryParam(hostparams[SecondParam]);
_hostEntryParams.Add(hostEntry);
HostEntryParam entryTwo = null;
if (Count == 3)
{
hostEntry = new HostEntryParam(hostparams[ThirdParam]);
_hostEntryParams.Add(hostEntry);
}
}
}
public HostsAction CurrentAction { get; set; }
public List<HostEntryParam> HostEntryParams
{
get {return _hostEntryParams;}
private set {_hostEntryParams =value; }
}
public int Count
{
get { return _count; }
private set { _count = value; }
}
}
Program:
class Program
{
static void Main(string[] args)
{
var arguments = new Params(args);
var hostEntries = new HostEntries();
IList<HostEntryParam> hostEntryParams = arguments.HostEntryParams;
var processor = new HostProcessor(arguments, hostEntries, hostEntryParams);
processor.ProcessAction();
}
}
As you can see from the Program.cs code above. The Program itself does very little. The first step is to create the Params class and pass in the arguments that my static void Main(string [] args) takes in. If the count of args is greater than what is expected the app throws an exception. If, however, the args are less than 3 it simply figures out the best guess for what you wanted to do and does it. If no args are passed it is the same as calling: Hostu –l or ‘list all’. I considered outputting help, but decided that since I wrote it, and I’m the only one using it, help would be rather silly.
Next entry I’ll dig into some of the design decisions and analyze how this could have been done better.



