From VisualWorks®, 7.6 of February 1, 2008 on February 1, 2008 at 12:34:00 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>IMAPClient</name> <environment>Net</environment> <super>Net.NetClient</super> <private>false</private> none handler <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPClient
IMAPClient implements user agent interface to IMAP services. These all are in category. All methods in this category support synchromous execution of a specific IMAP request. They creates an instance of command, queues it for IMAP protocol interpreter and waits for command's completion. All methods answer IMAPCommand. Utility category provides higher level requests that extract data from a response, i.e. after fetch request. IMAP Client uses state objects to validate if a request is valid in a given state and to handle state transitions. IMAPClient is also responsible for a number of policies such as automatic reconnect, idle timeout, etc. The client #close and #logout methods are different from other clients: #logout - the connection is being terminated, and the server will close the connection #close - command permanently removes from the currently selected mailbox all messages that have the \Deleted flag set, and returns to authenticated state from selected state Instance Variables: handler <IMAPClientHandler> An instance of IMAPClientHolder handles unsolicited server messages
</comment> <class> <name>IMAPState</name> <environment>Net</environment> <super>Net.NetClientState</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPState
IMAPState is implemented to support IMAP4rev1 server states. An IMAP4rev1 server is in one of four states. Most commands are valid in only certain states. It is a protocol error for the client to attempt a command while the command is in an inappropriate state. The NetClientStateError is raised in this case.
</comment> <class> <name>IMAPAuthenticatedState</name> <environment>Net</environment> <super>Net.IMAPState</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPAuthenticatedState
IMAPAuthenticatedState is implemented to support IMAP4rev1 server authenticated state. In this state the client is authenticated and must select a mailbox to access before commands that affect messages will be permitted.
</comment> <class> <name>IMAPSelectedState</name> <environment>Net</environment> <super>Net.IMAPAuthenticatedState</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPSelectedState
IMAPSelectedState is implemented to support IMAP4rev1 server selected state. In this state a mailbox has been selected to access.
</comment> <class> <name>IMAPArgumentError</name> <environment>Net</environment> <super>Net.NetError</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPArgumentError
This error is raised when server refuses provided command arguments, presumably because they are invalid in some way.
</comment> <class> <name>IMAPAppendError</name> <environment>Net</environment> <super>Net.IMAPArgumentError</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPAppendError
This error will be raised if the server sends a NO reply which means the server cannot append to that mailbox, or there is an error in the flags, date/time or message text.
</comment> <class> <name>IMAPMailbox</name> <environment>Net</environment> <super>Net.Mailbox</super> <private>false</private> none name <imports></imports> <category>Net-IMAP-Mailbox</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPMailbox
IMAPMailbox provides IMAP mail box support Instance Variables: name <String> mail box name. The default name is "inbox"
</comment> <class> <name>IMAPResponse</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none cmdName value source parameters <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPResponse
IMAPResponse is implemented to read and parse responses directly from the IMAP server socket stream. Instance Variables: cmdName <String> The name of a command value <Collection> The response value source <Stream> Row server response parameters <Collection> The parameters hold the fetch command message data and represented as a collection. For example: for the fetch command 1 FETCH (BODY[HEADER]) the response parameters are: #( 'BODY[HEADER]' 'Date: Wed, 17 Jul 1996 02:23:25 -0700 (PDT)...') In order to adequately parse a response, response parser has to read up to three tokens. If the first response is a tag, the response is tagged status response and can be parsed immediately. In this case tag becomes response's command name and the rest of response is parsed and placed in response value. Otherwise, second token is read. If this token starts with a letter, this second token becomes response's command name, the remainder or response is deep tokenized and put in value. So for the following response IMAPResponse readFrom: '* FLAGS (\Seen \Answered \Deleted)' readStream cmdName = 'FLAGS' and value = #('\Seen' '\Answered' '\Deleted') If the second token starts with a digit, the third token is read. This third token becomes response command name and the second token becomes reponse value. So for the following response: IMAPResponse readFrom: '* 3 EXPUNGE' readStream cmdName = 'EXPUNGE' and value = 3 Status responses as well as tagged responses may have bracketed status as in '* OK [UIDVALIDITY 11349] UIDVALIDITY value' (pain to handle because brackets syntactically are atom-chars). This bracketed expression is parsed into value. 'OK' is stored into status field. Every parsed response contains 'command name' and response value. Command name is used to match a response to potentially multiple active commands. Command name is also useful for user agent when user agent needs information in specific response or set of responses. A standard situation is when user agent needs values of all responses whose command name is the same as the name of their command.
</comment> <class> <name>IMAPResponseStatus</name> <environment>Net</environment> <super>Net.IMAPResponse</super> <private>false</private> none text status <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPResponseStatus
IMAPResponseStatus parses IMAP response status Instance Variables: status <String> the first token from the response text <String> the response text
</comment> <class> <name>IMAPResponseTagged</name> <environment>Net</environment> <super>Net.IMAPResponseStatus</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPResponseTagged
IMAPResponseTagged parses tagged response
</comment> <class> <name>IMAPCommand</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none client sequenceID name arguments status responses completionResponse promise <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPCommand
IMAPCommand is implemented to create IMAP4rev1 command, execute it and extract the response data. Instance Variables: client <IMAPProtocolInterpreter> The client manages IMAP server connection and and provides interface for sending IMAP commands and handling IMAP responses. sequenceID <IMAPCommandSequencer> Creates next command identifier (tag) name <String> the command name arguments <Array> An array of arguments status <Symbol> The command status as: #sent #done responses <Collection> The IMAPResponses collection completionResponse <IMAPResponseTagged> An instance of the IMAPResponseTagged like: "xxx OK FETCH completed" promise <TimedPromise> An instance of TimedPromise IMAPCommand support Promise pattern. Each command represents a promise to deliver responses in the future. Once command is queued for IMAP protocol, interpreter, control is returned to the calling code immediately. In order to wait till command execution is complete, send command message #wait. This gives application an easy and flexible means to control the level of concurrency with IMAP server. Most of IMAP API methods execute synchronously -- that is, they create new command, queue it, wait for command's completion and answer the command. Aplication that needs asynchronous execution should create a new command and queue it via IMAPClient>>executeCommand:. IMAPCommand and its responses
</comment> <class> <name>IMAPConnection</name> <environment>Net</environment> <super>Net.NetConnection</super> <private>false</private> none <imports> private OS.IOConstants.* </imports> <category>Net-SMTP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPConnection
IMAPConnection is the IMAP connection stream wrapper
</comment> <class> <name>IMAPStreamEncoder</name> <environment>Net</environment> <super>Net.UTF7StreamEncoder</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPStreamEncoder
IMAPStreamEncoder is used to encode/decode IMAP messages
</comment> <class> <name>IMAPScanner</name> <environment>Net</environment> <super>Net.MessageScanner</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPScanner
IMAPScanner parses IMAP messages
</comment> <class> <name>IMAPCommandSequencer</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none prefix value <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPCommandSequencer
IMAPCommandSequencer generates unique identifier (tag) for IMAP4rev1 command. Instance Variables: prefix <String> value <Number>
</comment> <class> <name>IMAPProtocolInterpreter</name> <environment>Net</environment> <super>Net.NetProtocolInterpreter</super> <private>false</private> none commandSequencer readResponseSignal commandsInProgress connectionMutex client <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPProtocolInterpreter
IMAP Protocol Interpreter manages IMAP server connection and provides interface for sending IMAP commands and handling IMAP responses. Instance Variables: client <IMAPClient> Instance of IMAPClient to handle unsolicited responses socket <Socket> Socket for inter-process communication connectionStream <Stream> Is used to read from and write to the external connection stream. responseStream <Stream> Is used for testing purpose to execute script respresenting complete execution of one or more commands. commandSequencer <IMAPCommandSequencer> Creates for each command identifier called a "tag" readResponseMutex <Semaphore> Protects access to commandsInProgress readResponseSignal <Semaphore> Is signalled when commandsInProgress is non-empty commandsInProgress <List> List of outstanding commands that has been sent to IMAP server and has not completed yet connectionMutex <Semaphore> Protects access to the connectionStream IMAP Protocol Interpreter owns and controls access to IMAP server socket connection. The only interface between IMAPClient and PI is through executeCommand: connect disconnect IMAP implements sharing mechanisms as follows: PI's Shared Resources: IMAP Connection (connectionMutex, connectionStream responseStream) - IMAP connection is shared between command sender and response reader loop. A process must own the connection before it accesses the connection. Response List (readResponseSignal commandsInProgress) -- this is a LIFO list of commands waiting for their response. Command Processing Once a command is sent, it is added to the response list and readResponseSignal is signalled. It then adds the command to the list of commands waiting for reply. This list is LIFO list, so the last command will be the first to handle server responses. It is managed by IMAP response reader loop. IMAP Response reader loop is a separate thread that is blocked on the semaphore asociated with the response list. This semaphore's excess signals count is equal to the number of commands in response list. Therefore, when there are outstanding commands waiting for a response, this loop will be constantly reading next response from the server and dispatching it. Responses are read from the server and parsed, so that an appropriate instance is created for the next response. The response is checked with the IMAPClient if this response is one of untagged responses that have to be recorded by the client.The response is then offered to every outstanding command beginning with the most recent. In this sense the most recent command provides 'context' for processing responses, especially for untagged responses containing mailbox and message data. This process continues until a command consumes this response by answering true. Each command, when given a request, tried to process it. If it recognizes the response as its, it typically adds the response to the list of responses associated with this request and answers that it consumed the response. If the response is a completion (tagged) response for this command, the execution of this command is complete. At this point the command is removed from the list of outstanding commands, command's semaphore is signalled and the excess signal counter for the list's semaphore is decremented by one. If the list is empty, the list semaphore's excess counter will be zero and response reader loop will be blocked on the semaphore. Even though this response can be processed by a command in outstanding command list, this command will not indicate it has consumed this response, so it will eventually get to the protocol interpreter. When processing a response, response reader loop should do the following: Lock IMAP connection Read next response Process the response If some command is now complete Remove it from response list Decrement response loop semaphore Unlock IMAP connection
</comment> <class> <name>IMAPClientHandler</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none existingMessages recentMessages expungedMessage mailboxFlags <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPClientHandler
IMAPClientHandles is implemented to handle unsolicited server responses. Certain server data must be recorded by the client when it is received. Such data conveys critical information which affects the interpretetion of all subsequent commands and responses. Instance Variables: existingMessages <Number> The value of the EXISTS command recentMessages <Number> The value of the RECENT command expungedMessage <Number> The value of the EXPUNGE command mailboxFlags <Collection> The value of the FLAGS command as: #('\Seen' '\Answered' '\Deleted' )
</comment> <class> <name>IMAPLogoutState</name> <environment>Net</environment> <super>Net.IMAPState</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPLogoutState
In logout state, the connection is being terminated, and the server will close the connection. This state can be entered as a result of a client request or by unilateral server decision.
</comment> <class> <name>IMAPNonAuthenticatedState</name> <environment>Net</environment> <super>Net.IMAPState</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPNonAuthenticatedState
IMAPNonAuthenticatedState is implemented to support IMAP4rev1 server non-authenticated state. In this state the client must supply authentication credentials before most commands will be permitted. This state in entered when a connection starts.
</comment> <class> <name>IMAPStateError</name> <environment>Net</environment> <super>Net.NetClientError</super> <private>false</private> none <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPStateError
NetClientStateError is raised if the client attempts to send a command in inappropriate state
</comment> <class> <name>IMAPErrorResponse</name> <environment>Net</environment> <super>Net.IMAPResponse</super> <private>false</private> none exception <imports></imports> <category>Net-IMAP-Support</category> <attributes> <package>IMAP</package> </attributes> </class> <comment> Net.IMAPErrorResponse
IMAPCommand is terminated with the IMAPErrorResponse when the exception was raised because the client was disconnected or specified timeout was expired. Instance Variables: exception <Exception> Holds the exception
</comment> <name>QuotedTextMask</name> <environment>Net.IMAPScanner</environment> <private>false</private> <constant>false</constant> <category>As yet unclassified</category> <initializer>4096</initializer> <attributes> <package>IMAP</package> </attributes> <name>AtomMask</name> <environment>Net.IMAPScanner</environment> <private>false</private> <constant>false</constant> <category>As yet unclassified</category> <initializer>256</initializer> <attributes> <package>IMAP</package> </attributes> <name>TextMask</name> <environment>Net.IMAPScanner</environment> <private>false</private> <constant>false</constant> <category>As yet unclassified</category> <initializer>8192</initializer> <attributes> <package>IMAP</package> </attributes> <name>QuotedPairChar</name> <environment>Net.IMAPScanner</environment> <private>false</private> <constant>false</constant> <category>As yet unclassified</category> <initializer>$\</initializer> <attributes> <package>IMAP</package> </attributes> <name>ResponseRegistry</name> <environment>Net.IMAPCommand</environment> <private>false</private> <constant>false</constant> <category>As yet unclassified</category> <initializer>IdentityDictionary new</initializer> <attributes> <package>IMAP</package> </attributes> <name>MaxRetries</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>unclassified</category> <attributes> <package>IMAP</package> </attributes> <name>NetworkErrorSignal</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>signals</category> <attributes> <package>IMAP</package> </attributes> <name>LoginIncorrectSignal</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>signals</category> <attributes> <package>IMAP</package> </attributes> <name>DelaySeconds</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>unclassified</category> <attributes> <package>IMAP</package> </attributes> <name>ProtocolErrorSignal</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>signals</category> <attributes> <package>IMAP</package> </attributes> <name>ConnectionFailedSignal</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>signals</category> <attributes> <package>IMAP</package> </attributes> <name>ConnectionClosedSignal</name> <environment>Net.IMAPProtocolInterpreter</environment> <private>false</private> <constant>false</constant> <category>signals</category> <attributes> <package>IMAP</package> </attributes> <methods> Net.IMAPClient <category>commands</category>
append: aString to: aMailboxName "APPEND Command Responses: no specific responses for this command Result: OK - append completed NO - append error: can't append to that mailbox, error in flags or date/time or message text BAD - command unknown or arguments invalid If the destination mailbox does not exist, a server MUST return an error, and MUST NOT automatically create the mailbox. Unless it is certain that the destination mailbox can not be created, the server MUST send the response code [TRYCREATE] as the prefix of the text of the tagged NO response. This gives a hint to the client that it can attempt a CREATE command and retry the APPEND if the CREATE is successful." ^self state append: aString to: aMailboxName flags: nil date: nil
append: aString to: aMailboxName flags: flags date: dateString ^self state append: aString to: aMailboxName flags: flags date: dateString
authenticate: aString | comm | ^(comm := self state authenticate: aString) failed ifTrue: [NetClientError signalWith: #authenticate message: comm completionResponse text] ifFalse: [comm ]
capability ^ self state capability
check ^ self state check
close "The command permanently removes from the currently selected mailbox all messages that have the \Deleted flag set, and returns to authenticated state from selected state" ^ self state close
copy: messageNumbers to: aMailboxName ^self state copy: messageNumbers to: aMailboxName
create: aMailBoxName ^ self state create: aMailBoxName
delete: aMailBoxName ^ self state delete: aMailBoxName
examine: aMailBoxName ^ self state examine: aMailBoxName
expunge ^ self state expunge
fetch: aCriteria " Fetch messages based on specified criteria client fetch: '1:3 (uid Body.Peek[Header.Fields (Message-Id From)])' client fetch: '1:2 (flags internaldate uid RFC822)'. " ^ self state fetch: aCriteria
fetch: messageNumbers retrieve: criteria ^ self state fetch: messageNumbers retrieve: criteria
list: aMailbox " client list: 'inbox' " ^ self state list: #quotedText->'' mailbox: aMailbox
list: refName mailbox: name " client list: 'test/' mailbox: 'mail/*' " ^ self state list: refName mailbox: name
login | comm | ^(comm := self state login) failed ifTrue: [NetClientError signalWith: #login message: comm completionResponse text] ifFalse: [comm ]
logout " In logout state, the connection is being terminated, and the server will close the connection. " ^self state logout
lsub: refName mailbox: name ^ self state lsub: refName mailbox: name
noop ^self state noop
rename: oldMailBox newName: newMailBox ^self state rename: oldMailBox newName: newMailBox
rlist: aMailbox " client list: 'inbox' " ^ self state rlist: #quotedText->'' mailbox: aMailbox
rlist: refName mailbox: name " client list: 'test/' mailbox: 'mail/*' " ^ self state rlist: refName mailbox: name
rlsub: refName mailbox: name ^ self state rlsub: refName mailbox: name
search: aCriteria ^ self state search: aCriteria
select: aMailBoxName ^ self state select: aMailBoxName
status: aMailBoxName " Obtain status of messages in specified mailbox. client status: 'inbox' " ^ self status: aMailBoxName criteria: 'messages'
status: aMailBoxName criteria: anArrayOfStatusData " Obtain status of specified mailbox using specified criteria. criterial may be either string or array or strings if multiple criteria is given client status: 'inbox' criteria: #('messages' 'unseen'). client status: 'sent items' criteria: 'messages'. " ^ self state status: aMailBoxName criteria: anArrayOfStatusData
store: args ^ self state store: args
subscribe: aMailBoxName ^ self state subscribe: aMailBoxName
uid: aString ^ self state uid: aString
unsubscribe: aMailBoxName ^ self state unsubscribe: aMailBoxName
</methods> <methods> Net.IMAPClient <category>defaults</category>
defaulfHandler ^ IMAPClientHandler new
defaultMailbox ^(#inbox << #net >> 'inbox') asString
</methods> <methods> Net.IMAPClient <category>responses</category>
handle: aResponse "Ask IMAPClientHandler to handle responses that must be recorded by the client" ^self handler handle: aResponse for: self
</methods> <methods> Net.IMAPClient <category>accessing</category>
handler ^handler isNil ifTrue: [handler := self defaulfHandler] ifFalse: [handler]
handler: imapClientHandler handler := imapClientHandler
</methods> <methods> Net.IMAPClient <category>private</category>
canonicalizeMailboxName: aMailboxName " Mailbox names are encoded in UTF-7 format. " | str | str := (ByteArray new withEncoding: #UTF_7IMAP) writeStream. str nextPutAll: aMailboxName. str close. ^IMAPScanner stringAsAssociation: (str encodedContents withEncoding: #ASCII) readStream contents
handlePeerFaultError: exception retry: retry "The Server we are connecting to might be too busy to handle our connection request. In this case, delay for a little while and try again. Once we have hit our connection retry limit, return true to the client so that it can handle the error as it sees fit." retry > 0 ifTrue: [(Delay forSeconds: self delaySeconds) wait. self log: [(#FailedToConnectWillTryMore << #net >> 'Failed to connect to <1s>. Will try <2p> more times.') expandMacrosWith: self hostName with: retry] level: #IMAPClient. exception retry]. ^true
messageSetAsString: messageNumbers | stream | stream := (String new: 64) writeStream. messageNumbers do: [:messageNumber | stream nextPutAll: messageNumber printString] separatedBy: [stream nextPut: $,]. ^stream contents
</methods> <methods> Net.IMAPClient <category>low-level command interface</category>
commandClassFor: cmdName ^self class commandClassFor: cmdName
createCommand: aString ^ self createCommand: aString arguments: nil
createCommand: aString arguments: anArray ^IMAPCommand forClient: clientPI name: aString arguments: anArray
execute: cmd arguments: args changeStateTo: aStateBlock ^self execute: [self createCommand: cmd arguments: args ] changeStateTo: aStateBlock.
execute: aBlock changeStateTo: aStateBlock | command | command := aBlock value. self executeCommand: command. command wait. command successful ifTrue: [self state: aStateBlock value ]. ^ command
executeAndWait: aString ^self executeAndWait: aString arguments: nil
executeAndWait: aString arguments: anArray | command | command := self createCommand: aString arguments: anArray. self executeCommand: command. command waitFor: self timeout. ^ command
executeAndWait: aString arguments: anArray for: ms | command | command := self createCommand: aString arguments: anArray. self executeCommand: command. command waitFor: ms. ^ command
executeAndWait: aString for: ms ^self executeAndWait: aString arguments: nil for: ms
executeCommand: aCommand ^self isConnected not ifFalse: [self clientPI executeCommand: aCommand] ifTrue: [NetClientError signalWith: #connection message: (#ServerIsNotAccessible << #net >> 'Server is not accessible.')]
</methods> <methods> Net.IMAPClient <category>connection management</category>
connect "Establish a connection to the host " | nRetry | nRetry := self retries. [super connect. self state: IMAPNonAuthenticatedState new. ] on: OSErrorHolder peerFaultSignal, OsInaccessibleError do: [:ex | nRetry := nRetry - 1. (ex getSignal = OSErrorHolder peerFaultSignal and: [(self handlePeerFaultError: ex retry: nRetry)]) ifTrue: [ NetClientError signalWith: #connection message: (#UnableToConnectToServer << #net >> 'Unable to connect to the server.')]. ex class = OsInaccessibleError ifTrue: [ NetClientError signalWith: #connection message: (#ServerIsNotAccessible << #net >> 'Server is not accessible.')]]
disconnect "Disconnect from the host" self logout. self clientPI disconnect.
isConnected ^self clientPI isConnected
loginToHost self login successful ifFalse: [ ^nil].
</methods> <methods> Net.IMAPClient <category>utilities</category>
examineMailbox " Examine default mailbox (inbox) " ^ self examineMailbox: self defaultMailbox
examineMailbox: mailbox ^ (self examine: mailbox) responses.
fetch: anArrayOfNumbers with: qualifierOrListOfQualifiers " Returns an OrderedCollection of associations which key is the message number and value the message" | args result dict data messageNumber | args := qualifierOrListOfQualifiers isCharacters ifTrue: [Array with: qualifierOrListOfQualifiers] ifFalse: [qualifierOrListOfQualifiers]. result := self fetch: anArrayOfNumbers retrieve: args. dict := Dictionary new. ^result successful ifFalse: [self error: result completionResponse text. nil] ifTrue: [result commandResponses collect: [:response | messageNumber := response value. data := response parameters first. 1 to: data size by: 2 do: [:idx | dict at: (data at: idx) asUppercase put: (data at: idx + 1)]. messageNumber -> (qualifierOrListOfQualifiers collect: [:tag | dict at: tag asUppercase])]]
fetchMessage: messageNumber " Returns an association which key is the message number and value the message" | result | result := self fetch: (Array with: messageNumber) with: #('RFC822'). ^result notNil ifTrue: [result first] ifFalse: [nil]
fetchMessageHeaders: anArrayOfNumbers " Returns an OrderedCollection of associations which key is the message number and value the message header" ^self fetch: anArrayOfNumbers with: #('RFC822.HEADER')
fetchMessages: anArrayOfNumbers " Returns an OrderedCollection of associations which key is the message number and value the message" ^self fetch: anArrayOfNumbers with: #('RFC822')
fetchRFC822MessageHeaders: anArrayOfNumbers " Returns an OrderedCollection of associations which key is the message number and value the message header" ^self fetchMessageHeaders: anArrayOfNumbers
fetchRFC822Messages: anArrayOfNumbers " Returns an OrderedCollection of associations which key is the message number and value the message" ^self fetchMessages: anArrayOfNumbers
markAsSeen: aCollectionOfNumbers "Sets \Seen flags for the specified message number" | string | string := self messageSetAsString: aCollectionOfNumbers. ^ (self store: (string, ' +FLAGS (\Seen)')) successful
markForDelete: anArrayOfNumbers "Sets deleted flags for the specified message numbers" | string | string := self messageSetAsString: anArrayOfNumbers. ^ (self store: (string, ' +FLAGS (\Deleted)')) successful
messageCount "Returns the number of messages in the default mailbox (inbox). See messageCount: to retrieve message count from any mailbox " ^self messageCount: self defaultMailbox
messageCount: mailbox "Returns the number of messages in specified mailbox " | resps | resps := self examineMailbox: mailbox. ^(resps detect: [:each | each cmdName = 'EXISTS']) value asNumber
newMessages "Returns the number of recent messages in in the default mailbox (inbox). See newMessages: to retrieve new messages from any mailbox " ^self newMessages: self defaultMailbox
newMessages: mailbox "Returns the number of recent messages in specified mailbox " | resps | resps := self examineMailbox: mailbox. ^(resps detect: [:each | each cmdName = 'RECENT']) value asNumber
searchMessages: aCriteria " Returns a collection of message numbers that match the given searching criteria" | result resp | result := self search: aCriteria. ^ result successful ifFalse: [ self error: result completionResponse text. ] ifTrue: [ (resp := result commandResponse parameters) isEmpty not ifTrue: [ resp collect: [ :each | each asNumber]] ifFalse: [ resp ] ]
searchMessagesForUids: aCollectionOfUidStrings " Returns a collection of message numbers that match the message uids" | stream | stream := (String new: 64) writeStream. stream nextPutAll: 'uid '. aCollectionOfUidStrings do: [:uid | stream nextPutAll: uid] separatedBy: [stream nextPut: $,]. ^ self searchMessages: stream contents
status: anArrayOfStatusData forMailbox: aName "Returns a list of message numbers" " For example the command: client status: (Array with: 'messages' with: 'unseen') forMailbox: 'inbox'. returns: #('23' '3') " | resp result | resp := (self status: aName criteria: anArrayOfStatusData) commandResponse parameters at: 2. result := OrderedCollection new: anArrayOfStatusData size. 2 to: resp size by: 2 do: [ :each | result add: ( resp at: each) ]. ^ result.
uidFor: anArrayOfMessageNumbers ^(self fetch: anArrayOfMessageNumbers with: #('UID') ) collect: [ :assoc | assoc value first ].
unmarkAsSeen: aCollectionOfNumbers "Sets \Seen flags for the specified message number" | string | string := self messageSetAsString: aCollectionOfNumbers. ^ (self store: (string, ' -FLAGS (\Seen)')) successful
unmarkForDelete: anArrayOfNumbers "Sets deleted flags for the specified message numbers" | string | string := self messageSetAsString: anArrayOfNumbers. ^ (self store: (string, ' -FLAGS (\Deleted)')) successful
unseenMessages "Returns the number of unseen messages in inbox " ^ self searchMessages: 'unseen'
</methods> <methods> Net.IMAPClient <category>initialize-release</category>
initialize super initialize. self state: IMAPLogoutState new. self clientPI client: self
</methods> <methods> Net.IMAPClient class <category>testing</category>
isIncomingMailClient ^true
isMailClient ^true
isOutgoingMailClient ^false
</methods> <methods> Net.IMAPClient class <category>class initialization</category>
initialize super initialize
registerClass self mailRegistry at: self serverType put: self name
</methods> <methods> Net.IMAPClient class <category>constants</category>
description ^(#incomingMailServer << #net >> 'incoming mail server') asString
serverType ^#IMAP
</methods> <methods> Net.IMAPClient class <category>default values</category>
defaultProtocolInterpreterClassValue ^IMAPProtocolInterpreter
</methods> <methods> Net.IMAPState <category>commands</category>
append: message to: aMailboxName flags: flags date: dateString self signalError
authenticate: args self signalError
check self signalError
close self signalError
copy: messageNumbers to: aName self signalError
create: aList self signalError
delete: aList self signalError
examine: aList self signalError
expunge: aClient self signalError
fetch: aList self signalError
fetch: messageNumbers retrieve: criteria self signalError
list: refName mailbox: name self signalError
login self signalError
lsub: refName mailbox: name self signalError
rename: oldMailBox newName: newMailBox self signalError
rlist: refName mailbox: name self signalError
rlsub: refName mailbox: name self signalError
search: aLIst self signalError
select: aList self signalError
status self signalError
store: aList self signalError
subscribe: aList self signalError
uid: aList self signalError
unsubscribe: aList self signalError
</methods> <methods> Net.IMAPState <category>any state valid commands</category>
capability ^client executeAndWait: 'capability'
logout | command | (command := client executeAndWait: 'logout' ) successful ifTrue: [ client state: IMAPLogoutState new]. ^command
noop ^client executeAndWait: 'noop'
</methods> <methods> Net.IMAPState <category>accessing</category>
stateErrorClass ^IMAPStateError
</methods> <methods> Net.IMAPState class <category>instance creation</category>
forClient: client ^self new client: client
</methods> <methods> Net.IMAPAuthenticatedState <category>commands</category>
append: message to: aMailboxName flags: flags date: dateString "APPEND Command. If the destination mailbox does not exist, a server MUST return an error, and MUST NOT automatically create the mailbox. Unless it is certain that the destination mailbox can not be created, the server MUST send the response code [TRYCREATE] as the prefix of the text of the tagged NO response." | args | args := OrderedCollection with: (client canonicalizeMailboxName: aMailboxName). flags notNil ifTrue: [args add: flags]. dateString notNil ifTrue: [args add: #atom -> dateString]. args add: #literalString -> message. ^[client executeAndWait: 'append' arguments: args ] on: IMAPAppendError do: [ :ex | ex errorString: ( (ex parameter value key match: 'TRYCREATE') ifTrue: [#MailboxDoesNotExist << #net >> 'Mailbox "<1s>" does not exist. Try creating it first!' expandMacrosWith: aMailboxName] ifFalse: [ex parameter source] ). ex pass ]
create: aMailboxName ^client execute: 'create' arguments: (client canonicalizeMailboxName: aMailboxName) changeStateTo: [IMAPSelectedState new]
delete: aMailboxName ^client executeAndWait: 'delete' arguments: (client canonicalizeMailboxName: aMailboxName)
examine: aMailBoxName ^client execute: 'examine' arguments: (client canonicalizeMailboxName: aMailBoxName) changeStateTo: [IMAPSelectedState new]
list: refName mailbox: name ^client executeAndWait: 'list' arguments: (Array with: refName with: (client canonicalizeMailboxName: name))
logout | command | (command := client executeAndWait: 'logout' ) successful ifTrue: [ client state: IMAPLogoutState new]. ^command
lsub: refName mailbox: name ^client executeAndWait: 'lsub' arguments: (Array with: refName with: (client canonicalizeMailboxName: name))
rename: oldMailBox newName: newMailBox ^client executeAndWait: 'rename' arguments: (Array with: (client canonicalizeMailboxName: oldMailBox) with: (client canonicalizeMailboxName: newMailBox))
rlist: refName mailbox: name ^client executeAndWait: 'rlist' arguments: (Array with: refName with: (client canonicalizeMailboxName: name))
rlsub: refName mailbox: name ^client executeAndWait: 'rlsub' arguments: (Array with: refName with: (client canonicalizeMailboxName: name))
select: aMailBoxName ^client execute: 'select' arguments: (client canonicalizeMailboxName: aMailBoxName) changeStateTo: [IMAPSelectedState new]
status: aMailboxName ^client executeAndWait: 'status' arguments: (client canonicalizeMailboxName: aMailboxName)
status: aMailboxName criteria: anArrayOfStatusData | args | args := OrderedCollection new: 5. args add: (client canonicalizeMailboxName: aMailboxName). args add: (#parenthesizedList-> (anArrayOfStatusData isCharacters ifTrue: [Array with: (#atom->anArrayOfStatusData)] ifFalse: [anArrayOfStatusData])). ^client executeAndWait: 'status' arguments: args
subscribe: aMailBoxName ^client executeAndWait: 'subscribe' arguments: (Array with: (client canonicalizeMailboxName: aMailBoxName))
unsubscribe: aMailBoxName ^client executeAndWait: 'unsubscribe' arguments: (Array with: (client canonicalizeMailboxName: aMailBoxName))
</methods> <methods> Net.IMAPSelectedState <category>commands</category>
check ^client executeAndWait: 'check'
close ^client execute: 'close' arguments: nil changeStateTo: [IMAPAuthenticatedState new]
copy: messageNumbers to: aName | msgString args | msgString := client messageSetAsString: messageNumbers. args := OrderedCollection with: msgString with: (client canonicalizeMailboxName: aName). ^client executeAndWait: 'copy' arguments: args.
expunge ^client executeAndWait: 'expunge'
fetch: aCriteria ^client executeAndWait: 'fetch' arguments: aCriteria
fetch: messageNumbers retrieve: criteria | msgString args | msgString := client messageSetAsString: messageNumbers. args := OrderedCollection with: msgString. args add: (#parenthesizedList->criteria). ^client executeAndWait: 'fetch' arguments: args.
search: aCriteria ^client executeAndWait: 'search' arguments: aCriteria
store: args ^client executeAndWait: 'store' arguments: args
uid: aString ^client executeAndWait: 'uid' arguments: aString
</methods> <methods> Net.IMAPMailbox <category>private</category>
clientSpecies ^IMAPClient
createConnection super createConnection. ^(connection select: self name) successful ifTrue: [connection] ifFalse: [self logout. NetClientError signalWith: #select message: ((#UnableToSelectMailbox << #net >> 'Unable to select the mailbox: <1s>') expandMacrosWith: self name).].
deliverMessages: numbers "Queue up all new messages onto the queue. Clients of this method should be aware that the state should be such that there is new mail to download." | messages bundlingFactor n stream msec subSet | numbers isEmpty ifTrue: [^self]. bundlingFactor := 5. n := 0. self safelyExecute: [self log: [(#DownloadingMessages << #net >> 'Downloading <1p> messages from <2s>') expandMacrosWith: numbers size with: server] level: #IMAPMailboxDownload. stream := numbers readStream. [stream atEnd] whileFalse: [subSet := stream nextAvailable: bundlingFactor. msec := Time millisecondsToRun: [messages := self getMessagesByNumbers: subSet ]. self logMessages: messages position: n from: numbers size time: msec. messages do: [ :ms | self deliver: ms]. n := n + messages size]]. self lastMessage: (self lastMessage + n). self log: [(#DoneDownloaded << #net >> 'Done; downloaded <1p> messages from <2s>') expandMacrosWith: n with: server] level: #IMAPMailboxDownload
getMessagesByNumbers: arrayOfMessageNumbers " Read specified messages from IMAP server. arrayOfMessageNumbers is an array of integers or strings representing message numbers " | fetchedMessages messages uids n each | fetchedMessages := self safelyExecute: [ connection fetchRFC822Messages: arrayOfMessageNumbers ]. uids := self getMessageUID: arrayOfMessageNumbers. messages := OrderedCollection new: fetchedMessages size. n := self lastMessage. fetchedMessages isEmpty not ifTrue: [ 1 to: fetchedMessages size do: [ :index | each := fetchedMessages at: index. n := n + 1. messages add: (LetterInTransit new letterText: each value first; uid: (uids at: index); server: self serverName; serverType: self serverType; deliveryAction: [self saveSettings: n])]]. ^messages
getMessagesFor: criteria "Read messages matching specified criteria from the mailbox" | numbers | numbers := self messageNumbersFor: criteria. ^self getMessagesByNumbers: numbers
getMessageUID: arrayOfMessageNumbers " Read specified messages from IMAP server. Returns an arrray strings representing message uids " ^self safelyExecute: [ connection uidFor: arrayOfMessageNumbers ].
handleResult: result result successful ifFalse: [ self error: (#toDoImplementExceptions << #net >> 'to do: implement exceptions') ]. ^result.
notifyServerMessageRead: uid | res | res := self safelyExecute: [connection searchMessagesForUids: (Array with: uid) ]. ^self safelyExecute: [ connection markAsSeen: res ].
</methods> <methods> Net.IMAPMailbox <category>accessing</category>
logout connection notNil ifTrue: [connection logout. connection := nil]
name ^name isNil ifTrue: [name := (#inbox << #net >> 'inbox') asString] ifFalse: [name]
name: aValue name := aValue
serverType ^'IMAP4'
</methods> <methods> Net.IMAPMailbox <category>commands</category>
allHeaders | numbers headers | ^self safelyExecute: [ (numbers := self messageNumbersFor: 'all') isEmpty ifTrue: [ numbers ] ifFalse: [connection select: self name. headers := connection fetchRFC822MessageHeaders: numbers. headers collect: [:header | MailIncrementNotification raiseSignal. MimeEntity readHeaderFrom: header value first readStream ]]]
anyNewMail "Any new mail to get?" self log: [(#Checking1sForMail << #net >> 'Checking <1s> for mail') expandMacrosWith: server name ] level: #IMAPMailboxCheck. ^ (self safelyExecute: [ connection unseenMessages ]) size > 0
deleteMessageNumbers: aSequenceOfUIDs "Delete all the letters on the server. Ignore non-existent ones" | res | res := self safelyExecute: [connection searchMessagesForUids: aSequenceOfUIDs ]. self safelyExecute: [connection markForDelete: res; expunge. ]. ^self messageCount.
getMessage: number "Get the <number> message from the IMAP server" | coll | ^(coll := self getMessagesByNumbers: (Array with: number)) notEmpty ifTrue: [ coll first ] ifFalse: [nil ]
messageCount "How many messages are on the server?" ^self safelyExecute: [connection messageCount]
messageNumbersFor: criteria "How many messages are on the server?" ^self safelyExecute: [connection searchMessages: criteria].
</methods> <methods> Net.IMAPMailbox <category>mail delivery</category>
deliverAllMail "Queue up all new messages onto the queue. Clients of this method should be aware that the state should be such that there is new mail to download." | numbers | numbers := self messageNumbersFor: 'all'. numbers size > 0 ifTrue: [self deliverMessages: numbers ]
deliverNewMail "Queue up all new messages onto the queue. Clients of this method should be aware that the state should be such that there is new mail to download." | numbers | numbers := self messageNumbersFor: 'unseen'. numbers size > 0 ifTrue: [self deliverMessages: numbers ]
</methods> <methods> Net.IMAPMailbox <category>debugging</category>
logMessages: messages position: position from: total time: downloadTime | n messageSize | n := position. messages size timesRepeat: [n := n + 1. n \\ 10 = 0 ifTrue: [self log: [(#DownloadedMessages << #net >> 'Downloaded <1p> messages from <2s>') expandMacrosWith: n with: server] level: #IMAPMailboxDownload]]. self debug: [messageSize := messages inject: 0 into: [:sum :message | sum + message size]. self class processTransferRate: downloadTime ofSize: messageSize fromServer: server] level: #IMAPTransferRate
</methods> <methods> Net.IMAPMailbox class <category>debugging</category>
debugLabelsAndValues ^OrderedCollection new add: ((#IMAPMailboxCheck << #net >> 'IMAP Mailbox Check')->#IMAPMailboxCheck); add: ((#IMAPMailboxDownload << #net >> 'IMAP Mailbox Download')->#IMAPMailboxDownload); yourself
initialize self registerToDebug
menuItemName ^(#IMAPMailboxMessages << #net >> 'IMAPMailbox Messages')
</methods> <methods> Net.IMAPResponse <category>accessing</category>
cmdName ^cmdName
cmdName: aString cmdName := aString
parameters ^parameters
parameters: aCollection parameters := aCollection. value isNil ifTrue: [ value := parameters ].
source ^source
source: aString source := aString
tag ^nil
value ^value
value: aValue value := aValue
</methods> <methods> Net.IMAPResponse <category>testing</category>
hasTag: aString ^false
terminatesCommand: aCommand ^false
</methods> <methods> Net.IMAPResponse <category>parsing, general</category>
parse: scanner " Nothing more to do here " self parameters: scanner deepTokenize
parse: scanner with: cmdNameString self cmdName: cmdNameString. self parse: scanner
parse: scanner with: cmdNameString with: codeValue self cmdName: cmdNameString. self value: codeValue. self parse: scanner
</methods> <methods> Net.IMAPResponse <category>printing</category>
printLog ^ self source
printOn: stream source notNil ifTrue: [ stream nextPutAll: source ]
</methods> <methods> Net.IMAPResponse class <category>parsing, general</category>
canParse: cmdName ^false
defaultResponseClass ^IMAPResponse
parse: scanner | token resp | scanner sourceTrailOn. token := scanner nextToken. resp := ($* = token or: [ '+' = token]) ifTrue: [ self parseUntagged: scanner with: token ] ifFalse: [ self parseTagged: scanner with: token ]. scanner upTo: Character cr. resp source: scanner sourceTrail. ^resp
parse: scanner with: cmdName ^self new parse: scanner with: cmdName
parse: scanner with: cmdName with: code ^self new parse: scanner with: cmdName with: code
parseContinuationResponse: scanner "Command continuation request responses use the token '+' instead of a tag. These responses are sent by the server to indicate acceptance of an incomplete client command and readiness for the remainder of the command" ^self new parse: scanner; yourself
parserForUntaggedCommand: cmdName ^IMAPResponse allSubclasses detect: [:each | each canParse: cmdName ] ifNone: [self defaultResponseClass].
parserTypeForTaggedStatus: status ^IMAPResponseTagged
parseTagged: scanner with: tag | status | status := scanner nextToken. ^(self parserTypeForTaggedStatus: status) parse: scanner tag: tag status: status.
parseUntagged: scanner with: starOrPlus | token token2 | starOrPlus = '+' ifTrue: [^self parseContinuationResponse: scanner]. token := scanner nextToken. ^token first isAlphabetic ifTrue: [(self parserForUntaggedCommand: token) parse: scanner with: token] ifFalse: [token2 := scanner nextToken. (self parserForUntaggedCommand: token2) parse: scanner with: token2 with: token]
readFrom: stream ^self parse: (self scannerOn: stream)
scannerOn: stream ^IMAPScanner on: stream
</methods> <methods> Net.IMAPResponseStatus <category>testing</category>
isBad ^ self status = 'BAD'
isOK ^ self status = 'OK'
</methods> <methods> Net.IMAPResponseStatus <category>parsing, general</category>
parse: scanner | val key bracketedValue scodeScanner | scanner skipWhiteSpace. (scanner peek == $[) ifTrue: [value := OrderedCollection new. bracketedValue := scanner nextToken. scodeScanner := IMAPScanner on: ((IMAPScanner tokenizeBracketedGroup: bracketedValue) at: 2) readStream. key := scodeScanner nextToken asUppercase. (#('UIDVALIDITY' 'UNSEEN' ) includes: key) ifTrue: [ val := scodeScanner nextToken asNumber ]. 'PERMANENTFLAGS' = key ifTrue: [ val := scodeScanner deepNextToken ]. 'NEWNAME' = key ifTrue: [ |old new| old := scodeScanner nextToken. new := scodeScanner nextToken. val := Array with: old with: new ]. ]. self text: scanner scanText. (#('ALERT' 'PARSE' 'TRYCREATE' 'READ-ONLY' 'READ-WRITE' ) includes: key) ifTrue: [ val := self text ]. self value: key->val.
parse: scanner with: token self cmdName: token. self status: token. self parse: scanner.
</methods> <methods> Net.IMAPResponseStatus <category>accessing</category>
status ^status
status: aString status := aString
text ^text
text: aString text := aString
</methods> <methods> Net.IMAPResponseStatus class <category>testing</category>
canParse: cmdName ^#('OK' 'NO' 'BAD' 'BYE') includes: cmdName
</methods> <methods> Net.IMAPResponseTagged <category>testing</category>
hasTag: tagString ^self tag match: tagString
terminatesCommand: aCommand ^self hasTag: aCommand sequenceID
</methods> <methods> Net.IMAPResponseTagged <category>parsing, general</category>
parse: scanner tag: tag status: statusString self cmdName: tag. self status: statusString. ^self parse: scanner
</methods> <methods> Net.IMAPResponseTagged <category>accessing</category>
tag ^self cmdName
</methods> <methods> Net.IMAPResponseTagged class <category>testing</category>
canParse: cmdName ^false
</methods> <methods> Net.IMAPResponseTagged class <category>parsing, general</category>
parse: scanner tag: tag status: status ^self new parse: scanner tag: tag status: status
</methods> <methods> Net.IMAPCommand <category>accessing</category>
arguments ^arguments
arguments: anObject arguments := anObject
client ^client
client: anObject client := anObject
completionResponse ^completionResponse
completionResponse: anObject completionResponse := anObject.
name ^name
name: anObject name := anObject
sequenceID ^sequenceID
sequenceID: anObject sequenceID := anObject
</methods> <methods> Net.IMAPCommand <category>status</category>
beDone self status: #done. self client commandIsDone: self. self value: self completionResponse.
beSent self status: #sent. self client commandIsInProgress: self.
status ^ status
status: anObject status := anObject.
value ^ self promise value
value: anObject self promise value: status.
</methods> <methods> Net.IMAPCommand <category>responses</category>
commandResponse | coll | ^(coll := self commandResponses) isEmpty ifTrue: [ nil ] ifFalse: [coll first]
commandResponses ^self responses select: [:resp | resp cmdName match: self name]
commandResponseValue | resp | ^(resp := self commandResponse) isNil ifTrue: [ nil ] ifFalse: [resp value]
responseNamed: aName ^self responsesNamed: aName
responses ^ responses notNil ifTrue: [ responses ] ifFalse: [ responses := OrderedCollection new ]
responsesNamed: aName ^self responses select: [:resp | resp cmdName = aName ]
statusResponse ^self statusResponses first
statusResponses ^self responses select: [:resp | resp isKindOf: IMAPResponseStatus]
</methods> <methods> Net.IMAPCommand <category>handle responses</category>
definedResponses ^self class definedResponsesAt: self name asUppercase
handle: aResponse (aResponse terminatesCommand: self) ifTrue: [self terminateWithResponse: aResponse. ^aResponse hasTag: self sequenceID]. (self isDefinedResponse: aResponse) ifTrue: [self responses add: aResponse. ^true]. ^false
isDefinedResponse: aResponse ^(self name equivalentTo: aResponse cmdName ignoreCase: true) or: [self definedResponses includes: aResponse cmdName]
</methods> <methods> Net.IMAPCommand <category>execute</category>
execute "Send the gived command to the server." self send. self beSent
wait ^ self promise value
waitFor: ms ms notNil ifTrue: [promise := TimedPromise forMilliseconds: ms]. ^[self promise value] on: PromiseExpiredError do: [:ex | self terminateWithException: ex. NetClientError signalWith: #timeout message: (#Timeout << #net >> 'Timeout')]
</methods> <methods> Net.IMAPCommand <category>testing</category>
failed ^self successful not
isDone ^ self status = #done
isSent ^ self status = #sent
successful ^ self isDone and: [ self completionResponse isOK ].
</methods> <methods> Net.IMAPCommand <category>initialization</category>
forClient: anIMAPPI name: aString arguments: args self client: anIMAPPI. self name: aString. self arguments: (self canonicalizeArguments: args).
initialize responses := OrderedCollection new: 1.
</methods> <methods> Net.IMAPCommand <category>parsing</category>
parse: scanner "Read and parse next command from a stream. This is mainly useful for testing previously stored exchange logs" | tokens | tokens := scanner deepTokenizeAsAssociation. self sequenceID: tokens first value; name: (tokens at: 2) value; arguments: (tokens copyFrom: 3 to: tokens size).
</methods> <methods> Net.IMAPCommand <category>printing</category>
printOn: aStream self scanner printTokenList: self asTokenList on: aStream.
scanner ^IMAPScanner
</methods> <methods> Net.IMAPCommand <category>private</category>
asTokenList | list | list := OrderedCollection with: (#atom->self sequenceID) with: (#atom->name). self arguments notNil ifTrue: [list addAll: self arguments]. ^list
canonicalizeArguments: args " Arguments can one of: integer, string or array of thereof, potentially nested. Scalars are converted into array with this scalar as a sole element " args isNil ifTrue: [^Array new]. ^(args isCharacters or: [args isSequenceable not]) ifTrue: [^Array with: args] ifFalse: [ args ]
promise ^promise isNil ifTrue: [promise := Promise new] ifFalse: [promise]
send self client sendTokenList: self asTokenList. self client connectionStream cr. self client connectionStream commit.
</methods> <methods> Net.IMAPCommand <category>command termination</category>
terminateWithException: exception self terminateWithResponse: (IMAPErrorResponse newFor: self exception: exception)
terminateWithResponse: response self completionResponse: response. self beDone
</methods> <methods> Net.IMAPCommand class <category>defined responses</category>
definedResponsesAt: aName ^self responseRegistry at: aName asUppercase ifAbsentPut: [IdentityDictionary new].
responseRegistry ^ResponseRegistry
</methods> <methods> Net.IMAPCommand class <category>instance creation</category>
forClient: anIMAPPI name: aString arguments: arguments " The intention here is to let users specify the complete string of command arguments. Because this string may contain atom-specials like $(, etc., this line may be sent as quoted string, which would be wrong. So we fool the printing logic to view this string as an atom. It is a hack, but seems like a convenient one " | args | args := arguments isCharacters ifTrue: [#atom -> arguments] ifFalse: [arguments]. ^self new forClient: anIMAPPI name: aString arguments: args
new ^super new initialize
parse: scanner "Read and parse next command from a stream. This is mainly useful for testing previously stored exchange logs" ^self new parse: scanner
readFrom: aStream " Read and parse next command from a stream. This is mainly useful for testing previously stored exchange logs " ^self parse: (IMAPScanner on: aStream)
</methods> <methods> Net.IMAPCommand class <category>class initialization</category>
initialize "IMAPCommand initialize" (ResponseRegistry := Dictionary new) at: 'FETCH' put: #('FETCH' 'OK' 'NO' 'BAD'); at: 'SEARCH' put: #('SEARCH' 'OK' 'NO' 'BAD'); at: 'SELECT' put: #('FLAGS' 'EXISTS' 'RECENT' 'OK' 'NO' 'BAD'); at: 'EXAMINE' put: #('FLAGS' 'EXISTS' 'RECENT' 'OK' 'NO' 'BAD'); at: 'LIST' put: #('LIST' 'OK' 'NO' 'BAD'); at: 'RLIST' put: #('LIST' 'OK' 'NO' 'BAD'); at: 'LSUB' put: #('LSUB' 'OK' 'NO' 'BAD'); at: 'RLSUB' put: #('LSUB' 'OK' 'NO' 'BAD'); at: 'STATUS' put: #('STATUS'); at: 'EXPUNGE' put: #('EXPUNGE' 'OK' 'NO' 'BAD'); at: 'STORE' put: #('FETCH' 'OK' 'NO' 'BAD'); at: 'UID' put: #('FETCH' 'SEARCH' 'OK' 'NO' 'BAD'); at: 'CAPABILITY' put: #('CAPABILITY' 'OK' 'BAD'); at: 'STORE' put: #('FETCH'); at: 'LOGOUT' put: #('BYE' 'OK' 'BAD'); at: 'CLOSE' put: #('OK' 'NO' 'BAD'); at: 'CHECK' put: #('OK' 'NO'); at: 'APPEND' put: #('OK' 'NO' 'BAD'); at: 'SUBSCRIBE' put: #('OK' 'NO' 'BAD'); at: 'RENAME' put: #('OK' 'NO' 'BAD'); at: 'DELETE' put: #('OK' 'NO' 'BAD'); at: 'CREATE' put: #('OK' 'NO' 'BAD'); at: 'LOGIN' put: #('OK' 'NO' 'BAD'); at: 'AUTHENTICATE' put: #('OK' 'NO' 'BAD'); at: 'NOOP' put: #('OK' 'BAD').
</methods> <methods> Net.IMAPConnection <category>accessing</category>
connectionStream ^stream
responseStream ^stream
</methods> <methods> Net.IMAPConnection class <category>default values</category>
defaultPortNumber ^SocketAccessor IPPORT_IMAP
</methods> <methods> Net.IMAPStreamEncoder <category>private</category>
shiftInCode ^$& asInteger
</methods> <methods> Net.IMAPStreamEncoder class <category>accessing</category>
streamEncodingType ^#UTF_7IMAP
</methods> <methods> Net.IMAPStreamEncoder class <category>private</category>
sextetMapCharacters ^'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,'
</methods> <methods> Net.IMAPStreamEncoder class <category>class initialization</category>
initialize "Initialize class variables" super initialize
</methods> <methods> Net.IMAPScanner <category>tokenization</category>
deepNextToken ^self nextToken == $( ifTrue: [self stepBack; scanParenthesizedList] ifFalse: [token]
deepNextTokenAsAssociation ^self nextToken == $( ifTrue: [self stepBack; scanParenthesizedListAsAssociation] ifFalse: [tokenType->token].
deepTokenize | stream | stream := (Array new: 4) writeStream. [self deepNextToken. tokenType = #doIt or: [token == Character cr]] whileFalse: [stream nextPut: token]. token == Character cr ifTrue: [ self stepBack ]. ^stream contents
deepTokenizeAsAssociation | stream assoc | stream := (Array new: 4) writeStream. [assoc := self deepNextTokenAsAssociation. assoc key = #doIt] whileFalse: [stream nextPut: assoc]. ^stream contents
deepTokenizeAsAssociationUntil: aBlock do: actionBlock | assoc | [self skipWhiteSpace. assoc := self deepNextTokenAsAssociation. assoc key = #doIt or: aBlock] whileFalse: [actionBlock value: assoc]
deepTokenizeUntil: aBlock do: actionBlock [self skipWhiteSpace; deepNextToken. tokenType == #doIt or: aBlock] whileFalse: [actionBlock value].
nextToken ^self nextIMAPToken
specials ^self class atomSpecials
</methods> <methods> Net.IMAPScanner <category>multi-character scans</category>
doSpecialScanProcessing "Hacks that require special handling of IMAP tokens go here. The most frustrating one for us was handling of message/mailbox flags that have format \<atom> as in \Seen. The problem is that $\ is not an atom-char, so these flags are tokenized as #( $\ 'Seen'). We make heuristical decision here if current token is $\ immediately followed by a letter. We will then read next token and merge $\ and next token answering a string. This is ONLY applied inside a parenthesized list" (token == $\ and: [(self classificationMaskFor: self peek) anyMask: AlphabeticMask]) ifTrue: [self nextToken. token := '\' , token. tokenType := #string]
scanAtom "atom = 1*<any CHAR except atom-specials (which includes atomSpecials, space and CTLs)>" token := self scanAtomToken. (token match: 'NIL') ifTrue: ["RFC2060 defines NIL as a special atom type, atoms are not case-sensitive" token := nil. tokenType := #nil] ifFalse: [tokenType := #atom]. ^token
scanAtomToken "atom = 1*<any CHAR except atom-specials (which includes atomSpecials, space and CTLs)>" ^self scanWhile: [self matchCharacterType: AtomMask] do: [:stream :char | stream nextPut: char. char == $[ ifTrue: [stream nextPutAll: self xScanBrackets]]
scanLiteralText "<{> nnn <}> <CRLF> <nnn bytes>" | nbytes string | nbytes := self scanLiteralTextLength. string := self nextBytesAsString: nbytes. token := string copyReplaceAll: (String with: Character cr with: Character lf) with: (String with: Character cr). tokenType := #literalString. ^token
scanLiteralTextLength "<{> nnn <}> <CRLF>" " We are positioned at the first brace character " token := self scanToken: [ self matchCharacterType: DigitMask ] delimitedBy: '{}' notify: (#MalformedLiteralLength << #net >> 'Malformed literal length'). self upTo: Character cr. ^Integer readFrom: token readStream
scanParenthesizedList | stream | stream := (Array new: 4) writeStream. self mustMatch: $( notify: (#ParenthesizedListShouldBeginWith << #net >> 'Parenthesized list should begin with ('). self deepTokenizeUntil: [token == $)] do: [self doSpecialScanProcessing. stream nextPut: token]. token ~~ $) ifTrue: [self notify: (#NonterminatedParenthsizedList << #net >> 'Non-terminated parenthsized list')]. token := stream contents. tokenType := #parenthesizedList. ^token
scanParenthesizedListAsAssociation | stream | stream := (Array new: 4) writeStream. self mustMatch: $( notify: (#ParenthesizedListShouldBeginWith << #net >> 'Parenthesized list should begin with ('). self deepTokenizeAsAssociationUntil: [token == $)] do: [:assoc | self doSpecialScanProcessing. stream nextPut: (tokenType->token)]. token ~~ $) ifTrue: [self notify: (#NonterminatedParenthsizedList << #net >> 'Non-terminated parenthsized list')]. token := stream contents. tokenType := #parenthesizedList. ^tokenType->token
scanQuotedChar "Scan possible quoted character. If the current char is $\, read in next character and make it a quoted string character" ^(hereChar == $\) ifTrue: [self step. classificationMask := QuotedTextMask. true] ifFalse: [false]
scanQuotedText " quoted-string = <""> *(quoted_char / quoted-pair) <""> quoted_char = <any CHAR except <""> and <\>" " We are positioned at the first double quote character " token := self scanToken: [ self scanQuotedChar; matchCharacterType: QuotedTextMask ] delimitedBy: '""' notify: (#UnmatchedQuotedText << #net >> 'Unmatched quoted text'). tokenType := #quotedText. ^token.
scanText "RFC822: text = <Any CHAR, including bare CR & bare LF, but not including CRLF. This is a 'catchall' category and cannot be tokenized. Text is used only to read values of unstructured fields" ^self skipWhiteSpace; scanWhile: [ (self matchCharacterType: CRLFMask) not ].
</methods> <methods> Net.IMAPScanner <category>private</category>
nextBytesAsString: nbytes | str | ^[ self binary. str := (source next: nbytes) asString. self sourceTrailNextPutAll: str. str ] ensure: [self text]
nextIMAPToken | char | self skipWhiteSpace. char := self peek. char isNil "end of input" ifTrue: [tokenType := #doIt. ^token := nil]. char == $" ifTrue: [^self scanQuotedText]. char == ${ ifTrue: [^self scanLiteralText]. ((char < Character space) or: [self specials includes: char]) ifTrue: [tokenType := #special. " Special character. Make it token value and set token type " ^token := self next.]. (self matchCharacterType: AtomMask) ifTrue: [^self scanAtom]. tokenType := #doIt. token := char. ^token
xScanBrackets ^(self scanUntil: [hereChar == $] ]), ']'.
</methods> <methods> Net.IMAPScanner <category>printing</category>
printLiteralString: aString on: stream self class printLiteralStringLength: aString on: stream. self class printLiteralStringContents: aString on: stream.
</methods> <methods> Net.IMAPScanner class <category>character classification</category>
atomSpecials " These characters cannot occur inside an atom" ^'(){%*"\'
specials ^self atomSpecials
</methods> <methods> Net.IMAPScanner class <category>printing</category>
defaultTokenType ^#string
printAtom: atom on: stream atom isNil ifTrue: [stream nextPutAll: 'NIL'] ifFalse: [stream nextPutAll: atom ]
printIMAPString: value on: stream "Print string as either atom or quoted text" value isNil ifTrue: [ self printNilOn: stream]. (self shouldBeQuoted: value) ifTrue: [self printQuotedText: value on: stream ] ifFalse: [self printAtom: value on: stream]
printLiteralString: aString on: stream "Note that this method is good for printing but not for sending. IMAP requires sender to send string length, then wait for continuation response" self printLiteralStringLength: aString on: stream. self printLiteralStringContents: aString on: stream
printLiteralStringContents: aString on: stream stream nextPutAll: aString
printLiteralStringLength: aString on: stream stream nextPut: ${. aString size printOn: stream. stream nextPut: $}; cr
printNilOn: stream stream nextPutAll: 'NIL'
printParenthesizedList: arrayOfAssociations on: stream "In order to accurately print parenthesized list, we need to know token types of every element. This is applied recursively" stream nextPut: $(. self printTokenList: arrayOfAssociations on: stream. stream nextPut: $)
printToken: value tokenType: aSymbol on: stream aSymbol = #string ifTrue: [^self printIMAPString: value on: stream]. aSymbol = #literalString ifTrue: [^self printLiteralString: value on: stream]. aSymbol = #atom ifTrue: [^self printAtom: value on: stream]. aSymbol = #quotedText ifTrue: [^self printQuotedText: value on: stream]. aSymbol = #nil ifTrue: [^self printNilOn: stream]. aSymbol = #parenthesizedList ifTrue: [^self printParenthesizedList: value on: stream]. "Invalid token type" aSymbol = #special ifTrue: [^stream nextPut: value]. self halt
stringAsAssociation: string (self shouldBeQuoted: string) ifFalse: [^#atom -> string]. (string first == $\ and: [string size > 1 and: [self shouldBeQuoted: (string copyFrom: 2 to: string size) not]]) ifTrue: [^#atom -> string]. ^#quotedText -> string
tokenAsAssociation: token (token isKindOf: Association) ifTrue: [^token]. token isNil ifTrue: [^'NIL']. token isCharacters ifTrue: [^self stringAsAssociation: token]. (token isKindOf: Number) ifTrue: [^#number -> token]. token isSequenceable ifTrue: [^#parentthesizedList -> token]. ^token
</methods> <methods> Net.IMAPScanner class <category>class initialization</category>
initClassificationTable super initClassificationTable. self initClassificationTableWith: TextMask when: [:c | c ~~ Character cr ]. self initClassificationTableWith: AtomMask when: [:c | c > Character space and: [ (self atomSpecials includes: c) not] ]. self initClassificationTableWith: QuotedTextMask when: [:c | c ~~ $" and: [ c ~~ $\ and: [ c ~~ Character cr ]]].
initialize " IMAPScanner initialize " self initializeConstants; initClassificationTable
initializeConstants AtomMask := 256. QuotedTextMask := 4096. TextMask := 8192.
</methods> <methods> Net.IMAPScanner class <category>testing</category>
isAtomChar: char ^((self classificationMaskFor: char) bitAnd: AtomMask) ~= 0
shouldBeQuoted: string ^(string detect: [ :char | (self isAtomChar: char) not ] ifNone: [ nil ]) notNil
</methods> <methods> Net.IMAPScanner class <category>utility, parsing</category>
tokenizeBracketedGroup: string " If argument contains bracketed group, break it into pre-group, group and post-group. Answer nil if there is no bracketed group or 3-element array if there is one " | first last | first := string indexOf: $[. first = 0 ifTrue: [ ^nil ]. last := string indexOf: $]. ^Array with: (string copyFrom: 1 to: first - 1) with: (string copyFrom: first + 1 to: last - 1) with: (string copyFrom: last + 1 to: string size).
</methods> <methods> Net.IMAPCommandSequencer <category>private</category>
increment self value: ( self value + 1 ).
</methods> <methods> Net.IMAPCommandSequencer <category>initialization</category>
initialize value := 0.
reset self value: 0.
</methods> <methods> Net.IMAPCommandSequencer <category>accessing</category>
next self increment. ^ self prefix, self value printString.
prefix ^prefix
prefix: aValue prefix := aValue
value ^value
value: aValue value := aValue
</methods> <methods> Net.IMAPCommandSequencer class <category>instance creation</category>
new ^super new initialize
newPrefix: prefix ^self new prefix: prefix; yourself
</methods> <methods> Net.IMAPProtocolInterpreter <category>sending tokens</category>
argumentAsAssociation: argument (argument isKindOf: Association) ifTrue: [^argument]. argument isNil ifTrue: [^'NIL']. argument isCharacters ifTrue: [^#string->argument]. (argument isKindOf: Number) ifTrue: [^#number->argument]. argument isSequenceable ifTrue: [^#parentthesizedList->argument]. ^argument.
literalStringAsBytes: string | output input char | output := (ByteArray new: string size) writeStream. input := string readStream. [input atEnd] whileFalse: [char := input next. output nextPut: char asInteger. char == Character cr ifTrue: [output nextPut: Character lf asInteger. input peekFor: Character lf]]. ^output contents
processSendLiteralStringResponse: aString | resp | resp := self defaultResponseClass readFrom: aString readStream. resp tag ifNil: [ ^nil ]. resp status = 'NO' ifTrue: [ IMAPAppendError raiseWith: resp ]. IMAPArgumentError raiseWith: resp
sendLiteralString: string | resp bytes continue | bytes := self literalStringAsBytes: string. IMAPScanner printLiteralStringLength: bytes on: self connectionStream. self connectionStream commit. continue := true. [continue and: [self responseStream atEnd not]] whileTrue: [resp := self responseStream upTo: Character cr. self class log: ['S: ' , resp] level: #IMAPClient. (resp first ~= $+) ifTrue: [self processSendLiteralStringResponse: resp ]. resp first == $+ ifTrue: [ ["S: + Ready for additional command text." self connectionStream binary. IMAPScanner printLiteralStringContents: bytes on: self connectionStream. self connectionStream commit. continue := false] ensure: [self connectionStream text]]]
sendToken: token tokenType: tokenType tokenType = #literalString ifTrue: [self sendLiteralString: token] ifFalse: [IMAPScanner printToken: token tokenType: tokenType on: self connectionStream]
sendTokenList: listOfTokens | assoc | listOfTokens do: [:arg | assoc := self argumentAsAssociation: arg. self sendToken: assoc value tokenType: assoc key] separatedBy: [self connectionStream space]
</methods> <methods> Net.IMAPProtocolInterpreter <category>events</category>
commandIsDone: command commandsInProgress remove: command ifAbsent: [^self]. readResponseSignal wait
commandIsInProgress: command commandsInProgress addFirst: command. readResponseSignal signal
connectionIsClosed self liveAcrossSnapshot ifTrue: [ObjectMemory removeDependent: self].
connectionIsReady commandSequencer reset. self liveAcrossSnapshot ifTrue: [ObjectMemory addDependent: self].
isAnyCommandInProgress commandsInProgress isEmpty
isNoCommandInProgress "For testing purpose" ^ commandsInProgress isEmpty
</methods> <methods> Net.IMAPProtocolInterpreter <category>accessing</category>
client ^client
client: aNetClient client := aNetClient
commandPrefix: aString commandSequencer prefix: aString
connectionStream ^self stream
</methods> <methods> Net.IMAPProtocolInterpreter <category>private</category>
commandsInProgress ^commandsInProgress
createSocket: hostName port: portNumber "Return a SocketAccessor instance or nil if a connection can not be made." ^SocketAccessor newTCPclientToHost: hostName port: portNumber
nextCommandSequenceNumber ^ commandSequencer next.
resetCommandSequence commandSequencer reset
responseReaderLoop ^[[[[readResponseSignal wait; signal. connectionMutex critical: [self handleNextResponse] ] repeat] on: Error do: [:exc | "An exception is being raised. If it is non-proceedable, save it so that the Promise will raise it instead of returning a value." self terminateCommandsWith: exc. exc return]] on: Process terminateSignal do: [:exc | self terminateCommandsWith: ((Promise terminateSignal newException) setSignal: Promise terminateSignal; parameter: exc; errorString: nil). exc return]]
terminateCommandsWith: exception commandsInProgress do: [:command | command terminateWithException: exception]
</methods> <methods> Net.IMAPProtocolInterpreter <category>constants and defaults</category>
defaultCommandPrefix ^'imapv4_'
defaultResponseClass ^ IMAPResponse
</methods> <methods> Net.IMAPProtocolInterpreter <category>responses</category>
getResponse | resp | ^ [ resp := self defaultResponseClass readFrom: self responseStream. self log: ['S: ' , resp printLog] level: #IMAPServer. resp] on: OsInaccessibleError do: [:ex | self terminateCommandsWith: ex. self disconnect. NetClientError signalWith: #connection message: (#ServerIsNotAccessible << #net >> 'Server is not accessible.')]
handle: aResponse ^self client handle: aResponse
handleNextResponse | resp | resp := self getResponse. self lookAtResponse: resp. commandsInProgress detect: [:command | command handle: resp] ifNone: [self handle: resp]
lookAtResponse: resp "Some responses must be recorded at all times. Insert logic here" self client handle: resp
</methods> <methods> Net.IMAPProtocolInterpreter <category>updating</category>
liveAcrossSnapshot ^false
update: aSymbol "Dependents of ObjectMemory are sent update: #returnFromSnapshot when a snapshot is started." self liveAcrossSnapshot ifTrue: [aSymbol == #returnFromSnapshot ifTrue: [self disconnect]. "(aSymbol == #aboutToSnapshot or: [aSymbol == #aboutToQuit]) ifTrue: [self closeConnection]."]. super update: aSymbol
</methods> <methods> Net.IMAPProtocolInterpreter <category>initialize-release</category>
initialize connectionMutex := Semaphore forMutualExclusion. readResponseSignal := Semaphore new. commandsInProgress := OrderedCollection new: 4. commandSequencer := IMAPCommandSequencer newPrefix: self defaultCommandPrefix. self responseReaderLoop fork.
release self disconnect.
</methods> <methods> Net.IMAPProtocolInterpreter <category>connection management</category>
connectToHost: hostName port: portNumber timeout: tmNumber self resetCommandSequence. super connectToHost: hostName port: portNumber timeout: tmNumber. self connectionIsReady. self getResponse.
disconnect super disconnect. readResponseSignal terminateProcess. self connectionIsClosed
</methods> <methods> Net.IMAPProtocolInterpreter <category>public</category>
executeCommand: aCommand aCommand sequenceID isNil ifTrue: [aCommand sequenceID: self nextCommandSequenceNumber]. self class log: ['----------------------------------'] level: #IMAPClient. self class log: ['C: ' , aCommand printString] level: #IMAPClient. ^[ connectionMutex critical: [ aCommand execute ]] on: OsInaccessibleError do: [:ex | self terminateCommandsWith: ex. aCommand terminateWithException: ex. self disconnect. NetClientError signalWith: #connection message: (#ServerIsNotAccessible << #net >> 'Server is not accessible.'). ex return: aCommand]
</methods> <methods> Net.IMAPProtocolInterpreter <category>ssl connection</category>
loadSecureConnection super loadSecureConnection. #{Net.IMAPSSLConnection} valueOrDo: [URI tryToLoad: 'IMAPS']
sslConnectionClass ^#{Net.IMAPSSLConnection} value
</methods> <methods> Net.IMAPProtocolInterpreter class <category>debugging</category>
debugLabelsAndValues ^OrderedCollection new add: ((#IMAPClientMessages << #net >> 'IMAP Client Messages')->#IMAPClient); add: ((#IMAPServerMessages << #net >> 'IMAP Server Messages')->#IMAPServer); yourself
menuItemName ^(#IMAPClientMessages << #net >> 'IMAP Client Messages')
</methods> <methods> Net.IMAPProtocolInterpreter class <category>class initialization</category>
initialize "self initialize." self registerToDebug
</methods> <methods> Net.IMAPProtocolInterpreter class <category>default values</category>
defaultNetConnectionValue ^IMAPConnection
</methods> <methods> Net.IMAPClientHandler <category>accessing</category>
existingMessages ^existingMessages
existingMessages: aNumber existingMessages := aNumber
expungedMessage ^expungedMessage
expungedMessage: aNumber expungedMessage := aNumber
mailboxFlags ^mailboxFlags
mailboxFlags: aCollection mailboxFlags := aCollection
recentMessages ^recentMessages
recentMessages: aNumber recentMessages := aNumber
</methods> <methods> Net.IMAPClientHandler <category>private</category>
exists: aString self existingMessages: aString asNumber
expunge: aString self expungedMessage: aString asNumber
flags: aCollection self mailboxFlags: aCollection
recent: aString self recentMessages: aString asNumber
</methods> <methods> Net.IMAPClientHandler <category>handle responses</category>
handle: aResponse " (#('EXISTS' 'RECENT' 'FLAGS' 'EXPUNGE') includes: aResponse cmdName) " ^[self perform: (aResponse cmdName asLowercase , ':') asSymbol with: aResponse value. true] on: self messageNotUnderstoodSignal do: [:ex | ex return: false]
handle: aResponse for: imapClient self handle: aResponse
</methods> <methods> Net.IMAPLogoutState <category>commands</category>
logout
</methods> <methods> Net.IMAPNonAuthenticatedState <category>commands</category>
authenticate: aString ^client execute: 'authenticate' arguments: (Array with: aString ) changeStateTo: [IMAPAuthenticatedState new]
login ^client execute: 'login' arguments: (Array with: client user username with: client user password) changeStateTo: [IMAPAuthenticatedState new]
</methods> <methods> Net.IMAPErrorResponse <category>accessing</category>
exception ^exception
exception: anException exception := anException
text ^ self exception description
</methods> <methods> Net.IMAPErrorResponse <category>testing</category>
isOK ^false
terminatesCommand: aCommand ^true
</methods> <methods> Net.IMAPErrorResponse class <category>instance creation</category>
newFor: aCommand exception: anException ^self new cmdName: aCommand name; exception: anException; yourself
</methods> <initialize> Net.IMAPClient </initialize> <initialize> Net.IMAPMailbox </initialize> <initialize> Net.IMAPCommand </initialize> <initialize> Net.IMAPStreamEncoder </initialize> <initialize> Net.IMAPScanner </initialize> <initialize> Net.IMAPProtocolInterpreter </initialize> "Imported Classes:" self error: 'Attempting to file-in parcel imports. Choose terminate or close' <class> <name>Object</name> <environment>Core</environment> <super></super> <private>false</private> none <imports></imports> <category>Kernel-Objects</category> <attributes> <package>Kernel-Objects</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> <class> <name>NetConnection</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none stream encoding <imports> private OS.IOConstants.* </imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>Mailbox</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none user server lastMessage keepMail deleteMail deliveryQueue pollingProcess delaySemaphore sync settingsSync connection <imports></imports> <category>Net-Mail</category> <attributes> <package>Mail</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>NetClientError</name> <environment>Net</environment> <super>Net.NetError</super> <private>false</private> none <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>NetClientState</name> <environment>Net</environment> <super>Core.Object</super> <private>false</private> none client <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class> <class> <name>MessageScanner</name> <environment>Net</environment> <super>Net.SimpleScanner</super> <private>false</private> none <imports></imports> <category>Net-MIME-Support</category> <attributes> <package>MIME</package> </attributes> </class> <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>UTF7StreamEncoder</name> <environment>Net</environment> <super>Protocols.Base64StreamEncoder</super> <private>false</private> none shifting <imports></imports> <category>Net-Clients</category> <attributes> <package>NetClientBase</package> </attributes> </class>