FTPCommon.mesa Common protocol and property lists
Last edited by:
Taft, October 7, 1983 2:23 pm
DIRECTORY
Atom USING [MakeAtom],
Convert USING [IntFromRope, TimeFromRope],
List USING [Nconc],
RefText USING [MakeAtom, MakeAtomFromRefText, TrustTextAsRope];
FTPCommon: CEDAR PROGRAM
IMPORTS Atom, BasicTime, Convert, FTP, FTPInternal, IO, List, PupStream, RefText, Rope
EXPORTS FTP, FTPInternal =
BEGIN OPEN FTP, FTPInternal;
FTP.
Object: PUBLIC TYPE = FTPInternal.Object;
IsOpen: PUBLIC PROCEDURE [h: Handle] RETURNS [open: BOOLEAN] =
{RETURN[h.byteStream # NIL]};
GetClientData: PUBLIC PROCEDURE [h: Handle] RETURNS [data: REF ANY] =
{RETURN[h.clientData]};
SetClientData: PUBLIC PROCEDURE [h: Handle, data: REF ANY] =
{h.clientData ← data};
GetDateProperty: PUBLIC PROCEDURE [h: Handle, prop: DateProperty, list: LocalOrRemote ← remote] RETURNS [value: BasicTime.GMT] =
{RETURN[h.pList[list].date[prop]]};
GetEnumeratedProperty: PUBLIC PROCEDURE [h: Handle, prop: EnumeratedProperty, list: LocalOrRemote ← remote] RETURNS [propValue: EnumPropValue] =
{RETURN[h.pList[list].enumerated[prop]]};
GetNumberProperty: PUBLIC PROCEDURE [h: Handle, prop: NumberProperty, list: LocalOrRemote ← remote] RETURNS [value: INT] =
{RETURN[h.pList[list].number[prop]]};
GetTextProperty: PUBLIC PROCEDURE [h: Handle, prop: TextProperty, list: LocalOrRemote ← remote] RETURNS [value: ROPE] =
{RETURN[h.pList[list].text[prop]]};
GetUserDefinedProperty: PUBLIC PROCEDURE [h: Handle, prop: ATOM, list: LocalOrRemote ← remote] RETURNS [value: ROPE] =
{RETURN[h.pList[list].userDefined.GetPropFromList[prop]]};
SetDateProperty: PUBLIC PROCEDURE [h: Handle, prop: DateProperty, value: BasicTime.GMT] =
{h.pList[local].date[prop] ← value};
SetEnumeratedProperty: PUBLIC PROCEDURE [h: Handle, prop: EnumeratedProperty, value: EnumPropValue] =
{h.pList[local].enumerated[prop] ← value};
SetNumberProperty: PUBLIC PROCEDURE [h: Handle, prop: NumberProperty, value: INT] =
{h.pList[local].number[prop] ← value};
SetTextProperty: PUBLIC PROCEDURE [h: Handle, prop: TextProperty, value: ROPE] =
{h.pList[local].text[prop] ← value};
SetUserDefinedProperty: PUBLIC PROCEDURE [h: Handle, prop: ATOM, value: ROPE] =
{h.pList[local].userDefined.PutPropOnList[prop: prop, val: value]};
ResetProperties: PUBLIC PROCEDURE [h: Handle] =
{h.pList[local]^ ← []};
GetDesiredProperties: PUBLIC PROCEDURE [h: Handle] RETURNS [props: PropertySet, userDefinedProps: LIST OF ATOM] =
{RETURN [h.desiredProps, h.desiredUserDefinedProps]};
SetDesiredProperties: PUBLIC PROCEDURE [h: Handle, props: PropertySet, userDefinedProps: LIST OF ATOMNIL] =
{h.desiredProps ← props; h.desiredUserDefinedProps ← userDefinedProps};
FTPInternal.
GetCommand: PUBLIC PROCEDURE [h: Handle] RETURNS [mark: Mark, code: ReplyCode] =
BEGIN
DO
FlushDataUntilMark[h];
mark ← LOOPHOLE[PupStream.ConsumeMark[h.byteStream]];
IF mark#comment THEN EXIT
ENDLOOP;
code ← IF HasReplyCode[mark] THEN LOOPHOLE[h.byteStream.GetChar[! IO.EndOfStream => GOTO error]] ELSE unspecified;
EXITS
error => h.GenerateFailed[protocolError];
END;
GetText: PUBLIC PROCEDURE [h: Handle, gobbleEOC: BOOLEANFALSE] RETURNS [text: ROPE] =
BEGIN
buffer: REF TEXT = h.GetBuffer[];
text ← NIL;
DO
buffer.length ← h.byteStream.GetBlock[buffer, 0, buffer.maxLength];
text ← text.Cat[Rope.FromRefText[buffer]];
IF h.byteStream.EndOf[] THEN EXIT;
ENDLOOP;
h.ReleaseBuffer[buffer];
IF gobbleEOC THEN
BEGIN
mark: Mark ← GetCommand[h].mark;
IF mark#endOfCommand THEN h.GenerateFailed[protocolError];
END;
END;
GetPList: PUBLIC PROCEDURE [h: Handle, gobbleEOC: BOOLEANFALSE, endOfPropertiesOK: BOOLFALSE] RETURNS [pList: PList] =
BEGIN
Append: PROC [c: CHAR] =
BEGIN
IF buffer.length = buffer.maxLength THEN h.GenerateFailed[badPList, "Property name or value too long"];
buffer[buffer.length] ← c;
buffer.length ← buffer.length+1;
END;
buffer: REF TEXT = h.GetBuffer[];
char: CHARACTER;
pList ← NEW [PListObject ← []];
char ← h.byteStream.GetChar[ !
IO.EndOfStream => IF endOfPropertiesOK THEN GOTO empty ELSE GOTO badPList];
IF char#'( THEN GOTO badPList;
DO
ENABLE IO.EndOfStream => GOTO badPList;
propName: ATOM;
property: Property;
found: BOOLEAN;
char ← h.byteStream.GetChar[];
SELECT char FROM
'( => NULL;
') => EXIT;
ENDCASE => GOTO badPList;
buffer.length ← 0;
DO -- accumulate property name
char ← h.byteStream.GetChar[];
SELECT char FROM
'' => Append[h.byteStream.GetChar[]];
'(, ') => GOTO badPList;
' => EXIT;
ENDCASE => Append[char];
ENDLOOP;
propName ← MakeCanonicalAtom[buffer];
[found, property] ← PropertyLookup[propertyNames, propName];
buffer.length ← 0;
DO -- accumulate property value
char ← h.byteStream.GetChar[];
SELECT char FROM
'' => Append[h.byteStream.GetChar[]];
'( => GOTO badPList;
') => EXIT;
ENDCASE => Append[char];
ENDLOOP;
IF found THEN
BEGIN ENABLE Convert.Error => GOTO badProp;
SELECT property FROM
IN DateProperty =>
pList.date[property] ← Convert.TimeFromRope[RefText.TrustTextAsRope[rope]];
eolConvention =>
BEGIN
eolConvention: EOLConvention;
propName ← MakeCanonicalAtom[buffer];
[found, eolConvention] ← EOLConventionLookup[eolConventionTable, propName];
IF found THEN pList.enumerated[eolConvention] ← [eolConvention: eolConvention]
ELSE GOTO badProp;
END;
type =>
BEGIN
[found, index] ← NameLookup[typeTable, rope];
IF found THEN pList.enumerated[type] ← [type: type]
ELSE GOTO badProp;
END;
IN NumberProperty =>
pList.number[property] ← Convert.IntFromRope[RefText.TrustTextAsRope[rope]];
IN TextProperty =>
pList.text[property] ← Rope.FromRefText[buffer];
desiredProperty =>
BEGIN
propName ← MakeCanonicalAtom[buffer];
[found, property] ← PropertyLookup[propertyNames, propName];
IF found THEN pList.desiredProps[property] ← TRUE
ELSE pList.desiredUserDefinedProps ← CONS[propName, pList.desiredUserDefinedProps];
END;
ENDCASE;
EXITS
badProp => h.GenerateFailed[propertyError[property]];
END
ELSE pList.userDefined ← Atom.PutPropOnList[propList: pList.userDefined, prop: propName, val: Rope.FromRefText[buffer]];
ENDLOOP;
h.ReleaseBuffer[buffer];
IF gobbleEOC THEN
BEGIN
mark: Mark ← GetCommand[h].mark;
IF mark#endOfCommand THEN h.GenerateFailed[protocolError];
END;
EXITS
empty => RETURN [NIL];
badPList => h.GenerateFailed[badPList];
END;
GetYesNo: PUBLIC PROCEDURE [h: Handle, gobbleEOC: BOOLEANFALSE, resumable: FALSE] RETURNS [ok: BOOLEAN] =
BEGIN
mark: Mark;
code: ReplyCode;
[mark, code] ← h.GetCommand[];
SELECT mark FROM
yes => [] ← h.GetText[gobbleEOC];
no => h.GenerateFailed[code, h.GetText[gobbleEOC: gobbleEOC], resumable];
ENDCASE => h.GenerateFailed[protocolError];
RETURN [mark=yes];
END;
GetEOC: PUBLIC PROCEDURE [h: Handle] =
BEGIN
mark: Mark ← h.GetCommand[].mark;
IF mark#endOfCommand THEN GenerateProtocolError[stp, eocExpected, mark];
END;
PutCommand: PUBLIC PROCEDURE [h: Handle, mark: Mark, code: ReplyCode ← unspecified, text: ROPENIL, sendEOC: BOOLEANTRUE] =
BEGIN
PupStream.SendMark[h.byteStream, LOOPHOLE[mark]];
IF HasReplyCode[mark] THEN h.byteStream.PutChar[LOOPHOLE[code]];
IF text.Length[]#0 THEN h.byteStream.PutRope[text];
IF sendEOC THEN PupStream.SendMark[h.byteStream, LOOPHOLE[Mark.endOfCommand]];
END;
PutPList: PUBLIC PROCEDURE [h: Handle, pList: PList, sendEOC: BOOLEANTRUE] =
BEGIN
PutDateProp: PROCEDURE [date: BasicTime.GMT] =
BEGIN
unpack: BasicTime.Unpacked = BasicTime.Unpack[date];
absZone: INTABS[IF unpack.dst THEN unpack.zone-60 ELSE unpack.zone];
Desired syntax is: "dd-mmm-yy hh:mm:ss +hh:mm"
h.byteStream.PutF["%2d-%g-%02d %2d", [integer[unpack.day]], [rope[monthName[unpack.month]]], [integer[unpack.year MOD 100]], [integer[unpack.hour]] ];
h.byteStream.PutF[":%02d:%02d %g%g:%02", [integer[unpack.minute]], [integer[unpack.second]], [character[IF unpack.zone < 0 THEN '- ELSE '+]], [integer[absZone/60]], [integer[absZone MOD 60]] ];
END;
PutRopeProp: PROCEDURE [rope: ROPE] =
BEGIN
FOR i: INT IN [0..rope.Length[]) DO
char: CHARACTER ← rope.Fetch[i];
SELECT char FROM
'(, '), '' => h.byteStream.PutChar[''];
ENDCASE;
h.byteStream.PutChar[char];
ENDLOOP;
END;
h.byteStream.PutChar['(];
FOR property: Property IN Property DO
nonNilProp: BOOLEANSELECT property FROM
IN DateProperty => pList.date[property]#BasicTime.nullGMT,
IN EnumeratedProperty => pList.enumerated[property]#nullEnumPropValue,
IN NumberProperty => pList.number[property]#0,
IN TextProperty => pList.text[property]#NIL,
desiredProperty => FALSE,
ENDCASE => ERROR;
IF nonNilProp THEN
BEGIN
h.byteStream.PutChar['(];
h.byteStream.PutRope[Atom.GetPName[propertyNames[property]]];
h.byteStream.PutChar[' ];
SELECT property FROM
IN DateProperty => PutDateProp[pList.date[property]];
eolConvention => PutRopeProp[Atom.GetPName[eolConventionTable[ pList.enumerated[eolConvention].eolConvention]]];
type => PutRopeProp[Atom.GetPName[typeTable[pList.enumerated[type].type]]];
IN NumberProperty => h.byteStream.Put[[int[pList.number[property]]]];
IN TextProperty => PutRopeProp[pList.text[property]];
ENDCASE => ERROR;
h.byteStream.PutChar[')];
END;
ENDLOOP;
FOR prop: Atom.PropList ← pList.userDefined, prop.rest UNTIL prop=NIL DO
h.byteStream.PutChar['(];
PutRopeProp[Atom.GetPName[prop.first.key]];
h.byteStream.PutChar[' ];
PutRopeProp[prop.first.val];
h.byteStream.PutChar[')];
ENDLOOP;
IF (pList.desiredProps#ALL[FALSE] AND pList.desiredProps#ALL[TRUE]) OR pList.desiredUserDefinedProps#NIL THEN
BEGIN
FOR property: Property IN Property DO
IF pList.desiredProps[property] THEN
BEGIN
h.byteStream.PutChar['(];
h.byteStream.PutRope[Atom.GetPName[propertyNames[desiredProperty]]];
h.byteStream.PutChar[' ];
h.byteStream.PutRope[Atom.GetPName[propertyNames[property]]];
h.byteStream.PutChar[')];
END;
ENDLOOP;
FOR propItem: LIST OF ATOM ← pList.desiredUserDefinedProps, propItem.rest UNTIL propItem=NIL DO
h.byteStream.PutChar['(];
h.byteStream.PutRope[Atom.GetPName[propertyNames[desiredProperty]]];
h.byteStream.PutChar[' ];
PutRopeProp[Atom.GetPName[propItem.first]];
h.byteStream.PutChar[')];
ENDLOOP;
END;
h.byteStream.PutChar[')];
IF sendEOC THEN PupStream.SendMark[h.byteStream, LOOPHOLE[Mark.endOfCommand]];
END;
MarkEncountered: PUBLIC ERROR = CODE;
BadProperty: PUBLIC ERROR = CODE;
NameToPList: PUBLIC PROCEDURE [plist: PList, name: Rope.ROPE, type: FilenameType] =
BEGIN
pos: INT ← 0;
length: INT = name.Length[];
IF pos >= length THEN RETURN;
IF name.Fetch[pos] = '[
THEN BEGIN
end: INT = name.Find["]"];
IF end > pos THEN { plist[device] ← name.Substr[pos+1, end-pos-1]; pos ← end+1 };
IF pos >= length THEN RETURN;
END;
IF name.Fetch[pos] = '<
THEN BEGIN
end: INT = name.Find[">"];
IF end > pos THEN { plist[directory] ← name.Substr[pos+1, end-pos-1]; pos ← end+1 };
IF pos >= length THEN RETURN;
END;
BEGIN
end: INT ← name.Find["!"];
IF end >= 0 THEN plist[version] ← name.Substr[end+1, length-end-1] ELSE end ← length;
plist[nameBody] ← name.Substr[pos, end-pos];
END;
END;
PListToName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [name: Rope.ROPENIL] =
BEGIN
dir: Rope.ROPE = plist[directory];
dirLength: INT = dir.Length[];
IF dirLength # 0 THEN
BEGIN
SELECT dir.Fetch[0] FROM
'> => name ← name.Cat[dir.Substr[1, dirLength-1]];
'< => name ← name.Cat[dir];
ENDCASE => name ← name.Cat["<", dir];
IF dir.Fetch[dirLength - 1] # '> THEN name ← name.Cat[">"];
END;
IF plist[nameBody].Length[] # 0 THEN name ← name.Cat[plist[nameBody]];
IF plist[version].Length[] # 0 THEN
name ← name.Cat[IF type = alto THEN "!" ELSE ";", plist[version]];
END;
Private
HasReplyCode: PROCEDURE [mark: Mark] RETURNS [BOOLEAN] =
Returns TRUE if the mark is among those that carry a ReplyCode in the immediately following data byte.
BEGIN
RETURN [SELECT mark FROM
yes, no, version, mailboxException => TRUE,
ENDCASE => FALSE];
END;
MakeCanonicalAtom: PROCEDURE [text: REF TEXT] RETURNS [atom: ATOM] =
Returns an ATOM for the canonical form of the specified text (property name or other keyword). May modify the text.
BEGIN
FOR i: CARDINAL IN [0..text.length) DO
IF text[i] IN ['A..'Z] THEN text[i] ← text[i]+('a-'A);
ENDLOOP;
RETURN [Atom.MakeAtomFromRefText[text]];
END;
AtomLookup: PROCEDURE [table: NameTable, name: ATOM] RETURNS [found: BOOLEAN, ordinal: CARDINAL] =
BEGIN
FOUNDTRUE;
FOR ordinal IN [0..table.length) DO
IF table[ordinal]=name THEN RETURN;
ENDLOOP;
FOUNDFALSE;
END;
PropertyLookup: PROCEDURE [table: NameTable, name: ATOM] RETURNS [found: BOOLEAN, property: Property] = LOOPHOLE [AtomLookup];
EOLConventionLookup: PROCEDURE [table: NameTable, name: ATOM] RETURNS [found: BOOLEAN, eolConvention: EOLConvention] = LOOPHOLE [AtomLookup];
TypeLookup: PROCEDURE [table: NameTable, name: ATOM] RETURNS [found: BOOLEAN, type: Type] = LOOPHOLE [AtomLookup];
Error handling
GenerateFailed: PUBLIC PROCEDURE [h: Handle, code: FailureCode, text: Rope.ROPENIL, resumable: BOOLEANFALSE] =
BEGIN
IF text=NIL THEN text ← GenerateErrorText[code];
IF resumable THEN SIGNAL h.Failed[code, text, TRUE]
ELSE ERROR h.Failed[code, text, FALSE];
END;
GenerateNo: PUBLIC PROCEDURE [h: Handle, code: FailureCode, text: Rope.ROPENIL] =
BEGIN
IF text=NIL THEN text ← GenerateErrorText[code];
h.PutCommand[mark: no, code: code, text: text, sendEOC: ??what??];
END;
GenerateNoAndFailed: PUBLIC PROCEDURE [h: Handle, code: FailureCode, text: Rope.ROPENIL, resumable: BOOLEANFALSE] =
BEGIN
h.GenerateNo[code, text];
h.GenerateFailed[code, text, resumable];
END;
GenerateErrorString: PUBLIC PROCEDURE [stp: STPOps.Handle, errorCode: STP.ErrorCode, string: Rope.ROPE, code: CHARACTER ← 0C] =
BEGIN
ERROR Error[stp,
ErrorCodeToSTPErrorCode[errorCode, code],
IF string.Length[] # 0 THEN string
ELSE
SELECT errorCode FROM
noSuchHost => "No such host!",
noRouteToNetwork => "No route to network!",
noNameLookupResponse => "Name lookup server is not responding",
alreadyAConnection => "You already have a connection!",
noConnection => "Please open a connection!",
connectionClosed => "Connection closed (local or remote)!",
connectionRejected => "Connection rejected by remote host!",
connectionTimedOut => "Connection timed out!",
accessDenied => "Access denied by remote server!",
illegalUserName => "Invalid or illegal UserName!",
illegalUserPassword => "Invalid or illegal UserPassword!",
illegalUserAccount => "Invalid or illegal UserAccount!",
illegalConnectName => "Invalid or illegal ConnectName!",
illegalConnectPassword => "Invalid or illegal ConnectPassword!",
credentailsMissing => "Name and/or Password not supplied!",
protocolError => "Internal FTP protocol error!",
illegalFileName => "Illegal filename!",
noSuchFile => "File not found!",
requestRefused => "Request refused by remote host!",
accessError => "Illegal access attempt on remote stream!",
undefinedError => "Undefined error!",
ENDCASE => ERROR, code];
END;
GenerateStreamClosingError: PUBLIC PROCEDURE [stp: STPOps.Handle, why: PupStream.CloseReason] =
BEGIN
GenerateErrorString[stp,
SELECT why FROM
localClose, remoteClose => connectionClosed,
noRouteToNetwork => noRouteToNetwork,
transmissionTimeout => connectionTimedOut,
remoteReject => connectionRejected,
ENDCASE => ERROR, NIL];
END;
GenerateProtocolError: PUBLIC PROCEDURE [stp: STPOps.Handle, type: ProtocolError, mark: [0..256), code: CHARACTER ← 0C] =
BEGIN
text: Rope.ROPE = IO.PutFR["%g, mark = %b, code = %b",
[rope[SELECT type FROM
badVersion => "Incompatable protocol version",
badMark => "Invalid or undefined mark byte",
badPList => "Invalid or malformed property list",
eocExpected => "End-Of-Command mark byte expected",
noCode => "error code is required after error mark byte",
ENDCASE => ERROR]],
[integer[mark]],
[integer[code-0C]] ];
ERROR Error[stp, protocolError, text, code];
END;
SelectError: PUBLIC PROCEDURE [stp: STPOps.Handle, s: Rope.ROPE , mark: [0..256)] =
BEGIN
code: CHARACTER ← 0C;
IF mark = markNo OR mark = markComment THEN
BEGIN
IF mark # markComment THEN code ← CollectCode[stp];
stp.remoteString ← CollectString[stp];
GenerateErrorString[stp, requestRefused, IF stp.remoteString.Length[] = 0 THEN s ELSE stp.remoteString, code];
END
ELSE GenerateProtocolError[stp, badMark, mark, code];
END;
Global data and initialization
NameTable: TYPE = RECORD [
SEQUENCE length: CARDINAL OF ATOM];
propertyNames, typeNames, eolConventionNames: NameTable;
nProperties: CARDINAL = Property.LAST.ORD+1;
nTypes: CARDINAL = Type.LAST.ORD+1;
nEOLConventions: CARDINAL = EOLConvention.LAST.ORD+1;
monthName: ARRAY BasicTime.MonthOfYear OF Rope.ROPE = [January: "Jan", February: "Feb", March: "Mar", April: "Apr", May: "May", June: "Jun", July: "Jul", August: "Aug", September: "Sep", October: "Oct", November: "Nov", December: "Dec"];
propertyError: ARRAY Property OF FailureCode = [createDate: illegalCreationDate, readDate: illegalReadDate, writeDate: illegalWriteDate, eolConvention: illegalEOLConversion, type: illegalType, byteSize: illegalByteSize, checksum: badPList, size: badPList, author: illegalAuthor, connectName: illegalConnectName, connectPassword: illegalConnectPassword, device: illegalDevice, directory: illegalDirectory, nameBody: illegalNameBody, serverFileName: illegalServerFileName, userAccount: illegalUserAccount, userName: illegalUserName, userPassword: illegalUserPassword, version: illegalVersion, desiredProperty: badPList];
Init: PROCEDURE =
BEGIN OPEN Atom;
propertyNames ← NEW [NameTable[nProperties]];
propertyNames[Property.author.ORD] ← $author;
propertyNames[Property.byteSize.ORD] ← MakeAtom["byte-size"];
propertyNames[Property.checksum.ORD] ← $checksum;
propertyNames[Property.connectName.ORD] ← MakeAtom["connect-name"];
propertyNames[Property.connectPassword.ORD] ← MakeAtom["connect-password"];
propertyNames[Property.createDate.ORD] ← MakeAtom["creation-date"];
propertyNames[Property.desiredProperty.ORD] ← MakeAtom["desired-property"];
propertyNames[Property.device.ORD] ← $device;
propertyNames[Property.directory.ORD] ← $directory;
propertyNames[Property.eolConvention.ORD] ← MakeAtom["end-of-line-convention"];
propertyNames[Property.nameBody.ORD] ← MakeAtom["name-body"];
propertyNames[Property.readDate.ORD] ← MakeAtom["read-date"];
propertyNames[Property.serverFileName.ORD] ← MakeAtom["server-filename"];
propertyNames[Property.size.ORD] ← $size;
propertyNames[Property.type.ORD] ← $type;
propertyNames[Property.userAccount.ORD] ← MakeAtom["user-account"];
propertyNames[Property.userName.ORD] ← MakeAtom["user-name"];
propertyNames[Property.userPassword.ORD] ← MakeAtom["user-password"];
propertyNames[Property.version.ORD] ← $version;
propertyNames[Property.writeDate.ORD] ← MakeAtom["write-date"];
typeNames ← NEW [NameTable[nTypes]];
typeNames[Type.binary.ORD] ← $binary;
typeNames[Type.text.ORD] ← $text;
typeNames[Type.unknown.ORD] ← $unknown;
eolConventionNames ← NEW [NameTable[nEOLConventions]];
eolConventionNames[EOLConvention.cr.ORD] ← $cr;
eolConventionNames[EOLConvention.crlf.ORD] ← $crlf;
eolConventionNames[EOLConvention.transparent.ORD] ← $transparent;
eolConventionNames[EOLConvention.unknown.ORD] ← $unknown;
END;
Init[];
END.