<> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> <> 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 <> Error: PUBLIC SIGNAL [stp: Handle, code: ErrorCode, error: ROPE, reply: CHAR _ 0C] = CODE; <> <<>> 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"; <> 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]}; }; }; <> 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]; }; <> 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]; }; <> 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]; <> ENDCASE; IF LOOPHOLE[code, Reply] = badCommand THEN RETURN[tryOldDirectory]; GenerateError[stp, errorCode, code]; }; ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C]; ENDLOOP; }; <> 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; <> 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; }; <> 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]] }; <> 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;}; }; <> 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]; }; }; <> 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]; }; <> 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; }; <> 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]; <> 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; }; <> 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; }; <> 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[')]; }; }. <> <> <> <> <>