Class: EventMachine::IMAP::Client

Inherits:
Object
  • Object
show all
Includes:
EM::Deferrable, Authenticators
Defined in:
lib/em-imap/client.rb

Overview

TODO: Anything that accepts or returns a mailbox name should have UTF7 support.

Instance Method Summary collapse

Methods included from Authenticators

#add_authenticator

Constructor Details

#initialize(host, port, usessl = false) ⇒ Client

Returns a new instance of Client.


10
11
12
# File 'lib/em-imap/client.rb', line 10

def initialize(host, port, usessl=false)
  @connect_args=[host, port, usessl]
end

Instance Method Details

#add_response_handler(&block) ⇒ Object


451
452
453
# File 'lib/em-imap/client.rb', line 451

def add_response_handler(&block)
  @connection.add_response_handler(&block)
end

#append(mailbox, message, flags = nil, date_time = nil) ⇒ Object

Add a message to the mailbox.

The tagged response with which this command succeeds contains the UID of the email that was appended.

Parameters:

  • mailbox,

    the mailbox to add to,

  • message,

    the full text (including headers) of the email to add.

  • flags,

    A list of flags to set on the email.

  • date_time,

    The time to be used as the internal date of the email.


214
215
216
217
218
219
220
# File 'lib/em-imap/client.rb', line 214

def append(mailbox, message, flags=nil, date_time=nil)
  args = [to_utf7(mailbox)]
  args << flags if flags
  args << date_time if date_time
  args << Net::IMAP::Literal.new(message)
  tagged_response("APPEND", *args)
end

#authenticate(auth_type, *args) ⇒ Object

Authenticate using a custom authenticator.

By default there are two custom authenticators available:

'LOGIN', username, password
'CRAM-MD5', username, password (see RFC 2195)

Though you can add new mechanisms using EM::IMAP.add_authenticator, see for example the gmail_xoauth gem.


91
92
93
94
95
96
97
98
99
100
# File 'lib/em-imap/client.rb', line 91

def authenticate(auth_type, *args)
  # Extract these first so that any exceptions can be raised
  # before the command is created.
  auth_type = auth_type.to_s.upcase
  auth_handler = authenticator(auth_type, *args)

  tagged_response('AUTHENTICATE', auth_type).tap do |command|
    @connection.send_authentication_data(auth_handler, command)
  end
end

#capabilityObject

Ask the server which capabilities it supports.

Succeeds with an array of capabilities.


32
33
34
# File 'lib/em-imap/client.rb', line 32

def capability
  one_data_response("CAPABILITY").transform{ |response| response.data }
end

#checkObject

Checkpoint the current mailbox.

This is an implementation-defined operation, when in doubt, NOOP should be used instead.


229
230
231
# File 'lib/em-imap/client.rb', line 229

def check
  tagged_response("CHECK")
end

#closeObject

Unselect the current mailbox.

As a side-effect, permanently removes any messages that have the Deleted flag. (Unless the mailbox was selected using the EXAMINE, in which case no side effects occur).


239
240
241
# File 'lib/em-imap/client.rb', line 239

def close
  tagged_response("CLOSE")
end

#connectObject


14
15
16
17
18
19
20
# File 'lib/em-imap/client.rb', line 14

def connect
  @connection = EM::IMAP::Connection.connect(*@connect_args)
  @connection.errback{ |e| fail e }.
              callback{ |*args| succeed *args }

  @connection.hello_listener
end

#copy(seq, mailbox) ⇒ Object

Copy the specified messages to another mailbox.


347
348
349
# File 'lib/em-imap/client.rb', line 347

def copy(seq, mailbox)
  tagged_response("COPY", Net::IMAP::MessageSet.new(seq), to_utf7(mailbox))
end

#create(mailbox) ⇒ Object

Create a new mailbox with the given name.


136
137
138
# File 'lib/em-imap/client.rb', line 136

def create(mailbox)
  tagged_response("CREATE", to_utf7(mailbox))
end

#delete(mailbox) ⇒ Object

Delete the mailbox with this name.


142
143
144
# File 'lib/em-imap/client.rb', line 142

def delete(mailbox)
  tagged_response("DELETE", to_utf7(mailbox))
end

#disconnectObject


22
23
24
# File 'lib/em-imap/client.rb', line 22

def disconnect
  @connection.close_connection
end

#examine(mailbox) ⇒ Object

Select a mailbox for performing read-only commands.

This is exactly the same as select, except that no operation may cause a change to the state of the mailbox or its messages.


130
131
132
# File 'lib/em-imap/client.rb', line 130

def examine(mailbox)
  tagged_response("EXAMINE", to_utf7(mailbox))
end

#expungeObject

Permanently remove any messages with the Deleted flag from the current mailbox.

Succeeds with a list of message sequence numbers that were deleted.

NOTE: If you're planning to EXPUNGE and then SELECT a new mailbox, and you don't care which messages are removed, consider using CLOSE instead.


252
253
254
255
256
# File 'lib/em-imap/client.rb', line 252

def expunge
  multi_data_response("EXPUNGE").transform do |untagged_responses|
    untagged_responses.map(&:data)
  end
end

#fetch(seq, attr = "FULL") ⇒ Object

Get the contents of, or information about, a message.

Possible attribute names (see RFC 3501 for a full list):

ALL: Gets all header information,
FULL: Same as ALL with the addition of the BODY,
FAST: Same as ALL without the message envelope.

BODY: The body
BODY[<section>] A particular section of the body
BODY[<section>]<<start>,<length>> A substring of a section of the body.
BODY.PEEK: The body (but doesn't change the \Recent flag)
FLAGS: The flags
INTERNALDATE: The internal date
UID: The unique identifier

Parameters:

  • seq,

    a message or sequence of messages (a number, a range or an array of numbers)

  • attr,

    the name of the attribute to fetch, or a list of attributes.


316
317
318
# File 'lib/em-imap/client.rb', line 316

def fetch(seq, attr="FULL")
  fetch_internal("FETCH", seq, attr)
end

#idle(&block) ⇒ Object

The IDLE command allows you to wait for any untagged responses that give status updates about the contents of a mailbox.

Until you call stop on the idler, no further commands can be sent over this connection.

idler = connection.idle do |untagged_response|

case untagged_response.name
#...
end

end

EM.timeout(60) { idler.stop }


371
372
373
374
375
376
# File 'lib/em-imap/client.rb', line 371

def idle(&block)
  send_command("IDLE").tap do |command|
    @connection.prepare_idle_continuation(command)
    command.listen(&block) if block_given?
  end
end

#list(refname = "", pattern = "*") ⇒ Object

List all available mailboxes.

@param: refname, an optional context in which to list. @param: mailbox, a which mailboxes to return.

Succeeds with a list of Net::IMAP::MailboxList structs, each of which has:

.name, the name of the mailbox (in UTF8)
.delim, the delimeter (normally "/")
.attr, A list of attributes, e.g. :Noselect, :Haschildren, :Hasnochildren.

174
175
176
# File 'lib/em-imap/client.rb', line 174

def list(refname="", pattern="*")
  list_internal("LIST", refname, pattern)
end

#login(username, password) ⇒ Object

Authenticate with a username and password.

NOTE: this SHOULD only work over a tls connection.

If the password is wrong, the command will fail with a Net::IMAP::NoResponseError.


109
110
111
# File 'lib/em-imap/client.rb', line 109

def (username, password)
  tagged_response("LOGIN", username, password)
end

#logoutObject

Logout and close the connection.

This will cause any other listeners or commands that are still active to fail, and render this client unusable.


52
53
54
55
56
57
58
59
60
# File 'lib/em-imap/client.rb', line 52

def logout
  command = tagged_response("LOGOUT").errback do |e|
    if e.is_a? Net::IMAP::ByeResponseError      # RFC 3501 says the server MUST send a BYE response and then close the connection.

      disconnect
      command.succeed
    end
  end
end

#lsub(refname, pattern) ⇒ Object

List all subscribed mailboxes.

This is the same as list, but restricted to mailboxes that have been subscribed to.


182
183
184
# File 'lib/em-imap/client.rb', line 182

def lsub(refname, pattern)
  list_internal("LSUB", refname, pattern)
end

#noopObject

Actively do nothing.

This is useful as a keep-alive, or to persuade the server to send any untagged responses your listeners would like.

Succeeds with nil.


43
44
45
# File 'lib/em-imap/client.rb', line 43

def noop
  tagged_response("NOOP")
end

#rename(oldname, newname) ⇒ Object

Rename the mailbox with this name.


148
149
150
# File 'lib/em-imap/client.rb', line 148

def rename(oldname, newname)
  tagged_response("RENAME", to_utf7(oldname), to_utf7(newname))
end

#search(*args) ⇒ Object

Search for messages in the current mailbox.

Parameters:

  • *args

    The arguments to search, these can be strings, arrays or ranges specifying sub-groups of search arguments or sets of messages.

    If you want to use non-ASCII characters, then the first two arguments should be 'CHARSET', 'UTF-8', though not all servers support this.


269
270
271
# File 'lib/em-imap/client.rb', line 269

def search(*args)
  search_internal("SEARCH", *args)
end

#select(mailbox) ⇒ Object

Select a mailbox for performing commands against.

This will generate untagged responses that you can subscribe to by adding a block to the listener with .listen, for more detail, see RFC 3501, section 6.3.1.


121
122
123
# File 'lib/em-imap/client.rb', line 121

def select(mailbox)
  tagged_response("SELECT", to_utf7(mailbox))
end

#sort(sort_keys, *args) ⇒ Object

SORT and THREAD (like SEARCH) from tools.ietf.org/search/rfc5256

Raises:

  • (NotImplementedError)

281
282
283
# File 'lib/em-imap/client.rb', line 281

def sort(sort_keys, *args)
  raise NotImplementedError
end

#starttlsObject Also known as: start_tls

Run a STARTTLS handshake.

C: “STARTTLSrn” S: “OK go aheadrn” C: <tls handshake> S: <tls handshake>

Succeeds with the OK response after the TLS handshake is complete.


72
73
74
75
76
# File 'lib/em-imap/client.rb', line 72

def starttls
  tagged_response("STARTTLS").bind! do |response|
    @connection.start_tls.transform{ response }
  end
end

#status(mailbox, attrs = ['MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN']) ⇒ Object

Get the status of a mailbox.

This provides similar information to the untagged responses you would get by running SELECT or EXAMINE without doing so.

Succeeds with a hash of attribute name to value returned by the server.

Parameters:

  • mailbox,

    a mailbox to query

  • attrs,

    a list of attributes to query for (valid values include MESSAGES, RECENT, UIDNEXT, UIDVALIDITY and UNSEEN — RFC3501#6.3.8)


197
198
199
200
201
202
# File 'lib/em-imap/client.rb', line 197

def status(mailbox, attrs=['MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'])
  attrs = [attrs] if attrs.is_a?(String)
  one_data_response("STATUS", to_utf7(mailbox), attrs).transform do |response|
    response.data.attr
  end
end

#store(seq, name, value) ⇒ Object

Update the flags on a message.

The .SILENT versions suppress the server's responses.

Parameters:

  • seq,

    a message or sequence of messages (a number, a range, or an array of numbers)

  • name,

    any of FLAGS FLAGS.SILENT, replace the flags FLAGS, FLAGS.SILENT, add the following flags -FLAGS, -FLAGS.SILENT, remove the following flags

  • value,

    a list of flags (symbols)


335
336
337
# File 'lib/em-imap/client.rb', line 335

def store(seq, name, value)
  store_internal("STORE", seq, name, value)
end

#subscribe(mailbox) ⇒ Object

Add this mailbox to the list of subscribed mailboxes.


154
155
156
# File 'lib/em-imap/client.rb', line 154

def subscribe(mailbox)
  tagged_response("SUBSCRIBE", to_utf7(mailbox))
end

#thread(algorithm, *args) ⇒ Object

Raises:

  • (NotImplementedError)

289
290
291
# File 'lib/em-imap/client.rb', line 289

def thread(algorithm, *args)
  raise NotImplementedError
end

#uid_copy(seq, mailbox) ⇒ Object

The same as copy, but keyed off UIDs instead of sequence numbers.


353
354
355
# File 'lib/em-imap/client.rb', line 353

def uid_copy(seq, mailbox)
  tagged_response("UID", "COPY", Net::IMAP::MessageSet.new(seq), to_utf7(mailbox))
end

#uid_fetch(seq, attr = "FULL") ⇒ Object

The same as fetch, but keyed of UIDs instead of sequence numbers.


322
323
324
# File 'lib/em-imap/client.rb', line 322

def uid_fetch(seq, attr="FULL")
  fetch_internal("UID FETCH", seq, attr)
end

#uid_search(*args) ⇒ Object

The same as search, but succeeding with a list of UIDs not sequence numbers.


275
276
277
# File 'lib/em-imap/client.rb', line 275

def uid_search(*args)
  search_internal("UID SEARCH", *args)
end

#uid_sort(sort_keys, *args) ⇒ Object

Raises:

  • (NotImplementedError)

285
286
287
# File 'lib/em-imap/client.rb', line 285

def uid_sort(sort_keys, *args)
  raise NotImplementedError
end

#uid_store(seq, name, value) ⇒ Object

The same as store, but keyed off UIDs instead of sequence numbers.


341
342
343
# File 'lib/em-imap/client.rb', line 341

def uid_store(seq, name, value)
  store_internal("UID STORE", seq, name, value)
end

#uid_thread(algorithm, *args) ⇒ Object

Raises:

  • (NotImplementedError)

293
294
295
# File 'lib/em-imap/client.rb', line 293

def uid_thread(algorithm, *args)
  raise NotImplementedError
end

#unsubscribe(mailbox) ⇒ Object

Remove this mailbox from the list of subscribed mailboxes.


160
161
162
# File 'lib/em-imap/client.rb', line 160

def unsubscribe(mailbox)
  tagged_response("UNSUBSCRIBE", to_utf7(mailbox))
end

#wait_for_new_emails(wrapper = Listener.new, &block) ⇒ Object

Wait for new emails to arrive, and call the block when they do.

This method will run until the upstream connection is closed, re-idling after every 29 minutes as implied by the IMAP spec. If you want to stop it, call .stop on the returned listener

idler = client.wait_for_new_emails do |exists_response, &stop_waiting|

client.fetch(exists_response.data).bind! do |response|
  puts response
end

end

idler.stop

NOTE: the block should return a deferrable that succeeds when you are done processing the exists_response. At that point, the idler will be turned back on again.


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/em-imap/client.rb', line 433

def wait_for_new_emails(wrapper=Listener.new, &block)
  wait_for_one_email.listen do |response|
    wrapper.receive_event response
  end.bind! do |response|
    block.call response if response
  end.bind! do
    if wrapper.stopped?
      wrapper.succeed
    else
      wait_for_new_emails(wrapper, &block)
    end
  end.errback do |*e|
    wrapper.fail *e
  end

  wrapper
end

#wait_for_one_email(timeout = 29 * 60) ⇒ Object

A Wrapper around the IDLE command that lets you wait until one email is received

Returns a deferrable that succeeds when the IDLE command succeeds, or fails when the IDLE command fails.

If a new email has arrived, the deferrable will succeed with the EXISTS response, otherwise it will succeed with nil.

client.wait_for_one_email.bind! do |response|

process_new_email(response) if response

end

This method will be default wait for 29minutes as suggested by the IMAP spec.

WARNING: just as with IDLE, no further commands can be sent over this connection until this deferrable has succeeded. You can stop it ahead of time if needed by calling stop on the returned deferrable.

idler = client.wait_for_one_email.bind! do |response|

process_new_email(response) if response

end idler.stop

See also #wait_for_new_emails


403
404
405
406
407
408
409
410
411
412
413
# File 'lib/em-imap/client.rb', line 403

def wait_for_one_email(timeout=29 * 60)
  exists_response = nil
  idler = idle
  EM::Timer.new(timeout) { idler.stop }
  idler.listen do |response|
    if Net::IMAP::UntaggedResponse === response && response.name =~ /\AEXISTS\z/i
      exists_response = response
      idler.stop
    end
  end.transform{ exists_response }
end