1
0
mirror of https://github.com/livingcomputermuseum/IFS.git synced 2026-02-02 06:50:59 +00:00
Files
livingcomputermuseum.IFS/PUP/FTP/PropertyList.cs
2016-12-12 12:42:00 -08:00

368 lines
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace IFS.FTP
{
/// <summary>
/// Defines the well-known set of FTP property names, both Mandatory and Optional.
/// </summary>
public static class KnownPropertyNames
{
// Mandatory
public static readonly string ServerFilename = "Server-Filename";
public static readonly string Type = "Type";
public static readonly string EndOfLineConvention = "End-of-Line-Convention";
public static readonly string ByteSize = "Byte-Size";
public static readonly string Device = "Device";
public static readonly string Directory = "Directory";
public static readonly string NameBody = "Name-Body";
public static readonly string Version = "Version";
// Optional
public static readonly string Size = "Size";
public static readonly string UserName = "User-Name";
public static readonly string UserPassword = "User-Password";
public static readonly string UserAccount = "User-Account";
public static readonly string ConnectName = "Connect-Name";
public static readonly string ConnectPassword = "Connect-Password";
public static readonly string CreationDate = "Creation-Date";
public static readonly string WriteDate = "Write-Date";
public static readonly string ReadDate = "Read-Date";
public static readonly string Author = "Author";
public static readonly string Checksum = "Checksum";
public static readonly string DesiredProperty = "Desired-Property";
// Mail
public static readonly string Mailbox = "Mailbox";
public static readonly string Length = "Length";
public static readonly string DateReceived = "Date-Received";
public static readonly string Opened = "Opened";
public static readonly string Deleted = "Deleted";
}
/// <summary>
/// Defines an FTP PropertyList and methods to work with the contents of one.
/// From the FTP spec:
///
/// "5.1 Syntax of a file property list
///
/// A file property list consists of a string of ASCII characters, beginning with a left parenthesis and
/// ending with a matching right parenthesis. Within that list, each property is represented similarly
/// as a parenthesized list. For example:
/// ((Server-Filename TESTFILE.7)(Byte-Size 36))
///
/// This scheme has the advantage of being human readable, although it will require some form of
/// scanner or interpreter. Nevertheless, this is a rigid format, with minimum flexibility ni form; FTP is
/// a machine-to-machine protocol, not a programming language.
///
/// The first item in each property (delimited by a left parenthesis and a space) is the property name,
/// taken from a fixed but extensible set. Upper- and lower-case letters are considered equivalent in the
/// property name. The text between the first space and the right parenthesis is the property value. All
/// characters in the property value are taken literally, except in accordance with the quoting convention
/// described below.
///
/// All spaces are significant, and multiple spaces may not be arbitrarily included. There should be no space
/// between the two leading parentheses, for example, and a single space separates a property
/// name from the property value. Other spaces in a property value will become part of that value, so
/// that the following example will work properly:
/// ((Server-Filename xxxxx)(Read-Date 23-Jan-76 11:30:22 PST))
///
/// A single apostrophe is used as the quote character in a property value, and should be used before a
/// parenthesis or a desired apostrophe:
/// Don't(!)Goof ==> (PropertyName Don''t'(!')Goof)"
///
///
/// </summary>
public class PropertyList
{
public PropertyList()
{
_propertyList = new Dictionary<string, List<string>>();
}
/// <summary>
/// Parses a property list from the specified string.
/// </summary>
/// <param name="list"></param>
public PropertyList(string list) : this()
{
ParseList(list, 0);
}
/// <summary>
/// Parses a property list from the specified string at the given starting offset.
/// endIndex returns the end of the parsed property list in the string.
/// </summary>
/// <param name="input"></param>
/// <param name="startIndex"></param>
/// <param name="endIndex"></param>
public PropertyList(string input, int startIndex, out int endIndex) : this()
{
endIndex = ParseList(input, startIndex);
}
/// <summary>
/// Indicates whether the Property List contains the specified property
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool ContainsPropertyValue(string name)
{
return (_propertyList.ContainsKey(name.ToLowerInvariant()));
}
/// <summary>
/// Returns the first value for the specified property, if present. Otherwise returns null.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public string GetPropertyValue(string name)
{
name = name.ToLowerInvariant();
if (_propertyList.ContainsKey(name))
{
return _propertyList[name][0];
}
else
{
return null;
}
}
/// <summary>
/// Returns the list of property values associated with the given property name, if present.
/// Otherwise returns null.
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public List<string> GetPropertyValues(string name)
{
name = name.ToLowerInvariant();
if (_propertyList.ContainsKey(name))
{
return _propertyList[name];
}
else
{
return null;
}
}
/// <summary>
/// Sets a single value for the specified property, if present.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void SetPropertyValue(string name, string value)
{
name = name.ToLowerInvariant();
List<string> newpList = new List<string>();
newpList.Add(value);
if (_propertyList.ContainsKey(name))
{
_propertyList[name] = newpList;
}
else
{
_propertyList.Add(name, newpList);
}
}
/// <summary>
/// Sets multiple values for the specified property, if present.
/// </summary>
/// <param name="name"></param>
/// <param name="value"></param>
public void SetPropertyValues(string name, List<string> values)
{
name = name.ToLowerInvariant();
if (_propertyList.ContainsKey(name))
{
_propertyList[name] = values;
}
else
{
_propertyList.Add(name, values);
}
}
/// <summary>
/// Serialize the PropertyList back to its string representation.
/// </summary>
/// <returns></returns>
public override string ToString()
{
StringBuilder sb = new StringBuilder();
// Opening paren
sb.Append("(");
foreach(string key in _propertyList.Keys)
{
foreach (string value in _propertyList[key])
{
sb.AppendFormat("({0} {1})", key, EscapeString(value));
}
}
// Closing paren
sb.Append(")");
return sb.ToString();
}
private string EscapeString(string value)
{
StringBuilder sb = new StringBuilder(value.Length);
for (int i = 0; i < value.Length; i++)
{
if (value[i] == '\'' || value[i] == '(' || value[i] == ')')
{
// Escape this thing
sb.Append('\'');
sb.Append(value[i]);
}
else
{
sb.Append(value[i]);
}
}
return sb.ToString();
}
/// <summary>
/// Parses a string representation of a property list into our hash table.
/// </summary>
/// <param name="list"></param>
private int ParseList(string input, int startOffset)
{
string list = input.Substring(startOffset);
//
// First check the basics; the string must start and end with left and right parens, respectively.
// We do not trim whitespace as there should not be any per the spec.
//
if (!list.StartsWith("(") || !list.EndsWith(")"))
{
throw new InvalidOperationException("Property list must begin and end with parentheses.");
}
//
// Looking good so far; parse individual properties now. These also start and end with
// left and right parens.
//
int index = 1;
//
// Loop until we hit the end of the string (minus the closing paren)
//
while (index < list.Length)
{
// If this is a closing paren, this denotes the end of the property list.
if (list[index] == ')')
{
break;
}
// Start of next property, must begin with a left paren.
if (list[index] != '(')
{
throw new InvalidOperationException("Property must begin with a left parenthesis.");
}
index++;
//
// Read in the full property name. Property names can't have escaped characters in them
// so we don't need to watch out for those, just find the first space.
//
int endIndex = list.IndexOf(' ', index);
if (endIndex < 0)
{
throw new InvalidOperationException("Badly formed property list, no space delimiter found.");
}
string propertyName = list.Substring(index, endIndex - index).ToLowerInvariant();
index = endIndex + 1; // Move past space
//
// Read in the property value. This may contain spaces or escaped characters and it ends with an
// unescaped right paren.
//
StringBuilder propertyValue = new StringBuilder();
while(true)
{
// End of value?
if (list[index] == ')')
{
// Move past closing paren
index++;
// And we're done with this property.
break;
}
// Quoted value?
else if (list[index] == '\'')
{
// Add quoted character
index++;
// Ensure we don't walk off the end of the string
if (index >= list.Length)
{
throw new InvalidOperationException("Invalid property list syntax.");
}
propertyValue.Append(list[index]);
}
// Just a normal character
else
{
propertyValue.Append(list[index]);
}
index++;
// Ensure we don't walk off the end of the string
if (index >= list.Length)
{
throw new InvalidOperationException("Invalid property list syntax.");
}
}
//
// Add name/value pair to the hash table.
//
if (!_propertyList.ContainsKey(propertyName))
{
// New property key
List<string> newpList = new List<string>();
newpList.Add(propertyValue.ToString());
_propertyList.Add(propertyName, newpList);
}
else
{
// Property key with multiple values
_propertyList[propertyName].Add(propertyValue.ToString());
}
}
return index + startOffset + 1;
}
private Dictionary<string, List<string>> _propertyList;
}
}