From VisualWorks®, 7.6 of February 1, 2008 on February 1, 2008 at 12:33:58 pm (Dialog confirm: 'You are filing-in a Parcel source file!\\While this is possible it will not have\the same effect as loading the parcel.\None of the Parcel''s prerequisites will\be loaded and none of its load actions\will be performed.\\Are you sure you want to file-in?' withCRs) ifFalse: [self error: 'Parcel file-in abandoned. Choose terminate or close.'] <class> <name>FTPSignal</name> <environment>Net</environment> <super>Net.NetError</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPSignal
FTPSignal is the abstract class for all Ftp signals. Instance Variables:
</comment> <class> <name>FTPConnectionSignal</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPConnectionSignal
Signaled when a lack of connection is detected. Instance Variables:
</comment> <class> <name>FtpURL</name> <environment>Net</environment> <super>OS.URLwithAuthority</super> <private>false</private> none passiveMode user password userAddress <imports></imports> <category>Net-Resources</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FtpURL
FtpURL implements FTP URL as defined in RFC2396 (URL). It is responsible for parsing FTP url string and publishes methods providing access to FTP file storages. FtpURL is designed to resemble Filename class, so it implements many of the methods providing directory access and stream creation. Instance Variables: host <String> FTP server host name port <Integer> FTP server port number user <String> User name password <String> Password
</comment> <class> <name>FTPResponse</name> <environment>Net</environment> <super>Net.NetworkResponse</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPResponse
The File Transfer Protocol defines that each request to the server will have a response. The response syntax is a three digit number followed by an english message. Here is an excerpt from RFC959 exlaining the five different types of responses. 1yz Positive Preliminary reply The requested action is being initiated; expect another reply before proceeding with a new command. (The user-process sending another command before the completion reply would be in violation of protocol; but server-FTP processes should queue any commands that arrive while a preceding command is in progress.) This type of reply can be used to indicate that the command was accepted and the user-process may now pay attention to the data connections, for implementations where simultaneous monitoring is difficult. The server-FTP process may send at most, one 1yz reply per command. 2yz Positive Completion reply The requested action has been successfully completed. A new request may be initiated. 3yz Positive Intermediate reply The command has been accepted, but the requested action is being held in abeyance, pending receipt of further information. The user should send another command specifying this information. This reply is used in command sequence groups. 4yz Transient Negative Completion reply The command was not accepted and the requested action did not take place, but the error condition is temporary and the action may be requested again. The user should return to the beginning of the command sequence, if any. It is difficult to assign a meaning to "transient", particularly when two distinct sites (Server- and User-processes) have to agree on the interpretation. Each reply in the 4yz category might have a slightly different time value, but the intent is that the user-process is encouraged to try again. A rule of thumb in determining if a reply fits into the 4yz or the 5yz (Permanent Negative) category is that replies are 4yz if the commands can be repeated without any change in command form or in properties of the User or Server (e.g., the command is spelled the same with the same arguments used; the user does not change his file access or user name; the server does not put up a new implementation.) 5yz Permanent Negative Completion reply The command was not accepted and the requested action did not take place. The User-process is discouraged from repeating the exact request (in the same sequence). Even some "permanent" error conditions can be corrected, so the human user may want to direct his User-process to reinitiate the command sequence by direct action at some point in the future (e.g., after the spelling has been changed, or the user has altered his directory status.) The following function groupings are encoded in the second digit: x0z Syntax - These replies refer to syntax errors, syntactically correct commands that don't fit any functional category, unimplemented or superfluous commands. x1z Information - These are replies to requests for information, such as status or help. x2z Connections - Replies referring to the control and data connections. x3z Authentication and accounting - Replies for the login process and accounting procedures. x4z Unspecified as yet. x5z File system - These replies indicate the status of the Server file system vis-a-vis the requested transfer or other file system action. The third digit gives a finer gradation of meaning in each of the function categories, specified by the second digit. The list of replies below will illustrate this. Note that the text associated with each reply is recommended, rather than mandatory, and may even change according to the command with which it is associated. The reply codes, on the other hand, must strictly follow the specifications in the last section; that is, Server implementations should not invent new codes for situations that are only slightly different from the ones described here, but rather should adapt codes already defined. Instance Variables:
</comment> <class> <name>FTPProtocolInterpreter</name> <environment>Net</environment> <super>Net.NetProtocolInterpreter</super> <private>false</private> none socket stream passiveServer mutex <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPProtocolInterpreter
FTPProtocolInterpreter communicates with the FTP server via the control and data connection. It has knowledge of the connection via the socket instance variable. It also handles server transfers on the stream instance variable. FTPProtocolInterpreter receives server replies (as an FTPResponse), handles them where appropriate, and passes replies to the FTPClient where appropriate. Instance Variables: socket <SocketAccessor> socket connection to the serverr stream <ExternalReadStream> transfer stream passiveServer <Boolean> boolean to determine method of transfer controlReadProcess <Process> description of variable's function dataTransferProcess <Process> description of variable's function abortingProcess <Process> description of variable's function controlPromises <Dictionary> description of variable's function mutex <Semaphore> description of variable's function Class Variables: CrLf <ClassOfVariable> description of variable''s function
</comment> <class> <name>MacFTPProtocolInterpreter</name> <environment>Net</environment> <super>Net.FTPProtocolInterpreter</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.MacFTPProtocolInterpreter
MacFTPProtocolInterpreter implements Macintosh type TCP socket commands
</comment> <class> <name>FTPTransientReply</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPTransientReply
FTPTransientReply is signaled by the FTPProtocolInterpreter when the command was not accepted by the server and the requested action did not take place. However, the error condition is temporary and the action may be requested again. Instance Variables:
</comment> <class> <name>FTPNoSocketSignal</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPNoSocketSignal
This exception is signaled if FTP client does not have active socket connection to FTP server at the time of the request. It is application's responsibility to catch this signal. A possible strategy might be to send connect message to FTP client and retry the request Instance Variables:
</comment> <class> <name>FTPChallenge</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPChallenge
FTPChallenge is signaled when the FTPProtocolInterpreter discovers that socket is no longer active and is used to restart client connection. Instance Variables:
</comment> <class> <name>FTPPreliminaryReply</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPPreliminaryReply
FTPPreliminaryReply is signaled when the FTPProtocolInterpreter receives a preliminary reply from the server when the FTPProtocolInterpreter is not expecting a preliminary reply. Instance Variables:
</comment> <class> <name>FTPClient</name> <environment>Net</environment> <super>Net.NetClient</super> <private>false</private> none binaryBlockSize transferOffset delayedResponse finalResponse commandInProgress <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPClient
The combination of FTPClient, FTPProtocolInterpreter, FTPResponse is a complete File Transfer Protocol client implementation based on RFC959 and RFC1123. FTPClient contains methods to do server transfers and other commands. File Transfer Protocol is a protocol to enable two remote computers to share their file systems. This is done with a two connections. A control connection and a data connection. The control connection between the client and the maintains synchronization with the use of command-reply structure. Commands are sent as strings on the control connection. Each command issued from the client to the server on the control connection returns a reply from the server. Data connections are used for transfering of files between the file systems. Note that the data connection may be used in either direction and that the data connection need not exist all of the time. In this client implementation, the client does not have direct knowledge of how its control and data connections are handled. That is done by FTPProtocolInterpreter. The FTPClient keeps track of the FTPUser and is the API to the protocol. The FTP-Restart protocol contains methods for restarting previous FTP transfers which have failed. This is done with the use of Smalltalk Streams, and not with markers (the way that RFC959 specifies). Only the defaul modes of transfer (Stream) and structure (File) are used in this implementation. Instance Variables: binaryBlockSize <Number> holds a Number that determines the amount of bytes transfered at a time transferOffset <Number> used as the server marker at which file transfer should be restarted delayedResponse <FTPResponse> delayed server response finalResponse <FTPResponse> final server response Class Variables:
</comment> <class> <name>FTPUser</name> <environment>Net</environment> <super>Net.NetUser</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPUser
Read the comment for NetUser. FTPUser adds the account instance variable. Account is used by the Ftp protocol. Instance Variables: account <String> holds an account name, where the account is on the ftp server
</comment> <class> <name>FTPIntermediateReply</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPIntermediateReply
FTPIntermediateReply is signaled when the FTPProtocolInterpreter receives a intermediate reply from the server. It is trapped in the FTPClient and used to execute a series of Ftp commands. Instance Variables:
</comment> <class> <name>FTPSocketAccessor</name> <environment>Net</environment> <super>OS.SocketAccessor</super> <private>false</private> none ftpConn callback <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPSocketAccessor
This class provides additional logic for FTP-based data transfer. FTP data transfer involves two socket connections -- control and data connections; both should be monitored during data transfer. Instance Variables: ftpConn <FTPClient> An instance of FTP Client representing the control connection
</comment> <class> <name>FTPPermSignal</name> <environment>Net</environment> <super>Net.FTPSignal</super> <private>false</private> none <imports></imports> <category>Net-FTP-Support</category> <attributes> <package>FTP</package> </attributes> </class> <comment> Net.FTPPermSignal
FTPPermSignal is signaled when the FTPProtocolInterpreter receives a permanent error reply from a server. It is analagous to the Permanent Negative Completion reply documented in the FTPResponse class comment.
</comment> <name>PermError</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>TempError</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>ReplyError</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>CrLf</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>statics</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>ChallengeError</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>ProtocolError</name> <environment>Net.FTPClient</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>DebugLevels</name> <environment>Net.FTPClient</environment> <private>false</private> <constant>false</constant> <category>debugging</category> <initializer>Core.Set new</initializer> <attributes> <package>FTP</package> </attributes> <name>DefaultPassiveMode</name> <environment>Net.FtpURL</environment> <private>false</private> <constant>false</constant> <category>defaults</category> <attributes> <package>FTP</package> </attributes> <name>Resp257</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>PermErrorSignal</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>CrLf</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>statics</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>Resp227</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>Resp200</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>TempErrorSignal</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>Resp331</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>Resp150</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>Resp250</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>Resp332</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>ProtocolErrorSignal</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>obsolete</category> <initializer>nil</initializer> <attributes> <package>FTP</package> </attributes> <name>Resp125</name> <environment>Net.FTPProtocolInterpreter</environment> <private>true</private> <constant>false</constant> <category>responses</category> <attributes> <package>FTP</package> </attributes> <name>CrLf</name> <environment>Net.MacFTPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>const</category> <initializer>Array new: 5</initializer> <attributes> <package>FTP</package> </attributes> <methods> Net.FTPSignal <category>printing</category>
defaultMessageText ^super defaultMessageText, '\(' withCRs, parameter printString, ')'
</methods> <methods> Net.FTPSignal class <category>handling ftp responses</category>
handleResponse: resp "trigger exceptions for all responses but 2xx (success) " resp isSuccess ifFalse: [ resp isChallenge ifTrue: [ FTPChallenge signalWith: resp]. resp isPreliminaryReply ifTrue: [ FTPPreliminaryReply signalWith: resp]. resp isTransientError ifTrue: [ FTPTransientReply raiseRequestWith: resp errorString: resp printString]. resp isPermanentError ifTrue: [ FTPPermSignal raiseWith: resp errorString: resp printString]]. ^resp
</methods> <methods> Net.FtpURL <category>private</category>
beCurrentDirectoryFor: anFtpUrl with: aClient " Opportunistic code: try chaging directory using full path. This failed, try segment-by-segment descent " [aClient setCurrentDirectory: anFtpUrl filePathString] on: FTPPermSignal do: [:ex | 1 to: anFtpUrl path size do: [ :ind | aClient setCurrentDirectory: (anFtpUrl pathAt: ind) ]]
client | effectiveUser effectivePassword client | user == nil ifTrue: [effectiveUser := 'anonymous'. effectivePassword := self class emailAddress] ifFalse: [effectiveUser := user. effectivePassword := password]. client := FTPClient connectToHost: host port: port. [client login: effectiveUser passwd: effectivePassword ] on: FTPChallenge do: [ :ex | (self handleChallenge: ex parameter for: client) ifTrue: [ ex return ] ifFalse: [ ex pass ] ]. self passiveMode ifTrue: [ client setServerPassive ]. ^client
copyFromFTP: aURI "Copied from superclass, but we could eventually optimize this to do server to server transfers." self writeStreamDo: [:dest | aURI readStreamDo: [:src :parms | [src atEnd] whileFalse: [dest nextPutAll: (src nextAvailable: 1024)]]]
fileDirectoryDo: aDyadicBlock " Position to current directory and evaluate dyadic block. First argument is canonicalized uri, the second is FTP client used in operations " | client canonical | ^self safelyExecuteBlock: [client := self client. canonical := self canonicalize. self beCurrentDirectoryFor: canonical directory with: client. aDyadicBlock value: canonical value: client]
fileDirectoryDoAndClose: aDyadicBlock " Same as fileDirectoryDo: but ensure client is closed at the end " ^self fileDirectoryDo: [:canonicalUri :client | [aDyadicBlock value: canonicalUri value: client] ensure: [client close]]
handleChallenge: aResponse for: client | str | aResponse needAccountForLogin ifTrue: [ (str := Dialog request: ((#ProvideLoginAccountFor << #net >> 'Please, provide login account for <1s>') expandMacrosWith: client hostName)) isEmpty not ifTrue: [ client account: str. ^true]]. aResponse needPassword ifTrue: [(str := SimplePasswordDialog new request: ((#ProvideLoginPasswordFor << #net >> 'Please, provide login password for <1s>') expandMacrosWith: client hostName) initialAnswer: '' onCancel: []) notNil ifTrue: [ client pass: str. ^true ]]. ^false
indexFor: aClient | index | path last = '' ifTrue: [index := path size-1] ifFalse: [index := path size]. 1 to: index-1 do: [:i | aClient setCurrentDirectory: (self pathAt: i)]. ^index
passwordSameAs: aURI | p1 p2 | p1 := self password ifNil: [ String new]. p2 := aURI password ifNil: [ String new]. ^p1 = p2 or: [ ('anonymous' match: self user) and: [ p1 match: p2 ]]
printPath: aPathArray on: str aPathArray size = 0 ifTrue: [str nextPutAll: '/'] ifFalse: [aPathArray do: [:s | str nextPutAll: '/', s]].
protocolSignals ^FTPSignal
simpleDirectory ^super directory
userSameAs: aURL | us1 us2 | us1 := self user ifNil: [ String new]. us2 := aURL user ifNil: [ String new]. ^us1 match: us2
</methods> <methods> Net.FtpURL <category>accessing</category>
asString | str | str := (String new: 64) writeStream. self printProtocolOn: str. self printUserOn: str. self printAuthorityOn: str. ^str contents
passiveMode passiveMode isNil ifTrue: [ passiveMode := self class defaultPassiveMode ]. ^passiveMode
passiveMode: aBoolean passiveMode := aBoolean
password ^password
resource | data | data := self readStreamDo: [:str :params | str contents]. ^XML.InputSource uri: self encoding: nil stream: (data withEncoding: #UTF_8) readStream lineEndTransparent
setServerActive self passiveMode: false
setServerPassive self passiveMode: true
user ^user
</methods> <methods> Net.FtpURL <category>stream -- unguarded</category>
appendStream self isDirectory ifTrue: [self error: (#AppendStreamCanntBeOpenedOnDirectory << #net >> 'An appendStream cannot be opened on a directory')]. ^self fileDirectoryDo: [ :canonicalUri :client | client appendBinaryStream: canonicalUri tail]
binaryReadStream ^self fileDirectoryDo: [ :canonicalUri :client | client readBinaryStream: canonicalUri tail]
binaryWriteStream ^self fileDirectoryDo: [ :canonicalUri :client | client writeBinaryStream: canonicalUri tail]
envelopeAppendStream ^self appendStream
envelopeReadStream ^self readStream
envelopeWriteStream ^self writeStream
</methods> <methods> Net.FtpURL <category>utilities</category>
asFilename " Just have this method, please "
contentsOfEntireFile "Answer the contents of the file represented by the receiver." ^self readStreamDo: [ :stream :context | stream contents]
copyTo: aURI aURI asURI copyFromFTP: self
delete | isDir | isDir := self isDirectory. ^isDir ifTrue: [self removeDirectory] ifFalse: [self removeFile]
directory ^self canonicalize simpleDirectory
fastDelete " This is an opportunistic version of delete that first attempts to delete it as file and only if this fails tries to deleye it as a directory " [self removeFile] on: FTPPermSignal do: [ :ex | self removeDirectory]
fileSize " Try SIZE command first, this failed, try getting file size harder way that requires us to set up transfer " ^self fileDirectoryDoAndClose: [:canonicalUri :client | [(client fileSize: canonicalUri tail) parse213AsBytes ] on: FTPPermSignal do: [:ex | ex return: (client fileSizeFromTransferSetup: canonicalUri tail)]]
head | p index | p := (String new: 32) writeStream. path isEmpty ifTrue: [^'']. self isDirectory ifTrue: [index := path size] ifFalse: [index := path size - 1]. ^index > 0 ifTrue: [1 to: index -1 do: [:i | p nextPutAll: ((self pathAt: i), '/')]. p nextPutAll: (self pathAt: index). p contents] ifFalse: [String new]
makeDirectory ^self fileDirectoryDoAndClose: [:canonicalUri :client | client makeDirectory: canonicalUri tail]
modTime "Return modification time of fileName." ^self fileDirectoryDoAndClose: [:canonicalUri :client | (client modTime: canonicalUri tail) parse213AsTimestamp ]
removeDirectory ^self fileDirectoryDoAndClose: [:canonicalUri :client | client removeDirectory: canonicalUri tail]
removeFile ^self fileDirectoryDoAndClose: [:canonicalUri :client | client deleteFile: canonicalUri tail]
</methods> <methods> Net.FtpURL <category>printing</category>
directoryPathString | stream | stream := (String new: 64) writeStream. self printDirectoryPathOn: stream. ^stream contents
filePathString | stream | stream := (String new: 32) writeStream. self printPathOn: stream. ^stream contents
printDirectoryPathOn: aStream ^self directory printPathOn: aStream
printUserOn: aStream user == nil ifFalse: [self class encodeUserPass: user on: aStream. password == nil ifFalse: [aStream nextPutAll: ':'. self class encodeUserPass: password on: aStream]. aStream nextPutAll: '@'].
</methods> <methods> Net.FtpURL <category>stream -- guarded</category>
readStreamDo: aBlock ^self fileDirectoryDoAndClose: [:canonicalUri :client | client readBinaryStream: canonicalUri tail do: [:data | aBlock value: data value: (Dictionary new at: #MIME put: canonicalUri guessMimeType; yourself)]]
writeStreamDo: aBlock | res | ^self fileDirectoryDoAndClose: [:canonicalUri :client | res := client writeBinaryStream: canonicalUri tail do: aBlock. (Delay forMilliseconds: 100) wait. res]
</methods> <methods> Net.FtpURL <category>testing</category>
exists path isEmpty ifTrue: [^true]. path asSet asArray = #('') ifTrue: [^true]. ^self fileDirectoryDoAndClose: [:canonicalUri :client | client parseDirectoryContents: #() do: [:dictionary | ((dictionary at: #name) match: self tail) ifTrue: [^true]]. false]
isDirectory path isEmpty ifTrue: [^true]. path asSet asArray = #('') ifTrue: [^true]. ^self fileDirectoryDoAndClose: [:canonicalUri :client | client parseDirectoryContents: #() do: [:dictionary | ((dictionary at: #name) match: self tail) ifTrue: [^dictionary at: #isDir]]. false]
</methods> <methods> Net.FtpURL <category>file utilities</category>
canonicalize ^(path size > 0 and: [path last = '']) ifTrue: [self copy path: (path copyFrom: 1 to: path size - 1); yourself] ifFalse: [self]
directoryContents | client results basePath canonical | ^self safelyExecuteBlock: [client := self client. results := OrderedCollection new. [canonical := self canonicalize. basePath := canonical path. self beCurrentDirectoryFor: canonical with: client. client parseDirectoryContents: #() do: [:dictionary | | name newPath | name := dictionary at: #name. newPath := (dictionary at: #isDir) ifTrue: [basePath , (Array with: (canonical escaped: name) with: '')] ifFalse: [basePath copyWith: (self escaped: name)]. results add: (canonical copy path: newPath)]] ensure: [client close]. results]
</methods> <methods> Net.FtpURL <category>resolution</category>
construct: aFileComponent ^self canonicalize copy addComponent: aFileComponent; yourself
</methods> <methods> Net.FtpURL <category>initialize</category>
parseHost: aHostSpecification | fullHost | fullHost := self parseUserinfo: aHostSpecification. self parseHostPort: fullHost
parseUserinfo: aHostSpecification | idx fullUser fullHost | (aHostSpecification includes: $@) ifTrue: [idx := aHostSpecification lastIndexOf: $@. fullUser := aHostSpecification copyFrom: 1 to: idx-1. fullHost := aHostSpecification copyFrom: idx+1 to: aHostSpecification size. (fullUser includes: $:) ifTrue: [idx := fullUser indexOf: $:. user := self validateUser: (fullUser copyFrom: 1 to: idx-1). password := self validatePassword: (fullUser copyFrom: idx+1 to: fullUser size)] ifFalse: [user := self validateUser: fullUser. password := nil]] ifFalse: [user := nil. password := nil. fullHost := aHostSpecification]. ^fullHost
</methods> <methods> Net.FtpURL <category>comparing</category>
sameAs: aURI ^(super sameAs: aURI) and: [ (user isNil and: [ aURI user isNil]) or: [ (self userSameAs: aURI ) and: [ self passwordSameAs: aURI ]]]
</methods> <methods> Net.FtpURL class <category>class initialization</category>
initialize "FtpURL initialize." (ObjectMemory dependents includes: self) ifFalse: [ObjectMemory addDependent: self]. DefaultPassiveMode := Settings ftpPassiveMode.
obsolete "Break dependencies" [ObjectMemory dependents includes: self] whileTrue: [ObjectMemory removeDependent: self]. ^super obsolete
</methods> <methods> Net.FtpURL class <category>accessing</category>
defaultPassiveMode ^DefaultPassiveMode
defaultPassiveMode: aBoolean DefaultPassiveMode := aBoolean
emailAddress " self emailAddress " ^userAddress isNil ifFalse: [userAddress] ifTrue: [userAddress := Settings anonymousLoginPassword]
emailAddress: aString self userAddress: aString
protocol ^'ftp'
userAddress ^userAddress
userAddress: aString userAddress := aString
userAllowedChars ^self unreservedChar, ';&=+$,'
</methods> <methods> Net.FtpURL class <category>updating</category>
update: anAspectSymbol with: aParameter from: aSender "Record snapshot changes to we now whther we're installed or not." anAspectSymbol == #earlySystemInstallation ifTrue: [userAddress := nil]
</methods> <methods> Net.FtpURL class <category>private</category>
getAddressFromMailFile ^#{MailFile} ifDefinedDo: [:mailFile| | domain | mailFile installed ifTrue: [domain := mailFile profileAt: 'set' at: 'domain' ifAbsent: []. domain isNil ifTrue: [self queryAddress] ifFalse: [mailFile userName, '@', domain]] ifFalse: [self queryAddress]] elseDo: [self queryAddress]
queryAddress | address | address := Dialog request: (#WhatIsYourEmailAddress << #net >> 'What is your email address?'). ^address isEmpty ifTrue: [self error: (#NoEmailAddressSupplied << #net >> 'No email address supplied'). nil] ifFalse: [address]
</methods> <methods> Net.FtpURL class <category>encoding/decoding</category>
encodeUserPass: aString on: stream self encode: aString on: stream allowed: self userAllowedChars
</methods> <methods> Net.FtpURL class <category>defaults</category>
defaultPortNumber ^SocketAccessor IPPORT_FTP
</methods> <methods> Net.FTPResponse <category>testing</category>
hasConnectionClosed ^self responseEquals: '421'
isChallenge ^ self isIntermediateReply
isClosedConnection " Last digit = 6 means 'connection closed' " ^(self codeAt: 3) == $6
isError ^self isPermanentError or: [ self isTransientError or: [ self isUnknownError ] ]
isIntermediateReply "Positive Intermediate reply. The command has been accepted, but the requested action is being held" ^self responseTypeIs: $3
isPermanentError ^self responseTypeIs: $5
isPreliminaryReply ^(self responseEquals: '125') or: [ self responseEquals: '150']
isTransferAborted " This response is returned when transfer in progress is interrupted by sending an ABORT command. Either thread sending abort or thread performing transfer may receive this response. If this response is seen by ABORT command, it is returned to the application but exception is suppressed. If data transfer thread sees it, it causes FTPTransientReply exception " ^self responseEquals: '426'
isTransferCompletion ^self code = '226' or: [self code = '250']
isTransientError ^self responseTypeIs: $4
needAccountForLogin ^self responseEquals: '332'
needFileInfo ^self responseEquals: '350'
needMoreInfo ^self isIntermediateReply
needPassword ^self responseEquals: '331'
notImplementedCommand ^self responseEquals: '502'
</methods> <methods> Net.FTPResponse <category>parsing</category>
parse150ForReceive "Parse a 150 response for a RETR request" "for RETR- 150 Sending ""c:\serv\test2.bmp"" (100150 bytes). Mode STREAM Type BINARY." "for STOR- 150 Ready to receive ""c:\serv\test4.bmp"". Mode STREAM Type BINARY." ^self message first equivalentTo: $R ignoreCase: true
parse150Size "Parse a 150 response for a RETR request. Returns the expected transfer size or nil. Size is not guaranteed to be present in the 150 message." "150 Sending ""c:\serv\test2.bmp"" (100150 bytes). Mode STREAM Type BINARY." | string | (string := self parseForStringFrom: $( toEndString: ' bytes') isEmpty ifTrue: [^nil]. ^string asNumber
parse213AsBytes "Parse the '213' response for file size in bytes." ^self code = '213' ifTrue: [ self message asNumber ] ifFalse: [ FTPProtocolInterpreter permSignal raiseWith: self ]
parse213AsTimestamp "Parse the '213' response for a timestamp represented as YYYYMMDDhhmmss." | year month day hour minute second | self code = '213' ifFalse: [^FTPProtocolInterpreter permSignal raiseWith: self]. stream := self message readStream. year := (stream next: 4) asNumber. month := (stream next: 2) asNumber. day := (stream next: 2) asNumber. hour := (stream next: 2) asNumber. minute := (stream next: 2) asNumber. second := (stream next: 2) asNumber. ^Timestamp fromDate: (Date newDay: day monthNumber: month year: year) andTime: (Time fromSeconds: hour * 3600 + (minute * 60) + second)
parse227HostPort "Parse the '227' response for a PASV request. Raise error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' Return ('host.addr.as.numbers' , port#) tuple." | numbers | numbers := self parse227HostPortString tokensBasedOn: $,. numbers size ~~ 6 ifTrue: [ FTPProtocolInterpreter permSignal raiseWith: self message]. numbers := numbers collect: [:ea | Integer readFrom: ea readStream ]. ^IPSocketAddress hostAddress: (ByteArray withAll: (numbers copyFrom: 1 to: 4)) port: (ByteArray with: (numbers at: 5) with: (numbers at: 6))
parse227HostPortString "Parse the '227' response for a PASV request. Raise error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' " | str left right | code ~= '227' ifTrue: [ FTPProtocolInterpreter permSignal raiseWith: self message]. left := (str := self message) nextIndexOf: $( from: 1 to: str size. left isNil ifTrue: [ FTPProtocolInterpreter permSignal raiseWith: self message]. right := str nextIndexOf: $) from: left + 1 to: str size. right isNil ifTrue: [ FTPProtocolInterpreter permSignal raiseWith: str]. ^(str copyFrom: left + 1 to: right - 1)
parse230 "230 User logged in, proceed." "not done yet"
parse250Dir "Parse the '250' response for a CWD request. Returns the directory name in the 250 reply." code ~= '250' ifTrue: [FTPProtocolInterpreter permSignal raiseWith: self printString]. ^self parseForQuotedString
parse257Dir "Parse the '257' response for a MKD or PWD request. Returns the directory name in the 250 reply." code ~= '257' ifTrue: [FTPProtocolInterpreter permSignal raiseWith: self printString]. ^self parseForQuotedString
parseForQuotedString "Parse the FTPresponse for the string inside a pair of quotes" ^self parseForStringFrom: $" to: $"
parseForStringFrom: aChar1 to: aChar2 "Parse the FTPresponse for the string inside a pair of aCharacter" | str start stop | str := self message. start := str findFirst: [:ch | ch == aChar1]. start = 0 ifTrue: [ ^'' ]. stop := str nextIndexOf: aChar2 from: start + 1 to: str size. stop = 0 ifTrue: [ ^'' ]. ^str copyFrom: start + 1 to: stop - 1 "get rid of characters"
parseForStringFrom: aChar toEndString: aString "Parse the FTPresponse for the string inside a pair of aCharacter and aString" | str start stop | str := self message. stop := str indexOfSubCollection: aString startingAt: 1. stop = 0 ifTrue: [ ^'' ]. start := str prevIndexOf: aChar from: stop to: 1. start = 0 ifTrue: [ ^'' ]. ^str copyFrom: start + 1 to: stop - 1 "get rid of characters"
</methods> <methods> Net.FTPResponse class <category>signals</category>
connectionSignal ^FTPConnectionSignal
</methods> <methods> Net.FTPResponse class <category>initialize</category>
initialize self initializeSignals
initializeSignals FTPConnectionSignal notifierString: (#needToMakeConnection << #net >> 'need to make connection'); nameClass: self message: #connectionSignal.
</methods> <methods> Net.FTPProtocolInterpreter <category>connection management</category>
connectToHost: aHostName port: aNumber | port | port := aNumber isNil ifTrue: [ self defaultPortNumber ] ifFalse: [ aNumber]. self socket:( SocketAccessor family: SocketAccessor pfInet type: SocketAccessor sockStream). self socket connectTo: (IPSocketAddress hostName: aHostName port: port). self stream: self socket readStream lineEndCRLF. [self getResponse] on: FTPPreliminaryReply do: [ :ex | ex retry ]. ^self
disconnect self closeConnection
</methods> <methods> Net.FTPProtocolInterpreter <category>accessing</category>
closed ^self socket == nil or: [ self socket isActive not ]
defaultPortNumber ^SocketAccessor IPPORT_FTP
mutex mutex == nil ifTrue: [mutex := Semaphore forMutualExclusion]. ^mutex
passiveServer ^passiveServer
passiveServer: aBoolean passiveServer := aBoolean
setServerActive "Use passive or active mode for data transfers. With a false argument, use the normal PORT mode. With a true argument, use the PASV command and the server will provide a port on which it is listening." self passiveServer: false
setServerPassive "Use passive or active mode for data transfers. With a false argument, use the normal PORT mode. With a true argument, use the PASV command and the server will provide a port on which it is listening." self passiveServer: true
socket ^socket
socket: aSocket socket := aSocket
stream ^stream
stream: aStream stream := aStream
</methods> <methods> Net.FTPProtocolInterpreter <category>private</category>
closeConnection stream notNil ifTrue: [stream close]. socket notNil ifTrue: [socket close]
parseResponse "get a response" ^FTPResponse readFrom: self stream
privatePollResponse "Answer next response if available, otherwise answer nil without waiting. We first check our internal buffer with #basicAtEnd, and then the operating system buffer with #readWaitWithTimeoutMs:. A zero argument ensures we don't block." ^socket notNil ifTrue: [(stream basicAtEnd and: [socket readWaitWithTimeoutMs: 0]) ifFalse: [self parseResponse]] ifFalse: [nil]
putCommand: aString | line | line := aString , CrLf. self class log: [((#put1s << #net >> '*put* <1s>') expandMacrosWith: (self sanitize: line))] level: #FTPClient. self socket isActive ifFalse: [FTPProtocolInterpreter log: [(#ExceptionSocketIsNotActive << #net >> '*exception* socket is not active')] level: #FTPException. FTPNoSocketSignal raise ]. [self socket writeWaitWithTimeoutMs: 200 ] whileTrue: [self pollResponse]. self socket sendTo: self socket getPeer buffer: line
sanitize: aString "Internal" | s | s := aString copy. ((s copyFrom: 1 to: (5 min: aString size)) sameAs: 'pass ') ifTrue: [| i | i := s size. [i > 5 and: [CrLf includes: (s at: i - 1)]] whileTrue: [i := i - 1]. s := (s copyFrom: 1 to: 5) , ((String new: i - 5) atAllPut: $*) , (s copyFrom: i + 1 to: s size)]. ^s
</methods> <methods> Net.FTPProtocolInterpreter <category>protocol commands</category>
abort "Abort a file transfer " | line | self class log: ['*put* ABOR'] level: #FTPClient. self mutex critical: [line := 'ABOR' , CrLf. self socket sendTo: self socket getPeer buffer: line start: 1 for: line size flags: SocketAccessor msgOob]. ^self getResponse
makeport "Create a new socket and send a PORT command for it." | sock resp | sock := FTPSocketAccessor newTCP. sock listenFor: 1. (resp := self sendport: (self socket getName hostAddress) with: sock getName port) isSuccess ifFalse: [ self class permSignal raiseWith: resp. ]. ^sock
pollResponse " Answer next response if one is waiting, nil otherwise " | resp | ^(resp := self privatePollResponse) notNil ifTrue: [FTPSignal handleResponse: resp] ifFalse: [ nil ]
sendCommand: aString "Send a command and return the response. Expect a successful response, if not generate an exception." ^self putCommand: aString; getResponse
sendport: aHostAddress with: aPort "Send a PORT command with the current [sic] host and the given port number." | bytes cmd | bytes := aHostAddress, (ByteArray with: aPort // 256 with: aPort \\ 256). cmd := 'PORT '. bytes do: [:byte | cmd := cmd , byte printString] separatedBy: [cmd := cmd , ',']. ^self sendCommand: cmd
</methods> <methods> Net.FTPProtocolInterpreter <category>initialize-release</category>
initialize Settings ftpPassiveMode ifTrue: [ self setServerPassive] ifFalse: [self setServerActive].
</methods> <methods> Net.FTPProtocolInterpreter <category>ssl connection</category>
useSecureConnection ^self error: (#NotImplementedForThisClient << #net >> 'This function has not been implemented for this client.')
</methods> <methods> Net.FTPProtocolInterpreter <category>responses</category>
getResponse "Obtains and answers an instance of FTPResponse " ^FTPSignal handleResponse: self parseResponse
</methods> <methods> Net.FTPProtocolInterpreter class <category>signals</category>
challengeSignal ^FTPChallenge
connectionSignal ^FTPConnectionSignal
intermediateSignal ^FTPIntermediateReply
permErrorSignal ^FTPPermSignal
permSignal ^FTPPermSignal
preliminarySignal ^FTPPreliminaryReply
transientErrorSignal ^FTPTransientReply
</methods> <methods> Net.FTPProtocolInterpreter class <category>initialization</category>
initialize "self initialize" self initializeSequences. self initializeSignals. self registerToDebug
initializeSequences CrLf := String with: Character cr with: Character lf.
initializeSignals "self initializeSignals" "unexpected [123]xx reply" FTPPreliminaryReply notifierString: (#x1xxReply << #net >> '1xx reply'); nameClass: self message: #preliminarySignal. FTPIntermediateReply notifierString: (#x3xxReply << #net >> '3xx reply'); nameClass: self message: #intermediateSignal. FTPTransientReply notifierString: (#x4xxError << #net >> '4xx error'); nameClass: self message: #transientErrorSignal. FTPPermSignal notifierString: (#x5xxError << #net >> '5xx error'); nameClass: self message: #permSignal. FTPChallenge notifierString: (#needToMakeConnection << #net >> 'need to make connection'); nameClass: self message: #challengeSignal. FTPConnectionSignal notifierString: (#needToMakeConnection << #net >> 'need to make connection'); nameClass: self message: #connectionSignal.
</methods> <methods> Net.FTPProtocolInterpreter class <category>debugging</category>
debugLabelsAndValues ^OrderedCollection new add: ((#FTPClientCommands << #net >> 'FTPClient Commands')->#FTPClient); add: ((#FTPResponses << #net >> 'FTPResponses')->#FTPResponse); add: ((#FTPTesting << #net >> 'FTP Testing')->#FTPTest); add: ((#FTPExceptions << #net >> 'FTPExceptions')->#FTPException); yourself
menuItemName ^(#FTPMessages << #net >> 'FTP Messages')
</methods> <methods> Net.FTPProtocolInterpreter class <category>instance creation</category>
connectToHost: aHostName port: aNumber ^self new connectToHost: aHostName port: aNumber
</methods> <methods> Net.MacFTPProtocolInterpreter <category>connection management</category>
abort "Abort a file transfer. Uses out-of-band data. Doesn''t follow RFC proceedure to send Telnet IP and Synch, so maybe this should be changed." "Macintosh wants propper TCP socket commands, like writeAll:." | line | self mutex critical: [line := 'ABOR' , CrLf. self socket writeAll: line]. ^self getResponse
</methods> <methods> Net.MacFTPProtocolInterpreter <category>private</category>
putCommand: aString "Macintosh wants propper TCP socket commands, like writeAll:." | line | line := aString , CrLf. self class log: [((#put1s << #net >> '*put* <1s>') expandMacrosWith: (self sanitize: line))] level: #FTPClient. self socket isActive ifTrue: [self socket writeWait ] ifFalse: [ FTPProtocolInterpreter log: [(#exception << #net >> '*exception*')] level: #FTPException. FTPChallenge raise ]. self socket writeAll: line
</methods> <methods> Net.MacFTPProtocolInterpreter class <category>class initialization</category>
initialize CrLf := String with: Character cr with: Character lf
</methods> <methods> Net.FTPTransientReply class <category>testing</category>
mayResume ^true
</methods> <methods> Net.FTPChallenge <category>initialize-release</category>
initialize super initialize. messageText := (#PositiveIntermediateReply << #net >> '3xx Positive Intermediate reply') asString
</methods> <methods> Net.FTPClient <category>accessing</category>
binaryBlockSize ^binaryBlockSize
binaryBlockSize: anInteger binaryBlockSize := anInteger
commandInProgress ^commandInProgress ifNil: [ commandInProgress := false ]
commandInProgress: aBoolean commandInProgress := aBoolean
userAccount ^self user account
userPassword ^self user password
</methods> <methods> Net.FTPClient <category>commands</category>
abort "Abort a file transfer. Intercept response 426. This response, if received, actually belongs to the data transfer thread, so we poll next response expecting it to be 226, which is ours. Once found, we make 426 to be the delayed response so that data transfer thread will get it on close, and return " | nextResp | ^[self clientPI abort] on: FTPTransientReply do: [ :ex | ex parameter isTransferAborted ifTrue: [(nextResp := self clientPI getResponse) isTransferCompletion ifTrue: [delayedResponse := ex parameter. FTPProtocolInterpreter log: [((#SetDelayedResponse1s << #net >> 'Set delayed response <1s>') expandMacrosWith: delayedResponse code)] level: #FTPResponse. FTPProtocolInterpreter log: [((#ReturningResponse1s << #net >> 'Returning response <1s>') expandMacrosWith: nextResp code)] level: #FTPResponse. ex return: nextResp ]. " If we didn't find 226, just return this response " FTPProtocolInterpreter log: [((#ReturningResponse1s << #net >> 'Returning response <1s>') expandMacrosWith: ex parameter code)] level: #FTPResponse. ex return: ex parameter.] ifFalse: [ ex pass ]]
account: aString "Send new account name." ^self executeCommand: 'ACCT ' , aString retryChallenge: false
beCurrentDirectory: directoryName "obsolete" ^self setCurrentDirectory: directoryName
cmdRest: position | resp | ^[resp := self executeCommand: 'REST ', position printString. FTPSignal raiseWith: resp] on: FTPChallenge do: [:ex | ex parameter needFileInfo ifTrue: [ex return: resp] ifFalse: [ex pass]]
cmdUser: aString "Send user" ^self executeCommand: 'USER ' , aString retryChallenge: false
currentWorkingDirectory ^self executeSequence: [self executeCommand: 'PWD']
deleteFile: aFileNameString ^self executeSequence: [self executeCommand: 'DELE ' , aFileNameString]
fileSize: fileName "Return size of fileName." ^self executeSequence: [self executeCommand: 'SIZE ' , fileName]
fileSizeFromTransferSetup: fileName "Return size of fileName. Setup transfer to get the remote side to tell us about file size, then promptly close data connection" | array size accessor | ^self executeSequence: [ self binaryTransfer. array := self privateStartTransferCmd: 'RETR ', fileName asString. size := array last. accessor := array first. accessor callback: nil. accessor close. [self readFinalResponseBlock value] on: FTPSignal do: [ :ex | ex return]. size].
fileStructure ^self structure: $F
help "Return server's default help information." ^self help: nil
help: aString "ask for specific HELP with the string value, not implemented on all servers." ^self executeSequence: [self executeCommand: ('HELP',( aString isString ifTrue: [ ' ',aString] ifFalse:[ '']))]
makeDirectory: directoryNameString ^self executeSequence: [self executeCommand: 'MKD ' , directoryNameString]
mode: aChar "This implementation only supports Stream mode" "S - Stream B - Block C - Compressed" ^self executeSequence: [self executeCommand: 'MODE ' , aChar asSymbol asString]
modTime: fileName "Return modification time of fileName." ^self executeSequence: [self executeCommand: 'MDTM ' , fileName]
noop "this is the No Operation command used to check the server for a connection and proper response" ^self executeSequence: [self executeCommand: 'NOOP']
pass: aString "Send password" ^ self executeCommand: 'PASS ' , aString retryChallenge: false
presentWorkingDirectory ^self executeSequence: [self executeCommand: 'PWD ']
quit | resp | self clientPI socket isActive ifTrue: [ resp := self executeCommand: 'QUIT']. self disconnect. ^resp
quote: aString ^self executeSequence: [self executeCommand: 'QUOTE ', aString]
remoteSystemType ^self executeSequence: [self executeCommand: 'SYST ']
removeDirectory: directoryNameString ^self executeSequence: [self executeCommand: 'RMD ' , directoryNameString]
rename: dirOrFileName to: targetName "Rename initialFileName to targetFileName. Where targetFileName is a filename only, not the path" ^self executeSequence: [ [self executeCommand: 'RNFR ' , dirOrFileName] on: FTPChallenge do: [:ex | ex parameter needFileInfo ifTrue: [ex return: (self executeCommand: 'RNTO ' , targetName)] ifFalse: [ex pass]]]
setCurrentDirectory: directoryName "Change to the directory named directoryName." | cmd | cmd := directoryName = '..' ifTrue: ['CDUP'] ifFalse: ['CWD ' , directoryName]. ^self executeSequence: [self executeCommand: cmd]
setRemoteStreamPosition: position " Set position in remote stream for the next transfer operation. This command should result in 350 response. Anything elese is error " ^[self executeCommand: 'REST ' , position asString] on: FTPChallenge do: [:ex | ex parameter needFileInfo ifTrue: [ex return] ifFalse: [ex pass]]
setServerActive "Use this command in conjunction with a file transfer command to set the server to active. This message must be sent before each transfer command that requires a passive server." self clientPI setServerActive.
setServerPassive "Use this command in conjunction with a file transfer command to set the server to PASV. This message must be sent before each transfer command that requires a passive server." self clientPI setServerPassive.
site: cmdString "SITE commands are server specific. Some commands require permission granted by and administrator. User the #siteHelp to determine which commands are valid. Such as the following, MSG <any message> Sends a message to the console. or, KICK <user handle> 214- Throw a user off." ^self executeSequence: [self executeCommand: 'SITE ', cmdString]
siteHelp ^self help: 'SITE'
stat ^self executeSequence: [self executeCommand: 'STAT']
streamMode "sets the transfer MODE as Stream. Other possibilites are Bock and Compressed" ^self mode: $S
structure: aChar "set the file transfer structure. This implementation only supports the File Structure" "F - File R - Record P - Page" ^self executeSequence: [self executeCommand: 'STRU ' , aChar asSymbol asString]
transferOffset ^transferOffset
transferOffset: position transferOffset := position
</methods> <methods> Net.FTPClient <category>streams -- guarded</category>
appendBinaryStreamCmd: aString do: aBlock ^self executeSequence: [self guardedBinaryTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor writeStream binary do: aBlock ]]
appendStreamCmd: aString do: aBlock ^self appendBinaryStreamCmd: aString do: aBlock
appendTextStreamCmd: aString do: aBlock ^self executeSequence: [self guardedTextTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor writeStream text do: aBlock ]]
readBinaryStream: aFileNameString do: aBlock ^self readBinaryStreamCmd: 'RETR ', aFileNameString do: aBlock
readBinaryStreamCmd: aString do: aBlock ^self executeSequence: [self guardedBinaryTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor readStream binary do: aBlock ]]
readStream: aFileNameString do: aBlock ^self readBinaryStream: aFileNameString do: aBlock
readStreamCmd: aString do: aBlock ^self readBinaryStreamCmd: aString do: aBlock
readTextStream: aFileNameString do: aBlock ^self readTextStreamCmd: 'RETR ', aFileNameString do: aBlock
readTextStreamCmd: aString do: aBlock ^self executeSequence: [self guardedTextTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor readStream text do: aBlock ]]
writeBinaryStream: aFileNameString do: aBlock ^self writeBinaryStreamCmd: 'STOR ', aFileNameString do: aBlock
writeBinaryStreamCmd: aString do: aBlock ^self executeSequence: [self guardedBinaryTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor writeStream binary do: aBlock ]]
writeStream: aFileNameString do: aBlock ^self writeBinaryStream: aFileNameString do: aBlock
writeStreamCmd: aString do: aBlock ^self writeBinaryStreamCmd: aString do: aBlock
writeTextStream: aFileNameString do: aBlock ^self writeTextStreamCmd: 'STOR ', aFileNameString do: aBlock
writeTextStreamCmd: aString do: aBlock ^self executeSequence: [self guardedTextTransferCmd: aString do: [ :ftpAccessor | self stream: ftpAccessor writeStream text do: aBlock ]]
</methods> <methods> Net.FTPClient <category>private -- guarded stream setup</category>
guardedBinaryTransferCmd: aString do: aBlock "Perform guarded binary transfer over data connection. Returns final response. This is used for transfers that are done under our control, so we ensure that data stream is closed and final data response is read" ^self binaryTransfer; guardedTransferCmd: aString do: aBlock
guardedTextTransferCmd: aString do: aBlock "Perform guarded text transfer over data connection. Returns final response. This is used for transfers that are done under our control, so we ensure that data stream is closed and final data response is read" ^self textTransfer; guardedTransferCmd: aString do: aBlock
guardedTransferCmd: aString do: aBlock "Perform guarded transfer over data connection. Returns result of the block. This is used for transfers that are done under our control, so we ensure that data stream is closed and final data response is read" | accessor result | accessor := self startTransferCmd: aString. result := [accessor callback: nil. aBlock value: accessor] ensure: [accessor close]. finalResponse := self readFinalResponseBlock value. ^result
stream: externalStream do: aBlock ^[ aBlock value: externalStream ] ensure: [ externalStream close ]
</methods> <methods> Net.FTPClient <category>private -- command interface</category>
checkCommandInProgress self commandInProgress ifTrue: [self disconnect. self error: #CanNotStartTransferComamnd << #net >> 'Can not start the transfer command. There is already a command in progress'].
executeCommand: aString retryChallenge: aBoolean " Most command travel though here. If retryChallenge is true and the response is intermediate reply (3xx), we handle it as a challenge (by sending user name, password, etc.) and then retry the original command. To avoid recursion, user authorisation commands themselves (USER, PASS, ACCT) are sent with no retry on challenge" ^[ self clientPI sendCommand: aString ] on: FTPChallenge do: [ :ex | FTPProtocolInterpreter log: [ 'FTPChallenge: ', ex messageText ] level: #FTPException. (self handleChallenge: ex parameter command: aString) ifTrue: [ aBoolean ifTrue: [ ex retry ]] ifFalse: [ ex pass ]]
handleChallenge: aResponse command: aString aResponse needAccountForLogin ifTrue: [aString = 'ACCT ' ifTrue: [ ^false ]. self userAccount notNil ifTrue: [ self account: self userAccount. ^true]]. aResponse needPassword ifTrue: [aString = 'PASS ' ifTrue: [ ^false ]. self userPassword notNil ifTrue: [ self pass: self userPassword. ^true ]]. ^false
privateStartTransferCmd: aString "Initiate a transfer over the data connection. If the transfer is active, send a port command and the transfer command, and accept the connection. If the server is passive, send a pasv command, connect to it, and start the transfer command. Return the passiveServer instance variable to false after the operation. Either way, return the socket for the connection and the expected size of the transfer. The expected size may be None if it could not be determined." | conn resp | delayedResponse := nil. self clientPI passiveServer ifTrue: [| sockAddress | sockAddress := (self executeCommand: 'PASV') parse227HostPort. (conn := FTPSocketAccessor newTCP) connectTo: sockAddress. transferOffset > 0 ifTrue: [self cmdRest: transferOffset]. resp := self transferCmd: aString ] ifFalse: [| sock counter | sock := self clientPI makeport. transferOffset > 0 ifTrue: [self cmdRest: transferOffset]. resp := self transferCmd: aString. "!!!this didn't work on Macs: conn := sock accept." counter := 50. [conn := sock acceptNonBlock. conn isNil and: [counter > 0]] whileTrue: [counter := counter - 1. (Delay forSeconds: 1) wait]. "If no client solicited a connection, give up." conn isNil ifTrue: [Transcript show: (#givingUp << #dialogs >> 'Giving up!') asString; cr. sock close. ^nil]. ]. conn ftpConn: self. ^Array with: conn with: resp parse150Size.
startTransferCmd: aString "Initiate a transfer over the data connection. Returns the socket for the connection." ^(self privateStartTransferCmd: aString ) first
transferCmd: aString " This one is used to send transfer command (RETR, STOR, STOU, LIST, NLST). The only valid reply is a preliminary reply " | resp | ^[resp := self clientPI sendCommand: aString. FTPSignal raiseWith: resp ] on: FTPPreliminaryReply do:[:ex | ex return: ex parameter ]
</methods> <methods> Net.FTPClient <category>low-level command interface</category>
executeCommand: aString ^ self executeCommand: aString retryChallenge: true
executeSequence: aBlock " Catch reply 421 (Server closed control connection. This should help in most time-out/stale connection situations. There is a small chance of getting this in the middle of a command sequence and this would be bad " self connectIfClosed. ^[[aBlock value] on: FTPTransientReply do: [ :ex | ex parameter hasConnectionClosed ifTrue: [ self reconnect. ex retry ] ifFalse: [ ex pass ]]] on: OSErrorHolder peerFaultSignal do: [ :ex | self reconnect. ex retry ]
executeSequenceSafely: aBlock self checkCommandInProgress. ^self executeSequence: aBlock
getResponse ^self clientPI getResponse
sendCommand: aString " This is direct passthrough to protocol interpreter. We are not using it, but if somebody needs to send unadited command directly to FTP server, this is the place " ^self clientPI sendCommand: aString
</methods> <methods> Net.FTPClient <category>streams -- unguarded</category>
appendBinaryStream: aFileString ^ self executeSequence: [(self openBinaryTransferCmd: 'APPE ', aFileString) writeStream binary]
appendStream: aFileString ^self appendBinaryStream: aFileString
appendTextStream: aFileString ^ self executeSequence: [(self openTextTransferCmd: 'APPE ', aFileString) writeStream lineEndCRLF ]
readBinaryStream: cmd ^ self executeSequence: [(self openBinaryTransferCmd: 'RETR ', cmd) readStream binary]
readStream: cmd ^self readBinaryStream: cmd
readStreamCmd: cmd ^self executeSequence: [(self openBinaryTransferCmd: cmd) readStream ]
readTextStream: cmd ^self executeSequence: [(self openTextTransferCmd: 'RETR ', cmd) readStream lineEndCRLF]
writeBinaryStream: aFileString ^ self executeSequence: [(self openBinaryTransferCmd: 'STOR ', aFileString) writeStream binary ]
writeStream: aString ^self writeBinaryStream: aString
writeStreamCmd: cmd ^self executeSequence: [(self openBinaryTransferCmd: cmd) writeStream ]
writeTextStream: aFileString ^ self executeSequence: [(self openTextTransferCmd: 'STOR ', aFileString) writeStream lineEndCRLF ]
</methods> <methods> Net.FTPClient <category>private -- unguarded stream setup</category>
openBinaryTransferCmd: aString "Open binary transfer over the data connection. Returns socket accessor for the connection.This is used for transfers that are entirely controller by user agent. User agent has a responsibility to close data stream. We make sure that when this happens, the final data response is read" ^self binaryTransfer; openTransferCmd: aString
openTextTransferCmd: aString "Open text transfer over the data connection. Returns socket accessor for the connection.This is used for transfers that are entirely controller by user agent. User agent has a responsibility to close data stream. We make sure that when this happens, the final data response is read" ^self textTransfer; openTransferCmd: aString
openTransferCmd: aString "Open transfer over the data connection. Returns socket accessor for the connection.This is used for transfers that are entirely controller by user agent. User agent has a responsibility to close data stream. We make sure that when this happens, the final data response is read" ^(self startTransferCmd: aString) callback: self readFinalResponseBlock; yourself
</methods> <methods> Net.FTPClient <category>ftp experimental commands</category>
xBeCurrentDirectory: directoryName "Change to the directory named directoryName." ^directoryName = '..' ifTrue: [self executeCommand: 'XCUP'] ifFalse: [self executeCommand: 'XCWD ' , directoryName]
xCurrentWorkingDirectory ^self executeCommand: 'XPWD'
xMakeDirectory: directoryNameString ^self executeCommand: 'XMKD ' , directoryNameString
xRemoveDirectory: directoryNameString ^self executeCommand: 'XRMD ' , directoryNameString
</methods> <methods> Net.FTPClient <category>private -- utilities</category>
binaryTransfer ^self executeCommand: 'TYPE I'
copyFrom: inputStream to: outputStream ^self copyFrom: inputStream to: outputStream blockSize: self binaryBlockSize
copyFrom: inputStream to: outputStream blockSize: blockSize | data counter | data := inputStream contentsSpecies new: blockSize. [ inputStream atEnd ] whileFalse: [counter := inputStream nextAvailable: blockSize into: data startingAt: 1. outputStream next: counter putAll: data startingAt: 1].
dosAttributesFromStream: aStream | attributes date time dirOrSize sizeString | attributes := Dictionary new. date := Date readFrom: aStream. aStream skipSeparators. time := Time readFrom: aStream. attributes at: #timestamp put: (Timestamp fromDate: date andTime: time). dirOrSize := aStream skipSeparators; upToSeparator. (attributes at: #isDir put: dirOrSize = '<DIR>') ifFalse: [sizeString := dirOrSize copyWithout: $,. attributes at: #size put: (Integer readFrom: sizeString readStream)]. attributes at: #name put: (aStream skipSeparators; upToEnd). ^attributes
lines: cmd do: aBlock "Retrieve data in line mode. Suppress all empty lines. Used by directory listing routines. The argument is a RETR or LIST command." self checkCommandInProgress. self commandInProgress: true. ^[self executeSequence: [self readTextStreamCmd: cmd do: [ :remoteStream | remoteStream linesDo: [ :line | FTPProtocolInterpreter log: [ ((#data1s << #net >> '*data*<1s>') expandMacrosWith: line) ] level: #FTPResponse. line isEmpty ifFalse: [aBlock value: line]]] ] ] ensure: [ self commandInProgress: false ]
parseDirLine: line (line size < 3 or: ['total*' match: line]) ifTrue: [^nil]. "Assume the line format is either DOS or Unix. If the first two characters are digits, then it's DOS (?)." ^(line first isDigit and: [(line at: 2) isDigit]) ifTrue: [self dosAttributesFromStream: line readStream] ifFalse: [self unixAttributesFromStream: line readStream]
parseDirLine: line do: aBlock | isUnix isDir name index parser | ('total*' match: line) ifTrue: [^self]. "Assume it's either Windows or Unix. If it's Windows, the first two characters must be digits (?)." isUnix := false. #('0123456789' '0123456789' ) keysAndValuesDo: [:i :str | (line size >= i and: [str includes: (line at: i)]) ifFalse: [isUnix := true]]. index := 1. parser := [index := (index to: line size) detect: [:i | (line at: i) isSeparator] ifNone: [index]. index := (index to: line size) detect: [:i | (line at: i) isSeparator not] ifNone: [index]]. isUnix ifTrue: [isDir := line first = $d. parser value. "skip permissions" parser value. "skip link count" parser value. "skip user" parser value. "skip group" parser value. "skip size" 3 timesRepeat: [parser value]. "skip month, day, and time" name := line copyFrom: index to: line size] ifFalse: [isDir := ('*<DIR>*' match: line). 3 timesRepeat: [parser value]. "skip date, time, dir/size" name := name := line copyFrom: index to: line size.]. aBlock value: isDir value: name
restoreFrom: position do: aBlock " This method aids in restoring file transfers from a specified position. This position will cause the server to start transfer operation not at the beginning of the file, but at specified position. This method can be used if a long transfer was interrupted in the middle. The application may then look at how much data was already transferred and restart transfer at the point immediately after this data. " self transferOffset: position. ^aBlock ensure: [self transferOffset: 0].
textTransfer ^self executeCommand: 'TYPE A'
unixAttributesFromStream: aStream | attributes type month day yearOrTime year time date | attributes := Dictionary new. type := aStream next. attributes at: #type put: type; at: #isDir put: type == $d; at: #permissions put: aStream upToSeparator. aStream skipSeparators. attributes at: #linkCount put: (Integer readFrom: aStream); at: #user put: (aStream skipSeparators; upToSeparator); at: #group put: (aStream skipSeparators; upToSeparator). aStream skipSeparators. attributes at: #size put: (Integer readFrom: aStream). month := aStream skipSeparators; upToSeparator. aStream skipSeparators. day := Integer readFrom: aStream. yearOrTime := aStream skipSeparators; upToSeparator. (yearOrTime includes: $:) ifTrue: [year := Date today year. time := Time readFrom: yearOrTime readStream] ifFalse: [year := Integer readFrom: yearOrTime readStream. time := Time fromSeconds: 0]. date := Date newDay: day month: month year: year. attributes at: #timestamp put: (Timestamp fromDate: date andTime: time); at: #name put: aStream upToEnd. ^attributes
</methods> <methods> Net.FTPClient <category>file and directory utilities</category>
appendBinaryFileNamed: aString to: aDestString | file | self checkCommandInProgress. ^[file := aString asFilename readStream binary. self appendBinaryStreamCmd: 'APPE ', aDestString do: [ :stream | self copyFrom: file to: stream blockSize: self binaryBlockSize ]. finalResponse ] ensure: [file close]
appendFileNamed: aString to: aDestString self checkCommandInProgress. ^self appendBinaryFileNamed: aString to: aDestString
appendTextFileNamed: aString to: aDestString | file | self checkCommandInProgress. ^[file := aString asFilename readStream text. self appendTextStreamCmd: 'APPE ', aDestString do: [ :stream | self copyFrom: file to: stream ]. finalResponse ] ensure: [file close]
copyFileNamed: remoteFileString on: aBinaryStream " Copy file onto specified binary stream " ^self executeSequenceSafely: [self readBinaryStreamCmd: 'RETR ', remoteFileString do: [ :remoteStream | self copyFrom: remoteStream to: aBinaryStream]. finalResponse ]
copyFrom: aBinaryStream toFileNamed: remoteFileString " Copy specified binary stream to remote file " ^self executeSequenceSafely: [self writeBinaryStreamCmd: 'STOR ', remoteFileString do: [ :remoteStream | self copyFrom: aBinaryStream to: remoteStream]. finalResponse]
copyFromFTP: otherServer file: otherFile toFile: myFile passive: aBoolean " Direct file transfer between two servers. Copy file from specified file on otheServer to specified file on myself. The transfer is setup in such a way that the two servers talk directly to each other " ^self executeSequenceSafely: [otherServer executeSequence: [aBoolean ifTrue: [otherServer executeCommand: 'PORT ', (self executeCommand: 'PASV') parse227HostPortString] ifFalse: [self executeCommand: 'PORT ', (otherServer executeCommand: 'PASV') parse227HostPortString]. self binaryTransfer; transferCmd: 'STOR ', myFile. otherServer binaryTransfer; transferCmd: 'RETR ', otherFile. otherServer getResponse. self getResponse]]
directoryContents: argumentCollection do: aBlock "List a directory in long form. All non-empty arguments before it are concatenated to the LIST command. (This *should* only be used for a pathname.)" | cmd | cmd := 'LIST'. argumentCollection isString ifTrue: [cmd := cmd, ' ', argumentCollection] ifFalse: [ argumentCollection do: [:each | each isNil ifFalse: [cmd := cmd , ' ' , each]]]. ^self lines: cmd do: aBlock
filesInDirectory: argumentCollection do: aBlock "Evaluate block for every filenames in a given directory " "Using this command for a file transfer can cause getting wrong responses for the transfer command. Instead of using: ftpClient filesInDirectory: (Array with: dir) do: [:entry | entry = 'xxx.xxx' ifTrue: [ ftpClient retrieveFileNamed: entry as: 'xxx.jpg']]. Consider two step approach: ftpClient filesInDirectory: (Array with: dir) do: [:entry | coll add: entry]. ftpClient setCurrentDirectory: dir. ftpClient retrieveFileNamed: coll first as: 'xxx.jpg'. " | cmd | cmd := 'NLST'. argumentCollection isString ifTrue: [cmd := cmd, ' ', argumentCollection] ifFalse: [ argumentCollection do: [:each | each isNil ifFalse: [cmd := cmd , ' ' , each]]]. ^self lines: cmd do: aBlock
parseDirectoryContents: argumentCollection do: aBlock "List a directory in long form. All non-empty arguments before it are concatenated to the LIST command. (This *should* only be used for a pathname.)" | cmd attributes | cmd := 'LIST'. argumentCollection isString ifTrue: [cmd := cmd , ' ' , argumentCollection] ifFalse: [argumentCollection do: [:each | each isNil ifFalse: [cmd := cmd , ' ' , each]]]. self lines: cmd do: [:line | attributes := self parseDirLine: line. attributes notNil ifTrue: [aBlock value: attributes]]
retrieveFileNamed: remoteFileString as: localFileString | localFileStream | ^self executeSequenceSafely: [[localFileStream := localFileString asFilename writeStream binary. self readBinaryStreamCmd: 'RETR ', remoteFileString do: [ :remoteStream | self copyFrom: remoteStream to: localFileStream]. finalResponse] ensure: [localFileStream close]]
storeFileNamed: localFileString to: remoteFileString | localFileStream | ^self executeSequenceSafely: [[localFileStream := localFileString asFilename readStream binary. self writeBinaryStreamCmd: 'STOR ', remoteFileString do: [ :remoteStream | self copyFrom: localFileStream to: remoteStream]. finalResponse] ensure: [localFileStream close]]
</methods> <methods> Net.FTPClient <category>restart</category>
restartRetrieveLocalFile: localFileNameString fromFile: remoteFileNameString "restart retrieve by checking size of local file and transfering only data that has not been transferred yet. Useful to recover from transient transfer errors " | localFilename localFileStream localSize remoteSize | (localFilename := localFileNameString asFilename) exists not ifTrue: [localFilename writeStream close]. " Create file if does not exist yet " [localFileStream := localFilename appendStream binary setToEnd. localSize := localFilename fileSize. remoteSize := [(self fileSize: remoteFileNameString) parse213AsBytes] on: FTPPermSignal do: [ :ex | ex parameter notImplementedCommand ifTrue: [ ex return: 0] ifFalse: [ ex pass] ]. (remoteSize == 0 or: [localSize < remoteSize]) ifTrue: [self restoreFrom: localFileStream position do: [ self copyFileNamed: remoteFileNameString on: localFileStream ]]] ensure: [localFileStream close]
restartStoreRemoteFile: remoteFileNameString fromFile: localFileNameString " restart retrieve by checking size of remote file and transfering only data that has not been transferred yet. Useful to recover from transient transfer errors " | localFilename localFileStream remoteSize | (localFilename := localFileNameString asFilename) exists not ifTrue: [^self error: ((#File1sDoesNotExist << #net >> 'File <1s> does not exist') expandMacrosWith: localFileNameString)]. [localFileStream := localFilename readStream binary. remoteSize := [(self fileSize: remoteFileNameString) parse213AsBytes] on: FTPPermSignal do: [ :ex | ex parameter notImplementedCommand ifTrue: [ ex return: 0] ifFalse: [ ex pass] ]. localFilename fileSize > remoteSize ifTrue: [localFileStream position: remoteSize. self appendBinaryStreamCmd: 'APPE ', remoteFileNameString do: [ :remoteStream | self copyFrom: localFileStream to: remoteStream ]]] ensure: [localFileStream close]
</methods> <methods> Net.FTPClient <category>initialization-release</category>
initialize self binaryBlockSize: self class defaultBinaryBlockSize. transferOffset := 0. self portNumber: SocketAccessor IPPORT_FTP.
</methods> <methods> Net.FTPClient <category>ftp accessor callbacks</category>
pollResponses | resp | [(resp := self clientPI pollResponse) notNil] whileTrue: [resp isTransferCompletion ifTrue: [FTPProtocolInterpreter log: [((#SetDelayedResponse1s << #net >> 'Set delayed response <1s>') expandMacrosWith: resp code)] level: #FTPResponse. delayedResponse := resp.]]. ^resp
readFinalResponseBlock | resp | ^[resp := delayedResponse notNil ifTrue: [FTPProtocolInterpreter log: [((#UsingDelayedResp1s << #net >> 'Using delayed resp <1s>') expandMacrosWith: delayedResponse code)] level: #FTPResponse. FTPSignal handleResponse: delayedResponse] ifFalse: [self clientPI getResponse]. delayedResponse := nil. self transferOffset: 0. resp]
</methods> <methods> Net.FTPClient <category>connection management</category>
close self disconnect
connect "Establish a connection to the host" ^self clientPI connectToHost: self hostName port: self portNumber
connectIfClosed self clientPI closed ifTrue: [ self reconnect ]
disconnect clientPI notNil ifTrue: [clientPI disconnect]. clientPI := nil
doLogin ^self cmdUser: self user username
isConnected ^self clientPI closed not
login: userString passwd: passwdString self login: userString passwd: passwdString acct: nil
login: userString passwd: passwdString acct: acctString ^self loginWithUser: (FTPUser createUser: userString password: passwdString account: acctString)
loginToHost: aHostName asUser: userString withPassword: passwdString self loginToHost: aHostName asUser: userString withPassword: passwdString withAcct: nil
loginToHost: aHostName asUser: userString withPassword: passwdString withAcct: acctString ^self connectToHost: aHostName port: nil; login: userString passwd: passwdString acct: acctString.
loginToHost: aHostName withUser: anFTPUser self connectToHost: aHostName port: nil; loginWithUser: anFTPUser
loginWithUser: anFTPUser self user: anFTPUser. ^self doLogin
reconnect FTPProtocolInterpreter log: [(#reconnect << #net >> '*reconnect*')] level: #FTPException. self disconnect; connect; doLogin
</methods> <methods> Net.FTPClient class <category>instance creation</category>
connectToHost: aHostname ^self new connectToHost: aHostname
connectToHost: aHostname port: aPort ^self new connectToHost: aHostname port: aPort
loginToHost: aHostName asUser: userString withPassword: passwdString ^self loginToHost: aHostName asUser: userString withPassword: passwdString withAcct: nil
loginToHost: aHostName asUser: userString withPassword: passwdString withAcct: acctString | conn | conn := self new. conn loginToHost: aHostName asUser: userString withPassword: passwdString withAcct: acctString. ^conn
loginToHost: aHostName withUser: anFTPUser | conn | conn := self new. conn loginToHost: aHostName withUser: anFTPUser. ^conn
</methods> <methods> Net.FTPClient class <category>constants</category>
description ^(#FTPServer << #net >> 'FTP server') asString
serverType ^#FTP
</methods> <methods> Net.FTPClient class <category>defaults</category>
ftpAnonymousUser ^self netSettings ftpAnonymousUser
</methods> <methods> Net.FTPClient class <category>default values</category>
defaultBinaryBlockSize ^4096
defaultProtocolInterpreterClassValue "Use a special protocolInterpreter on Macintosh, because its socket implementation insists on using the right 'write*' primitives for TCP connections." ^OSHandle currentOS == #mac ifTrue: [MacFTPProtocolInterpreter] ifFalse: [FTPProtocolInterpreter]
</methods> <methods> Net.FTPUser <category>accessing</category>
account ^account
account: aString account := aString
</methods> <methods> Net.FTPUser <category>instance creation</category>
createUser: nameString password: passwdString account: acctString self username: nameString. self password: passwdString. self account: acctString.
</methods> <methods> Net.FTPUser class <category>instance creation</category>
createUser: nameString password: passwdString account: acctString ^self new createUser: nameString password: passwdString account: acctString
</methods> <methods> Net.FTPSocketAccessor <category>accessing</category>
callback ^callback
callback: aBlock callback := aBlock
ftpConn ^ftpConn
ftpConn: aValue ftpConn := aValue
</methods> <methods> Net.FTPSocketAccessor <category>private</category>
close | res | super close. self callback notNil ifTrue: [[res := callback value] on: FTPSignal do: [:ex | ex return]]. ^res
privateReadInto: buffer startingAt: index for: count "buffer is a byte-object, the place where IO is performed. index is a SmallInteger, the starting-point in the buffer. count is a SmallInteger, the number of bytes to be read." "Returns a SmallInteger, which is the count of the bytes read." "WouldBlock is a potential error condition." <primitive: 679> (self class restartableError: errorCode) ifTrue: [ftpConn notNil ifTrue: [ftpConn pollResponses]. self readWaitWithTimeoutMs: self readPauseInterval. ^OSErrorHolder needRetrySignal raise]. ^self reportErrorProceeding
</methods> <initialize> Net.FtpURL </initialize> <initialize> Net.FTPResponse </initialize> <initialize> Net.FTPProtocolInterpreter </initialize> <initialize> Net.MacFTPProtocolInterpreter </initialize> "Imported Classes:" self error: 'Attempting to file-in parcel imports. Choose terminate or close' <class> <name>NetProtocolInterpreter</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none connection defaultNetConnection <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>NetUser</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none username password fullName account savePassword mailAddress <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>URLwithAuthority</name> <environment>OS</environment> <super>OS.URLwithPath</super> <private>false</private> none host port <imports></imports> <category>Net-Resources</category> <attributes> <package>URISupport</package> </attributes> </class> <class> <name>NetworkResponse</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none code messageStream stream <imports> Tools.* </imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>SocketAccessor</name> <environment>OS</environment> <super>OS.BlockableIOAccessor</super> <private>false</private> none concreteClass <imports></imports> <category>OS-Sockets</category> <attributes> <package>OS-Sockets</package> </attributes> </class> <class> <name>NetClient</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none user hostName portNumber clientPI retries timeout delaySeconds state defaultProtocolInterpreterClass <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>NetError</name> <environment>Net</environment> <super>Core.Error</super> <private>false</private> none <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class>