{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
  Rev 1.6    3/23/2005 4:52:28 AM  JPMugaas
  Expansion with MLSD and WIN32.ea fact in MLSD directories as described by:

  http://www.raidenftpd.com/kb/kb000000049.htm

  This returns Win32 file attributes including some that Borland does not
  support.

  Rev 1.5    12/8/2004 8:35:18 AM  JPMugaas
  Minor class restructure to support Unisys ClearPath.

  Rev 1.4    11/29/2004 2:45:30 AM  JPMugaas
  Support for DOS attributes (Read-Only, Archive, System, and Hidden) for use
  by the Distinct32, OS/2, and Chameleon FTP list parsers.

  Rev 1.3    10/26/2004 9:27:34 PM  JPMugaas
  Updated references.

  Rev 1.2    6/27/2004 1:45:36 AM  JPMugaas
  Can now optionally support LastAccessTime like Smartftp's FTP Server could.
  I also made the MLST listing object and parser support this as well.

  Rev 1.1    6/4/2004 2:11:00 PM  JPMugaas
  Added an indexed read-only Facts property to the MLST List Item so you can
  get information that we didn't parse elsewhere.  MLST is extremely flexible.

  Rev 1.0    4/20/2004 2:43:20 AM  JPMugaas
  Abstract FTPList objects for reuse.
}

unit IdFTPListTypes;

interface

{$i IdCompilerDefines.inc}

uses
  Classes,
  IdGlobal,
  IdFTPList;

type
  { For FTP servers using OS/2 and other MS-DOS-like file systems that report file attributes }
  TIdDOSAttributes = class(TPersistent)
  protected
    FFileAttributes: UInt32;
    function GetRead_Only: Boolean;
    procedure SetRead_Only(const AValue: Boolean);
    function GetHidden: Boolean;
    procedure SetHidden(const AValue: Boolean);
    function GetSystem: Boolean;
    procedure SetSystem(const AValue: Boolean);
    function GetArchive: Boolean;
    procedure SetArchive(const AValue: Boolean);
    function GetDirectory: Boolean;
    procedure SetDirectory(const AValue: Boolean);
    function GetNormal: Boolean;
    procedure SetNormal(const AValue: Boolean);
  public
    procedure Assign(Source: TPersistent); override;
    function GetAsString: String; virtual;
    function AddAttribute(const AString : String) : Boolean;
  published
    property FileAttributes : UInt32 read FFileAttributes write FFileAttributes;
    property AsString : String read GetAsString;
    // can't be ReadOnly because that's a reserved word
    property Read_Only : Boolean read GetRead_Only write SetRead_Only;
    property Archive : Boolean read GetArchive write SetArchive;
    property System : Boolean read GetSystem write SetSystem;
    property Directory : Boolean read GetDirectory write SetDirectory;
    property Hidden : Boolean read GetHidden write SetHidden;
    property Normal : Boolean read GetNormal write SetNormal;
  end;

  { Win32 Extended Attributes as in WIN32_FIND_DATA data structure in the Windows API.
    Analagous to the System.IO.FileAttributes enumeration in .Net }
  TIdWin32ea = class(TIdDOSAttributes)
  protected
    function GetDevice: Boolean;
    procedure SetDevice(const AValue: Boolean);
    function GetTemporary: Boolean;
    procedure SetTemporary(const AValue: Boolean);
    function GetSparseFile: Boolean;
    procedure SetSparseFile(const AValue: Boolean);
    // this is also called a junction and it works like a Unix Symbolic link to a dir
    function GetReparsePoint: Boolean;
    procedure SetReparsePoint(const AValue: Boolean);
    function GetCompressed: Boolean;
    procedure SetCompressed(const AValue: Boolean);
    function GetOffline: Boolean;
    procedure SetOffline(const AValue: Boolean);
    function GetNotContextIndexed: Boolean;
    procedure SetNotContextIndexed(const AValue: Boolean);
    function GetEncrypted: Boolean;
    procedure SetEncrypted(const AValue: Boolean);
  public
    function GetAsString: String; override;
  published
    property Device : Boolean read GetDevice write SetDevice;
    property Temporary : Boolean read GetTemporary write SetTemporary;
    property SparseFile : Boolean read GetSparseFile write SetSparseFile;
    property ReparsePoint : Boolean read GetReparsePoint write SetReparsePoint;
    property Compressed : Boolean read GetCompressed write SetCompressed;
    property Offline : Boolean read GetOffline write SetOffline;
    property NotContextIndexed : Boolean read GetNotContextIndexed write SetNotContextIndexed;
    property Encrypted : Boolean read GetEncrypted write SetEncrypted;
  end;

  //For NLST and Cisco IOS
  TIdMinimalFTPListItem = class(TIdFTPListItem)
  public
    constructor Create(AOwner: TCollection); override;
  end;

  //This is for some mainframe items which are based on records
  TIdRecFTPListItem = class(TIdFTPListItem)
  protected
    //These are for VM/CMS which uses a record type of file system
    FRecLength : Integer;
    FRecFormat : String;
    FNumberRecs : Integer;
    property RecLength : Integer read FRecLength write FRecLength;
    property RecFormat : String read FRecFormat write FRecFormat;
    property NumberRecs : Integer read FNumberRecs write FNumberRecs;
  end;

  { listing formats that include Creation timestamp information }
  TIdCreationDateFTPListItem = class(TIdFTPListItem)
  protected
    FCreationDate: TDateTime;
  public
    constructor Create(AOwner: TCollection); override;
    property CreationDate: TDateTime read FCreationDate write FCreationDate;

  end;

  // for MLST, MLSD listing outputs
  TIdMLSTFTPListItem = class(TIdCreationDateFTPListItem)
  protected
    FAttributesAvail : Boolean;
    FAttributes :  TIdWin32ea;
    FCreationDateGMT : TDateTime;
    FLastAccessDate: TDateTime;
    FLastAccessDateGMT : TDateTime;
    FLinkedItemName : String;
    //Unique ID for an item to prevent yourself from downloading something twice
    FUniqueID : String;
    //MLIST things
    FMLISTPermissions : String;
    function GetFact(const AName : String) : String;
  public
    constructor Create(AOwner: TCollection); override;
    destructor Destroy; override;
    //Creation time values are for MLSD data output and can be returned by the
    //the MLSD parser in some cases
    property ModifiedDateGMT;
    property CreationDateGMT : TDateTime read FCreationDateGMT write FCreationDateGMT;

    property LastAccessDate: TDateTime read FLastAccessDate write FLastAccessDate;
    property LastAccessDateGMT : TDateTime read FLastAccessDateGMT write FLastAccessDateGMT;

    //Valid only with EPLF and MLST
    property UniqueID : string read FUniqueID write FUniqueID;
    //MLIST Permissions
    property MLISTPermissions : string read FMLISTPermissions write FMLISTPermissions;
    property Facts[const Name: string] : string read GetFact;
    property AttributesAvail : Boolean read FAttributesAvail write FAttributesAvail;
    property Attributes :  TIdWin32ea read FAttributes;
    property LinkedItemName : String read FLinkedItemName write FLinkedItemName;
  end;

  //for some parsers that output an owner sometimes
  TIdOwnerFTPListItem = class(TIdFTPListItem)
  protected
    FOwnerName : String;
  public
    property OwnerName : String read FOwnerName write FOwnerName;
  end;

  { This class type is used by Novell Netware, Novell Print Services for
    Unix with DOS namespace, and HellSoft FTPD for Novell Netware }
  TIdNovellBaseFTPListItem = class(TIdOwnerFTPListItem)
  protected
    FNovellPermissions : String;
  public
    property NovellPermissions : string read FNovellPermissions write FNovellPermissions;
  end;

  //Bull GCOS 8 uses this and Unix will use a descendent
  TIdUnixPermFTPListItem = class(TIdOwnerFTPListItem)
  protected
    FUnixGroupPermissions: string;
    FUnixOwnerPermissions: string;
    FUnixOtherPermissions: string;
  public
    property UnixOwnerPermissions: string read FUnixOwnerPermissions write FUnixOwnerPermissions;
    property UnixGroupPermissions: string read FUnixGroupPermissions write FUnixGroupPermissions;
    property UnixOtherPermissions: string read FUnixOtherPermissions write FUnixOtherPermissions;
  end;

  // Unix and Novell Netware Print Services for Unix with NFS namespace need to use this
  TIdUnixBaseFTPListItem = class(TIdUnixPermFTPListItem)
  protected
    FLinkCount: Integer;
    FGroupName: string;
    FLinkedItemName : string;
  public
    property LinkCount: Integer read FLinkCount write FLinkCount;
    property GroupName: string read FGroupName write FGroupName;
    property LinkedItemName : string read FLinkedItemName write FLinkedItemName;
  end;

  TIdDOSBaseFTPListItem = class(TIdFTPListItem)
  protected
    FAttributes : TIdDOSAttributes;
    procedure SetAttributes(AAttributes : TIdDOSAttributes);
  public
    constructor Create(AOwner: TCollection); override;
    destructor Destroy; override;
    property Attributes : TIdDOSAttributes read FAttributes write SetAttributes;
  end;

{These are needed for interpretting Win32.ea in some MLSD output}
const
  IdFILE_ATTRIBUTE_READONLY             = $00000001;
  IdFILE_ATTRIBUTE_HIDDEN               = $00000002;
  IdFILE_ATTRIBUTE_SYSTEM               = $00000004;
  IdFILE_ATTRIBUTE_DIRECTORY            = $00000010;
  IdFILE_ATTRIBUTE_ARCHIVE              = $00000020;
  IdFILE_ATTRIBUTE_DEVICE               = $00000040;
  IdFILE_ATTRIBUTE_NORMAL               = $00000080;
  IdFILE_ATTRIBUTE_TEMPORARY            = $00000100;
  IdFILE_ATTRIBUTE_SPARSE_FILE          = $00000200;
  IdFILE_ATTRIBUTE_REPARSE_POINT        = $00000400;
  IdFILE_ATTRIBUTE_COMPRESSED           = $00000800;
  IdFILE_ATTRIBUTE_OFFLINE              = $00001000;
  IdFILE_ATTRIBUTE_NOT_CONTENT_INDEXED  = $00002000;
  IdFILE_ATTRIBUTE_ENCRYPTED            = $00004000;

implementation

uses
  IdException,
  IdFTPCommon, SysUtils;

{ TIdMinimalFTPListItem }

constructor TIdMinimalFTPListItem.Create(AOwner: TCollection);
begin
  inherited Create(AOwner);
  FSizeAvail := False;
  FModifiedAvail := False;
end;

{ TIdMLSTFTPListItem }

constructor TIdMLSTFTPListItem.Create(AOwner: TCollection);
begin
  inherited Create(AOwner);
  FAttributesAvail := False;
  FAttributes :=  TIdWin32ea.Create;
end;

destructor TIdMLSTFTPListItem.Destroy;
begin
  FreeAndNil(FAttributes);
  inherited Destroy;
end;

function TIdMLSTFTPListItem.GetFact(const AName: String): String;
var
  LFacts : TStrings;
begin
  LFacts := TStringList.Create;
  try
    ParseFacts(Data, LFacts);
    Result := LFacts.Values[AName];
  finally
    FreeAndNil(LFacts);
  end;
end;

{ TIdDOSBaseFTPListItem }

constructor TIdDOSBaseFTPListItem.Create(AOwner: TCollection);
begin
  inherited Create(AOwner);
  FAttributes := TIdDOSAttributes.Create;
end;

destructor TIdDOSBaseFTPListItem.Destroy;
begin
  FreeAndNil(FAttributes);
  inherited Destroy;
end;

procedure TIdDOSBaseFTPListItem.SetAttributes(
  AAttributes: TIdDOSAttributes);
begin
  FAttributes.Assign(AAttributes);
end;

{ TIdDOSAttributes }

function TIdDOSAttributes.AddAttribute(const AString: String): Boolean;
var
  i : Integer;
  S: String;
begin
  S := UpperCase(AString);
  for i := 1 to Length(S) do begin
    case CharPosInSet(S, i, 'RASHW-D') of
      //R
      1 : Read_Only := True;
      //A
      2 : Archive := True;
      //S
      3 : System := True;
      //H
      4 : Hidden := True;
      //W - W was added only for Distinct32's FTP server which reports 'w' if you
      //write instead of a r for read-only
      5 : Read_Only := False;
      6,7 : ;//for the "-" and "d" that Distinct32 may give
    else
      begin
        Result := False; //failure
        Exit;
      end;
    end;
  end;
  Result := True;
end;

procedure TIdDOSAttributes.Assign(Source: TPersistent);
begin
  if Source is TIdDOSAttributes then begin
    FFileAttributes := (Source as TIdDOSAttributes).FFileAttributes;
  end else begin
    inherited Assign(Source);
  end;
end;

function TIdDOSAttributes.GetAsString: String;
//This is just a handy thing for some programs to try
//to output attribute bits similarly to the DOS
//ATTRIB command
//
//which is like this:
//
//     R     C:\File
//A  SH      C:\File
{$IFDEF STRING_IS_IMMUTABLE}
var
  LSB: TIdStringBuilder;
{$ENDIF}
begin
  {$IFDEF STRING_IS_IMMUTABLE}
  LSB := TIdStringBuilder.Create('      ');
  {$ELSE}
  Result := '      ';
  {$ENDIF}
  if Archive then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[0]{$ELSE}Result[1]{$ENDIF} := 'A'; {do not localize}
  end;
  if System then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[3]{$ELSE}Result[4]{$ENDIF} := 'S'; {do not localize}
  end;
  if Hidden then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[4]{$ELSE}Result[5]{$ENDIF} := 'H'; {do not localize}
  end;
  if Read_Only then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[5]{$ELSE}Result[6]{$ENDIF} := 'R'; {do not localize}
  end;
  {$IFDEF STRING_IS_IMMUTABLE}
  Result := LSB.ToString;
  {$ENDIF}
end;

function TIdDOSAttributes.GetRead_Only: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_READONLY) > 0;
end;

function TIdDOSAttributes.GetHidden: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_HIDDEN) > 0;
end;

function TIdDOSAttributes.GetSystem: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_SYSTEM) > 0;
end;

function TIdDOSAttributes.GetDirectory: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_DIRECTORY) > 0;
end;

function TIdDOSAttributes.GetArchive: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_ARCHIVE) > 0;
end;

function TIdDOSAttributes.GetNormal: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_NORMAL) > 0;
end;

procedure TIdDOSAttributes.SetRead_Only(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_READONLY;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_READONLY);
  end;
end;

procedure TIdDOSAttributes.SetHidden(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_HIDDEN;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_HIDDEN);
  end;
end;

procedure TIdDOSAttributes.SetSystem(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_SYSTEM;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_SYSTEM);
  end;
end;

procedure TIdDOSAttributes.SetDirectory(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_DIRECTORY;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_DIRECTORY);
  end;
end;

procedure TIdDOSAttributes.SetArchive(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_ARCHIVE;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_ARCHIVE);
  end;
end;

procedure TIdDOSAttributes.SetNormal(const AValue: Boolean);
begin
  if AValue then begin
     FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_NORMAL;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_NORMAL);
  end;
end;

{ TIdCreationDateFTPListItem }

constructor TIdCreationDateFTPListItem.Create(AOwner: TCollection);
begin
  inherited Create(AOwner);
  SizeAvail := False;
  ModifiedAvail := False;
end;

{ TIdWin32ea }

function TIdWin32ea.GetDevice: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_DEVICE) > 0;
end;

function TIdWin32ea.GetTemporary: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_TEMPORARY) > 0;
end;

function TIdWin32ea.GetSparseFile: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_SPARSE_FILE) > 0;
end;

function TIdWin32ea.GetReparsePoint: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_REPARSE_POINT) > 0;
end;

function TIdWin32ea.GetCompressed: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_COMPRESSED) > 0;
end;

function TIdWin32ea.GetOffline: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_OFFLINE) > 0;
end;

function TIdWin32ea.GetNotContextIndexed: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_NOT_CONTENT_INDEXED) > 0;
end;

function TIdWin32ea.GetEncrypted: Boolean;
begin
  Result := (FFileAttributes and IdFILE_ATTRIBUTE_ENCRYPTED) > 0;
end;

procedure TIdWin32ea.SetDevice(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_DEVICE;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_DEVICE);
  end;
end;

procedure TIdWin32ea.SetTemporary(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_TEMPORARY;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_TEMPORARY);
  end;
end;

procedure TIdWin32ea.SetSparseFile(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_SPARSE_FILE;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_SPARSE_FILE);
  end;
end;

procedure TIdWin32ea.SetReparsePoint(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_NORMAL;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_NORMAL);
  end;
end;

procedure TIdWin32ea.SetCompressed(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_NORMAL;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_NORMAL);
  end;
end;

procedure TIdWin32ea.SetOffline(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_OFFLINE;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_OFFLINE);
  end;
end;

procedure TIdWin32ea.SetNotContextIndexed(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_NOT_CONTENT_INDEXED;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_NOT_CONTENT_INDEXED);
  end;
end;

procedure TIdWin32ea.SetEncrypted(const AValue: Boolean);
begin
  if AValue then begin
    FFileAttributes := FFileAttributes or IdFILE_ATTRIBUTE_ENCRYPTED;
  end else begin
    FFileAttributes := FFileAttributes and (not IdFILE_ATTRIBUTE_ENCRYPTED);
  end;
end;

function TIdWin32ea.GetAsString: String;
//we'll do this similarly to 4NT
//which renders the bits like this order:
//RHSADENTJPCOI
{$IFDEF STRING_IS_IMMUTABLE}
var
  LSB: TIdStringBuilder;
{$ENDIF}
begin
  {$IFDEF STRING_IS_IMMUTABLE}
  LSB := TIdStringBuilder.Create('             ');
  {$ELSE}
  Result := '             ';
  {$ENDIF}
  if Read_Only then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[0]{$ELSE}Result[1]{$ENDIF} := 'R'; {do not localize}
  end;
  if Hidden then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[1]{$ELSE}Result[2]{$ENDIF} := 'H'; {do not localize}
  end;
  if System then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[2]{$ELSE}Result[3]{$ENDIF} := 'S'; {do not localize}
  end;
  if Archive then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[3]{$ELSE}Result[4]{$ENDIF} := 'A'; {do not localize}
  end;
  if Directory then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[4]{$ELSE}Result[5]{$ENDIF} := 'D'; {do not localize}
  end;
  if Encrypted then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[5]{$ELSE}Result[6]{$ENDIF} := 'E'; {do not localize}
  end;
  if Normal then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[6]{$ELSE}Result[7]{$ENDIF} := 'N'; {do not localize}
  end;
  if Temporary then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[7]{$ELSE}Result[8]{$ENDIF} := 'T'; {do not localize}
  end;
  if ReparsePoint then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[8]{$ELSE}Result[9]{$ENDIF} := 'J'; {do not localize}
  end;
  if SparseFile then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[9]{$ELSE}Result[10]{$ENDIF} := 'P'; {do not localize}
  end;
  if Compressed then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[10]{$ELSE}Result[11]{$ENDIF} := 'C'; {do not localize}
  end;
  if Offline then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[11]{$ELSE}Result[12]{$ENDIF} := 'O'; {do not localize}
  end;
  if NotContextIndexed then begin
    {$IFDEF STRING_IS_IMMUTABLE}LSB[12]{$ELSE}Result[13]{$ENDIF} := 'I'; {do not localize}
  end;
  {$IFDEF STRING_IS_IMMUTABLE}
  Result := LSB.ToString;
  {$ENDIF}
end;

end.
