PageRenderTime 54ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/ruby/1.9/net/imap.rb

https://bitbucket.org/nicksieger/jruby
Ruby | 3613 lines | 2363 code | 261 blank | 989 comment | 140 complexity | 059b3c469cdae53d6898613e9112349e MD5 | raw file
Possible License(s): GPL-3.0, JSON

Large files files are truncated, but you can click here to view the full file

  1. #
  2. # = net/imap.rb
  3. #
  4. # Copyright (C) 2000 Shugo Maeda <shugo@ruby-lang.org>
  5. #
  6. # This library is distributed under the terms of the Ruby license.
  7. # You can freely distribute/modify this library.
  8. #
  9. # Documentation: Shugo Maeda, with RDoc conversion and overview by William
  10. # Webber.
  11. #
  12. # See Net::IMAP for documentation.
  13. #
  14. require "socket"
  15. require "monitor"
  16. require "digest/md5"
  17. require "strscan"
  18. begin
  19. require "openssl"
  20. rescue LoadError
  21. end
  22. module Net
  23. #
  24. # Net::IMAP implements Internet Message Access Protocol (IMAP) client
  25. # functionality. The protocol is described in [IMAP].
  26. #
  27. # == IMAP Overview
  28. #
  29. # An IMAP client connects to a server, and then authenticates
  30. # itself using either #authenticate() or #login(). Having
  31. # authenticated itself, there is a range of commands
  32. # available to it. Most work with mailboxes, which may be
  33. # arranged in an hierarchical namespace, and each of which
  34. # contains zero or more messages. How this is implemented on
  35. # the server is implementation-dependent; on a UNIX server, it
  36. # will frequently be implemented as a files in mailbox format
  37. # within a hierarchy of directories.
  38. #
  39. # To work on the messages within a mailbox, the client must
  40. # first select that mailbox, using either #select() or (for
  41. # read-only access) #examine(). Once the client has successfully
  42. # selected a mailbox, they enter _selected_ state, and that
  43. # mailbox becomes the _current_ mailbox, on which mail-item
  44. # related commands implicitly operate.
  45. #
  46. # Messages have two sorts of identifiers: message sequence
  47. # numbers, and UIDs.
  48. #
  49. # Message sequence numbers number messages within a mail box
  50. # from 1 up to the number of items in the mail box. If new
  51. # message arrives during a session, it receives a sequence
  52. # number equal to the new size of the mail box. If messages
  53. # are expunged from the mailbox, remaining messages have their
  54. # sequence numbers "shuffled down" to fill the gaps.
  55. #
  56. # UIDs, on the other hand, are permanently guaranteed not to
  57. # identify another message within the same mailbox, even if
  58. # the existing message is deleted. UIDs are required to
  59. # be assigned in ascending (but not necessarily sequential)
  60. # order within a mailbox; this means that if a non-IMAP client
  61. # rearranges the order of mailitems within a mailbox, the
  62. # UIDs have to be reassigned. An IMAP client cannot thus
  63. # rearrange message orders.
  64. #
  65. # == Examples of Usage
  66. #
  67. # === List sender and subject of all recent messages in the default mailbox
  68. #
  69. # imap = Net::IMAP.new('mail.example.com')
  70. # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
  71. # imap.examine('INBOX')
  72. # imap.search(["RECENT"]).each do |message_id|
  73. # envelope = imap.fetch(message_id, "ENVELOPE")[0].attr["ENVELOPE"]
  74. # puts "#{envelope.from[0].name}: \t#{envelope.subject}"
  75. # end
  76. #
  77. # === Move all messages from April 2003 from "Mail/sent-mail" to "Mail/sent-apr03"
  78. #
  79. # imap = Net::IMAP.new('mail.example.com')
  80. # imap.authenticate('LOGIN', 'joe_user', 'joes_password')
  81. # imap.select('Mail/sent-mail')
  82. # if not imap.list('Mail/', 'sent-apr03')
  83. # imap.create('Mail/sent-apr03')
  84. # end
  85. # imap.search(["BEFORE", "30-Apr-2003", "SINCE", "1-Apr-2003"]).each do |message_id|
  86. # imap.copy(message_id, "Mail/sent-apr03")
  87. # imap.store(message_id, "+FLAGS", [:Deleted])
  88. # end
  89. # imap.expunge
  90. #
  91. # == Thread Safety
  92. #
  93. # Net::IMAP supports concurrent threads. For example,
  94. #
  95. # imap = Net::IMAP.new("imap.foo.net", "imap2")
  96. # imap.authenticate("cram-md5", "bar", "password")
  97. # imap.select("inbox")
  98. # fetch_thread = Thread.start { imap.fetch(1..-1, "UID") }
  99. # search_result = imap.search(["BODY", "hello"])
  100. # fetch_result = fetch_thread.value
  101. # imap.disconnect
  102. #
  103. # This script invokes the FETCH command and the SEARCH command concurrently.
  104. #
  105. # == Errors
  106. #
  107. # An IMAP server can send three different types of responses to indicate
  108. # failure:
  109. #
  110. # NO:: the attempted command could not be successfully completed. For
  111. # instance, the username/password used for logging in are incorrect;
  112. # the selected mailbox does not exists; etc.
  113. #
  114. # BAD:: the request from the client does not follow the server's
  115. # understanding of the IMAP protocol. This includes attempting
  116. # commands from the wrong client state; for instance, attempting
  117. # to perform a SEARCH command without having SELECTed a current
  118. # mailbox. It can also signal an internal server
  119. # failure (such as a disk crash) has occurred.
  120. #
  121. # BYE:: the server is saying goodbye. This can be part of a normal
  122. # logout sequence, and can be used as part of a login sequence
  123. # to indicate that the server is (for some reason) unwilling
  124. # to accept our connection. As a response to any other command,
  125. # it indicates either that the server is shutting down, or that
  126. # the server is timing out the client connection due to inactivity.
  127. #
  128. # These three error response are represented by the errors
  129. # Net::IMAP::NoResponseError, Net::IMAP::BadResponseError, and
  130. # Net::IMAP::ByeResponseError, all of which are subclasses of
  131. # Net::IMAP::ResponseError. Essentially, all methods that involve
  132. # sending a request to the server can generate one of these errors.
  133. # Only the most pertinent instances have been documented below.
  134. #
  135. # Because the IMAP class uses Sockets for communication, its methods
  136. # are also susceptible to the various errors that can occur when
  137. # working with sockets. These are generally represented as
  138. # Errno errors. For instance, any method that involves sending a
  139. # request to the server and/or receiving a response from it could
  140. # raise an Errno::EPIPE error if the network connection unexpectedly
  141. # goes down. See the socket(7), ip(7), tcp(7), socket(2), connect(2),
  142. # and associated man pages.
  143. #
  144. # Finally, a Net::IMAP::DataFormatError is thrown if low-level data
  145. # is found to be in an incorrect format (for instance, when converting
  146. # between UTF-8 and UTF-16), and Net::IMAP::ResponseParseError is
  147. # thrown if a server response is non-parseable.
  148. #
  149. #
  150. # == References
  151. #
  152. # [[IMAP]]
  153. # M. Crispin, "INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1",
  154. # RFC 2060, December 1996. (Note: since obsoleted by RFC 3501)
  155. #
  156. # [[LANGUAGE-TAGS]]
  157. # Alvestrand, H., "Tags for the Identification of
  158. # Languages", RFC 1766, March 1995.
  159. #
  160. # [[MD5]]
  161. # Myers, J., and M. Rose, "The Content-MD5 Header Field", RFC
  162. # 1864, October 1995.
  163. #
  164. # [[MIME-IMB]]
  165. # Freed, N., and N. Borenstein, "MIME (Multipurpose Internet
  166. # Mail Extensions) Part One: Format of Internet Message Bodies", RFC
  167. # 2045, November 1996.
  168. #
  169. # [[RFC-822]]
  170. # Crocker, D., "Standard for the Format of ARPA Internet Text
  171. # Messages", STD 11, RFC 822, University of Delaware, August 1982.
  172. #
  173. # [[RFC-2087]]
  174. # Myers, J., "IMAP4 QUOTA extension", RFC 2087, January 1997.
  175. #
  176. # [[RFC-2086]]
  177. # Myers, J., "IMAP4 ACL extension", RFC 2086, January 1997.
  178. #
  179. # [[RFC-2195]]
  180. # Klensin, J., Catoe, R., and Krumviede, P., "IMAP/POP AUTHorize Extension
  181. # for Simple Challenge/Response", RFC 2195, September 1997.
  182. #
  183. # [[SORT-THREAD-EXT]]
  184. # Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - SORT and THREAD
  185. # Extensions", draft-ietf-imapext-sort, May 2003.
  186. #
  187. # [[OSSL]]
  188. # http://www.openssl.org
  189. #
  190. # [[RSSL]]
  191. # http://savannah.gnu.org/projects/rubypki
  192. #
  193. # [[UTF7]]
  194. # Goldsmith, D. and Davis, M., "UTF-7: A Mail-Safe Transformation Format of
  195. # Unicode", RFC 2152, May 1997.
  196. #
  197. class IMAP
  198. include MonitorMixin
  199. if defined?(OpenSSL)
  200. include OpenSSL
  201. include SSL
  202. end
  203. # Returns an initial greeting response from the server.
  204. attr_reader :greeting
  205. # Returns recorded untagged responses. For example:
  206. #
  207. # imap.select("inbox")
  208. # p imap.responses["EXISTS"][-1]
  209. # #=> 2
  210. # p imap.responses["UIDVALIDITY"][-1]
  211. # #=> 968263756
  212. attr_reader :responses
  213. # Returns all response handlers.
  214. attr_reader :response_handlers
  215. # The thread to receive exceptions.
  216. attr_accessor :client_thread
  217. # Flag indicating a message has been seen
  218. SEEN = :Seen
  219. # Flag indicating a message has been answered
  220. ANSWERED = :Answered
  221. # Flag indicating a message has been flagged for special or urgent
  222. # attention
  223. FLAGGED = :Flagged
  224. # Flag indicating a message has been marked for deletion. This
  225. # will occur when the mailbox is closed or expunged.
  226. DELETED = :Deleted
  227. # Flag indicating a message is only a draft or work-in-progress version.
  228. DRAFT = :Draft
  229. # Flag indicating that the message is "recent", meaning that this
  230. # session is the first session in which the client has been notified
  231. # of this message.
  232. RECENT = :Recent
  233. # Flag indicating that a mailbox context name cannot contain
  234. # children.
  235. NOINFERIORS = :Noinferiors
  236. # Flag indicating that a mailbox is not selected.
  237. NOSELECT = :Noselect
  238. # Flag indicating that a mailbox has been marked "interesting" by
  239. # the server; this commonly indicates that the mailbox contains
  240. # new messages.
  241. MARKED = :Marked
  242. # Flag indicating that the mailbox does not contains new messages.
  243. UNMARKED = :Unmarked
  244. # Returns the debug mode.
  245. def self.debug
  246. return @@debug
  247. end
  248. # Sets the debug mode.
  249. def self.debug=(val)
  250. return @@debug = val
  251. end
  252. # Returns the max number of flags interned to symbols.
  253. def self.max_flag_count
  254. return @@max_flag_count
  255. end
  256. # Sets the max number of flags interned to symbols.
  257. def self.max_flag_count=(count)
  258. @@max_flag_count = count
  259. end
  260. # Adds an authenticator for Net::IMAP#authenticate. +auth_type+
  261. # is the type of authentication this authenticator supports
  262. # (for instance, "LOGIN"). The +authenticator+ is an object
  263. # which defines a process() method to handle authentication with
  264. # the server. See Net::IMAP::LoginAuthenticator,
  265. # Net::IMAP::CramMD5Authenticator, and Net::IMAP::DigestMD5Authenticator
  266. # for examples.
  267. #
  268. #
  269. # If +auth_type+ refers to an existing authenticator, it will be
  270. # replaced by the new one.
  271. def self.add_authenticator(auth_type, authenticator)
  272. @@authenticators[auth_type] = authenticator
  273. end
  274. # Disconnects from the server.
  275. def disconnect
  276. begin
  277. begin
  278. # try to call SSL::SSLSocket#io.
  279. @sock.io.shutdown
  280. rescue NoMethodError
  281. # @sock is not an SSL::SSLSocket.
  282. @sock.shutdown
  283. end
  284. rescue Errno::ENOTCONN
  285. # ignore `Errno::ENOTCONN: Socket is not connected' on some platforms.
  286. rescue Exception => e
  287. @receiver_thread.raise(e)
  288. end
  289. @receiver_thread.join
  290. synchronize do
  291. unless @sock.closed?
  292. @sock.close
  293. end
  294. end
  295. raise e if e
  296. end
  297. # Returns true if disconnected from the server.
  298. def disconnected?
  299. return @sock.closed?
  300. end
  301. # Sends a CAPABILITY command, and returns an array of
  302. # capabilities that the server supports. Each capability
  303. # is a string. See [IMAP] for a list of possible
  304. # capabilities.
  305. #
  306. # Note that the Net::IMAP class does not modify its
  307. # behaviour according to the capabilities of the server;
  308. # it is up to the user of the class to ensure that
  309. # a certain capability is supported by a server before
  310. # using it.
  311. def capability
  312. synchronize do
  313. send_command("CAPABILITY")
  314. return @responses.delete("CAPABILITY")[-1]
  315. end
  316. end
  317. # Sends a NOOP command to the server. It does nothing.
  318. def noop
  319. send_command("NOOP")
  320. end
  321. # Sends a LOGOUT command to inform the server that the client is
  322. # done with the connection.
  323. def logout
  324. send_command("LOGOUT")
  325. end
  326. # Sends a STARTTLS command to start TLS session.
  327. def starttls(options = {}, verify = true)
  328. send_command("STARTTLS") do |resp|
  329. if resp.kind_of?(TaggedResponse) && resp.name == "OK"
  330. begin
  331. # for backward compatibility
  332. certs = options.to_str
  333. options = create_ssl_params(certs, verify)
  334. rescue NoMethodError
  335. end
  336. start_tls_session(options)
  337. end
  338. end
  339. end
  340. # Sends an AUTHENTICATE command to authenticate the client.
  341. # The +auth_type+ parameter is a string that represents
  342. # the authentication mechanism to be used. Currently Net::IMAP
  343. # supports authentication mechanisms:
  344. #
  345. # LOGIN:: login using cleartext user and password.
  346. # CRAM-MD5:: login with cleartext user and encrypted password
  347. # (see [RFC-2195] for a full description). This
  348. # mechanism requires that the server have the user's
  349. # password stored in clear-text password.
  350. #
  351. # For both these mechanisms, there should be two +args+: username
  352. # and (cleartext) password. A server may not support one or other
  353. # of these mechanisms; check #capability() for a capability of
  354. # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
  355. #
  356. # Authentication is done using the appropriate authenticator object:
  357. # see @@authenticators for more information on plugging in your own
  358. # authenticator.
  359. #
  360. # For example:
  361. #
  362. # imap.authenticate('LOGIN', user, password)
  363. #
  364. # A Net::IMAP::NoResponseError is raised if authentication fails.
  365. def authenticate(auth_type, *args)
  366. auth_type = auth_type.upcase
  367. unless @@authenticators.has_key?(auth_type)
  368. raise ArgumentError,
  369. format('unknown auth type - "%s"', auth_type)
  370. end
  371. authenticator = @@authenticators[auth_type].new(*args)
  372. send_command("AUTHENTICATE", auth_type) do |resp|
  373. if resp.instance_of?(ContinuationRequest)
  374. data = authenticator.process(resp.data.text.unpack("m")[0])
  375. s = [data].pack("m").gsub(/\n/, "")
  376. send_string_data(s)
  377. put_string(CRLF)
  378. end
  379. end
  380. end
  381. # Sends a LOGIN command to identify the client and carries
  382. # the plaintext +password+ authenticating this +user+. Note
  383. # that, unlike calling #authenticate() with an +auth_type+
  384. # of "LOGIN", #login() does *not* use the login authenticator.
  385. #
  386. # A Net::IMAP::NoResponseError is raised if authentication fails.
  387. def login(user, password)
  388. send_command("LOGIN", user, password)
  389. end
  390. # Sends a SELECT command to select a +mailbox+ so that messages
  391. # in the +mailbox+ can be accessed.
  392. #
  393. # After you have selected a mailbox, you may retrieve the
  394. # number of items in that mailbox from @responses["EXISTS"][-1],
  395. # and the number of recent messages from @responses["RECENT"][-1].
  396. # Note that these values can change if new messages arrive
  397. # during a session; see #add_response_handler() for a way of
  398. # detecting this event.
  399. #
  400. # A Net::IMAP::NoResponseError is raised if the mailbox does not
  401. # exist or is for some reason non-selectable.
  402. def select(mailbox)
  403. synchronize do
  404. @responses.clear
  405. send_command("SELECT", mailbox)
  406. end
  407. end
  408. # Sends a EXAMINE command to select a +mailbox+ so that messages
  409. # in the +mailbox+ can be accessed. Behaves the same as #select(),
  410. # except that the selected +mailbox+ is identified as read-only.
  411. #
  412. # A Net::IMAP::NoResponseError is raised if the mailbox does not
  413. # exist or is for some reason non-examinable.
  414. def examine(mailbox)
  415. synchronize do
  416. @responses.clear
  417. send_command("EXAMINE", mailbox)
  418. end
  419. end
  420. # Sends a CREATE command to create a new +mailbox+.
  421. #
  422. # A Net::IMAP::NoResponseError is raised if a mailbox with that name
  423. # cannot be created.
  424. def create(mailbox)
  425. send_command("CREATE", mailbox)
  426. end
  427. # Sends a DELETE command to remove the +mailbox+.
  428. #
  429. # A Net::IMAP::NoResponseError is raised if a mailbox with that name
  430. # cannot be deleted, either because it does not exist or because the
  431. # client does not have permission to delete it.
  432. def delete(mailbox)
  433. send_command("DELETE", mailbox)
  434. end
  435. # Sends a RENAME command to change the name of the +mailbox+ to
  436. # +newname+.
  437. #
  438. # A Net::IMAP::NoResponseError is raised if a mailbox with the
  439. # name +mailbox+ cannot be renamed to +newname+ for whatever
  440. # reason; for instance, because +mailbox+ does not exist, or
  441. # because there is already a mailbox with the name +newname+.
  442. def rename(mailbox, newname)
  443. send_command("RENAME", mailbox, newname)
  444. end
  445. # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
  446. # the server's set of "active" or "subscribed" mailboxes as returned
  447. # by #lsub().
  448. #
  449. # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
  450. # subscribed to, for instance because it does not exist.
  451. def subscribe(mailbox)
  452. send_command("SUBSCRIBE", mailbox)
  453. end
  454. # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name
  455. # from the server's set of "active" or "subscribed" mailboxes.
  456. #
  457. # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
  458. # unsubscribed from, for instance because the client is not currently
  459. # subscribed to it.
  460. def unsubscribe(mailbox)
  461. send_command("UNSUBSCRIBE", mailbox)
  462. end
  463. # Sends a LIST command, and returns a subset of names from
  464. # the complete set of all names available to the client.
  465. # +refname+ provides a context (for instance, a base directory
  466. # in a directory-based mailbox hierarchy). +mailbox+ specifies
  467. # a mailbox or (via wildcards) mailboxes under that context.
  468. # Two wildcards may be used in +mailbox+: '*', which matches
  469. # all characters *including* the hierarchy delimiter (for instance,
  470. # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
  471. # which matches all characters *except* the hierarchy delimiter.
  472. #
  473. # If +refname+ is empty, +mailbox+ is used directly to determine
  474. # which mailboxes to match. If +mailbox+ is empty, the root
  475. # name of +refname+ and the hierarchy delimiter are returned.
  476. #
  477. # The return value is an array of +Net::IMAP::MailboxList+. For example:
  478. #
  479. # imap.create("foo/bar")
  480. # imap.create("foo/baz")
  481. # p imap.list("", "foo/%")
  482. # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
  483. # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
  484. # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
  485. def list(refname, mailbox)
  486. synchronize do
  487. send_command("LIST", refname, mailbox)
  488. return @responses.delete("LIST")
  489. end
  490. end
  491. # Sends the GETQUOTAROOT command along with specified +mailbox+.
  492. # This command is generally available to both admin and user.
  493. # If mailbox exists, returns an array containing objects of
  494. # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
  495. def getquotaroot(mailbox)
  496. synchronize do
  497. send_command("GETQUOTAROOT", mailbox)
  498. result = []
  499. result.concat(@responses.delete("QUOTAROOT"))
  500. result.concat(@responses.delete("QUOTA"))
  501. return result
  502. end
  503. end
  504. # Sends the GETQUOTA command along with specified +mailbox+.
  505. # If this mailbox exists, then an array containing a
  506. # Net::IMAP::MailboxQuota object is returned. This
  507. # command generally is only available to server admin.
  508. def getquota(mailbox)
  509. synchronize do
  510. send_command("GETQUOTA", mailbox)
  511. return @responses.delete("QUOTA")
  512. end
  513. end
  514. # Sends a SETQUOTA command along with the specified +mailbox+ and
  515. # +quota+. If +quota+ is nil, then quota will be unset for that
  516. # mailbox. Typically one needs to be logged in as server admin
  517. # for this to work. The IMAP quota commands are described in
  518. # [RFC-2087].
  519. def setquota(mailbox, quota)
  520. if quota.nil?
  521. data = '()'
  522. else
  523. data = '(STORAGE ' + quota.to_s + ')'
  524. end
  525. send_command("SETQUOTA", mailbox, RawData.new(data))
  526. end
  527. # Sends the SETACL command along with +mailbox+, +user+ and the
  528. # +rights+ that user is to have on that mailbox. If +rights+ is nil,
  529. # then that user will be stripped of any rights to that mailbox.
  530. # The IMAP ACL commands are described in [RFC-2086].
  531. def setacl(mailbox, user, rights)
  532. if rights.nil?
  533. send_command("SETACL", mailbox, user, "")
  534. else
  535. send_command("SETACL", mailbox, user, rights)
  536. end
  537. end
  538. # Send the GETACL command along with specified +mailbox+.
  539. # If this mailbox exists, an array containing objects of
  540. # Net::IMAP::MailboxACLItem will be returned.
  541. def getacl(mailbox)
  542. synchronize do
  543. send_command("GETACL", mailbox)
  544. return @responses.delete("ACL")[-1]
  545. end
  546. end
  547. # Sends a LSUB command, and returns a subset of names from the set
  548. # of names that the user has declared as being "active" or
  549. # "subscribed". +refname+ and +mailbox+ are interpreted as
  550. # for #list().
  551. # The return value is an array of +Net::IMAP::MailboxList+.
  552. def lsub(refname, mailbox)
  553. synchronize do
  554. send_command("LSUB", refname, mailbox)
  555. return @responses.delete("LSUB")
  556. end
  557. end
  558. # Sends a STATUS command, and returns the status of the indicated
  559. # +mailbox+. +attr+ is a list of one or more attributes that
  560. # we are request the status of. Supported attributes include:
  561. #
  562. # MESSAGES:: the number of messages in the mailbox.
  563. # RECENT:: the number of recent messages in the mailbox.
  564. # UNSEEN:: the number of unseen messages in the mailbox.
  565. #
  566. # The return value is a hash of attributes. For example:
  567. #
  568. # p imap.status("inbox", ["MESSAGES", "RECENT"])
  569. # #=> {"RECENT"=>0, "MESSAGES"=>44}
  570. #
  571. # A Net::IMAP::NoResponseError is raised if status values
  572. # for +mailbox+ cannot be returned, for instance because it
  573. # does not exist.
  574. def status(mailbox, attr)
  575. synchronize do
  576. send_command("STATUS", mailbox, attr)
  577. return @responses.delete("STATUS")[-1].attr
  578. end
  579. end
  580. # Sends a APPEND command to append the +message+ to the end of
  581. # the +mailbox+. The optional +flags+ argument is an array of
  582. # flags to initially passing to the new message. The optional
  583. # +date_time+ argument specifies the creation time to assign to the
  584. # new message; it defaults to the current time.
  585. # For example:
  586. #
  587. # imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
  588. # Subject: hello
  589. # From: shugo@ruby-lang.org
  590. # To: shugo@ruby-lang.org
  591. #
  592. # hello world
  593. # EOF
  594. #
  595. # A Net::IMAP::NoResponseError is raised if the mailbox does
  596. # not exist (it is not created automatically), or if the flags,
  597. # date_time, or message arguments contain errors.
  598. def append(mailbox, message, flags = nil, date_time = nil)
  599. args = []
  600. if flags
  601. args.push(flags)
  602. end
  603. args.push(date_time) if date_time
  604. args.push(Literal.new(message))
  605. send_command("APPEND", mailbox, *args)
  606. end
  607. # Sends a CHECK command to request a checkpoint of the currently
  608. # selected mailbox. This performs implementation-specific
  609. # housekeeping, for instance, reconciling the mailbox's
  610. # in-memory and on-disk state.
  611. def check
  612. send_command("CHECK")
  613. end
  614. # Sends a CLOSE command to close the currently selected mailbox.
  615. # The CLOSE command permanently removes from the mailbox all
  616. # messages that have the \Deleted flag set.
  617. def close
  618. send_command("CLOSE")
  619. end
  620. # Sends a EXPUNGE command to permanently remove from the currently
  621. # selected mailbox all messages that have the \Deleted flag set.
  622. def expunge
  623. synchronize do
  624. send_command("EXPUNGE")
  625. return @responses.delete("EXPUNGE")
  626. end
  627. end
  628. # Sends a SEARCH command to search the mailbox for messages that
  629. # match the given searching criteria, and returns message sequence
  630. # numbers. +keys+ can either be a string holding the entire
  631. # search string, or a single-dimension array of search keywords and
  632. # arguments. The following are some common search criteria;
  633. # see [IMAP] section 6.4.4 for a full list.
  634. #
  635. # <message set>:: a set of message sequence numbers. ',' indicates
  636. # an interval, ':' indicates a range. For instance,
  637. # '2,10:12,15' means "2,10,11,12,15".
  638. #
  639. # BEFORE <date>:: messages with an internal date strictly before
  640. # <date>. The date argument has a format similar
  641. # to 8-Aug-2002.
  642. #
  643. # BODY <string>:: messages that contain <string> within their body.
  644. #
  645. # CC <string>:: messages containing <string> in their CC field.
  646. #
  647. # FROM <string>:: messages that contain <string> in their FROM field.
  648. #
  649. # NEW:: messages with the \Recent, but not the \Seen, flag set.
  650. #
  651. # NOT <search-key>:: negate the following search key.
  652. #
  653. # OR <search-key> <search-key>:: "or" two search keys together.
  654. #
  655. # ON <date>:: messages with an internal date exactly equal to <date>,
  656. # which has a format similar to 8-Aug-2002.
  657. #
  658. # SINCE <date>:: messages with an internal date on or after <date>.
  659. #
  660. # SUBJECT <string>:: messages with <string> in their subject.
  661. #
  662. # TO <string>:: messages with <string> in their TO field.
  663. #
  664. # For example:
  665. #
  666. # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
  667. # #=> [1, 6, 7, 8]
  668. def search(keys, charset = nil)
  669. return search_internal("SEARCH", keys, charset)
  670. end
  671. # As for #search(), but returns unique identifiers.
  672. def uid_search(keys, charset = nil)
  673. return search_internal("UID SEARCH", keys, charset)
  674. end
  675. # Sends a FETCH command to retrieve data associated with a message
  676. # in the mailbox. The +set+ parameter is a number or an array of
  677. # numbers or a Range object. The number is a message sequence
  678. # number. +attr+ is a list of attributes to fetch; see the
  679. # documentation for Net::IMAP::FetchData for a list of valid
  680. # attributes.
  681. # The return value is an array of Net::IMAP::FetchData. For example:
  682. #
  683. # p imap.fetch(6..8, "UID")
  684. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
  685. # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
  686. # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
  687. # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
  688. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
  689. # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
  690. # p data.seqno
  691. # #=> 6
  692. # p data.attr["RFC822.SIZE"]
  693. # #=> 611
  694. # p data.attr["INTERNALDATE"]
  695. # #=> "12-Oct-2000 22:40:59 +0900"
  696. # p data.attr["UID"]
  697. # #=> 98
  698. def fetch(set, attr)
  699. return fetch_internal("FETCH", set, attr)
  700. end
  701. # As for #fetch(), but +set+ contains unique identifiers.
  702. def uid_fetch(set, attr)
  703. return fetch_internal("UID FETCH", set, attr)
  704. end
  705. # Sends a STORE command to alter data associated with messages
  706. # in the mailbox, in particular their flags. The +set+ parameter
  707. # is a number or an array of numbers or a Range object. Each number
  708. # is a message sequence number. +attr+ is the name of a data item
  709. # to store: 'FLAGS' means to replace the message's flag list
  710. # with the provided one; '+FLAGS' means to add the provided flags;
  711. # and '-FLAGS' means to remove them. +flags+ is a list of flags.
  712. #
  713. # The return value is an array of Net::IMAP::FetchData. For example:
  714. #
  715. # p imap.store(6..8, "+FLAGS", [:Deleted])
  716. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
  717. # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
  718. # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
  719. def store(set, attr, flags)
  720. return store_internal("STORE", set, attr, flags)
  721. end
  722. # As for #store(), but +set+ contains unique identifiers.
  723. def uid_store(set, attr, flags)
  724. return store_internal("UID STORE", set, attr, flags)
  725. end
  726. # Sends a COPY command to copy the specified message(s) to the end
  727. # of the specified destination +mailbox+. The +set+ parameter is
  728. # a number or an array of numbers or a Range object. The number is
  729. # a message sequence number.
  730. def copy(set, mailbox)
  731. copy_internal("COPY", set, mailbox)
  732. end
  733. # As for #copy(), but +set+ contains unique identifiers.
  734. def uid_copy(set, mailbox)
  735. copy_internal("UID COPY", set, mailbox)
  736. end
  737. # Sends a SORT command to sort messages in the mailbox.
  738. # Returns an array of message sequence numbers. For example:
  739. #
  740. # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
  741. # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
  742. # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
  743. # #=> [6, 7, 8, 1]
  744. #
  745. # See [SORT-THREAD-EXT] for more details.
  746. def sort(sort_keys, search_keys, charset)
  747. return sort_internal("SORT", sort_keys, search_keys, charset)
  748. end
  749. # As for #sort(), but returns an array of unique identifiers.
  750. def uid_sort(sort_keys, search_keys, charset)
  751. return sort_internal("UID SORT", sort_keys, search_keys, charset)
  752. end
  753. # Adds a response handler. For example, to detect when
  754. # the server sends us a new EXISTS response (which normally
  755. # indicates new messages being added to the mail box),
  756. # you could add the following handler after selecting the
  757. # mailbox.
  758. #
  759. # imap.add_response_handler { |resp|
  760. # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
  761. # puts "Mailbox now has #{resp.data} messages"
  762. # end
  763. # }
  764. #
  765. def add_response_handler(handler = Proc.new)
  766. @response_handlers.push(handler)
  767. end
  768. # Removes the response handler.
  769. def remove_response_handler(handler)
  770. @response_handlers.delete(handler)
  771. end
  772. # As for #search(), but returns message sequence numbers in threaded
  773. # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
  774. # are:
  775. #
  776. # ORDEREDSUBJECT:: split into single-level threads according to subject,
  777. # ordered by date.
  778. # REFERENCES:: split into threads by parent/child relationships determined
  779. # by which message is a reply to which.
  780. #
  781. # Unlike #search(), +charset+ is a required argument. US-ASCII
  782. # and UTF-8 are sample values.
  783. #
  784. # See [SORT-THREAD-EXT] for more details.
  785. def thread(algorithm, search_keys, charset)
  786. return thread_internal("THREAD", algorithm, search_keys, charset)
  787. end
  788. # As for #thread(), but returns unique identifiers instead of
  789. # message sequence numbers.
  790. def uid_thread(algorithm, search_keys, charset)
  791. return thread_internal("UID THREAD", algorithm, search_keys, charset)
  792. end
  793. # Sends an IDLE command that waits for notifications of new or expunged
  794. # messages. Yields responses from the server during the IDLE.
  795. #
  796. # Use #idle_done() to leave IDLE.
  797. def idle(&response_handler)
  798. raise LocalJumpError, "no block given" unless response_handler
  799. response = nil
  800. synchronize do
  801. tag = Thread.current[:net_imap_tag] = generate_tag
  802. put_string("#{tag} IDLE#{CRLF}")
  803. begin
  804. add_response_handler(response_handler)
  805. @idle_done_cond = new_cond
  806. @idle_done_cond.wait
  807. @idle_done_cond = nil
  808. ensure
  809. remove_response_handler(response_handler)
  810. put_string("DONE#{CRLF}")
  811. response = get_tagged_response(tag, "IDLE")
  812. end
  813. end
  814. return response
  815. end
  816. # Leaves IDLE.
  817. def idle_done
  818. synchronize do
  819. if @idle_done_cond.nil?
  820. raise Net::IMAP::Error, "not during IDLE"
  821. end
  822. @idle_done_cond.signal
  823. end
  824. end
  825. # Decode a string from modified UTF-7 format to UTF-8.
  826. #
  827. # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
  828. # slightly modified version of this to encode mailbox names
  829. # containing non-ASCII characters; see [IMAP] section 5.1.3.
  830. #
  831. # Net::IMAP does _not_ automatically encode and decode
  832. # mailbox names to and from utf7.
  833. def self.decode_utf7(s)
  834. return s.gsub(/&(.*?)-/n) {
  835. if $1.empty?
  836. "&"
  837. else
  838. base64 = $1.tr(",", "/")
  839. x = base64.length % 4
  840. if x > 0
  841. base64.concat("=" * (4 - x))
  842. end
  843. base64.unpack("m")[0].unpack("n*").pack("U*")
  844. end
  845. }.force_encoding("UTF-8")
  846. end
  847. # Encode a string from UTF-8 format to modified UTF-7.
  848. def self.encode_utf7(s)
  849. return s.gsub(/(&)|([^\x20-\x7e]+)/u) {
  850. if $1
  851. "&-"
  852. else
  853. base64 = [$&.unpack("U*").pack("n*")].pack("m")
  854. "&" + base64.delete("=\n").tr("/", ",") + "-"
  855. end
  856. }.force_encoding("ASCII-8BIT")
  857. end
  858. # Formats +time+ as an IMAP-style date.
  859. def self.format_date(time)
  860. return time.strftime('%d-%b-%Y')
  861. end
  862. # Formats +time+ as an IMAP-style date-time.
  863. def self.format_datetime(time)
  864. return time.strftime('%d-%b-%Y %H:%M %z')
  865. end
  866. private
  867. CRLF = "\r\n" # :nodoc:
  868. PORT = 143 # :nodoc:
  869. SSL_PORT = 993 # :nodoc:
  870. @@debug = false
  871. @@authenticators = {}
  872. @@max_flag_count = 10000
  873. # call-seq:
  874. # Net::IMAP.new(host, options = {})
  875. #
  876. # Creates a new Net::IMAP object and connects it to the specified
  877. # +host+.
  878. #
  879. # +options+ is an option hash, each key of which is a symbol.
  880. #
  881. # The available options are:
  882. #
  883. # port:: port number (default value is 143 for imap, or 993 for imaps)
  884. # ssl:: if options[:ssl] is true, then an attempt will be made
  885. # to use SSL (now TLS) to connect to the server. For this to work
  886. # OpenSSL [OSSL] and the Ruby OpenSSL [RSSL] extensions need to
  887. # be installed.
  888. # if options[:ssl] is a hash, it's passed to
  889. # OpenSSL::SSL::SSLContext#set_params as parameters.
  890. #
  891. # The most common errors are:
  892. #
  893. # Errno::ECONNREFUSED:: connection refused by +host+ or an intervening
  894. # firewall.
  895. # Errno::ETIMEDOUT:: connection timed out (possibly due to packets
  896. # being dropped by an intervening firewall).
  897. # Errno::ENETUNREACH:: there is no route to that network.
  898. # SocketError:: hostname not known or other socket error.
  899. # Net::IMAP::ByeResponseError:: we connected to the host, but they
  900. # immediately said goodbye to us.
  901. def initialize(host, port_or_options = {},
  902. usessl = false, certs = nil, verify = true)
  903. super()
  904. @host = host
  905. begin
  906. options = port_or_options.to_hash
  907. rescue NoMethodError
  908. # for backward compatibility
  909. options = {}
  910. options[:port] = port_or_options
  911. if usessl
  912. options[:ssl] = create_ssl_params(certs, verify)
  913. end
  914. end
  915. @port = options[:port] || (options[:ssl] ? SSL_PORT : PORT)
  916. @tag_prefix = "RUBY"
  917. @tagno = 0
  918. @parser = ResponseParser.new
  919. @sock = TCPSocket.open(@host, @port)
  920. if options[:ssl]
  921. start_tls_session(options[:ssl])
  922. @usessl = true
  923. else
  924. @usessl = false
  925. end
  926. @responses = Hash.new([].freeze)
  927. @tagged_responses = {}
  928. @response_handlers = []
  929. @tagged_response_arrival = new_cond
  930. @continuation_request_arrival = new_cond
  931. @idle_done_cond = nil
  932. @logout_command_tag = nil
  933. @debug_output_bol = true
  934. @exception = nil
  935. @greeting = get_response
  936. if @greeting.name == "BYE"
  937. @sock.close
  938. raise ByeResponseError, @greeting
  939. end
  940. @client_thread = Thread.current
  941. @receiver_thread = Thread.start {
  942. begin
  943. receive_responses
  944. rescue Exception
  945. end
  946. }
  947. end
  948. def receive_responses
  949. connection_closed = false
  950. until connection_closed
  951. synchronize do
  952. @exception = nil
  953. end
  954. begin
  955. resp = get_response
  956. rescue Exception => e
  957. synchronize do
  958. @sock.close
  959. @exception = e
  960. end
  961. break
  962. end
  963. unless resp
  964. synchronize do
  965. @exception = EOFError.new("end of file reached")
  966. end
  967. break
  968. end
  969. begin
  970. synchronize do
  971. case resp
  972. when TaggedResponse
  973. @tagged_responses[resp.tag] = resp
  974. @tagged_response_arrival.broadcast
  975. if resp.tag == @logout_command_tag
  976. return
  977. end
  978. when UntaggedResponse
  979. record_response(resp.name, resp.data)
  980. if resp.data.instance_of?(ResponseText) &&
  981. (code = resp.data.code)
  982. record_response(code.name, code.data)
  983. end
  984. if resp.name == "BYE" && @logout_command_tag.nil?
  985. @sock.close
  986. @exception = ByeResponseError.new(resp)
  987. connection_closed = true
  988. end
  989. when ContinuationRequest
  990. @continuation_request_arrival.signal
  991. end
  992. @response_handlers.each do |handler|
  993. handler.call(resp)
  994. end
  995. end
  996. rescue Exception => e
  997. @exception = e
  998. synchronize do
  999. @tagged_response_arrival.broadcast
  1000. @continuation_request_arrival.broadcast
  1001. end
  1002. end
  1003. end
  1004. synchronize do
  1005. @tagged_response_arrival.broadcast
  1006. @continuation_request_arrival.broadcast
  1007. end
  1008. end
  1009. def get_tagged_response(tag, cmd)
  1010. until @tagged_responses.key?(tag)
  1011. raise @exception if @exception
  1012. @tagged_response_arrival.wait
  1013. end
  1014. resp = @tagged_responses.delete(tag)
  1015. case resp.name
  1016. when /\A(?:NO)\z/ni
  1017. raise NoResponseError, resp
  1018. when /\A(?:BAD)\z/ni
  1019. raise BadResponseError, resp
  1020. else
  1021. return resp
  1022. end
  1023. end
  1024. def get_response
  1025. buff = ""
  1026. while true
  1027. s = @sock.gets(CRLF)
  1028. break unless s
  1029. buff.concat(s)
  1030. if /\{(\d+)\}\r\n/n =~ s
  1031. s = @sock.read($1.to_i)
  1032. buff.concat(s)
  1033. else
  1034. break
  1035. end
  1036. end
  1037. return nil if buff.length == 0
  1038. if @@debug
  1039. $stderr.print(buff.gsub(/^/n, "S: "))
  1040. end
  1041. return @parser.parse(buff)
  1042. end
  1043. def record_response(name, data)
  1044. unless @responses.has_key?(name)
  1045. @responses[name] = []
  1046. end
  1047. @responses[name].push(data)
  1048. end
  1049. def send_command(cmd, *args, &block)
  1050. synchronize do
  1051. args.each do |i|
  1052. validate_data(i)
  1053. end
  1054. tag = generate_tag
  1055. put_string(tag + " " + cmd)
  1056. args.each do |i|
  1057. put_string(" ")
  1058. send_data(i)
  1059. end
  1060. put_string(CRLF)
  1061. if cmd == "LOGOUT"
  1062. @logout_command_tag = tag
  1063. end
  1064. if block
  1065. add_response_handler(block)
  1066. end
  1067. begin
  1068. return get_tagged_response(tag, cmd)
  1069. ensure
  1070. if block
  1071. remove_response_handler(block)
  1072. end
  1073. end
  1074. end
  1075. end
  1076. def generate_tag
  1077. @tagno += 1
  1078. return format("%s%04d", @tag_prefix, @tagno)
  1079. end
  1080. def put_string(str)
  1081. @sock.print(str)
  1082. if @@debug
  1083. if @debug_output_bol
  1084. $stderr.print("C: ")
  1085. end
  1086. $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
  1087. if /\r\n\z/n.match(str)
  1088. @debug_output_bol = true
  1089. else
  1090. @debug_output_bol = false
  1091. end
  1092. end
  1093. end
  1094. def validate_data(data)
  1095. case data
  1096. when nil
  1097. when String
  1098. when Integer
  1099. if data < 0 || data >= 4294967296
  1100. raise DataFormatError, num.to_s
  1101. end
  1102. when Array
  1103. data.each do |i|
  1104. validate_data(i)
  1105. end
  1106. when Time
  1107. when Symbol
  1108. else
  1109. data.validate
  1110. end
  1111. end
  1112. def send_data(data)
  1113. case data
  1114. when nil
  1115. put_string("NIL")
  1116. when String
  1117. send_string_data(data)
  1118. when Integer
  1119. send_number_data(data)
  1120. when Array
  1121. send_list_data(data)
  1122. when Time
  1123. send_time_data(data)
  1124. when Symbol
  1125. send_symbol_data(data)
  1126. else
  1127. data.send_data(self)
  1128. end
  1129. end
  1130. def send_string_data(str)
  1131. case str
  1132. when ""
  1133. put_string('""')
  1134. when /[\x80-\xff\r\n]/n
  1135. # literal
  1136. send_literal(str)
  1137. when /[(){ \x00-\x1f\x7f%*"\\]/n
  1138. # quoted string
  1139. send_quoted_string(str)
  1140. else
  1141. put_string(str)
  1142. end
  1143. end
  1144. def send_quoted_string(str)
  1145. put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
  1146. end
  1147. def send_literal(str)
  1148. put_string("{" + str.length.to_s + "}" + CRLF)
  1149. @continuation_request_arrival.wait
  1150. raise @exception if @exception
  1151. put_string(str)
  1152. end
  1153. def send_number_data(num)
  1154. put_string(num.to_s)
  1155. end
  1156. def send_list_data(list)
  1157. put_string("(")
  1158. first = true
  1159. list.each do |i|
  1160. if first
  1161. first = false
  1162. else
  1163. put_string(" ")
  1164. end
  1165. send_data(i)
  1166. end
  1167. put_string(")")
  1168. end
  1169. DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
  1170. def send_time_data(time)
  1171. t = time.dup.gmtime
  1172. s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
  1173. t.day, DATE_MONTH[t.month - 1], t.year,
  1174. t.hour, t.min, t.sec)
  1175. put_string(s)
  1176. end
  1177. def send_symbol_data(symbol)
  1178. put_string("\\" + symbol.to_s)
  1179. end
  1180. def search_internal(cmd, keys, charset)
  1181. if keys.instance_of?(String)
  1182. keys = [RawData.new(keys)]
  1183. else
  1184. normalize_searching_criteria(keys)
  1185. end
  1186. synchronize do
  1187. if charset
  1188. send_command(cmd, "CHARSET", charset, *keys)
  1189. else
  1190. send_command(cmd, *keys)
  1191. end
  1192. return @responses.delete("SEARCH")[-1]
  1193. end
  1194. end
  1195. def fetch_internal(cmd, set, attr)
  1196. case attr
  1197. when String then
  1198. attr = RawData.new(attr)
  1199. when Array then
  1200. attr = attr.map { |arg|
  1201. arg.is_a?(String) ? RawData.new(arg) : arg
  1202. }
  1203. end
  1204. synchronize do
  1205. @responses.delete("FETCH")
  1206. send_command(cmd, MessageSet.new(set), attr)
  1207. return @responses.delete("FETCH")
  1208. end
  1209. end
  1210. def store_internal(cmd, set, attr, flags)
  1211. if attr.instance_of?(String)
  1212. attr = RawData.new(attr)
  1213. end
  1214. synchronize do
  1215. @responses.delete("FETCH")
  1216. send_command(cmd, MessageSet.new(set), attr, flags)
  1217. return @responses.delete("FETCH")
  1218. end
  1219. end
  1220. def copy_internal(cmd, set, mailbox)
  1221. send_command(cmd, MessageSet.new(set), mailbox)
  1222. end
  1223. def sort_internal(cmd, sort_keys, search_keys, charset)
  1224. if search_keys.instance_of?(String)
  1225. search_keys = [RawData.new(search_keys)]
  1226. else
  1227. normalize_searching_criteria(search_keys)
  1228. end
  1229. normalize_searching_criteria(search_keys)
  1230. synchronize do
  1231. send_command(cmd, sort_keys, charset, *search_keys)
  1232. return @responses.delete("SORT")[-1]
  1233. end
  1234. end
  1235. def thread_internal(cmd, algorithm, search_keys, charset)
  1236. if search_keys.instance_of?(String)
  1237. search_keys = [RawData.new(search_keys)]
  1238. else
  1239. normalize_searching_criteria(search_keys)
  1240. end
  1241. normalize_searching_criteria(search_keys)
  1242. send_command(cmd, algorithm, charset, *search_keys)
  1243. return @responses.delete("THREAD")[-1]
  1244. end
  1245. def normalize_searching_criteria(keys)
  1246. keys.collect! do |i|
  1247. case i
  1248. when -1, Range, Array
  1249. MessageSet.new(i)
  1250. else
  1251. i
  1252. end
  1253. end
  1254. end
  1255. def create_ssl_params(certs = nil, verify = true)
  1256. params = {}
  1257. if certs
  1258. if File.file?(certs)
  1259. params[:ca_file] = certs
  1260. elsif File.directory?(certs)
  1261. params[:ca_path] = certs
  1262. end
  1263. end
  1264. if verify
  1265. params[:verify_mode] = VERIFY_PEER
  1266. else
  1267. params[:verify_mode] = VERIFY_NONE
  1268. end
  1269. return params
  1270. end
  1271. def start_tls_session(params = {})
  1272. unless defined?(OpenSSL)
  1273. raise "SSL extension not installed"
  1274. end
  1275. if @sock.kind_of?(OpenSSL::SSL::SSLSocket)
  1276. raise RuntimeError, "already using SSL"
  1277. end
  1278. begin
  1279. params = params.to_hash
  1280. rescue NoMethodError
  1281. params = {}
  1282. end
  1283. context = SSLContext.new
  1284. context.set_params(params)
  1285. if defined?(VerifyCallbackProc)
  1286. context.verify_callback = VerifyCallbackProc
  1287. end
  1288. @sock = SSLSocket.new(@sock, context)
  1289. @sock.sync_close = true
  1290. @sock.connect
  1291. if context.verify_mode != VERIFY_NONE
  1292. @sock.post_connection_check(@host)
  1293. end
  1294. end
  1295. class RawData # :nodoc:
  1296. def send_data(imap)
  1297. imap.send(:put_string, @data)
  1298. end
  1299. def validate
  1300. end
  1301. private
  1302. def initialize(data)
  1303. @data = data
  1304. end
  1305. end
  1306. class Atom # :nodoc:
  1307. def send_data(imap)
  1308. imap.send(:put_string, @data)
  1309. end
  1310. def validate
  1311. end
  1312. private
  1313. def initialize(data)
  1314. @data = data
  1315. end
  1316. end
  1317. class QuotedString # :nodoc:
  1318. def send_data(imap)
  1319. imap.send(:send_quoted_string, @data)
  1320. end
  1321. def validate
  1322. end
  1323. private
  1324. def initialize(data)
  1325. @data = data
  1326. end
  1327. end
  1328. class Literal # :nodoc:
  1329. def send_data(imap)
  1330. imap.send(:send_literal, @data)
  1331. end
  1332. def validate
  1333. end
  1334. private
  1335. def initialize(data)
  1336. @data = data
  1337. end
  1338. end
  1339. class MessageSet # :nodoc:
  1340. def send_data(imap)
  1341. imap.send(:put_string, format_internal(@data))
  1342. end
  1343. def validate
  1344. validate_internal(@data)
  1345. end
  1346. private
  1347. def initialize(data)
  1348. @data = data
  1349. end
  1350. def format_internal(data)
  1351. case data
  1352. when "*"
  1353. return data
  1354. when Integer
  1355. if data == -1
  1356. return "*"
  1357. else
  1358. return data.to_s
  1359. end
  1360. when Range
  1361. return format_internal(data.first) +
  1362. ":" + format_internal(data.last)
  1363. when Array
  1364. return data.collect {|i| format_internal(i)}.join(",")
  1365. when ThreadMember
  1366. return data.seqno.to_s +
  1367. ":" + data.children.collect {|i| format_internal(i).join(",")}
  1368. end
  1369. end
  1370. def validate_internal(data)
  1371. case data
  1372. when "*"
  1373. when Integer
  1374. ensure_nz_number(data)
  1375. when Range
  1376. when Array
  1377. data.each do |i|
  1378. validate_internal(i)
  1379. end
  1380. when ThreadMember
  1381. data.children.each do |i|
  1382. validate_internal(i)
  1383. end
  1384. else
  1385. raise DataFormatError, data.inspect
  1386. end
  1387. end
  1388. def ensure_nz_number(num)
  1389. if num < -1 || num == 0 || num >= 4294967296
  1390. msg = "nz_number must be non-zero unsigned 32-bit integer: " +
  1391. num.inspect
  1392. raise DataFormatError, msg
  1393. end
  1394. end
  1395. end
  1396. # Net::IMAP::ContinuationRequest represents command continuation requests.
  1397. #
  1398. # The command continuation request response is indicated by a "+" token
  1399. # instead of a tag. This form of response indicates that the server is
  1400. # ready to accept the continuation of a command from the client. The
  1401. # remainder of this response is a line of text.
  1402. #
  1403. # continue_req ::= "+" SPACE (resp_text / base64)
  1404. #
  1405. # ==== Fields:
  1406. #
  1407. # data:: Returns the data (Net::IMAP::ResponseText).
  1408. #
  1409. # raw_data:: Returns the raw data string.
  1410. ContinuationRequest = Struct.new(:data, :raw_data)
  1411. # Net::IMAP::UntaggedResponse represents untagged responses.
  1412. #
  1413. # Data transmitted by the server to the client and status responses
  1414. # that do not indicate command completion are prefixed with the token
  1415. # "*", and are called untagged responses.
  1416. #
  1417. # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
  1418. # mailbox_data / message_data / capability_data)
  1419. #
  1420. # ==== Fields:
  1421. #
  1422. # name:: Returns the name such as "FLAGS", "LIST", "FETCH"....
  1423. #
  1424. # data:: Returns the data such as an array of flag symbols,
  1425. # a ((<Net::IMAP::MailboxList>)) object....
  1426. #
  1427. # raw_data:: Returns the raw data string.
  1428. UntaggedResponse = Struct.new(:name, :data, :raw_data)
  1429. # Net::IMAP::TaggedResponse represents tagged responses.
  1430. #
  1431. # The server completion result response indicates the success or
  1432. # failure of the operation. It is tagged with the same tag as the
  1433. # client command which…

Large files files are truncated, but you can click here to view the full file