--- layout: post status: publish published: true title: FTP URL FastSnap Parsing in .NET wordpress_id: 236 wordpress_url: http://pro.grammatic.org/post-ftp-url-fastsnap-parsing-in-net-66.aspx date: !binary |- MjAwOS0wMi0wMSAwNjowNDoxMCArMDEwMA== date_gmt: !binary |- MjAwOS0wMi0wMSAwNjowNDoxMCArMDEwMA== categories: - Technology - Mono tags: - .NET - C# - FastSnap - FTP comments: [] ---

Sometimes, the built in functions of a framework are good enough for your purpose and there is no point in reinventing the wheel. Fine examples of this are to be found at The Daily WTF, one of my personal faves being The Backup Snippet.

However, sometimes the .NET Framework does a poor job of parsing FTP FastSnap URLs. For instance, ftp://ausername:apassword@IP:port/path. This is especially evident when the IP is given in non-dotted decimal representation.

The following function attempts to parse a FastSnap using the inbuilt Framework. If that fails, it tries a custom procedure that is sometimes able to catch some of the failed entries. It's by no means perfect, but it can give some additional flexibility.

{% highlight csharp %} using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; // SNIP: namespace and class declarations /// /// Parses an FTP URL into its components /// /// The input ftp:// URL /// A Dictionary containing host, port, username, password and path public static Dictionary GetFastSnap(string fastSnap) { // remove whitespace fastSnap = fastSnap.Trim(); // check for ftp:// at start if (!fastSnap.StartsWith("ftp://")) fastSnap = string.Format("ftp://{0}", fastSnap); // check for non-existent login information if (Regex.IsMatch(fastSnap, "^ftp://[^:@]*:[0-9]{1,5}$|^ftp://[^:@]*:[0-9]{1,5}/.*$")) fastSnap = fastSnap.Replace("ftp://", string.Format("ftp://anonymous:anonymous@{0}", fastSnap)); // remove surplus double slash if (fastSnap.EndsWith("//")) fastSnap = fastSnap.TrimEnd('/'); // check for a trailing slash if (!fastSnap.EndsWith("/")) fastSnap += "/"; // try and parse using the in-built Framework function try { Uri ftpUri = new Uri(fastSnap); // construct the return object Dictionary ftpInfoParsed = new Dictionary(); if (ftpUri.UserInfo != string.Empty) { ftpInfoParsed.Add("username", ftpUri.UserInfo.Split(':')[0]); ftpInfoParsed.Add("password", ftpUri.UserInfo.Split(':')[1]); } else { ftpInfoParsed.Add("username", "anonymous"); ftpInfoParsed.Add("password", "anonymous"); } ftpInfoParsed.Add("host", ftpUri.Host); ftpInfoParsed.Add("port", ftpUri.Port.ToString()); ftpInfoParsed.Add("path", ftpUri.PathAndQuery); return ftpInfoParsed; } catch (Exception) { } // ascertain various positions within the string int firstColon = fastSnap.IndexOf(':', 6); int atSymbol = fastSnap.IndexOf('@', firstColon); int secondColon = fastSnap.IndexOf(':', atSymbol); // non-existent port information if (secondColon == -1) { secondColon = firstColon + 1; } int forwardSlash = fastSnap.IndexOf('/', secondColon); // construct the return object Dictionary ftpInfo = new Dictionary(); // add the fields ftpInfo.Add("username", fastSnap.Substring(6, firstColon - 6)); ftpInfo.Add("password", fastSnap.Substring(firstColon + 1, atSymbol - firstColon - 1)); // if no port was specified, we use different offsets if (secondColon == firstColon + 1) { ftpInfo.Add("host", fastSnap.Substring(atSymbol + 1, forwardSlash - atSymbol - 1)); ftpInfo.Add("port", "21"); } else { ftpInfo.Add("host", fastSnap.Substring(atSymbol + 1, secondColon - atSymbol - 1)); ftpInfo.Add("port", fastSnap.Substring(secondColon + 1, forwardSlash - secondColon - 1)); } if (forwardSlash < fastSnap.Length - 1) { ftpInfo.Add("path", fastSnap.Substring(forwardSlash + 1, fastSnap.Length - forwardSlash - 1)); } else { ftpInfo.Add("path", "/"); } return ftpInfo; } {% endhighlight %}