Sample code for parsing FtpwebRequest response for ListDirectoryDetails

Published 15 September 04 05:33 PM | adarshk 

This posting is valid for .Net frameworks 2.0 (Currently released as Whidbey Beta1)

ResponseStream of FtpWebResponse provides the raw data bytes to the user, some of you had asked that it would be more useful to provide methods which return list of directory and files on ListDirectory request to the server. Current .Net frameworks doesn't support this, so here is some sample code I had written for parsing, in general I found it works very well against most of the server. But just to make sure it is not extensively tested, so treat only as sample, its not guaranteed to work against every server.

using System;
using System.IO;
using System.Net;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;

   public struct FileStruct
   {
        public string Flags;
        public string Owner;
        public string Group;
        public bool IsDirectory;
        public DateTime CreateTime;
        public string Name;
    }
   public enum FileListStyle{
        UnixStyle,
        WindowsStyle,
        Unknown  
    }

public class ParseListDirectory
{
       public static void Main(string[] args)
       {
              if(args.Length < 1)
              {
                  Console.WriteLine("\n Usage FTPListDirParser <uriString>");
                  return;
              }
              try
              {
                     FtpWebRequest ftpclientRequest = WebRequest.Create(args[0]) as FtpWebRequest;
                     ftpclientRequest.Method = FtpMethods.ListDirectoryDetails;
                     ftpclientRequest.Proxy = null;  
                     FtpWebResponse response = ftpclientRequest.GetResponse() as FtpWebResponse;
                     StreamReader sr = new StreamReader(response.GetResponseStream(), System.Text.Encoding.ASCII);
                     string Datastring = sr.ReadToEnd(); 
                     response.Close();
   
                     FileStruct[]  list = (new ParseListDirectory()).GetList(Datastring);
                     Console.WriteLine ("------------After Parsing-----------");
                     foreach(FileStruct thisstruct in list)
                     { 
                            if(thisstruct.IsDirectory)
                                Console.WriteLine("<DIR> "+thisstruct.Name+","+thisstruct.Owner+","+thisstruct.Flags+","+thisstruct.CreateTime);
                            else
                                  Console.WriteLine(thisstruct.Name+","+thisstruct.Owner+","+thisstruct.Flags+","+thisstruct.CreateTime);
                     }  
           }
           catch(Exception e)
           {
                  Console.WriteLine(e);
           }
    }
 
 private FileStruct[] GetList(string datastring)
 {
  List<FileStruct> myListArray = new List<FileStruct>(); 
  string[] dataRecords = datastring.Split('\n');
  FileListStyle _directoryListStyle = GuessFileListStyle(dataRecords);
  foreach (string s in dataRecords)
  {    
   if (_directoryListStyle != FileListStyle.Unknown && s != "")
   {
    FileStruct f = new FileStruct();
    f.Name = "..";
    switch (_directoryListStyle)
    {
     case FileListStyle.UnixStyle:
      f = ParseFileStructFromUnixStyleRecord(s);
      break;
     case FileListStyle.WindowsStyle:
      f = ParseFileStructFromWindowsStyleRecord(s);
      break;
    }
    if (!(f.Name == "." || f.Name == ".."))
    {
     myListArray.Add(f);     
    }    
   }
  }
  return myListArray.ToArray(); ;
 }

 private FileStruct ParseFileStructFromWindowsStyleRecord(string Record)
 {
  ///Assuming the record style as
  /// 02-03-04  07:46PM       <DIR>          Append
  FileStruct f = new FileStruct();
  string processstr = Record.Trim();
  string dateStr = processstr.Substring(0,8);      
  processstr = (processstr.Substring(8, processstr.Length - 8)).Trim();
  string timeStr = processstr.Substring(0, 7);
  processstr = (processstr.Substring(7, processstr.Length - 7)).Trim();
  f.CreateTime = DateTime.Parse(dateStr + " " + timeStr);
  if (processstr.Substring(0,5) == "<DIR>")
  {
   f.IsDirectory = true;    
   processstr = (processstr.Substring(5, processstr.Length - 5)).Trim();
  }
  else
  {
   string[] strs = processstr.Split(new char[] { ' ' }, true);
   processstr = strs[1].Trim();
   f.IsDirectory = false;
  }   
  f.Name = processstr;  //Rest is name   
  return f;
 }


 public FileListStyle GuessFileListStyle(string[] recordList)
 {
  foreach (string s in recordList)
  {
   if(s.Length > 10
    && Regex.IsMatch(s.Substring(0,10),"(-|d)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)(-|r)(-|w)(-|x)"))
   {
    return FileListStyle.UnixStyle;
   }      
   else if (s.Length > 8
    && Regex.IsMatch(s.Substring(0, 8),  "[0-9][0-9]-[0-9][0-9]-[0-9][0-9]"))
   {
    return FileListStyle.WindowsStyle;
   }   
  }
  return FileListStyle.Unknown;
 }

 private FileStruct ParseFileStructFromUnixStyleRecord(string Record)
 {
  ///Assuming record style as
  /// dr-xr-xr-x   1 owner    group               0 Nov 25  2002 bussys
  FileStruct f= new FileStruct();   
  string processstr = Record.Trim();        
  f.Flags = processstr.Substring(0,9);
  f.IsDirectory = (f.Flags[0] == 'd');  
  processstr =  (processstr.Substring(11)).Trim();
  _cutSubstringFromStringWithTrim(ref processstr,' ',0);   //skip one part
  f.Owner = _cutSubstringFromStringWithTrim(ref processstr,' ',0);
  f.Group = _cutSubstringFromStringWithTrim(ref processstr,' ',0);
  _cutSubstringFromStringWithTrim(ref processstr,' ',0);   //skip one part
  f.CreateTime = DateTime.Parse(_cutSubstringFromStringWithTrim(ref processstr,' ',8));    
  f.Name =  processstr;   //Rest of the part is name
  return f;
 }

     private string _cutSubstringFromStringWithTrim(ref string s, char c, int startIndex)
     {
            int pos1 = s.IndexOf(c, startIndex);
           string retString = s.Substring(0,pos1);
           s = (s.Substring(pos1)).Trim();
           return retString;
       }
}
This posting is provided "AS IS" with no warranties, and confers no rights

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Sim said on November 9, 2006 3:31 PM:

Getting the following error while doing this:

   using (FtpWebResponse response = (FtpWebResponse)ftp.GetResponse())

The Remote Server returned an error:(550) File unavailable(e.g., file not found, no access)

# Steffen Xavier xsteffen@ict7.com said on May 8, 2007 8:38 AM:

Replace:

string[] strs = processstr.Split(new char[] { ' ' }, true);

  processstr = strs[1];

By:

processstr = processstr.Remove(0,processstr.IndexOf(' ') + 1);

Otherwise you truncate file name with white spaces.

f.CreateTime = DateTime.Parse(dateStr + " " + timeStr); Can throw a InvalidFormatException. Replace by

f.CreateTime = DateTime.Parse(dateStr + " " + timeStr, CultureInfo.GetCultureInfo("en-US"));

Thanks for you code. He help me.

# Atam said on June 18, 2007 5:06 AM:

Thanks, saved me some time reverse engineering the crap out of this streamreader:)

# DeMi said on September 25, 2007 5:22 AM:

Hay,

Here some code using Regular Expressions which makes life a little easier (besides making the regular expression ;-)

public class FTPLineParser

{

private Regex unixStyle = new Regex(@"^(?<dir>[-dl])(?<ownerSec>[-r][-w][-x])(?<groupSec>[-r][-w][-x])(?<everyoneSec>[-r][-w][-x])\s+(?:\d)\s+(?<owner>\w+)\s+(?<group>\w+)\s+(?<size>\d+)\s+(?<month>\w+)\s+(?<day>\d{1,2})\s+(?<hour>\d{1,2}):(?<minutes>\d{1,2})\s+(?<name>.*)$");

private Regex winStyle = new Regex(@"^(?<month>\d{1,2})-(?<day>\d{1,2})-(?<year>\d{1,2})\s+(?<hour>\d{1,2}):(?<minutes>\d{1,2})(?<ampm>am|pm)\s+(?<dir>[<]dir[>])?\s+(?<size>\d+)?\s+(?<name>.*)$");

public FTPLineResult Parse(string line)

{

Match match = unixStyle.Match(line);

if (match.Success)

{

return ParseMatch(match.Groups, ListStyle.Unix);

}

match = winStyle.Match(line);

if (match.Success)

{

return ParseMatch(match.Groups, ListStyle.Unix);

}

throw new Exception("Invalid line format");

}

private FTPLineResult ParseMatch(GroupCollection matchGroups, ListStyle style)

{

string dirMatch = (style == ListStyle.Unix ? "d" : "<dir>");

FTPLineResult result = new FTPLineResult();

result.Style = style;

result.IsDirectory = matchGroups["dir"].Value.Equals(dirMatch, StringComparison.InvariantCultureIgnoreCase);

result.Name = matchGroups["name"].Value;

if (!result.IsDirectory)

result.Size = long.Parse(matchGroups["size"].Value);

return result;

}

}

public enum ListStyle

{

Unix,

Windows

}

public class FTPLineResult

{

public ListStyle Style;

public string Name;

public DateTime DateTime;

public bool IsDirectory;

public long Size;

}

# DeMi said on September 25, 2007 5:35 AM:

Hay, DeMi again.

I hit the <ENTER> to quick when adding my example :-O

Some details:

In my previous example its better to use the RegexOptions.IgnoreCase option in the RegEx constructor.

private Regex unixStyle = new Regex(@"^...$", RegexOptions.IgnoreCase);

private Regex winStyle = new Regex(@"^...$", RegexOptions.IgnoreCase);

To call the FTPLineParser you can use the code below:

//...

FtpWebResponse ftpResponse = (FtpWebResponse) ftpRequest.GetResponse();

Stream response = ftpResponse.GetResponseStream();

StreamReader responseReader = new StreamReader(response);

string line = null;

FTPLineParser parser = new FTPLineParser();

List<FTPLineResult> lines = new List<FTPLineResult>();

while ((line = responseReader.ReadLine()) != null)

{

FTPLineResult lineResult = parser.Parse(line);

lines.Add(lineResult);

}

DoSomethingWithTheParsedLines(lines);

Hope this is of some help for you.

Enjoy.

Dennis

# Bomzhang said on May 7, 2008 12:31 AM:

Stop! Try to read this interested book:,

# Sntzjyhf said on June 15, 2008 8:52 AM:

Try to look here and may be you find what do you want:,

# Kvovydtq said on June 25, 2008 11:09 AM:

Of course, but what do you think about that?,

# DM said on October 7, 2008 9:11 AM:

Do you know what is refactoring?

# Will said on October 24, 2008 9:10 AM:

Do you have this sample written in VB code?

# Stephen said on April 29, 2009 6:37 AM:

the date.pase() method which parses the concacated date and time was crapping out as my culture was "en-GB", it's betst to add a private field like

private CultureInfo _cultureInfo = CultureInfo.CreateSpecificCulture("en-US");

And then in the line where the date is parsed, it is changed to

f.CreateTime = DateTime.Parse(dateStr + " " + timeStr, _cultureInfo);

that way, it will know that it is parsing a US style date.

great work by the way, this has saved me at least two days of work!

# sunss said on May 13, 2009 5:06 AM:

DeMi's alternative code seems to work as well (and seems a little neater!).  The only bug I've found is changing the 2nd occurence of:

return ParseMatch(match.Groups, ListStyle.Unix);

to

return ParseMatch(match.Groups, ListStyle.Windows);

# tynar said on June 10, 2009 7:11 AM:

Regex for FileZilla Server;

    private Regex fileZilla = new Regex(@"(?<dir>[-dl])(?<ownerSec>)[-r][-w][-x](?<groupSec>)[-r][-w][-x](?<everyoneSec>)[-r][-w][-x]\s+(?:\d)\s+(?<owner>\w+)\s+(?<group>\w+)\s+(?<size>\d+)\s+(?<month>\w+)\s+(?<day>\d{1,2})\s+(?<year>\w+)\s+(?<name>.*)$");

           match = fileZilla.Match(line);

           if (match.Success)

           {

               return ParseMatch(match.Groups, ListStyle.Unix);

           }

# 家出 said on July 4, 2009 11:29 PM:

これから家出したい少女や、現在家出中の娘とそんな娘を助けたい人を繋げるSOS掲示板です。10代、20代の女の子が家庭内の問題などでやむなく家出している子が多数書き込みしています。女の子リストを見て彼女たちにアプローチしてみませんか

# 精神年齢 said on July 5, 2009 11:59 PM:

みんなの精神年齢を測定できる、メンタル年齢チェッカーで秘められた年齢がズバリわかっちゃう!かわいいあの子も実は精神年齢オバサンということも…合コンや話のネタに一度チャレンジしてみよう

Leave a Comment

(required) 
(optional)
(required) 

Search

Go

This Blog

Syndication

Page view tracker