STPsB.mesa - Simple/Stream Transfer Protocol (FTP-like interface)
Smokey, 17-Jul-81 7:48:31
JGS, 17-Aug-81 11:26:24
Karlton, 15-Mar-82 17:26:37
Loretta, 10-Nov-81 16:07:59
Davirro, 24-Sep-82 10:35:49
Daniels, 21-Sep-82 13:05:55
Andrew Birrell, June 1, 1983 3:51 pm
Schroeder, December 7, 1983 10:40 am
Bob Hagmann January 28, 1986 3:53:34 pm PST
Hadari, February 11, 1986 7:02:54 pm PST
DIRECTORY
BasicTime USING [GMT],
IO USING [EndOf, EndOfStream, Error, GetBlock, GetChar, GetIndex, GetLength, int, PutBlock, PutFR, SetIndex, STREAM],
PupStream USING [CloseReason, ConsumeMark, PupByteStreamAbort, SendMark, StreamClosing, TimeOut],
Rope USING [ROPE],
STP USING [Close, Completion, CompletionProcType, Confirmation, ConfirmProcType, CredentialsErrors, Error, ErrorCode, FileErrors, NoteFileProcType, Type],
STPOps USING [CheckConnection, CollectCode, CollectString, ErrorIfNextNotYes, ErrorIfNextNotEOC, ErrorCodeToSTPErrorCode, GenerateErrorString, GenerateProtocolError, GenerateStreamClosingError, GetCommand, GetHereIsAndPList, GetPList, Handle, LookAtMark, MarkEncountered, MakeRemoteName, markComment, markDelete, markDirectory, markEOC, markHereIsFile, markHereIsPList, markNo, markNewDirectory, markNewStore, markRename, markRetrieve, markYes, MyGetMark, NameToPList, Object, Operation, PList, PutCommand, PutPList, ResetPList, SelectError, SetByteSize, SetCreateTime, SetFileType, SetPListItem, UserStateToPList],
STPReplyCode USING[ReplyCode];
STPsB: CEDAR PROGRAM
IMPORTS IO, PupStream, STP, STPOps
EXPORTS STP, STPOps =
{ OPEN PupStream, STPOps;
Data and types
Object: PUBLIC TYPE = STPOps.Object;
Procedures for normal FTP-like interface
Delete: PUBLIC PROC [stp: STPOps.Handle, name: Rope.ROPE, confirm: STP.ConfirmProcType, complete: STP.CompletionProcType] = {
DoFiles[stp, name, confirm, complete, delete];
};
Enumerate: PUBLIC PROC [stp: STPOps.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: STPOps.Handle, old, new: Rope.ROPE] = {
mark, saveMark: [0..256);
code: CHAR ← 0C;
CheckConnection[stp];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, old, alto];
PutPList[stp, markRename, FALSE];
ResetPList[stp.plist];
UserStateToPList[stp];
NameToPList[stp.plist, new, alto];
PutPList[stp, 0B];
[saveMark, code, stp.remoteString] ← GetCommand[stp];
IF (mark ← MyGetMark[stp]) # markEOC THEN
GenerateProtocolError[stp, eocExpected, mark];
IF saveMark # markYes THEN
GenerateErrorString[stp, requestRefused, stp.remoteString, code];
};
Retrieve: PUBLIC PROC [stp: STPOps.Handle, file: Rope.ROPE, confirm: STP.ConfirmProcType ← NIL, complete: STP.CompletionProcType ← NIL] = {
DoFiles[stp, file, confirm, complete, retrieve];
};
Store: PUBLIC PROC [stp: STPOps.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, alto];
IF fileType = unknown THEN fileType ← FindFileType[stream];
SetFileType[stp, fileType];
STPOps.SetByteSize[stp, fileType];
STPOps.SetCreateTime[stp, creation];
size ← stream.GetLength[ ! IO.Error => CONTINUE;];
IF size # 0 THEN STPOps.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: STPOps.Handle, file: Rope.ROPE, confirm: STP.ConfirmProcType, complete: STP.CompletionProcType, op: STPOps.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, alto];
};
IF op = directory THEN
SELECT TryNewDirectory[stp, confirm ! STP.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 => markDelete,
directory => markDirectory,
retrieve => markRetrieve,
store => markNewStore,
ENDCASE => ERROR];
DO
IF LookAtMark[stp] = markEOC THEN {[] ← MyGetMark[stp]; EXIT};
GetHereIsAndPList[stp, op # directory ! STP.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, alto]];
SELECT reply FROM
do => {
completion: STP.Completion;
SELECT op FROM
delete => {
code: CHAR ← 0C;
mark: [0..256);
PutCommand[stp, markYes, 0C, "Yes, please"];
[mark, code, stp.remoteString] ← GetCommand[stp];
SELECT mark FROM
markYes => completion ← ok;
markNo => IF complete = NIL
THEN GenerateErrorString[stp, requestRefused, stp.remoteString, code]
ELSE completion ← error;
ENDCASE => GenerateErrorString[stp, protocolError, stp.remoteString, code]};
retrieve => {
code: CHAR ← 0C;
gotIt: BOOLEAN;
PutCommand[stp, markYes, 0C, "Yes, please"];
[gotIt, code] ← GetFile[stp, local, stp.plist[nameBody]];
completion ← IF gotIt THEN ok ELSE error;
IF completion = error AND complete = NIL
THEN GenerateErrorString[stp, requestRefused, stp.remoteString, code]};
store => {
PutFile[stp, local, stp.plist[nameBody]];
ErrorIfNextNotYes[stp]};
ENDCASE;
IF complete # NIL THEN complete[completion, stp.remoteString];
IF LookAtMark[stp] = markEOC THEN {[] ← MyGetMark[stp]; EXIT}};
skip => {
IF op # directory THEN
PutCommand[stp, markNo, 106C, "No Thanks"];
IF op = store THEN {
mark: [0..256);
code: CHAR;
[mark, code, stp.remoteString] ← GetCommand[stp];
IF mark # markNo THEN
GenerateErrorString[stp, protocolError, stp.remoteString, code]};
};
abort => {CleanUp[]; RETURN};
ENDCASE => ERROR;
ResetPList[stp.plist];
ENDLOOP;
EXITS
streamClosing => GenerateStreamClosingError[stp, reason]};
};
TryNewDirectory: PROC[stp: STPOps.Handle, confirm: STP.ConfirmProcType] RETURNS[{finished, aborted, tryOldDirectory}] = {
mark: [0..256);
PutPList[stp, markNewDirectory];
DO
SELECT (mark ← MyGetMark[stp]) FROM
markComment => stp.remoteString ← CollectString[stp];
markHereIsPList => {
string: Rope.ROPE;
reply: STP.Confirmation;
DO
GetPList[stp: stp, gobbleEOC: FALSE, propertiesOk: TRUE
! MarkEncountered => {
mark: [0..256) ← LookAtMark[stp];
IF mark = markEOC THEN EXIT
ELSE GenerateProtocolError[stp, eocExpected, mark]
}];
string ← MakeRemoteName[stp.plist, alto];
[answer: reply] ← confirm[string];
IF reply = abort THEN RETURN[aborted];
ENDLOOP;
ErrorIfNextNotEOC[stp];
RETURN[finished];
};
markNo => {
code: CHAR = CollectCode[stp];
errorCode: STP.ErrorCode =
ErrorCodeToSTPErrorCode[requestRefused, code];
stp.remoteString ← 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, STPReplyCode.ReplyCode] = badCommand THEN
RETURN[tryOldDirectory];
GenerateErrorString[stp, errorCode, stp.remoteString, code];
};
ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C];
ENDLOOP;
};
Procedures for doing FTP protocol operations
GetFile: PUBLIC PROC [ stp: STPOps.Handle, stream: IO.STREAM, file: Rope.ROPE] RETURNS[gotIt: BOOLEAN, code: CHAR] = {
mark: [0..256);
CheckConnection[stp];
SELECT (mark ← MyGetMark[stp]) FROM
markHereIsFile => NULL;
markNo => {
code ← CollectCode[stp];
stp.remoteString ← CollectString[stp];
gotIt ← FALSE;
RETURN};
ENDCASE => SelectError[stp, "HereIsFile Expected", mark];
TransferTheFile[stp: stp, from: stp.byteStream, to: stream];
stp.mark ← PupStream.ConsumeMark[stp.byteStream];
stp.gotMark ← TRUE;
ErrorIfNextNotYes[stp]; -- should do something about file on local disk
gotIt ← TRUE;
RETURN
};
PutFile: PUBLIC PROC [ stp: STPOps.Handle, stream: IO.STREAM, file: Rope.ROPE, sendEOC: BOOLEANTRUE] = {
CheckConnection[stp];
PupStream.SendMark[stp.byteStream, markHereIsFile];
TransferTheFile[stp: stp, from: stream, to: stp.byteStream];
PutCommand[stp, markYes, 0C, "Transfer Completed", sendEOC];
};
TransferTheFile: PROC [stp: STPOps.Handle, from, to: IO.STREAM] = {
buffer: REF TEXT = NEW[TEXT[1024]];
buffer.length ← buffer.maxLength;
DO nBytes: NAT = from.GetBlock[buffer, 0, buffer.maxLength];
to.PutBlock[buffer, 0, nBytes];
IF from.EndOf[] THEN EXIT;
ENDLOOP;
};
SmashClosed: PUBLIC PROC [stp: STPOps.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.PupByteStreamAbort[stp.byteStream, "Unwinding..."];
STP.Close[stp ! STP.Error => IF code = noConnection THEN CONTINUE];
};
FindFileType: PUBLIC PROC [stream: IO.STREAM] RETURNS [fileType: STP.Type] = {
ENABLE IO.Error => IF ec = NotImplementedForThisStream THEN GOTO unknown;
currentIndex: INT = stream.GetIndex[];
stream.SetIndex[0];
fileType ← text;
DO IF stream.GetChar[ ! IO.EndOfStream => EXIT] > 177C THEN {fileType ← binary; EXIT};
ENDLOOP;
stream.SetIndex[currentIndex];
EXITS unknown => fileType ← unknown;
};
}.
Bob Hagmann January 28, 1986 3:50:50 pm PST
add size to the store property list
changes to: Store, DIRECTORY