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.ROPE ← NIL;
local: IO.STREAM ← NIL;
killConnection: BOOLEAN ← TRUE;
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:
BOOL ←
FALSE] = {
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:
BOOL ←
TRUE] = {
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:
BOOL ←
TRUE] = {
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:
BOOL ←
TRUE] = {
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: INT ← ABS[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.ROPE ← SELECT 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.ROPE ← IF 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.
ROPE ←
NIL] = {
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.