<commit_before>﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

namespace System.Net.FtpClient {
	/// <summary>
	/// Parses MLST/MLSD and LIST formats
	/// </summary>
	public class FtpListItem {
		FtpObjectType _objectType = FtpObjectType.Unknown;
		/// <summary>
		/// Gets the type of object (File/Directory/Unknown)
		/// </summary>
		public FtpObjectType Type {
			get { return _objectType; }
			private set { _objectType = value; }
		}

		string _name = null;
		/// <summary>
		/// The file/directory name from the listing
		/// </summary>
		public string Name {
			get { return _name; }
			private set { _name = value; }
		}

		long _size = -1;
		/// <summary>
		/// The file size from the listing, default -1
		/// </summary>
		public long Size {
			get { return _size; }
			set { _size = value; }
		}

		DateTime _modify = DateTime.MinValue;
		/// <summary>
		/// The last write time from the listing
		/// </summary>
		public DateTime Modify {
			get { return _modify; }
			set { _modify = value; }
		}

		#region LIST parsing regular expressions
		/// <summary>
		/// Regular expression used for matching IIS DOS style file listings
		/// </summary>
		Regex RegexDosFile {
			get {
				return new Regex(@"\d+-\d+-\d+\s+\d+:\d+\w+\s+(\d+)\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching IIS DOS style directory listings
		/// </summary>
		Regex RegexDosDirectory {
			get {
				return new Regex(@"\d+-\d+-\d+\s+\d+:\d+\w+\s+<DIR>\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX directory listings
		/// </summary>
		Regex RegexUnixDirectory {
			get {
				return new Regex(@"d[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+\d+\s+\w+\s+\d+\s+\d+:?\d+\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX file listings
		/// </summary>
		Regex RegexUnixFile {
			get {
				return new Regex(@"-[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+(\d+)\s+\w+\s+\d+\s+\d+:?\d+\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX symbolic link listings
		/// </summary>
		Regex RegexUnixLink {
			get {
				return new Regex(@"l[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+(\d+)\s+\w+\s+\d+\s+\d+:?\d+\s+.*->\s+(.*)");
			}
		}
		#endregion

		/// <summary>
		/// Parses DOS and UNIX LIST style listings
		/// </summary>
		/// <param name="listing"></param>
		private void ParseListListing(string listing) {
			Match m;

			m = this.RegexDosDirectory.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.Directory;
				this.Name = m.Groups[1].Value;
			}

			m = this.RegexDosFile.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[1].Value);
				this.Name = m.Groups[2].Value;
			}

			m = this.RegexUnixDirectory.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.Directory;
				this.Name = m.Groups[1].Value;
			}

			m = this.RegexUnixFile.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[1].Value);
				this.Name = m.Groups[2].Value;
			}

			m = this.RegexUnixLink.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[1].Value);
				this.Name = m.Groups[2].Value;
			}
		}

		#region MLS* Parsing
		/// <summary>
		/// Parses MLST and MLSD formats
		/// </summary>
		/// <param name="listing"></param>
		private void ParseMachineListing(string listing) {
			string type = null, modify = null, size = null, name = null;
			string attribute = "";

			// remove any spaces from the beginning. this should
			// not happen unless the output we're parsing is from
			// MLST as opposed to MLSD.
			listing = listing.TrimStart(' ');

			for (int i = 0; i < listing.Length; i++) {
				if (listing[i] == ';') {
					// end of attribute
					string[] parts = attribute.Split('=');

					if (parts.Length == 2) {
						switch (parts[0].ToLower()) {
							case "type":
								type = parts[1].ToLower();
								break;
							case "modify":
								modify = parts[1];
								break;
							case "size":
								size = parts[1];<commit_msg>Added support directory modification times on IIS DOS format listings. File modification time reported in listing is still ignored because I've seen several cases were the seconds included in the listing are always 0 even when that's not the case. MDTM on files in IIS reports an accurate modification time, MDTM on directories in IIS does not work at all.<commit_after>﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

namespace System.Net.FtpClient {
	/// <summary>
	/// Parses MLST/MLSD and LIST formats
	/// </summary>
	public class FtpListItem {
		FtpObjectType _objectType = FtpObjectType.Unknown;
		/// <summary>
		/// Gets the type of object (File/Directory/Unknown)
		/// </summary>
		public FtpObjectType Type {
			get { return _objectType; }
			private set { _objectType = value; }
		}

		string _name = null;
		/// <summary>
		/// The file/directory name from the listing
		/// </summary>
		public string Name {
			get { return _name; }
			private set { _name = value; }
		}

		long _size = -1;
		/// <summary>
		/// The file size from the listing, default -1
		/// </summary>
		public long Size {
			get { return _size; }
			set { _size = value; }
		}

		DateTime _modify = DateTime.MinValue;
		/// <summary>
		/// The last write time from the listing
		/// </summary>
		public DateTime Modify {
			get { return _modify; }
			set { _modify = value; }
		}

		#region LIST parsing
		/// <summary>
		/// Regular expression used for matching IIS DOS style file listings
		/// </summary>
		Regex RegexDosFile {
			get {
				return new Regex(@"(\d+-\d+-\d+\s+\d+:\d+\w+)\s+(\d+)\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching IIS DOS style directory listings
		/// </summary>
		Regex RegexDosDirectory {
			get {
				return new Regex(@"(\d+-\d+-\d+\s+\d+:\d+\w+)\s+<DIR>\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX directory listings
		/// </summary>
		Regex RegexUnixDirectory {
			get {
				return new Regex(@"d[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+\d+\s+\w+\s+\d+\s+\d+:?\d+\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX file listings
		/// </summary>
		Regex RegexUnixFile {
			get {
				return new Regex(@"-[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+(\d+)\s+\w+\s+\d+\s+\d+:?\d+\s+(.*)");
			}
		}

		/// <summary>
		/// Regular expression used for matching UNIX symbolic link listings
		/// </summary>
		Regex RegexUnixLink {
			get {
				return new Regex(@"l[\w-]{9}\s+\d+\s+[\w\d]+\s+[\w\d]+\s+(\d+)\s+\w+\s+\d+\s+\d+:?\d+\s+.*->\s+(.*)");
			}
		}
		
		/// <summary>
		/// Parses DOS and UNIX LIST style listings
		/// </summary>
		/// <param name="listing"></param>
		private void ParseListListing(string listing) {
			Match m;

			m = this.RegexDosDirectory.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.Directory;
				this.Name = m.Groups[2].Value;

                // MDTM doesn't work on directories in all IIS
                // implementations so we're going to take the
                // less than accurate date time reported in the
                // file listing command. The date time reported
                // does not include the seconds, they're always
                // zero in a lot of cases even when that's not
                // the case.
                if (!DateTime.TryParse(m.Groups[1].Value, out _modify)) {
                    this.Modify = DateTime.MinValue;
                }
			}

			m = this.RegexDosFile.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[2].Value);
				this.Name = m.Groups[3].Value;

                // the modify date in the listing never
                // seems to include the seconds, however
                // using MDTM does. if you don't care
                // and want to increase performance a little
                // bit then uncomment the following code.
                //if (!DateTime.TryParse(m.Groups[1].Value, out _modify)) {
                //    this.Modify = DateTime.MinValue;
                //}
			}

			m = this.RegexUnixDirectory.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.Directory;
				this.Name = m.Groups[1].Value;
			}

			m = this.RegexUnixFile.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[1].Value);
				this.Name = m.Groups[2].Value;
			}

			m = this.RegexUnixLink.Match(listing);
			if (m.Success) {
				this.Type = FtpObjectType.File;
				this.Size = long.Parse(m.Groups[1].Value);
				this.Name = m.Groups[2].Value;
			}
		}
        #endregion

        #region MLS* Parsing
        /// <summary>
		/// Parses MLST and MLSD formats
		/// </summary>
		/// <param name="listing"></param>
		private void ParseMachineListing(string listing) {
			string type = null, modify = null, size = null, name = null;
			string attribute = "";

			// remove any spaces from the beginning. this should
			// not happen unless the output we're parsing is from
			// MLST as opposed to MLSD.
			listing = listing.TrimStart(' ');

			for (int i = 0; i < listing.Length; i++) {
				if (listing[i] == ';') {
					// end of attribute
					string[] parts = attribute.Split('=');

					if (parts.Length == 2) {
						switch (parts[0].ToLower()) {
							case "type":
								type = parts[1].ToLower();
								break;
							case "modify":
								modify = parts[1];
								break;
							case "size":
								size = parts[1];
{'fraction_non_alphanumeric': 0.16884843526283494, 'fraction_numerical': 0.004193086520760891, 'mean_word_length': 2.271662763466042, 'pattern_counts': {'":': 6, '<': 63, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4066714', 'n_tokens_mistral': 3925, 'n_tokens_neox': 3240, 'n_words': 842}