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

/Util/IronRuby/lib/ruby/1.8/net/imap.rb

http://github.com/IronLanguages/main
Ruby | 3373 lines | 2194 code | 221 blank | 958 comment | 159 complexity | e27a76d6c5c59c345819d2d814da4736 MD5 | raw file
Possible License(s): CPL-1.0, BSD-3-Clause, ISC, GPL-2.0, MPL-2.0-no-copyleft-exception

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

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