STPImpl.mesa - Simple/Stream Transfer Protocol
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Evans, Nov 14, 1980 9:35 AM
Mark, Feb 12, 1981 11:39 PM
Smokey, 17-Jul-81 7:48:31
JGS, 17-Aug-81 11:26:24
Sandman, 17-Aug-81 11:32:55
SHayes, 11-Sep-81 16:15:34
Bruce, 20-Oct-81 11:24:17
Johnsson, 16-Dec-81 17:39:42
Karlton, 15-Mar-82 17:26:37
Daniels, 21-Sep-82 13:05:55
Loretta, 27-Oct-82 10:45:58
Davirro, 15-Dec-82 15:32:52
Schmidt, January 20, 1983 7:10 pm
Andrew Birrell, June 1, 1983 5:39 pm
Levin, August 9, 1983 9:59 am
Schroeder, August 10, 1983 5:56 pm
MBrown, September 17, 1983 8:14 pm
HGM, February 20, 1984 10:29:03 pm PST
Russ Atkinson (RRA) April 4, 1985 11:40:25 am PST
Bob Hagmann May 9, 1986 1:51:17 pm PDT
Hal Murray, June 5, 1986 3:30:26 pm PDT
Doug Wyatt, May 16, 1986 3:00:32 pm PDT
Tim Diebert: May 19, 1986 10:22:53 am PDT
DIRECTORY
BasicTime USING [GMT, MonthOfYear, Unpack, Unpacked],
Convert USING [IntFromRope],
IO USING [Close, CreateStream, CreateStreamProcs, EndOf, EndOfStream, Error, GetBlock, GetChar, GetIndex, GetLength, int, PutBlock, PutChar, PutF, PutFR, PutRope, RopeFromROS, ROS, SetIndex, STREAM, StreamProcs, UnsafeBlock, UnsafeGetBlock, UnsafePutBlock],
Pup USING [Address],
PupName USING [NameLookup, Error],
PupStream USING [Abort, CloseReason, ConsumeMark, Create, SendMark, StreamClosing, Timeout, waitForever],
PupWKS USING [ftp],
RefText USING [Append, AppendChar, AppendRope, Equal, Length, ObtainScratch, ReleaseScratch, TrustTextAsRope],
Rope USING [Equal, Fetch, Find, FromRefText, Length, ROPE],
STP USING [Open, DesiredProperties, Access, Completion, CompletionProcType, Confirmation, ConfirmProcType, CredentialsErrors, ErrorCode, FileErrors, FileInfo, FileInfoObject, NoteFileProcType, Type, ValidProperties],
STPBackdoor,
STPPrivate;
STPImpl: CEDAR PROGRAM
IMPORTS BasicTime, Convert, IO, PupName, PupStream, RefText, Rope, STP
EXPORTS STP, STPBackdoor = { OPEN STPPrivate;
ErrorCode: TYPE = STP.ErrorCode;
ROPE: TYPE = Rope.ROPE;
Object: PUBLIC TYPE ~ STPPrivate.Object; -- export to STP
Errors
Error: PUBLIC SIGNAL [stp: Handle, code: ErrorCode, error: ROPE, reply: CHAR ← 0C] = CODE;
Global Data
MarkEncountered: ERROR = CODE;
BadProperty: PUBLIC ERROR = CODE;
propertyStrings: PListArray = [
userName: "User-Name",
userPassword: "User-Password",
connectName: "Connect-Name",
connectPassword: "Connect-Password",
byteSize: "Byte-Size",
type: "Type",
size: "Size",
directory: "Directory",
nameBody: "Name-Body",
version: "Version",
createDate: "Creation-Date",
readDate: "Read-Date",
writeDate: "Write-Date",
author: "Author",
eolConversion: "End-of-Line-Convention",
account: "Account",
userAccount: "User-Account",
device: "Device",
serverName: "Server-Filename"];
desiredPropString: REF TEXT = "Desired-property";
Public Interface Routines
Close: PUBLIC PROC [stp: Handle] = {
nilByteStream: BOOLEAN;
IF stp = NIL THEN RETURN;
nilByteStream ← stp.byteStream = NIL;
CloseInternal[stp];
IF nilByteStream THEN
ERROR Error[stp: stp, code: noConnection, error: "Attempt to Close a NIL connection"];
};
CloseInternal: PROC [stp: Handle] = {
ResetPList[stp.plist];
IF stp.byteStream # NIL THEN { IO.Close[stp.byteStream]; stp.byteStream ← NIL; };
};
Create: PUBLIC PROC RETURNS [stp: Handle] = {
stp ← NEW[Object ← []];
stp.info ← NEW[STP.FileInfoObject ← []];
stp.remoteString ← NEW[TEXT[100]];
stp.plist ← NEW[PListArray];
FOR p: ValidProperties IN ValidProperties DO
stp.plist[p] ← NEW[TEXT[100]];
ENDLOOP;
FOR u: UserProperties IN UserProperties DO
stp.userState[u] ← NEW[TEXT[100]];
ENDLOOP;
RETURN[stp];
};
Destroy: PUBLIC PROC [stp: Handle] RETURNS [Handle] = {
IF stp = NIL THEN RETURN[NIL];
IF stp.byteStream # NIL THEN Close[stp];
RETURN[NIL];
};
IsOpen: PUBLIC PROC [stp: Handle] RETURNS [yes: BOOL] = {
IF stp.byteStream = NIL THEN RETURN[FALSE];
yes ← TRUE;
[] ← IO.EndOf[stp.byteStream ! PupStream.StreamClosing => { yes ← FALSE; CONTINUE; } ];
};
Open: PUBLIC PROC [stp: Handle, host: ROPE] RETURNS [herald: ROPE] = {
reason: PupStream.CloseReason;
IF stp = NIL THEN RETURN["\"NIL\" STP handle"];
IF IsOpen[stp] THEN
ERROR Error[stp, alreadyAConnection, "You already have a connection?"];
IF stp.byteStream # NIL THEN CloseInternal[stp];
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
server: Pup.Address = PupName.NameLookup[host, PupWKS.ftp !
PupName.Error => GenerateErrorString[stp, noSuchHost, text]];
stp.host ← NIL;
stp.byteStream ← PupStream.Create[server, PupStream.waitForever, PupStream.waitForever];
stp.gotMark ← FALSE;
PutCommand[stp, iAmVersion, 1C, "STP calling"];
{
code: CHAR;
mark: Mark;
[mark, code] ← GetCommand[stp];
IF mark # iAmVersion THEN GenerateProtocolError[stp, badVersion, mark, code];
ErrorIfNextNotEOC[stp];
herald ← Rope.FromRefText[stp.remoteString];
};
stp.host ← host;
EXITS StreamClosing => {CloseInternal[stp]; GenerateStreamClosingError[stp, reason]};
};
};
Error generation routines
ErrorCodeToSTPErrorCode: PROC [errorCode: ErrorCode, code: CHAR] RETURNS [ErrorCode] = {
replyCode: Reply = LOOPHOLE[code];
RETURN[SELECT replyCode FROM
null => errorCode,
badCommand => protocolError,
noUserName => illegalUserName,
illegalCommand => protocolError,
badPList => protocolError,
illegalServerFilename => illegalFileName,
illegalDirectory => illegalFileName,
illegalNameBody => illegalFileName,
illegalVersion => illegalFileName,
illegalType => accessError,
illegalCharacterSize => accessError,
illegalEOLConversion => accessError,
illegalUserName => illegalUserName,
illegalUserPassword => illegalUserPassword,
illegalUserAccount => illegalUserAccount,
illegalConnectName => illegalConnectName,
illegalConnectPassword => illegalConnectPassword,
illegalCreationDate => illegalFileName,
illegalWriteDate => illegalFileName,
illegalReadDate => illegalFileName,
illegalAuthor => illegalFileName,
illegalDevice => illegalFileName,
fileNotFound => noSuchFile,
accessDenied => accessDenied,
inconsistent => protocolError,
fileDataError => errorCode,
tooLong => requestRefused,
dontSend => errorCode,
notCompleted => errorCode,
transientError => errorCode,
permanentError => errorCode,
fileBusy => accessError,
ENDCASE => errorCode] -- can't do any better--};
GenerateError: PROC [stp: Handle, errorCode: ErrorCode, code: CHAR ← 0C] = {
string: ROPE NIL;
IF RefText.Length[stp.remoteString] # 0 THEN string ← Rope.FromRefText[stp.remoteString];
GenerateErrorString[stp, errorCode, string, code];
};
GenerateErrorString: PROC [stp: Handle, errorCode: ErrorCode, string: ROPE, code: CHAR ← 0C] = {
ERROR Error[stp,
ErrorCodeToSTPErrorCode[errorCode, code],
IF Rope.Length[string] # 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];
};
GenerateStreamClosingError: PROC [stp: Handle, why: PupStream.CloseReason] = {
GenerateErrorString[stp,
SELECT why FROM
localClose, remoteClose => connectionClosed,
noRouteToNetwork => noRouteToNetwork,
transmissionTimeout => connectionTimedOut,
remoteReject => connectionRejected,
ENDCASE => ERROR, NIL];
};
GenerateProtocolError: PROC [ stp: Handle, type: ProtocolError, mark: Mark, code: CHAR ← 0C] = {
text: 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.ORD]],
[integer[code.ORD]] ];
ERROR Error[stp, protocolError, text, code];
};
SelectError: PROC [stp: Handle, s: ROPE, mark: Mark] = {
code: CHAR ← 0C;
IF mark = no OR mark = comment THEN {
IF mark # comment THEN code ← CollectCode[stp];
CollectString[stp];
GenerateError[stp, requestRefused, code]; }
ELSE GenerateProtocolError[stp, badMark, mark, code];
};
Procedures for normal FTP-like interface
Delete: PUBLIC PROC [stp: Handle, name: Rope.ROPE, confirm: STP.ConfirmProcType, complete: STP.CompletionProcType] = {
DoFiles[stp, name, confirm, complete, delete];
};
Enumerate: PUBLIC PROC [stp: Handle, name: Rope.ROPE, proc: STP.NoteFileProcType] = {
Foo1: STP.ConfirmProcType =
{
RETURN[answer: IF proc[file] = yes THEN do ELSE abort, localStream: NIL];
};
Foo2: STP.CompletionProcType = { };
DoFiles[stp, name, Foo1, Foo2, directory];
};
Rename: PUBLIC PROC [stp: Handle, old, new: Rope.ROPE] = {
mark, saveMark: Mark;
code: CHAR ← 0C;
CheckConnection[stp];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, old];
PutPList[stp, rename, FALSE];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, new];
PutPList[stp, null];
[saveMark, code] ← GetCommand[stp];
IF (mark ← MyGetMark[stp]) # eoc THEN GenerateProtocolError[stp, eocExpected, mark];
IF saveMark # yes THEN GenerateError[stp, requestRefused, code];
};
Retrieve: PUBLIC PROC [stp: Handle, file: Rope.ROPE, confirm: STP.ConfirmProcType ← NIL, complete: STP.CompletionProcType ← NIL] = {
DoFiles[stp, file, confirm, complete, retrieve];
};
Store: PUBLIC PROC [stp: Handle, file: Rope.ROPE, stream: IO.STREAM, noteFile: STP.NoteFileProcType ← NIL, fileType: STP.Type, creation: BasicTime.GMT] = {
size: INT ← 0;
Confirm: STP.ConfirmProcType = {
IF noteFile = NIL OR noteFile[file] = yes THEN
RETURN[do, stream] ELSE RETURN[skip, NIL]};
CheckConnection[stp];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, file];
IF fileType = unknown THEN fileType ← FindFileType[stream];
SetFileType[stp, fileType];
SetByteSize[stp, fileType];
SetCreateTime[stp, creation];
size ← stream.GetLength[ ! IO.Error => CONTINUE;];
IF size # 0 THEN SetPListItem[stp.plist, "Size", IO.PutFR["%g", IO.int[size]]];
DoFiles[stp, file, Confirm, NIL, store];
};
common routine for Delete, Enumerate and Retrieve
DoFiles: PUBLIC PROC [stp: Handle, file: Rope.ROPE, confirm: STP.ConfirmProcType, complete: STP.CompletionProcType, op: STPPrivate.Operation] = {
reason: PupStream.CloseReason;
reply: STP.Confirmation;
string: Rope.ROPENIL;
local: IO.STREAMNIL;
killConnection: BOOLEANTRUE;
CleanUp: PROC =
{
IF killConnection THEN SmashClosed[stp];
};
{ENABLE {
PupStream.StreamClosing => { reason ← why; GOTO StreamClosing; };
PupStream.Timeout => { reason ← transmissionTimeout; GOTO StreamClosing; };
UNWIND => CleanUp[]};
IF op # store THEN {
CheckConnection[stp];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, file]; };
IF op = directory THEN
SELECT TryNewDirectory[stp, confirm ! Error =>
IF code IN STP.CredentialsErrors OR code IN STP.FileErrors THEN
killConnection ← FALSE] FROM
finished => RETURN;
aborted => { CleanUp[]; RETURN; };
tryOldDirectory => NULL; -- just falls thru
ENDCASE => ERROR;
PutPList[
stp,
SELECT op FROM
delete => delete,
directory => directory,
retrieve => retrieve,
store => newStore,
ENDCASE => ERROR];
DO
IF LookAtMark[stp] = eoc THEN { [] ← MyGetMark[stp]; EXIT; };
GetHereIsAndPList[stp, op # directory ! Error =>
IF code IN STP.CredentialsErrors OR code IN STP.FileErrors THEN
killConnection ← FALSE];
SELECT TRUE FROM
confirm = NIL => {reply ← do; local ← NIL};
ENDCASE => [reply, local] ← confirm[string ← MakeRemoteName[stp.plist]];
SELECT reply FROM
do => {
completion: STP.Completion;
SELECT op FROM
delete => {
code: CHAR ← 0C;
mark: Mark;
PutCommand[stp, yes, 0C, "Yes, please"];
[mark, code] ← GetCommand[stp];
SELECT mark FROM
yes => completion ← ok;
no =>
IF complete = NIL THEN GenerateError[stp, requestRefused, code]
ELSE completion ← error;
ENDCASE => GenerateError[stp, protocolError, code]};
retrieve => {
code: CHAR ← 0C;
gotIt: BOOLEAN;
PutCommand[stp, yes, 0C, "Yes, please"];
[gotIt, code] ← GetFile[stp, local];
completion ← IF gotIt THEN ok ELSE error;
IF completion = error AND complete = NIL THEN
GenerateError[stp, requestRefused, code]; };
store => {
PutFile[stp, local];
ErrorIfNextNotYes[stp]};
ENDCASE;
IF complete # NIL THEN complete[completion, Rope.FromRefText[stp.remoteString]];
IF LookAtMark[stp] = eoc THEN { [] ← MyGetMark[stp]; EXIT; }; };
skip => {
IF op # directory THEN PutCommand[stp, no, 106C, "No Thanks"];
IF op = store THEN {
mark: Mark;
code: CHAR;
[mark, code] ← GetCommand[stp];
IF mark # no THEN GenerateError[stp, protocolError, code]};
};
abort => { CleanUp[]; RETURN; };
ENDCASE => ERROR;
ResetPList[stp.plist];
ENDLOOP;
EXITS StreamClosing => GenerateStreamClosingError[stp, reason]};
};
TryNewDirectory: PROC[stp: Handle, confirm: STP.ConfirmProcType] RETURNS [{finished, aborted, tryOldDirectory}] = {
mark: Mark;
PutPList[stp, newDirectory];
DO
SELECT (mark ← MyGetMark[stp]) FROM
comment => CollectString[stp];
hereIsPList => {
string: Rope.ROPE;
reply: STP.Confirmation;
DO
GetPList[stp: stp, gobbleEOC: FALSE, propertiesOk: TRUE
! MarkEncountered => {
mark ← LookAtMark[stp];
IF mark = eoc THEN EXIT
ELSE GenerateProtocolError[stp, eocExpected, mark]
}];
string ← MakeRemoteName[stp.plist];
[answer: reply] ← confirm[string];
IF reply = abort THEN RETURN[aborted];
ENDLOOP;
ErrorIfNextNotEOC[stp];
RETURN[finished];
};
no => {
code: CHAR = CollectCode[stp];
errorCode: STP.ErrorCode = ErrorCodeToSTPErrorCode[requestRefused, code];
CollectString[stp];
ErrorIfNextNotEOC[stp];
SELECT errorCode FROM
protocolError, requestRefused => RETURN[tryOldDirectory];
RRA: sometimes the new directory sends so much stuff that poor old servers can't handle it, so we try to use the old protocol.
ENDCASE;
IF LOOPHOLE[code, Reply] = badCommand THEN
RETURN[tryOldDirectory];
GenerateError[stp, errorCode, code];
};
ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
ENDLOOP;
};
Procedures for doing FTP protocol operations
GetFile: PROC [stp: Handle, stream: IO.STREAM] RETURNS [gotIt: BOOLEAN, code: CHAR] = {
mark: Mark;
CheckConnection[stp];
SELECT (mark ← MyGetMark[stp]) FROM
hereIsFile => NULL;
no => {
code ← CollectCode[stp];
CollectString[stp];
gotIt ← FALSE;
RETURN};
ENDCASE => SelectError[stp, "HereIsFile Expected", mark];
TransferTheFile[stp: stp, from: stp.byteStream, to: stream];
stp.mark ← VAL[PupStream.ConsumeMark[stp.byteStream]];
stp.gotMark ← TRUE;
ErrorIfNextNotYes[stp]; -- should do something about file on local disk
gotIt ← TRUE;
RETURN
};
PutFile: PROC [stp: Handle, stream: IO.STREAM] = {
CheckConnection[stp];
SendMark[stp.byteStream, hereIsFile];
TransferTheFile[stp: stp, from: stream, to: stp.byteStream];
PutCommand[stp, yes, 0C, "Transfer Completed", TRUE];
};
TransferTheFile: PROC [stp: Handle, from, to: IO.STREAM] = {
buffer: REF TEXT = RefText.ObtainScratch[512];
DO
nBytes: NAT = IO.GetBlock[from, buffer, 0];
IF nBytes = 0 THEN EXIT;
IO.PutBlock[to, buffer, 0, nBytes];
ENDLOOP;
RefText.ReleaseScratch[buffer];
};
SmashClosed: PROC [stp: Handle] = {
IF stp = NIL OR stp.byteStream = NIL THEN RETURN;
First smash connection so it will not give us any grief, THEN close it
PupStream.Abort[stp.byteStream, "Unwinding..."];
Close[stp ! Error => IF code = noConnection THEN CONTINUE];
};
FindFileType: PROC [stream: IO.STREAM] RETURNS [fileType: STP.Type] = {
ENABLE IO.Error => IF ec = NotImplementedForThisStream THEN GOTO Unknown;
currentIndex: INT = stream.GetIndex[];
buffer: REF TEXT = RefText.ObtainScratch[512];
fileType ← text;
DO
nBytes: NAT = IO.GetBlock[stream, buffer, 0];
IF nBytes = 0 THEN EXIT;
FOR i: NAT IN [0..buffer.length) DO
IF buffer[i] > 177C THEN GOTO Binary;
ENDLOOP;
REPEAT Binary => fileType ← binary;
ENDLOOP;
RefText.ReleaseScratch[buffer];
stream.SetIndex[currentIndex];
EXITS Unknown => fileType ← unknown;
};
Procedures implementing STP interface
remoteStreamProcs: REF IO.StreamProcs = IO.CreateStreamProcs[
variety: $inputOutput, class: $FTP,
endOf: EndOf,
unsafeGetBlock: UnsafeGetBlock,
putChar: PutChar,
unsafePutBlock: UnsafePutBlock,
close: DeleteRemoteStream];
CreateRemoteStream: PUBLIC PROC [stp: Handle, file: Rope.ROPE, access: STP.Access, fileType: STP.Type, creation: BasicTime.GMT] RETURNS [stream: IO.STREAM] = {
rs: RemoteStream = NEW[RemoteObject ← [access: access, state: initial, stp: stp]];
CheckConnection[stp];
IF stp.remoteStream # NIL THEN ERROR Error[stp, accessError, "Remote Stream Exists"];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, file];
SetFileType[stp, fileType];
IF access = write THEN {
SetCreateTime[stp, creation];
SetByteSize[stp, fileType]; };
IF fileType = text THEN stp.plist[eolConversion] ← "CR";
stp.remoteStream ← rs;
RETURN[IO.CreateStream[remoteStreamProcs, rs]]
};
NextFileName: PUBLIC PROC [remoteStream: IO.STREAM] RETURNS [file: Rope.ROPE] = {
rs: RemoteStream ← ConvertHandle[remoteStream];
reason: PupStream.CloseReason;
IF rs.access # read THEN ERROR Error[rs.stp, accessError, NIL];
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
SELECT rs.state FROM
initial => RequestRetrieve[rs];
confirm => {
PutCommand[rs.stp, no, 0C, "No Thanks"];
IF LookAtMark[rs.stp] = eoc THEN { [] ← MyGetMark[rs.stp]; rs.state ← end; }
ELSE GetHereIsAndPList[rs.stp];
};
data => {
ErrorIfNextNotYes[rs.stp];
IF LookAtMark[rs.stp] = eoc THEN { [] ← MyGetMark[rs.stp]; rs.state ← end; }
ELSE { GetHereIsAndPList[rs.stp]; rs.state ← confirm; };
};
complete =>
IF LookAtMark[rs.stp] = eoc THEN { [] ← MyGetMark[rs.stp]; rs.state ← end; }
ELSE { GetHereIsAndPList[rs.stp]; rs.state ← confirm; };
end => NULL;
ENDCASE;
EXITS StreamClosing => GenerateStreamClosingError[rs.stp, reason];
};
RETURN[IF rs.state = end THEN NIL ELSE MakeRemoteName[rs.stp.plist]]
};
Procedures for Remote streams
ConvertHandle: PROC [h: IO.STREAM] RETURNS [rs: RemoteStream] = INLINE {
RETURN[NARROW[h.streamData]] };
UnsafeGetBlock: UNSAFE PROC[self: IO.STREAM, block: IO.UnsafeBlock] RETURNS [nBytesRead: INT] = UNCHECKED {
rs: RemoteStream ← ConvertHandle[self];
reason: PupStream.CloseReason;
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
IF rs.state # data THEN
{
StartRemote[rs, read];
IF rs.state = end THEN RETURN[0];
};
nBytesRead ← rs.stp.byteStream.UnsafeGetBlock[block];
IF rs.stp.byteStream.EndOf[]
THEN {
rs.stp.mark ← VAL[PupStream.ConsumeMark[rs.stp.byteStream]];
rs.stp.gotMark ← TRUE;
SetupForNextOrEnd[rs.stp]; };
EXITS StreamClosing => { rs.state ← end; GenerateStreamClosingError[rs.stp, reason]; };
};
};
EndOf: PROC[self: IO.STREAM] RETURNS [BOOL] = {
rs: RemoteStream ← ConvertHandle[self];
RETURN[rs.state # data OR rs.stp.byteStream.EndOf[]]
};
PutChar: PROC[self: IO.STREAM, char: CHAR] = {
rs: RemoteStream ← ConvertHandle[self];
reason: PupStream.CloseReason;
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
IF rs.state # data THEN StartRemote[rs, write];
rs.stp.byteStream.PutChar[char];
EXITS StreamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
};
};
UnsafePutBlock: PROC [self: IO.STREAM, block: IO.UnsafeBlock] = {
rs: RemoteStream ← ConvertHandle[self];
reason: PupStream.CloseReason;
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
IF rs.state # data THEN StartRemote[rs, write];
rs.stp.byteStream.UnsafePutBlock[block];
EXITS StreamClosing => {rs.state ← end; GenerateStreamClosingError[rs.stp, reason]};
};
};
DeleteRemoteStream: PROC [self: IO.STREAM, abort: BOOLFALSE] = {
rs: RemoteStream ← ConvertHandle[self];
stp: Handle = rs.stp;
{
ENABLE UNWIND => stp.remoteStream ← NIL;
IF rs.access = read THEN {IF rs.state # end THEN SmashClosed[stp]} -- in mid-transfer
ELSE SELECT rs.state FROM
initial => NULL;
data => {
PutCommand[rs.stp, yes, 0C, "Transfer Completed"
! PupStream.StreamClosing => CONTINUE]; -- OK, if closed in advance
ErrorIfNextNotYes[rs.stp];
ErrorIfNextNotEOC[rs.stp]};
confirm, complete, end => NULL; -- worry about later
ENDCASE;
stp.remoteStream ← NIL;};
};
Utilities
SetupForNextOrEnd: PROC [stp: Handle] = {
ErrorIfNextNotYes[stp];
SELECT LookAtMark[stp] FROM
eoc => {[] ← MyGetMark[stp]; stp.remoteStream.state ← end};
hereIsPList => stp.remoteStream.state ← complete;
ENDCASE => GenerateProtocolError[stp, badMark, MyGetMark[stp]];
};
RequestRetrieve: PROC [rs: RemoteStream] = {
PutPList[rs.stp, retrieve];
GetHereIsAndPList[rs.stp ! Error =>
SELECT code FROM
IN STP.CredentialsErrors, IN STP.FileErrors => rs.state ← end;
ENDCASE];
rs.state ← confirm;
};
StartRemote: PROC [rs: RemoteStream, access: STP.Access] = {
reason: PupStream.CloseReason;
IF rs.access # access THEN ERROR Error[rs.stp, accessError, "Remote stream access Error"];
{
ENABLE PupStream.StreamClosing => {reason ← why; GOTO StreamClosing};
SELECT access FROM
read =>
{
IF rs.state = initial THEN RequestRetrieve[rs];
IF rs.state = complete THEN {
IF LookAtMark[rs.stp] = eoc THEN {rs.state ← end; RETURN}
ELSE GetHereIsAndPList[rs.stp]; rs.state ← confirm; };
IF rs.state = confirm THEN {
mark: Mark;
PutCommand[rs.stp, yes, 0C, "Yes, please"];
SELECT (mark ← MyGetMark[rs.stp]) FROM
hereIsFile => NULL;
no => { rs.state ← complete; SelectError[rs.stp, "He says NO", mark]; };
ENDCASE => SelectError[rs.stp, "HereIsFile Expected", mark]; }; };
write => {
PutPList[rs.stp, newStore];
GetHereIsAndPList[rs.stp];
SendMark[rs.stp.byteStream, hereIsFile]; };
ENDCASE => ERROR;
rs.state ← data;
EXITS StreamClosing => GenerateStreamClosingError[rs.stp, reason];
};
};
Commonly used stuff
Connect: PUBLIC PROC [stp: Handle, name, password: Rope.ROPE] = {
stp.userState[connectName] ← Replace[stp.userState[connectName], name];
stp.userState[connectPassword] ← Replace[stp.userState[connectPassword], password];
};
GetProperty: PUBLIC PROC [stp: Handle, prop: ValidProperties] RETURNS [Rope.ROPE] = {
RETURN[Rope.FromRefText[stp.plist[prop]]];
};
Login: PUBLIC PROC [stp: Handle, name, password: Rope.ROPE] = {
stp.userState[userName] ← Replace[stp.userState[userName], name];
stp.userState[userPassword] ← Replace[stp.userState[userPassword], password];
};
SetDirectory: PUBLIC PROC [stp: Handle, directory: Rope.ROPE] = {
stp.userState[directory] ← Replace[stp.userState[directory], directory];
};
Replace: PROC [text: REF TEXT, rope: Rope.ROPE] RETURNS [new: REF TEXT] = {
text.length ← 0;
new ← RefText.AppendRope[text, rope];
};
Procedures for doing FTP protocol operations
GetCommand: PROC [stp: Handle] RETURNS [mark: Mark, code: CHAR] = {
code ← 0C;
CheckConnection[stp];
mark ← MyGetMark[stp];
SELECT mark FROM
abort, comment => CollectString[stp];
iAmVersion, no, yes => { code ← CollectCode[stp]; CollectString[stp]; };
ENDCASE => GenerateProtocolError[stp, badMark, mark, code];
};
GetHereIsAndPList: PROC [stp: Handle, gobbleEOC: BOOLTRUE] = {
mark: Mark;
DO
SELECT (mark ← MyGetMark[stp]) FROM
comment => CollectString[stp];
hereIsPList => {GetPList[stp, gobbleEOC, FALSE]; EXIT};
no => {
code: CHAR = CollectCode[stp];
errorCode: STP.ErrorCode = ErrorCodeToSTPErrorCode[requestRefused, code];
CollectString[stp];
ErrorIfNextNotEOC[stp];
GenerateError[stp, errorCode, code]; };
ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
ENDLOOP;
};
GetPList: PROC [stp: Handle, gobbleEOC: BOOL, propertiesOk: BOOL] = {
property: REF TEXT ← RefText.ObtainScratch[512];
value: REF TEXT ← RefText.ObtainScratch[512];
Append: PROC [c: CHAR] = {
SELECT which FROM
property => property ← RefText.AppendChar[property, c];
value => value ← RefText.AppendChar[value, c];
ENDCASE;
};
parens: INT ← 0;
char: CHAR;
which: {property, value} ← property;
CheckConnection[stp];
stp.plist[nameBody].length ← 0;
DO
char ← MyGetChar[stp ! MarkEncountered => IF NOT propertiesOk THEN GOTO BadPList];
SELECT char FROM
'' =>
Append[MyGetChar[stp ! MarkEncountered => GOTO BadPList]];
'( => {
parens ← parens + 1;
which ← property;
property.length ← 0; };
' =>
IF which = property THEN { which ← value; value.length ← 0; }
ELSE Append[char];
') => {
IF property.length # 0 AND value.length # 0 THEN {
SetPListItem[
stp.plist,
RefText.TrustTextAsRope[property],
RefText.TrustTextAsRope[value] ! BadProperty => CONTINUE];
property.length ← 0;
value.length ← 0;
};
IF (parens ← parens - 1) = 0 THEN EXIT;
};
ENDCASE => Append[char];
ENDLOOP;
IF (property.length # 0 AND value.length # 0) THEN GOTO BadPList;
IF gobbleEOC AND MyGetMark[stp] # eoc THEN GOTO BadPList;
RefText.ReleaseScratch[property];
RefText.ReleaseScratch[value];
EXITS
BadPList => {ResetPList[stp.plist]; GenerateProtocolError[stp, badPList, stp.mark]};
};
SendMark: PROC [stream: IO.STREAM, mark: Mark] = {
PupStream.SendMark[stream, mark.ORD];
};
PutCommand: PROC [stp: Handle, mark: Mark, code: CHAR, string: Rope.ROPE, sendEOC: BOOLTRUE] = {
CheckConnection[stp];
SendMark[stp.byteStream, mark];
stp.byteStream.PutChar[code];
IF string.Length[] # 0 THEN MyPutString[stp.byteStream, string];
IF sendEOC THEN SendMark[stp.byteStream, eoc];
};
PutPList: PROC [stp: Handle, mark: Mark, sendEOC: BOOLTRUE] = {
state: {okay, tryOpen, triedOpen} ← okay;
DoIt: PROC = {
CheckConnection[stp];
IF mark # null THEN SendMark[stp.byteStream, mark];
stp.byteStream.PutChar['(];
FOR i: ValidProperties IN ValidProperties DO
IF stp.plist[i] # NIL AND stp.plist[i].length # 0 THEN
PutPListItem[stp.byteStream, i, stp.plist[i]];
ENDLOOP;
IF stp.desiredProps # ALL[TRUE] THEN PutDesiredProps[stp];
stp.byteStream.PutChar[')];
IF sendEOC THEN SendMark[stp.byteStream, eoc];
};
DO
DoIt[! PupStream.StreamClosing, PupStream.Timeout =>
IF state = okay AND stp.host.Length[] # 0 THEN
{state ← tryOpen; CONTINUE}];
IF state # tryOpen THEN EXIT;
{
savedPList: PList ← stp.plist;
stp.plist ← NIL;
SmashClosed[stp]; -- Call outside catch so Pup monitor unlocked
[] ← STP.Open[stp, stp.host];
stp.plist ← savedPList;
state ← triedOpen;
};
ENDLOOP;
};
Utility routines
CollectString: PROC [stp: Handle] = {
text: REF TEXT ← RefText.ObtainScratch[512];
stp.remoteString.length ← 0;
DO
text.length ← 0;
IF stp.byteStream.GetBlock[text] = 0 THEN EXIT;
stp.remoteString ← RefText.Append[stp.remoteString, text];
ENDLOOP;
stp.mark ← VAL[PupStream.ConsumeMark[stp.byteStream]];
stp.gotMark ← TRUE;
SELECT stp.mark FROM
hereIsPList, eoc => NULL;
ENDCASE => GenerateProtocolError[stp, eocExpected, stp.mark];
RefText.ReleaseScratch[text];
};
CollectCode: PROC [stp: Handle] RETURNS [code: CHAR] = {
code ← 0C;
CheckConnection[stp];
code ← MyGetChar[stp ! MarkEncountered =>
GenerateProtocolError[stp, noCode, MyGetMark[stp]]];
};
CheckConnection: PROC [stp: Handle] = {
WHILE stp.byteStream = NIL DO
SIGNAL Error[stp, noConnection, "Please open a connection!", 0C] ENDLOOP
};
ErrorIfNextNotYes: PROC [stp: Handle] = {
code: CHAR ← 0C;
mark: Mark;
[mark, code] ← GetCommand[stp];
IF mark # yes THEN GenerateError[stp, requestRefused, code];
};
ErrorIfNextNotEOC: PROC [stp: Handle] = {
mark: Mark ← MyGetMark[stp];
IF mark # eoc THEN GenerateProtocolError[stp, eocExpected, mark];
};
LookAtMark: PROC [stp: Handle] RETURNS [Mark] = {
IF ~stp.gotMark THEN
DO
[] ← MyGetChar[stp ! MarkEncountered => CONTINUE];
IF stp.gotMark THEN EXIT;
ENDLOOP;
RETURN[stp.mark]
};
MyGetChar: PUBLIC PROC [stp: Handle] RETURNS [char: CHAR] = {
char ← IO.GetChar[stp.byteStream ! IO.EndOfStream => GOTO End];
EXITS End => {
stp.mark ← VAL[PupStream.ConsumeMark[stp.byteStream]];
stp.gotMark ← TRUE;
ERROR MarkEncountered;
}
};
MyGetMark: PUBLIC PROC [stp: Handle] RETURNS [mark: Mark] = {
mark ← LookAtMark[stp];
stp.gotMark ← FALSE;
RETURN[mark];
};
MyPutString: PUBLIC PROC [byteStream: IO.STREAM, string: Rope.ROPE] = {
byteStream.PutRope[string];
};
MyPutBlock: PUBLIC PROC [byteStream: IO.STREAM, string: REF TEXT] = {
byteStream.PutBlock[string];
};
MyPutStringVal: PUBLIC PROC [byteStream: IO.STREAM, string: REF TEXT] = {
FOR i: INT IN [0..string.length) DO
char: CHAR = string[i];
SELECT char FROM
'(, '), '' => byteStream.PutChar['']
ENDCASE => NULL;
byteStream.PutChar[char];
ENDLOOP;
};
SetCreateTime: PUBLIC PROC [stp: Handle, creation: BasicTime.GMT] = {
month: 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" ];
zoneIndex: TYPE = [4 .. 10];
zoneChars: ARRAY zoneIndex OF CHAR = ['A, 'E, 'C, 'M, 'P, 'Y, 'H];
unpack: BasicTime.Unpacked = BasicTime.Unpack[creation];
absZone: INTABS[IF unpack.dst = yes THEN unpack.zone - 60 ELSE unpack.zone];
Desired syntax is: "dd-mmm-yy hh:mm:ss zzz"
str: IO.STREAM = IO.ROS[];
str.PutF["%02d-%g-%02d", [integer[unpack.day]], [rope[month[unpack.month]]], [integer[unpack.year MOD 100]] ];
str.PutF[" %02d:%02d:%02d ", [integer[unpack.hour]], [integer[unpack.minute]], [integer[unpack.second]] ];
IF (unpack.zone / 60 IN zoneIndex) AND absZone MOD 60 = 0 THEN {
str.PutChar[zoneChars[unpack.zone / 60]];
str.PutChar[IF unpack.dst = yes THEN 'D ELSE 'S];
str.PutChar['T]; }
ELSE str.PutF["%g%g:%02d", [character[IF unpack.zone < 0 THEN '- ELSE '+]],
[integer[absZone/60]], [integer[absZone MOD 60]] ];
stp.plist[createDate] ← Replace[stp.plist[createDate], str.RopeFromROS[]];
};
SetFileType: PUBLIC PROC [stp: Handle, fileType: STP.Type] = {
text: Rope.ROPESELECT fileType FROM text => "Text", binary => "Binary", ENDCASE => NIL;
stp.plist[type] ← Replace[stp.plist[type], text];
};
SetByteSize: PUBLIC PROC [stp: Handle, fileType: STP.Type] = {
text: Rope.ROPEIF fileType = text THEN NIL ELSE "8";
stp.plist[byteSize] ← Replace[stp.plist[byteSize], text];
};
StringToFileSize: PUBLIC PROC [string: REF TEXT] RETURNS [INT] = {
IF RefText.Length[string] = 0 THEN RETURN[0];
RETURN[Convert.IntFromRope[RefText.TrustTextAsRope[string]]];
};
StringToFileType: PUBLIC PROC [string: REF TEXT] RETURNS [type: STP.Type] = {
type ← SELECT TRUE FROM
RefText.Equal["Text", string, FALSE] => text,
RefText.Equal["Binary", string, FALSE] => binary,
ENDCASE => unknown;
};
PList and FileInfo Utilities
NameToPList: PUBLIC PROC [plist: PList, name: Rope.ROPE] = {
pos: INT ← 0;
length: INT = Rope.Length[name];
IF pos >= length THEN RETURN;
IF Rope.Fetch[name, pos] = '[ THEN {
end: INT = Rope.Find[name, "]"];
IF end > pos THEN {
plist[device].length ← 0;
FOR i: INT IN (pos..end) DO
plist[device] ← RefText.AppendChar[plist[device], Rope.Fetch[name, i] ];
ENDLOOP;
pos ← end+1 };
IF pos >= length THEN RETURN; };
IF Rope.Fetch[name, pos] = '< THEN {
end: INT = Rope.Find[name, ">"];
IF end > pos THEN {
plist[directory].length ← 0;
FOR i: INT IN (pos..end) DO
plist[directory] ← RefText.AppendChar[plist[directory], Rope.Fetch[name, i] ];
ENDLOOP;
pos ← end+1 };
IF pos >= length THEN RETURN;
};
{
end: INT ← Rope.Find[name, "!"];
IF end >= 0 THEN {
plist[version].length ← 0;
FOR i: INT IN (end..length) DO
plist[version] ← RefText.AppendChar[plist[version], Rope.Fetch[name, i] ];
ENDLOOP; }
ELSE end ← length;
plist[nameBody].length ← 0;
FOR i: INT IN [pos..end) DO
plist[nameBody] ← RefText.AppendChar[plist[nameBody], Rope.Fetch[name, i] ];
ENDLOOP;
};
};
PListToName: PUBLIC PROC [plist: PList] RETURNS [name: Rope.ROPENIL] = {
text: REF TEXT ← RefText.ObtainScratch[512];
dir: REF TEXT = plist[directory];
dirLength: INT = RefText.Length[dir];
IF dirLength # 0 THEN {
SELECT dir[0] FROM
'> =>
FOR i: INT IN [1..dirLength) DO
text ← RefText.AppendChar[text, dir[i]];
ENDLOOP;
'< => text ← RefText.Append[text, dir];
ENDCASE => {
text ← RefText.AppendChar[text, '<];
text ← RefText.Append[text, dir]; };
IF dir[dirLength - 1] # '> THEN text ← RefText.AppendChar[text, '>]; };
IF plist[nameBody].length # 0 THEN text ← RefText.Append[text, plist[nameBody]];
IF plist[version].length # 0 THEN {
text ← RefText.AppendChar[text, '!];
text ← RefText.Append[text, plist[version]]; };
name ← Rope.FromRefText[text];
RefText.ReleaseScratch[text];
};
MakeRemoteName: PUBLIC PROC [plist: PList] RETURNS [Rope.ROPE] = {
RETURN[IF RefText.Length[plist[serverName]] = 0 THEN PListToName[plist]
ELSE Rope.FromRefText[plist[serverName]] ]};
PutPListItem: PROC [byteStream: IO.STREAM, property: ValidProperties, value: REF TEXT] = {
byteStream.PutChar['(];
MyPutBlock[byteStream, propertyStrings[property]];
byteStream.PutChar[' ];
MyPutStringVal[byteStream, value];
byteStream.PutChar[')];
};
ResetPList: PUBLIC PROC [plist: PList] = {
IF plist = NIL THEN RETURN;
FOR p: ValidProperties IN ValidProperties DO
plist[p].length ← 0;
ENDLOOP;
};
GetFileInfo: PUBLIC PROC [stp: Handle] RETURNS [STP.FileInfo] = {
stp.info^ ← [
directory: Rope.FromRefText[stp.plist[directory]],
body: Rope.FromRefText[stp.plist[nameBody]],
version: Rope.FromRefText[stp.plist[version]],
author: Rope.FromRefText[stp.plist[author]],
create: Rope.FromRefText[stp.plist[createDate]],
read: Rope.FromRefText[stp.plist[readDate]],
write: Rope.FromRefText[stp.plist[writeDate]],
size: StringToFileSize[stp.plist[size]],
type: StringToFileType[stp.plist[type]] ];
RETURN[stp.info];
};
SetPListItem: PUBLIC PROC [plist: PList, property, value: Rope.ROPE] = {
FOR i: ValidProperties IN ValidProperties DO
IF Rope.Equal[property, RefText.TrustTextAsRope[propertyStrings[i]], FALSE] THEN {
plist[i] ← Replace[plist[i], value];
RETURN; };
ENDLOOP;
ERROR BadProperty
};
UserStateToPList: PUBLIC PROC [stp: Handle] = {
FOR i: UserProperties IN UserProperties DO
stp.plist[i].length ← 0;
stp.plist[i] ← RefText.Append[stp.plist[i], stp.userState[i]];
ENDLOOP;
};
Desired Property stuff
SetDesiredProperties: PUBLIC PROC[stp: Handle, props: STP.DesiredProperties] = {
stp.desiredProps ← props
};
GetDesiredProperties: PUBLIC PROC [stp: Handle] RETURNS [props: STP.DesiredProperties] = {
RETURN[stp.desiredProps]
};
PutDesiredProps: PUBLIC PROC [stp: Handle] = {
FOR i: FileProperties IN FileProperties DO
IF stp.desiredProps[i] THEN PutDesiredPropsItem[stp.byteStream, propertyStrings[i]]
ENDLOOP;
};
PutDesiredPropsItem: PUBLIC PROC [byteStream: IO.STREAM, value: REF TEXT] = {
byteStream.PutChar['(];
MyPutBlock[byteStream, desiredPropString];
byteStream.PutChar[' ];
MyPutBlock[byteStream, value];
byteStream.PutChar[')];
};
}.
Bob Hagmann May 9, 1986 1:44:55 pm PDT
merged STPsA, STPsB, STPsC, and STPsD
Hal Murray, May 12, 1986 5:46:15 pm PDT
Pruned many many things out of STPOps
Tim Diebert, May 19, 1986 10:20:30 am PDT Put some STPOps stuff in STPBackdoor.