DIRECTORY BasicTime USING [GMT], IO USING [STREAM, UnsafeBlock], Pup USING [Address, nullSocket, Socket], Rope USING [ROPE]; FTP: CEDAR DEFINITIONS = BEGIN ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Handle: TYPE = REF Object; Object: TYPE; CreateFromName: PROC [hostName: ROPE, localHerald: ROPE _ NIL] RETURNS [h: Handle, remoteHerald: ROPE]; CreateFromAddress: PROC [hostAddress: Pup.Address, localHerald: ROPE _ NIL] RETURNS [h: Handle, remoteHerald: ROPE]; Destroy: PROC [h: Handle]; IsOpen: PROC [h: Handle] RETURNS [open: BOOLEAN]; GetClientData: PROC [h: Handle] RETURNS [data: REF ANY]; SetClientData: PROC [h: Handle, data: REF ANY]; Property: TYPE = { --Date-- createDate, readDate, writeDate, --Enumerated-- eolConvention, type, --Number-- byteSize--bits per byte--, checksum, size--bytes--, --Text-- author, connectName, connectPassword, device, directory, nameBody, serverFileName, userAccount, userName, userPassword, version, --DesiredProperties-- desiredProperty}; DateProperty: TYPE = Property [createDate..writeDate]; EnumeratedProperty: TYPE = Property [eolConvention..type]; NumberProperty: TYPE = Property [byteSize..size]; TextProperty: TYPE = Property [author..version]; BuiltInProperty: TYPE = Property [FIRST[Property]..desiredProperty); EOLConvention: TYPE = MACHINE DEPENDENT {unknown(0), cr, crlf, transparent}; Type: TYPE = MACHINE DEPENDENT {unknown(0), text, binary}; EnumPropValue: TYPE = RECORD [ SELECT OVERLAID EnumeratedProperty FROM eolConvention => [eolConvention: EOLConvention], type => [type: Type], ENDCASE]; nullEnumPropValue: EnumPropValue = [type[type: unknown]]; LocalOrRemote: TYPE = {local, remote}; GetDateProperty: PROC [h: Handle, prop: DateProperty, list: LocalOrRemote _ remote] RETURNS [value: BasicTime.GMT]; GetEnumeratedProperty: PROC [h: Handle, prop: EnumeratedProperty, list: LocalOrRemote _ remote] RETURNS [propValue: EnumPropValue]; GetNumberProperty: PROC [h: Handle, prop: NumberProperty, list: LocalOrRemote _ remote] RETURNS [value: INT]; GetTextProperty: PROC [h: Handle, prop: TextProperty, list: LocalOrRemote _ remote] RETURNS [value: ROPE]; GetUserDefinedProperty: PROC [h: Handle, prop: ATOM, list: LocalOrRemote _ remote] RETURNS [value: ROPE]; SetDateProperty: PROC [h: Handle, prop: DateProperty, value: BasicTime.GMT]; SetEnumeratedProperty: PROC [h: Handle, prop: EnumeratedProperty, value: EnumPropValue]; SetNumberProperty: PROC [h: Handle, prop: NumberProperty, value: INT]; SetTextProperty: PROC [h: Handle, prop: TextProperty, value: ROPE]; SetUserDefinedProperty: PROC [h: Handle, prop: ATOM, value: ROPE]; ResetProperties: PROC [h: Handle]; PropertySet: TYPE = PACKED ARRAY BuiltInProperty OF FalseBool; FalseBool: TYPE = BOOLEAN _ FALSE; GetDesiredProperties: PROC [h: Handle] RETURNS [props: PropertySet, userDefinedProps: LIST OF ATOM]; SetDesiredProperties: PROC [h: Handle, props: PropertySet, userDefinedProps: LIST OF ATOM _ NIL]; ConfirmProc: TYPE = PROC [h: Handle] RETURNS [confirm: BOOLEAN]; TransferProc: TYPE = PROC [h: Handle, stream: IO.STREAM]; CompleteProc: TYPE = PROC [h: Handle, ok: BOOLEAN]; Delete: PROC [h: Handle, confirm: ConfirmProc _ NIL]; Enumerate: PROC [h: Handle, noteFile: PROC [h: Handle]]; Rename: PROC [h: Handle, changeName: PROC [h: Handle]]; Retrieve: PROC [h: Handle, confirm: ConfirmProc _ NIL, transfer: TransferProc, complete: CompleteProc _ NIL]; Store: PROC [h: Handle, confirm: ConfirmProc _ NIL, transfer: TransferProc, complete: CompleteProc _ NIL]; Listener: TYPE = REF ListenerObject; ListenerObject: TYPE; AcceptProc: TYPE = PROC [requestor: Pup.Address, connections: CARDINAL] RETURNS [accept: BOOLEAN, reason: ROPE _ NIL]; FileInfoProc: TYPE = PROC [fileName: ROPE, pupAddress: Pup.Address] RETURNS [found: BOOL, return: BOOL, version: CARDINAL, create: BasicTime.GMT, bytes: LONG CARDINAL]; CreateListener: PROC [socket: Pup.Socket _ Pup.nullSocket, procs: ServerProcs, accept: AcceptProc _ NIL, timeoutSeconds: INT _ 30, fileInfo: FileInfoProc _ NIL, fileInfoSocket: Pup.Socket _ Pup.nullSocket, fileInfoProcesses: CARDINAL [1..10] _ 1] RETURNS [l: Listener]; DestroyListener: PROC [l: Listener]; Server: PROC [stream: IO.STREAM, procs: ServerProcs]; ServerProcs: TYPE = REF ServerProcsRecord; ServerProcsRecord: TYPE = RECORD [ delete: ServerDeleteProc _ NIL, enumerate: ServerEnumerateProc _ NIL, rename: ServerRenameProc _ NIL, retrieve: ServerRetrieveProc _ NIL, store: ServerStoreProc _ NIL, version: ServerVersionProc _ NIL, -- NIL means a default version reply will be generated checkCredentials: CheckCredentialsProc _ NIL]; -- NIL means credentials will not be checked ConfirmTransferProc: TYPE = PROC [h: Handle] RETURNS [stream: IO.STREAM]; ServerCompleteProc: TYPE = PROC [h: Handle] RETURNS [ok: BOOLEAN]; ServerDeleteProc: TYPE = PROC [h: Handle, confirm: ConfirmProc, complete: ServerCompleteProc]; ServerEnumerateProc: TYPE = PROC [h: Handle, noteFile: PROC [h: Handle]]; ServerRenameProc: TYPE = PROC [h: Handle]; ServerRetrieveProc: TYPE = PROC [h: Handle, confirm: ConfirmTransferProc, complete: ServerCompleteProc]; ServerStoreProc: TYPE = PROC [h: Handle, confirm: ConfirmTransferProc, complete: ServerCompleteProc]; ServerVersionProc: TYPE = PROC [h: Handle, remoteHerald: ROPE] RETURNS [localHerald: ROPE]; CheckCredentialsProc: TYPE = PROC [h: Handle]; FileType: PROC [stream: STREAM] RETURNS [type: Type]; DataType: PROC [block: IO.UnsafeBlock] RETURNS [type: Type]; Failed: SIGNAL [h: Handle, code: FailureCode, text: ROPE _ NIL, resumable: BOOLEAN _ FALSE]; FailureCode: TYPE = MACHINE DEPENDENT { unspecified (0), -- unspecified reason badCommand (1), -- command undefined or unimplemented credentialsMissing (2), illegalCommand (3), -- illegal command in this context badPList (8), -- malformed property list (protocol error) illegalServerFileName (9), -- incorrect values of specific properties illegalDirectory (10), illegalNameBody (11), illegalVersion (12), illegalType (13), illegalByteSize (14), illegalEOLConversion (15), illegalUserName (16), illegalUserPassword (17), illegalUserAccount (18), illegalConnectName (19), illegalConnectPassword (20), illegalCreationDate (21), illegalWriteDate (22), illegalReadDate (23), illegalAuthor (24), illegalDevice (25), fileNotFound (64), accessDenied (65), -- e.g., due to file protection violation inconsistent (66), -- local/remote parameter mismatch fileDataError (67), -- file system reported error tooLong (68), -- file too long or storage full skipThisFile (69), -- (internal use only) notCompleted (70), -- (internal use only) transientError (71), -- unspecified transient server or file system failure (the text may give more information) permanentError (72), -- unspecified permanent server or file system failure (the text may give more information) fileBusy (73), -- open in conflicting way by some other client renameDestinationExists (74), -- attempted to change name to that of an existing file lastReplyCode (255), accessError, alreadyAConnection, connectionClosed, connectionRejected, connectionTimedOut, credentailsMissing, illegalFileName, noConnection, noNameLookupResponse, -- name lookup failed (CreateFromName) noRouteToNetwork, noSuchFile, noSuchHost, -- name lookup failed (CreateFromName) protocolError, -- probable bug in either the local or the remote FTP implementation requestRefused, undefinedError, aborted}; ReplyCode: TYPE = FailureCode[unspecified..lastReplyCode]; ConnectionFailure: TYPE = FailureCode[connectionClosed..noSuchHost]; ProtocolError: TYPE = {badVersion, badMark, badPList, eocExpected, noCode}; END. onFTP.mesa Pup File Transfer Protocol package. Derived from STP.mesa, originally by Smokey Wallace and a cast of thousands. This version provides access at a slightly lower level and thereby makes available all features of the FTP protocol. It is intended for use by knowledgable clients, e.g., FS and the Alpine FTP server, and not directly by ordinary clients. Last edited by: Andrew Birrell June 1, 1983 2:45 pm Taft, October 7, 1983 2:04 pm Bob Hagmann August 1, 1985 4:27:23 pm PDT Hal Murray, April 17, 1986 5:37:27 pm PST Open and close connection This represents the state of a single instance of an open FTP connection. Opens an FTP connection to the host specified by hostName. Then performs the initial version handshake, passing localHerald as the version text and returning the server's herald as remoteHerald. If this is successful, returns a Handle upon which other FTP operations may subsequently be performed. The local and remote property lists are initially empty (see below). Exceptions: any of the ConnectionErrors or protocolError. Same as createFromName except that the remote host is specified by its Pup.Address. The errors noNameLookupResponse and noSuchHost are not possible in this case. Closes the connection (if open) and invalidates the handle, which is not subsequently usable (Error[connectionClosed] if use is attempted). This is done automatically if the client ceases to hold onto the handle; but it is best to call Destroy explicitly so that resources (especially on the server) are released as quickly as possible. Exceptions: none. Returns TRUE if the connection is still open. Of course, there is no guarantee that this condition will persist for any length of time. Exceptions: none. Returns the value most recently passed to SetClientData, or NIL if SetClientData has never been called. Exceptions: none. Attaches to the handle a reference to some client data of the caller's choosing. Exceptions: none. Property list manipulation Information about files is communicated between FTP User and Server in both directions by means of FTP property lists. There is a built-in set of properties whose values are of predefined types. The package takes care of converting between the Cedar representations of these values (INT, ROPE, GMT, etc.) and the representations used on-the-wire. Additionally, there may be user-defined properties whose values are always treated by the FTP package as ROPEs. The FTP package will send any user-defined properties supplied by the client program; but of course there is no guarantee that the server will know what to do with them (in general, undefined properties are simply ignored). Property lists are used by both the FTP User and FTP Server portions of this package. However, the following paragraphs describe property lists as viewed by a client of the FTP User. There are two property lists associated with each FTP Handle: local and remote. The "local" property list is filled with values by the client program and is sent to the server as the argument to commands such as Retrieve, Store, etc. In response, the server sends back a property list, which the FTP package remembers as the "remote" property list before giving control to the client program (usually via a call-back procedure). Thus, the normal sequence of operations in any command is for the client program to set the local property list as required for a command, then issue the command, then read back the results from the remote property list. (For completeness, the client can also read back the local property list, but cannot set the remote property list.) The contents of the local property list persists from one command to the next; so the client need specify only those arguments that are changing prior to each command. For example, the userName and userPassword properties usually remain the same during an entire FTP connection. The remote property list, on the other hand, is replaced in its entirety by every server response. The set of properties that the client must supply varies, both among different commands and among different servers. The following guidelines are offered: 1. Most servers require that a userName and userPassword property be supplied in every command. The connectName and connectPassword are required only when it is desired to "connect" (obtain owner-like access) to a directory different from the user's default directory. 2. The name of the file to be operated on must be given either as individual components (directory, nameBody, and version, without any delimiting punctuation characters) or as a serverFileName punctuated according to server conventions (which may vary among different servers). If BOTH are provided, the individual components are used to default missing fields in the serverFileName. It should be mentioned that any file name components not specified either individually or as part of the serverFileName will be defaulted according to server conventions (which may vary among different servers). In commands that permit remote enumerations (Delete, Enumerate, and Retrieve), the file name properties may include "*" or other pattern match characters implemented by the server. 3. Most other properties pertain to the files being transferred, and need be supplied only during a local-to-remote transfer (i.e., a Store from an FTP User). In particular, the type and (if binary) byteSize properties must always be specified during a Store, and the createDate and size properties are extremely desirable. For further information, consult the "Pup FTP Specification", file [Maxc]FTPSpec.press. Returns BasicTime.nullGMT if the specified property is not present in the list. Exceptions: none. Returns the value whose representation is zero if the specified property is not present in the list. Example call: t: Type _ h.GetEnumeratedProperty[type].type; Exceptions: none. Returns -1 if the specified property is not present in the list. Exceptions: none. Returns NIL if the specified property is not present in the list. Exceptions: none. Returns NIL if the specified property is not present in the list. Exceptions: none. Operates on the local property list. Exceptions: none. Operates on the local property list. Example call: h.SetEnumeratedProperty[type, [type[binary]]]; Exceptions: none. Operates on the local property list. Exceptions: none. Operates on the local property list. Exceptions: none. Operates on the local property list. Exceptions: none. Renders the local property list empty. Exceptions: none. Returns the arguments of the most recent call to SetDesiredProperties. Exceptions: none. Establishes the set of properties that the server will send in response to subsequent commands. If props=ALL[FALSE] and userDefinedProps=NIL then the server will send all possible properties. Otherwise the server will send only those built-in properties specified by props and those user-defined properties specified by userDefinedProps. Specifying the minimum set of properties needed can have a significant performance advantage, particularly if all that is required is the names of files. (Note: the server may send more properties than you requested, and it will ignore requests for properties that it never heard of.) Exceptions: none. FTP User commands Command procedures all follow a similar pattern. When the client calls one of these procedures, the FTP package alternately interacts with the remote server and calls client-provided call-back procedures. The Failed signal is used to communicate exceptions in both directions between the FTP package and the client; see the "Exceptions" section at the end of this interface for a more detailed discussion. Note that in addition to the exceptions documented with each command, Failed may be raised with a ConnectionFailure any time control is inside the FTP package. Any client call-back procedure may raise Failed[code: aborted] to terminate the entire command. Requests the server to delete the file(s) described by the local property list. For each file, Delete obtains a remote property list and then calls confirm[h]. This procedure should examine the remote property list and decide whether or not the file is to be deleted, returning TRUE if so and FALSE if not. (A confirm procedure of NIL is equivalent to one that always returns TRUE.) If confirm returns TRUE, the server attempts to delete the remote file. The enumeration of deletion candidates continues regardless of the result returned by confirm and regardless of the outcome of the deletion attempt. Exceptions: If the server rejects the local property list, Failed is raised (unresumably) before the confirm procedure has been called for the first time. If the server reports failure to delete a specific file, Failed is raised; the client may resume this signal to continue the enumeration. Requests the server to enumerate the file(s) described by the local property list. For each file, Enumerate obtains a remote property list and then calls noteFile[h]. This procedure should read the remote property list to obtain the information it desires, then return. Exceptions: If the server rejects the local property list, Failed is raised (unresumably) before the noteFile procedure has been called for the first time. There are no further opportunities for server-reported failures. Requests the server to rename the file described by the local property list. Rename immediately calls the changeName procedure, which should change the local property list as desired and then return. (Generally it makes sense to change only file name components, i.e., serverFileName or one or more of directory, nameBody, and version. In principle FTP could be used to change other file properties; but no existing FTP server implements this. Note that the remote property list is not filled in during Rename.) The server then renames the file and Rename returns. Exceptions: If the server rejects either the unmodified or the modified local property list, Failed is raised (unresumably) after the changeName procedure has been called. Requests the server to retrieve the file(s) described by the local property list. For each file, Retrieve obtains a remote property list and then calls confirm[h]. This procedure should examine the remote property list and decide whether or not the file is to be retrieved, returning TRUE if so and FALSE if not. (A confirm procedure of NIL is equivalent to one that always returns TRUE.) If confirm returns TRUE, the server attempts to begin retrieval of the remote file. If that succeeds, Retrieve calls the transfer procedure, passing it the remote byte stream for the FTP connection. The transfer procedure is expected to consume data from the stream until IO.EndOfStream occurs, and then return. Finally, Retrieve calls the complete procedure with ok=TRUE if the entire protocol handshake completed correctly and FALSE if something went wrong; complete is expected to do whatever local cleanup is appropriate in those two situations. The complete procedure is always called if the transfer procedure was called (unless it is NIL). The enumeration of retrieval candidates continues regardless of the result returned by confirm and regardless of the outcome of the retrieval attempt. Exceptions: If the server rejects the local property list, Failed is raised (unresumably) before the confirm procedure has been called for the first time. If the server reports failure to retrieve a specific file, Failed is raised; the client may resume this signal to continue the enumeration. The transfer procedure may raise Failed if it detects that something has gone wrong with the file transfer (e.g., local disk overflow). It is not necessary for the transfer procedure to catch PupStream.StreamClosing. The FTP package catches this, unwinds the call of the transfer procedure, calls complete[ok: FALSE], and then raises the Failed signal with an appropriate code. (Of course, the transfer procedure should catch UNWIND if it has local cleanup to do.) Requests the server to store into a server file described by the local property list. This command is structured the same as Retrieve for symmetry, even though at most one file is stored per call. First, Store obtains a remote property list and then calls confirm[h]. This procedure should examine the remote property list and decide whether or not the file is to be stored, returning TRUE if so and FALSE if not. (A confirm procedure of NIL is equivalent to one that always returns TRUE. Note: a few servers do not return a property list during Store, in which case the remote property list will be entirely empty at this point.) If confirm returns TRUE, Store calls the transfer procedure, passing it the remote byte stream for the FTP connection. The transfer procedure is expected to copy the file data to the stream and then return. Finally, Store calls the complete procedure with ok=TRUE if the entire protocol handshake completed correctly and FALSE if something went wrong; complete is expected to do whatever local cleanup is appropriate in those two situations. The complete procedure is always called if the transfer procedure was called (unless it is NIL). Store then returns. Exceptions: If the server rejects the local property list, Failed is raised (unresumably) before the confirm procedure has been called. If the server reports failure to store the file, Failed is raised; the client may either resume or unwind this signal with essentially the same effect (the option to resume the signal is for symmetry with Retrieve). The transfer procedure may raise Failed if it detects that something has gone wrong with the file transfer (e.g., data error in the local file). It is not necessary for the transfer procedure to catch PupStream.StreamClosing. The FTP package catches this, unwinds the call of the transfer procedure, calls complete[ok: FALSE], and then raises the Failed signal with an appropriate code. (Of course, the transfer procedure should catch UNWIND if it has local cleanup to do.) FTP Server commands Unless you are implementing an FTP Server you should ignore this section. The server commands are included in FTP.mesa because the Server and User share many of the primitives for property lists, error handling, etc. An FTP server consists of two logically separate components. There is a "listener", which listens for connection requests on the FTP server rendezvous socket, establishes Pup byte stream connections, and spawns server instances. Each server instance then independently manages its byte stream connection and performs the FTP protocol. The listener is separated out since it doesn't really have anything to do with the FTP protocol itself. The FTP package does provide a default listener, but the client is free to construct its own if the default one is inadequate. Note: throughout the FTP Server commands, "local" refers to the server and "remote" refers to the FTP User. This is the reverse of the convention followed in the FTP User commands. Creates a default listener at the specified socket number which will accept connection requests and will FORK an instance of Server for each one. The procs record contains the server command procedures (see below). If accept is not NIL then the listener will call it before accepting each connection request, passing it the address of the requestor and the number of FTP connections already accepted and not yet closed. If it returns accept=TRUE then the connection will be accepted. If it returns accept=FALSE then the connection will be rejected; the reason, if supplied, will be transmitted to the requestor. "timeoutSeconds" specifies how long a stream will be kept open without activity. If "fileInfo" is specified, it causes a listener for the "single packet protocol for file info" to be created. This listener will run concurrently with the normal FTP server and with other instances of itself. For each packet received on the socket, "fileInfo" will be called. The values returned by "fileInfo" will be sent back to the caller machine provided that "return" is TRUE. Exceptions: none. Shuts down the listener so that it will accept no further connection requests. This is done automatically if the client ceases to hold onto the listener handle. DestroyListener has no effect on any Server instances that have already been spawned. Exceptions: none. Performs the FTP server protocol on the supplied Pup byte stream previously opened by the caller. This procedure does not return until the connection closes, so it is best to FORK it. The procs record contains the server command procedures (see below). Exceptions: none. The client of Server passes it a ServerProcs record, which defines the semantics of each of the FTP protocol commands. For some of the commands, the called procedure is required to invoke call-back procedures in a specific sequence, as detailed in the comments below. If the procedure for a given command is NIL then the FTP Server will not permit that command to be issued by the FTP User. Commands: Other operations: Most of the server command procedures follow a similar pattern. At the time of the call, the handle's remote property list is filled in with the properties sent by the FTP User in the request. The procedure is expected to fill in the local property list (which is initially empty) with the properties of the local file being operated on, and then invoke a call-back procedure which (among other things) transmits the local property list back to the FTP User. Ordinarily, the command procedure is expected to fill in the local property list with all available properties. However, if the FTP User specified the desired properties explicitly, the command procedure may fill in just the requested properties. The caller's desires may be obtained by calling GetDesiredProperties. If it returns [props: ALL[FALSE], userDefinedProps: NIL] then all available properties should be filled in; otherwise only the specified properties need be filled in. A command procedure reports locally-detected exceptions to the FTP package by raising Failed; the code and text passed to Failed should describe the problem as precisely as possible (this information is transmitted directly to the FTP User). A command procedure should not attempt to catch Failed (arising from call-back procedures) or PupStream.StreamClosing, but should catch UNWIND if it has local cleanup to do. The following paragraphs detail what each of the command procedures is expected to do. This procedure should examine the remote property list and determine whether the request is reasonable (at least one file matching the property list exists, credentials in order, etc.) If so, for each file matching the request it should do the following. First, it should fill in the local property list with all relevant properties of the local file. Then it should call the confirm procedure it was passed. If confirm returns FALSE then no action should be taken on this file, and delete should go on to the next file. If confirm returns TRUE then delete should attempt to delete the file. Regardless of the outcome, it should then call the complete procedure it was passed (the result returned by complete may be ignored). This should continue until all files matching the remote property list have been enumerated, at which point the delete procedure should return. Exceptions: If the remote property list is unacceptable, delete should raise ERROR Failed with resumable=FALSE before calling confirm for the first time; this will cause delete to be terminated. If the attempt to delete a specific file fails, delete should raise SIGNAL Failed with resumable=TRUE to report the problem; this signal will always be resumed, and delete should continue the enumeration of deletion candidates as described above. This procedure should examine the remote property list and determine whether the request is reasonable (at least one file matching the property list exists, credentials in order, etc.) If so, for each file matching the request it should fill in the local property list with all available properties of the local file, and then call the noteFile procedure it was passed. This should continue until all files matching the remote property list have been enumerated, at which point the enumerate procedure should return. Exceptions: If the remote property list is unacceptable, enumerate should raise ERROR Failed with resumable=FALSE before calling noteFile for the first time; this will cause enumerate to be terminated. There are no further opportunities for server-reported failures. When a Rename command is received, Server obtains the remote caller's existing and changed property lists and calls the rename procedure. The remote property list designates the existing file and the local property list specifies the requested modification (note that this is a peculiar use of the local property list). Rename should attempt to perform the requested operation and then return. Exceptions: If any failure occurs, enumerate should raise ERROR Failed with resumable=FALSE; this will cause rename to be terminated. This procedure should examine the remote property list and determine whether the request is reasonable (at least one file matching the property list exists, credentials in order, etc.) If so, for each file matching the request it should do the following. First, it should fill in the local property list with all relevant properties of the local file. Then it should call the confirm procedure it was passed. If confirm returns NIL then no action should be taken on this file, and retrieve should go on to the next file. If the result returned by confirm is not NIL then it is the remote byte stream for the FTP connection. Retrieve should attempt to copy the file data to the stream. Regardless of the outcome, retrieve should then call the complete procedure it was passed (the result returned by complete may be ignored). This should continue until all files matching the remote property list have been enumerated, at which point the retrieve procedure should return. Exceptions: If the remote property list is unacceptable, retrieve should raise ERROR Failed with resumable=FALSE before calling confirm for the first time; this will cause retrieve to be terminated. If the attempt to retrieve a specific file fails, retrieve should raise SIGNAL Failed with resumable=TRUE to report the problem; this signal will always be resumed, and retrieve should continue the enumeration of retrieval candidates as described above. This procedure should examine the remote property list and determine whether the request is reasonable (file can be opened or created, credentials in order, etc.) If so, it should fill in the local property list with all relevant properties of the local file (in particular, a complete set of name properties). Then it should call the confirm procedure it was passed. If confirm returns NIL then no action should be taken on this file, and store should return. If the result returned by confirm is not NIL then it is the remote byte stream for the FTP connection. Store is expected to consume data from the stream until IO.EndOfStream occurs. Regardless of the outcome of this, store should then call the complete procedure it was passed. Complete returns TRUE if the entire protocol handshake completed correctly and FALSE if something went wrong; store is expected to do whatever local cleanup is appropriate in those two situations. Finally, store should return. Note: the FTP user is not notified of successful completion until store returns. Exceptions: If the remote property list is unacceptable, store should raise ERROR Failed with resumable=FALSE before calling confirm; this will cause store to be terminated. If the attempt to transfer the file fails, store should raise SIGNAL Failed with resumable=TRUE to report the problem; this signal will always be resumed, and store should continue on and call complete as described above. This is called once during the initial version handshake. It is passed the remote caller's herald and should return the server's herald. Exceptions: none. This is called immediately upon receipt of any command besides Version when this is either the first command of the FTP connection or the credentials properties (userName, userPassword, connectName, connectPassword) are different from the ones in the previous command. CheckCredentials should read the credentials properties from the remote property list; and if they are acceptable it should return. Exceptions: If the credentials are unacceptable, checkCredentials should raise ERROR Failed with resumable=FALSE. This will abort the command, and the command procedure will not be called. Utilities A collection of useful procedures which, though they don't have anything directly to do with the FTP protocol, may be of interest to clients of FTP. Determines the file type by reading the stream. The stream should initially be positioned at the beginning of the file by the caller, and is left positioned at an arbitrary place. FileType reads the stream looking for bytes with the high-order bit equal to one. If it finds one, it returns type=binary; otherwise (having read the entire stream) it returns type=text. Exceptions: any signal besides IO.EndOfStream raised by calling stream.getBlock. Determines the file type of the data referenced by block. Returns type=binary if it finds any byte with the high-order bit equal to one; otherwise returns type=text. This procedure is quite efficient. Exceptions: none. Exceptions This is the only signal used by the FTP package. It is used to communicate exceptions both from the FTP package to the client and from client call-back procedures to the FTP package. The code is an indication of what went wrong and is intended to facilitate client error recovery. The text is a suggested error message for human consumption. If the error was generated by the remote server then code is the actual FTP error code that was returned and text is the server's error message if it provided one; otherwise text is generated locally as a function of code. (Note: if the FTP protocol gets extended, it is possible that codes in the FTPReplyCode range but not listed in the enumeration will be returned.) The Failed signal is resumable in certain situations, indicated by resumable=TRUE. The Retrieve and Delete commands actually perform remote enumerations with an option to retrieve or delete each file. If the retrieve or delete attempt fails for one file in the enumeration, the Failed signal is raised to notify the client of the error. If the client resumes the signal, the enumeration continues with the next file. Of course, it is also permissible to unwind the signal just as with unresumable signals; this aborts the remote enumeration. Simple clients that know they are always dealing with a single file at a time can simply unwind Failed unconditionally. When a client call-back procedure detects a failure, it should raise ERROR Failed to communicate the failure to the FTP package, passing arguments describing the nature of the failure as precisely as possible. This signal is in turn passed on to the original caller of the FTP operation. More precisely, the signal is caught, unwound, and regenerated by the FTP package, so the call-back procedure is always terminated. It is the FTP package which decides whether or not the failure is resumable. One should not call any of the FTP commands from within a catch phrase for this signal; however, it is OK to call the GetXXXProperty procedures. In general, once the signal has been unwound, it is OK to resume calling the FTP package with the same handle. In some situations, however, unwinding the signal causes the FTP connection to be aborted; any subsequent call will result in the unresumable failure connectionClosed. Reply codes returned by FTP servers General; typically generated immediately in response to a command: Property errors: syntactic or semantic errors in the presented properties; typically generated immediately in response to a command: Specific to file access commands; can occur during any file operation: Errors generated locally; can occur at any time: Connection errors (generated from within FTP package): Client-generated abort: Bob Hagmann February 11, 1985 2:16:37 pm PST changes to: DIRECTORY, Destroy Bob Hagmann May 1, 1985 11:21:01 am PDT added single packet protocol to CreateListener changes to: FileInfoProc, CreateListener Ê F– "cedar" style˜šœ™Jšœß™ß—šœ™Jšœ#™#Jšœ™Icode™)K™)—unitšÏk ˜ Kšœ œ˜Kšœœœ˜Kšœœ˜(Kšœœœ˜—šœœ œ˜Kš˜K˜Kšœœœ˜Kšœœœ˜—J˜J˜™J˜Kšœœœ˜šœœ˜ J™I—šÏnœœ œœœœœ˜gJ™îJ™9—š žœœ)œœœœ˜tJ™¡—šžœœ ˜J™ÐJ™—šžœœ œœ˜1Jšœœ{™‡J™—š ž œœ œœœ˜8Jšœ<œ:™y—šž œœœœ˜/J™b——J˜J˜™J™Jšœ›œœ œâ™ªJ™·J™¬J™ÐJ™ù™šJ™ŒJ™ˆJ™Ã—J™\K˜šœ œ˜KšÏcœ!˜)KšŸœ˜#KšŸ œ ŸœŸ œ˜>KšŸœ˜‰KšŸœ˜'—K˜Kšœœ$˜6Kšœœ"˜:Kšœœ˜1Kšœœ˜0Kšœœ œ˜DK˜Kšœœœ œ%˜LKšœœœ œ˜:šœœœ˜šœœ˜'Kšœ0˜0Kšœ˜Kšœ˜ ——Kšœ9˜9K˜Kšœœ˜&šžœœ?œ˜sJ™a—šžœœEœ˜ƒ™rJ™-—J™—šžœœAœ œ˜mJ™R—šžœœ?œ œ˜jJšœœH™S—š žœœœ œ œ˜iJšœœH™S—šžœœ7˜LJšœ6™6—šžœœ=˜Xšœ2™2Jšœ.™.—Jšœ™—šžœœ*œ˜FJšœ6™6—šžœœ(œ˜CJšœ6™6—šžœœœ œ˜BJšœ6™6—šžœœ ˜"Jšœ8™8—K˜Kš œ œœœœ ˜>Kšœ œœœ˜"š žœœ œ(œœœ˜dJšœX™X—š žœœ3œœœœ˜aJšœiœœœô™€——J˜J˜™J™J™•Lš ž œœœ œ œ˜@Lšž œœœœ˜9Lšž œœœœ˜3šžœœ$œ˜5Jš œ–œ œ!œ*œœÅ™ÛJ™¤—šž œœœ˜8Jšœ™J™Ü—šžœœœ˜7Jšœ´™´J™«—šžœœ$œ3œ˜mJš œœœ œ!œ*œ™„Jš œœØœ:œÏœ™†Jšœ–™–J™®Jšœ®œoœ ™È—šžœœ$œ3œ˜jJš œœ œ!œ*œ™öJš œœìœ:œÏœ™®J™ðJšœ®œoœ ™È——J˜J˜™J™J™ØJ™µJ™´J˜Kšœ œœ˜$Kšœœ˜Lšž œœœ'œœ œ œœ˜vLšž œœœ œœ œ œ œœ œœ˜¨šžœœPœœ œCœœ˜ŽJšœiœi™ÖJšœœÎœ<œe™ŒJšœP™PJšœýÏsœ™‚J™—šžœœ˜$J™öJ™—šžœœ œœ˜5Jšœ¯œI™üJ™—J™J™‹Jšœ(œO™zLšœ œœ˜*šœœœ˜"J™ Kšœœ˜Kšœ!œ˜%Kšœœ˜Kšœœ˜#Kšœœ˜KšœœŸÐckŸ0˜XJ™Kšœ)œŸ¡Ÿ&˜[—J™J™ÊJšœÓœœœo™ãJšœúœ™ŸJ™J™VLš žœœœ œ œ˜ILš žœœœ œœ˜BšžœœœA˜^Jšœþ™þJšœ­œW™‰Jšœœµ™ÌJ™Jš œMœœ™œœ‘™¹—šžœœœœ˜IJšœ„™„JšœPœœ™™Š—šžœœœ ˜*Jšœ‰™‰Jšœ:œœ*™…—šžœœœI˜hJšœþ™þJšœ­œY™‰Jšœ)œƒ™¯J™‘Jš œOœœŸœœ”™Ä—šžœœœI˜eJšœƒœF™ÌJšœ)œûœ:œß™ÉJš œLœœœœ~™‹—š žœœœœœœ˜[J™š—šžœœœ ˜.J™JšœOœœL™¼——J˜J˜™ J™J™”šžœœ œœ˜5J™îJ™P—šžœœœ˜KšœŸ7˜UK˜J™šœ0™0Jšœ6™6—K˜ K˜Kšœ˜Kšœ˜Kšœ˜K˜K˜K˜ KšœŸ&˜