FTPUser:
CEDAR
PROGRAM
IMPORTS
EXPORTS FTP, FTPInternal =
BEGIN OPEN FTP, FTPInternal;
FTP.
Object: PUBLIC TYPE = FTPInternal.Object;
CreateFromName:
PUBLIC
PROCEDURE [hostName:
ROPE, localHerald:
ROPE ←
NIL]
RETURNS [h: Handle, remoteHerald:
ROPE] =
BEGIN
hostAddress: PupStream.PupAddress = PupStream.GetPupAddress[PupTypes.ftpSoc, hostName !
PupStream.PupNameTrouble => h.GenerateFailed[MapNameLookupError[code], e]];
[h, remoteHerald] ← CreateFromAddress[hostAddress, localHerald];
END;
CreateFromAddress:
PUBLIC
PROCEDURE [hostAddress: PupStream.PupAddress, localHerald:
ROPE ←
NIL]
RETURNS [h: Handle, remoteHerald:
ROPE] =
BEGIN
mark: Mark;
code: FailureCode;
h ← NEW[Object ← []];
h.byteStream ← PupStream.PupByteStreamCreate[hostAddress, PupStream.veryLongWait];
h.PutCommand[mark: version, code: LOOPHOLE[ftpVersion], text: localHerald, sendEOC: TRUE];
[mark, code] ← h.GetCommand[];
IF mark#version THEN h.GenerateFailed[protocolError];
IF code#ftpVersion THEN h.GenerateFailed[protocolError, "Incompatible protocol version"];
remoteHerald ← h.GetText[gobbleEOC: TRUE];
END;
Delete:
PUBLIC
PROCEDURE [h: Handle, confirm: ConfirmProc ←
NIL] =
BEGIN
h.PutCommandAndPList[mark: delete, pList: h.pList[local], sendEOC: TRUE];
FOR firstTime:
BOOLEAN ←
TRUE,
FALSE
DO
mark: Mark;
code: ReplyCode;
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
hereIsPList =>
BEGIN
h.pList[remote] ← h.GetPList[gobbleEOC: TRUE];
IF confirm#
NIL
AND confirm[h]
THEN
BEGIN
h.PutCommand[mark: yes, text: "Delete it!", sendEOC: TRUE];
[] ← h.GetYesNo[resumable: TRUE];
END
ELSE h.PutCommand[mark: no, code: skipThisFile, sendEOC: FALSE];
END;
no =>
IF firstTime THEN h.GenerateFailed[code, h.GetText[gobbleEOC: TRUE], resumable: TRUE] ELSE h.GenerateFailed[protocolError];
ENDCASE =>
h.GenerateFailed[protocolError];
ENDLOOP;
END;
Enumerate:
PUBLIC
PROCEDURE [h: Handle, noteFile:
PROCEDURE [h: Handle]] =
BEGIN
NewEnumerate:
PROCEDURE
RETURNS [tryOld:
BOOLEAN ←
FALSE] =
BEGIN
mark: Mark;
code: ReplyCode;
h.PutCommandAndPList[mark: newEnumerate, pList: h.pList[local], sendEOC: TRUE];
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
hereIsPList =>
DO
-- process all property lists in (single) response to newEnumerate command
h.pList[remote] ← h.GetPList[endOfPropertiesOK: TRUE];
IF h.pList[remote]=NIL THEN EXIT;
noteFile[h];
ENDLOOP;
no =>
IF code=badCommand THEN RETURN [TRUE] -- newEnumerate unimplemented
ELSE h.GenerateFailed[code, h.GetText[gobbleEOC: TRUE]];
ENDCASE =>
h.GenerateFailed[protocolError];
END; -- NewEnumerate
OldEnumerate:
PROCEDURE =
BEGIN
h.PutCommandAndPList[mark: enumerate, pList: h.pList[local], sendEOC: TRUE];
FOR firstTime:
BOOLEAN ←
TRUE,
FALSE
DO
mark: Mark;
code: ReplyCode;
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
hereIsPList =>
BEGIN
h.pList[remote] ← h.GetPList[];
noteFile[h];
END;
no =>
IF firstTime THEN h.GenerateFailed[code, h.GetText[gobbleEOC: TRUE]] ELSE h.GenerateFailed[protocolError];
endOfCommand =>
IF firstTime THEN h.GenerateFailed[protocolError] ELSE EXIT;
ENDCASE =>
h.GenerateFailed[protocolError];
ENDLOOP;
END; -- OldEnumerate
IF ~NewEnumerate[] THEN OldEnumerate[];
END;
Rename:
PUBLIC
PROCEDURE [h: Handle, changeName:
PROCEDURE [h: Handle]] =
BEGIN
oldPList: PList ← h.pList[local];
h.pList[local] ← NIL;
changeName[h];
h.PutCommandAndPList[mark: rename, pList: oldPList];
h.PutPList[pList: h.pList[local], sendEOC: TRUE];
[] ← h.GetYesNo[gobbleEOC: TRUE];
END;
Retrieve:
PUBLIC
PROCEDURE [h: Handle, confirm: ConfirmProc ←
NIL, transfer: TransferProc, complete: CompleteProc ←
NIL] =
BEGIN
h.PutCommandAndPList[mark: retrieve, pList: h.pList[local], sendEOC: TRUE];
FOR firstTime:
BOOLEAN ←
TRUE,
FALSE
DO
mark: Mark;
code: ReplyCode;
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
hereIsPList =>
BEGIN
h.pList[remote] ← h.GetPList[gobbleEOC: TRUE];
IF confirm#
NIL
AND confirm[h]
THEN
BEGIN
h.PutCommand[mark: yes, text: "Yes, please", sendEOC: TRUE];
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
hereIsData =>
BEGIN
ok: BOOLEAN ← FALSE;
tranfer[h, h.byteStream];
ok ← h.GetYesNo[resumable: TRUE];
IF complete#NIL THEN complete[h, ok];
END;
no =>
h.GenerateFailed[code, h.GetText[gobbleEOC: TRUE], resumable: TRUE];
ENDCASE =>
h.GenerateFailed[protocolError];
END
ELSE h.PutCommand[mark: no, code: skipThisFile, sendEOC: FALSE];
END;
no =>
IF firstTime THEN h.GenerateFailed[code, h.GetText[gobbleEOC: TRUE], resumable: TRUE] ELSE h.GenerateFailed[protocolError];
ENDCASE =>
h.GenerateFailed[protocolError];
ENDLOOP;
END;
Store:
PUBLIC
PROCEDURE [h: Handle, confirm: ConfirmProc ←
NIL, transfer: TransferProc, complete: CompleteProc ←
NIL] =
BEGIN
mark: Mark;
code: ReplyCode;
FOR protocol: {new, old}
IN [new..old]
DO
h.PutCommandAndPList[mark: IF protocol=new THEN newStore ELSE store, pList: h.pList[local], sendEOC: TRUE];
[mark, code] ← h.GetCommand[];
SELECT mark
FROM
yes, hereIsPList =>
BEGIN
IF mark=hereIsPList THEN h.pList[remote] ← h.GetPList[gobbleEOC: TRUE];
IF confirm[h]
THEN
BEGIN
ok: BOOLEAN ← FALSE;
h.PutCommand[mark: hereIsFile];
tranfer[h, h.byteStream];
h.PutCommand[mark: yes, text: "Sent ok", sendEOC: TRUE];
ok ← h.GetYesNo[resumable: TRUE];
IF complete#NIL THEN complete[h, ok];
EXIT;
END
ELSE h.PutCommand[mark: no, code: skipThisFile, sendEOC: FALSE];
END;
no =>
BEGIN
text: ROPE = h.GetText[gobbleEOC: TRUE];
IF protocol=new AND code=badCommand THEN NULL -- try again with old protocol
ELSE h.GenerateFailed[code, text];
END;
ENDCASE =>
h.GenerateFailed[protocolError];
ENDLOOP;
END;
Procedures for doing FTP protocol operations
SmashClosed:
PUBLIC
PROCEDURE [stp: STPOps.Handle] =
BEGIN
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];
END;