-- File: STPsD.mesa - last edit: -- AOF 20-Feb-87 9:29:13 -- Copyright (C) 1981, 1982, 1984 , 1987 by Xerox Corporation. All rights reserved. -- file: STPsD.mesa - Simple/Stream Transfer Protocol -- Common and protocol stuff in here -- Edited by: -- Mark, Feb 12, 1981 11:39 PM -- SXW , 17-Jul-81 7:20:16 -- PXK , 17-Jul-81 17:45:12 -- JGS, 14-Aug-81 10:35:26 -- LXR 27-Oct-82 10:45:58 -- AXD 21-Sep-82 13:07:12 -- BJD 17-Feb-84 11:16:31 DIRECTORY Ascii USING [SP], Heap USING [systemZone], PupStream USING[StreamClosing], STP USING [DesiredProperties, Error, ErrorCode, FileInfo, Open, Type], STPOps USING [ DestroyPList, ErrorCodeToSTPErrorCode, FileProperties, FilenameType, GenerateErrorString, GenerateProtocolError, Handle, markAbort, markComment, markEOC, markHereIsPList, markIAmVersion, markNo, markYes, markYouAreUser, maxStringLength, Object, PList, PListArray, ServerType, SmashClosed, UserProperties, ValidProperties], Stream USING [ Block, GetChar, Handle, Object, PutChar, PutBlock, SetSST, SSTChange, SubSequenceType, TimeOut], String USING [ AppendCharAndGrow, AppendStringAndGrow, CopyToNewString, EquivalentString, EmptyString, FreeString, Replace, StringToLongNumber], Time USING [Append, Packed, Unpack]; STPsD: PROGRAM IMPORTS Heap, PupStream, STP, STPOps, Stream, String, Time EXPORTS STP, STPOps = BEGIN OPEN STPOps; -- Exported Types Object: PUBLIC TYPE = STPOps.Object; -- Global Data z: UNCOUNTED ZONE = Heap.systemZone; MarkEncountered: PUBLIC 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: LONG STRING = "Desired-property"; -- Commonly used stuff Connect: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: LONG STRING] = BEGIN String.Replace[@stp.userState[connectName], name, z]; String.Replace[@stp.userState[connectPassword], password, z]; END; IsOpen: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [yes: BOOLEAN] = { yes ← stp.byteStream # NIL}; GetProperty: PUBLIC PROCEDURE [stp: STPOps.Handle, prop: ValidProperties] RETURNS [LONG STRING] = { RETURN[stp.plist[prop]]}; Login: PUBLIC PROCEDURE [stp: STPOps.Handle, name, password: LONG STRING] = BEGIN String.Replace[@stp.userState[userName], name, z]; String.Replace[@stp.userState[userPassword], password, z]; END; SetHost: PUBLIC PROCEDURE [stp: STPOps.Handle, host: LONG STRING] = BEGIN String.Replace[@stp.host, host, z]; END; SetDirectory: PUBLIC PROCEDURE [stp: STPOps.Handle, directory: LONG STRING] = BEGIN String.Replace[@stp.userState[directory], directory, z]; END; -- Procedures for doing FTP protocol operations GetCommand: PUBLIC PROCEDURE [stp: STPOps.Handle, ps: LONG POINTER TO LONG STRING] RETURNS [mark: Stream.SubSequenceType, code: CHARACTER] = BEGIN code ← 0C; CheckConnection[stp]; mark ← MyGetMark[stp]; SELECT mark FROM markAbort, markComment, markYouAreUser => CollectString[stp, ps]; markIAmVersion, markNo, markYes => {code ← CollectCode[stp]; CollectString[stp, ps]}; ENDCASE => GenerateProtocolError[stp, badMark, mark, code]; RETURN[mark, code] END; GetHereIsAndPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN ← TRUE] = BEGIN mark: Stream.SubSequenceType; DO SELECT (mark ← MyGetMark[stp]) FROM markComment => CollectString[stp, @stp.remoteString]; markHereIsPList => {GetPList[stp, gobbleEOC]; EXIT}; markNo => BEGIN code: CHARACTER = CollectCode[stp]; errorCode: STP.ErrorCode = ErrorCodeToSTPErrorCode[requestRefused, code]; CollectString[stp, @stp.remoteString]; ErrorIfNextNotEOC[stp]; GenerateErrorString[stp, errorCode, stp.remoteString, code]; END; ENDCASE => GenerateProtocolError[stp, badMark, mark, 0C]; ENDLOOP; END; GetPList: PUBLIC PROCEDURE [stp: STPOps.Handle, gobbleEOC: BOOLEAN ← TRUE] = BEGIN property: LONG STRING ← z.NEW[StringBody[maxStringLength]]; value: LONG STRING ← z.NEW[StringBody[maxStringLength]]; mark: Stream.SubSequenceType; CheckConnection[stp]; BEGIN ENABLE UNWIND => {z.FREE[@property]; z.FREE[@value]}; parens: INTEGER ← 0; quote: CHARACTER = ''; char: CHARACTER; string: LONG STRING ← property; IF stp.plist[nameBody] # NIL THEN z.FREE[@stp.plist[nameBody]]; DO char ← MyGetChar[stp ! MarkEncountered => GOTO BadPList]; SELECT char FROM '( => BEGIN parens ← parens + 1; string ← property; string.length ← 0; END; quote => String.AppendCharAndGrow[ @string, MyGetChar[stp ! MarkEncountered => GOTO BadPList], z]; Ascii.SP => IF string = property THEN BEGIN string ← value; string.length ← 0; END ELSE String.AppendCharAndGrow[@string, char, z]; ') => BEGIN IF property.length # 0 AND value.length # 0 THEN BEGIN SetPListItem[ stp.plist, property, value ! BadProperty => CONTINUE]; property.length ← value.length ← 0; END; IF (parens ← parens - 1) = 0 THEN EXIT; END; ENDCASE => String.AppendCharAndGrow[@string, char, z]; ENDLOOP; IF (property.length # 0 AND value.length # 0) THEN GOTO BadPList; IF gobbleEOC AND MyGetMark[stp] # markEOC THEN GOTO BadPList; EXITS BadPList => {ResetPList[stp.plist]; GenerateProtocolError[stp, badPList, mark]}; END; z.FREE[@property]; z.FREE[@value]; END; PutCommand: PUBLIC PROCEDURE [ stp: STPOps.Handle, mark: Stream.SubSequenceType, code: CHARACTER, string: LONG STRING, sendEOC: BOOLEAN ← TRUE] = BEGIN CheckConnection[stp]; Stream.SetSST[stp.byteStream, mark]; Stream.PutChar[stp.byteStream, code]; IF ~String.EmptyString[string] THEN MyPutString[stp.byteStream, string]; IF sendEOC THEN Stream.SetSST[stp.byteStream, markEOC]; END; PutPList: PUBLIC PROCEDURE [ stp: STPOps.Handle, mark: Stream.SubSequenceType, sendEOC: BOOLEAN ← TRUE] = BEGIN state: {okay, tryOpen, triedOpen} ← okay; DoIt: PROCEDURE = BEGIN CheckConnection[stp]; IF mark # 0B THEN Stream.SetSST[stp.byteStream, mark]; Stream.PutChar[stp.byteStream, '(]; FOR i: ValidProperties IN ValidProperties DO IF ~String.EmptyString[stp.plist[i]] THEN PutPListItem[stp.byteStream, i, stp.plist[i]]; ENDLOOP; IF stp.desiredProps # ALL[TRUE] THEN PutDesiredProps[stp]; Stream.PutChar[stp.byteStream, ')]; IF sendEOC THEN Stream.SetSST[stp.byteStream, markEOC]; END; DO DoIt[! PupStream.StreamClosing, Stream.TimeOut => IF state = okay AND ~String.EmptyString[stp.host] THEN {state ← tryOpen; CONTINUE}]; IF state # tryOpen THEN EXIT; BEGIN savedPList: PList ← stp.plist; stp.plist ← NIL; SmashClosed[stp]; -- Call outside catch so Pup monitor unlocked String.FreeString[z, STP.Open[stp, stp.host]]; stp.plist ← DestroyPList[stp.plist]; stp.plist ← savedPList; state ← triedOpen; END; ENDLOOP; END; -- Utility routines CollectString: PUBLIC PROCEDURE [stp: STPOps.Handle, ps: LONG POINTER TO LONG STRING] = BEGIN char: CHARACTER; IF ps↑ # NIL THEN ps↑.length ← 0 ELSE ps↑ ← z.NEW[StringBody[15]]; DO char ← MyGetChar[stp ! MarkEncountered => { mark: Stream.SubSequenceType ← LookAtMark[stp]; SELECT mark FROM markHereIsPList, markEOC => EXIT; ENDCASE => GenerateProtocolError[stp, eocExpected, mark]}]; String.AppendCharAndGrow[ps, char, z]; ENDLOOP; END; CollectCode: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [code: CHARACTER] = BEGIN code ← 0C; CheckConnection[stp]; code ← MyGetChar[stp ! MarkEncountered => GenerateProtocolError[stp, noCode, MyGetMark[stp]]]; END; CheckConnection: PUBLIC PROCEDURE [stp: STPOps.Handle] = BEGIN WHILE stp.byteStream = NIL DO SIGNAL STP.Error[stp, noConnection, "Please open a connection!"L, 0C] ENDLOOP END; ErrorIfNextNotYes: PUBLIC PROCEDURE [stp: STPOps.Handle] = BEGIN code: CHARACTER ← 0C; mark: Stream.SubSequenceType; [mark, code] ← GetCommand[stp, @stp.remoteString]; IF mark # markYes THEN GenerateErrorString[stp, requestRefused, stp.remoteString, code]; END; ErrorIfNextNotEOC: PUBLIC PROCEDURE [stp: STPOps.Handle] = BEGIN mark: Stream.SubSequenceType ← MyGetMark[stp]; IF mark # markEOC THEN GenerateProtocolError[stp, eocExpected, mark]; END; GetServerType: PUBLIC PROCEDURE [server: LONG STRING] RETURNS [serverType: ServerType] = BEGIN OPEN String; RETURN[SELECT TRUE FROM EquivalentString[server, "MAXC"L], EquivalentString[server, "MAXC1"L], EquivalentString[server, "MAXC2"L] => tenex, ENDCASE => ifs]; END; LookAtMark: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [Stream.SubSequenceType] = BEGIN IF ~stp.gotMark THEN DO [] ← MyGetChar[stp ! MarkEncountered => CONTINUE]; IF stp.gotMark THEN EXIT; ENDLOOP; RETURN[stp.mark] END; MyGetChar: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [char: CHARACTER] = BEGIN char ← Stream.GetChar[ stp.byteStream ! Stream.SSTChange => BEGIN stp.mark ← sst; stp.gotMark ← TRUE; ERROR MarkEncountered; END]; RETURN[char]; END; MyGetMark: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [mark: Stream.SubSequenceType] = BEGIN mark ← LookAtMark[stp]; stp.gotMark ← FALSE; RETURN[mark] END; PutPListValue: PROCEDURE [ byteStream: Stream.Handle, string: LONG STRING] = BEGIN FOR i: NATURAL IN [0..string.length) DO char: CHAR = string[i]; SELECT char FROM '(, '), '' => byteStream.PutChar[''] ENDCASE => NULL; byteStream.PutChar[char]; ENDLOOP; END; MyPutString: PUBLIC PROCEDURE [byteStream: Stream.Handle, string: LONG STRING] = BEGIN block: Stream.Block ← [LOOPHOLE[@string.text], 0, string.length]; Stream.PutBlock[byteStream, block, FALSE]; END; PropertyString: PUBLIC PROCEDURE [prop: STPOps.ValidProperties] RETURNS [string: LONG STRING] = BEGIN RETURN[propertyStrings[prop]]; END; SetCreateTime: PUBLIC PROCEDURE [stp: STPOps.Handle, creation: Time.Packed] = BEGIN cDate: STRING ← [24]; Time.Append[ s: cDate, unpacked: Time.Unpack[creation], zone: TRUE, zoneStandard: Alto]; String.Replace[@stp.plist[createDate], cDate, z]; END; SetFileType: PUBLIC PROCEDURE [ stp: STPOps.Handle, fileType: STP.Type] = BEGIN String.Replace[@stp.plist[type], SELECT fileType FROM text => "Text"L, binary => "Binary"L, ENDCASE => NIL, z]; END; SetByteSize: PUBLIC PROCEDURE [stp: STPOps.Handle, fileType: STP.Type] = BEGIN String.Replace[@stp.plist[byteSize], SELECT stp.serverType FROM ifs, unknown, tenex => IF fileType = text THEN NIL ELSE "8", ENDCASE => ERROR STP.Error[stp, undefinedError, NIL], z]; END; StringToFileType: PUBLIC PROCEDURE [string: LONG STRING] RETURNS [type: STP.Type] = BEGIN type ← SELECT TRUE FROM string = NIL => unknown, String.EquivalentString["Text"L, string] => text, String.EquivalentString["Binary"L, string] => binary, ENDCASE => unknown; END; -- PList and FileInfo Utilities NameToPList: PUBLIC PROCEDURE [plist: PList, name: LONG STRING, type: FilenameType] = BEGIN i: CARDINAL; versionSeen: BOOLEAN ← FALSE; temp: LONG STRING; IF String.EmptyString[name] THEN RETURN; temp ← z.NEW[StringBody[100]]; FOR i IN [0..name.length) DO SELECT name[i] FROM '[ => IF i # 0 THEN String.AppendCharAndGrow[@temp, name[i], z]; '], ': => BEGIN String.Replace[@plist[device], temp, z]; temp.length ← 0; END; '< => BEGIN IF temp.length # 0 THEN String.Replace[@plist[device], temp, z]; temp.length ← 0; String.AppendCharAndGrow[@temp, name[i], z]; IF plist[directory] # NIL THEN plist[directory].length ← 0; END; '> => BEGIN IF (String.EmptyString[plist[directory]] OR plist[directory][plist[directory].length-1] # '>) AND (temp.length = 0 OR temp[0] # '<) THEN String.AppendCharAndGrow[@plist[directory], name[i], z]; String.AppendStringAndGrow[@plist[directory], temp, z]; temp.length ← 0; END; '!, '; => BEGIN IF temp.length # 0 THEN String.Replace[@plist[nameBody], temp, z]; versionSeen ← TRUE; temp.length ← 0; END; ENDCASE => String.AppendCharAndGrow[@temp, name[i], z]; ENDLOOP; IF temp.length # 0 THEN { IF versionSeen THEN String.Replace[@plist[version], temp, z] ELSE String.Replace[@plist[nameBody], temp, z]}; z.FREE[@temp]; END; PListToName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [name: LONG STRING] = BEGIN name ← z.NEW[StringBody[40]]; IF ~String.EmptyString[plist[directory]] THEN BEGIN SELECT plist[directory][0] FROM '> => FOR i: CARDINAL IN [1..plist[directory].length) DO String.AppendCharAndGrow[@name, plist[directory][i], z] ENDLOOP; '< => String.AppendStringAndGrow[@name, plist[directory], z]; ENDCASE => { String.AppendCharAndGrow[@name, '<, z]; String.AppendStringAndGrow[@name, plist[directory], z]}; IF plist[directory][plist[directory].length - 1] # '> THEN String.AppendCharAndGrow[@name, '>, z]; END; IF ~String.EmptyString[plist[nameBody]] THEN String.AppendStringAndGrow[@name, plist[nameBody], z]; IF ~String.EmptyString[plist[version]] THEN BEGIN IF type = alto THEN String.AppendCharAndGrow[@name, '!, z] ELSE String.AppendCharAndGrow[@name, ';, z]; String.AppendStringAndGrow[@name, plist[version], z]; END; END; MakeRemoteName: PUBLIC PROCEDURE [plist: PList, type: FilenameType] RETURNS [LONG STRING] = { RETURN[IF String.EmptyString[plist[serverName]] THEN PListToName[plist, type] ELSE String.CopyToNewString[plist[serverName], z]]}; PutPListItem: PUBLIC PROCEDURE [ byteStream: Stream.Handle, property: STPOps.ValidProperties, value: LONG STRING] = BEGIN Stream.PutChar[byteStream, '(]; MyPutString[byteStream, PropertyString[property]]; Stream.PutChar[byteStream, Ascii.SP]; PutPListValue[byteStream, value]; Stream.PutChar[byteStream, ')]; END; ResetPList: PUBLIC PROCEDURE [plist: PList] = BEGIN i: STPOps.ValidProperties; IF plist = NIL THEN RETURN; FOR i IN STPOps.ValidProperties DO IF plist[i] # NIL THEN z.FREE[@plist[i]] ENDLOOP; END; GetFileInfo: PUBLIC PROCEDURE [stp: STPOps.Handle] RETURNS [STP.FileInfo] = BEGIN FOR i: STPOps.FileProperties IN STPOps.FileProperties DO SELECT i FROM directory => stp.info.directory ← stp.plist[i]; nameBody => stp.info.body ← stp.plist[i]; version => stp.info.version ← stp.plist[i]; author => stp.info.author ← stp.plist[i]; createDate => stp.info.create ← stp.plist[i]; readDate => stp.info.read ← stp.plist[i]; writeDate => stp.info.write ← stp.plist[i]; size => stp.info.size ← IF String.EmptyString[stp.plist[i]] THEN 0 ELSE String.StringToLongNumber[stp.plist[i], 10]; type => stp.info.type ← StringToFileType[stp.plist[i]]; ENDCASE; ENDLOOP; RETURN[stp.info]; END; SetPListItem: PUBLIC PROCEDURE [plist: PList, property, value: LONG STRING] = BEGIN FOR i: STPOps.ValidProperties IN STPOps.ValidProperties DO IF String.EquivalentString[PropertyString[i], property] THEN { String.Replace[@plist[i], value, z]; RETURN}; ENDLOOP; ERROR BadProperty END; UserStateToPList: PUBLIC PROCEDURE [stp: STPOps.Handle] = BEGIN OPEN stp; FOR i: STPOps.UserProperties IN STPOps.UserProperties DO -- Solution 1 String.Replace[@plist[i], userState[i], z]; -- Solution 2 -- IF plist[i] # NIL THEN ERROR Error[stp, undefinedError]; -- IF ~String.EmptyString[userState[i] THEN -- plist[i] ← String.CopyToNewString[userState[i], z]; -- Solution 3 -- IF userState[i] # NIL THEN -- String.Replace[@plist[i], userState[i], z]; ENDLOOP; END; -- Desired Property stuff SetDesiredProperties: PUBLIC PROC[stp: STPOps.Handle, props: STP.DesiredProperties] = BEGIN stp.desiredProps ← props END; GetDesiredProperties: PUBLIC PROC [stp: STPOps.Handle] RETURNS [props: STP.DesiredProperties] = BEGIN RETURN[stp.desiredProps] END; PutDesiredProps: PUBLIC PROCEDURE [stp: STPOps.Handle] = BEGIN FOR i: STPOps.FileProperties IN STPOps.FileProperties DO IF stp.desiredProps[i] THEN PutDesiredPropsItem[stp.byteStream, PropertyString[i]] ENDLOOP; END; PutDesiredPropsItem: PUBLIC PROCEDURE [ byteStream: Stream.Handle, value: LONG STRING] = BEGIN Stream.PutChar[byteStream, '(]; MyPutString[byteStream, desiredPropString]; Stream.PutChar[byteStream, Ascii.SP]; PutPListValue[byteStream, value]; Stream.PutChar[byteStream, ')]; END; END. -- of STPsD