PageRenderTime 62ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/jruby-1.1.6RC1/lib/ruby/1.8/net/imap.rb

https://bitbucket.org/nicksieger/advent-jruby
Ruby | 3370 lines | 2193 code | 221 blank | 956 comment | 159 complexity | e862d5070edf0c72645934341e1fde79 MD5 | raw file
Possible License(s): CPL-1.0, AGPL-1.0, LGPL-2.1, JSON
  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. if SSL::SSLSocket === @sock
  266. @sock.io.shutdown
  267. else
  268. @sock.shutdown
  269. end
  270. @receiver_thread.join
  271. @sock.close
  272. end
  273. # Returns true if disconnected from the server.
  274. def disconnected?
  275. return @sock.closed?
  276. end
  277. # Sends a CAPABILITY command, and returns an array of
  278. # capabilities that the server supports. Each capability
  279. # is a string. See [IMAP] for a list of possible
  280. # capabilities.
  281. #
  282. # Note that the Net::IMAP class does not modify its
  283. # behaviour according to the capabilities of the server;
  284. # it is up to the user of the class to ensure that
  285. # a certain capability is supported by a server before
  286. # using it.
  287. def capability
  288. synchronize do
  289. send_command("CAPABILITY")
  290. return @responses.delete("CAPABILITY")[-1]
  291. end
  292. end
  293. # Sends a NOOP command to the server. It does nothing.
  294. def noop
  295. send_command("NOOP")
  296. end
  297. # Sends a LOGOUT command to inform the server that the client is
  298. # done with the connection.
  299. def logout
  300. send_command("LOGOUT")
  301. end
  302. # Sends an AUTHENTICATE command to authenticate the client.
  303. # The +auth_type+ parameter is a string that represents
  304. # the authentication mechanism to be used. Currently Net::IMAP
  305. # supports authentication mechanisms:
  306. #
  307. # LOGIN:: login using cleartext user and password.
  308. # CRAM-MD5:: login with cleartext user and encrypted password
  309. # (see [RFC-2195] for a full description). This
  310. # mechanism requires that the server have the user's
  311. # password stored in clear-text password.
  312. #
  313. # For both these mechanisms, there should be two +args+: username
  314. # and (cleartext) password. A server may not support one or other
  315. # of these mechanisms; check #capability() for a capability of
  316. # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5".
  317. #
  318. # Authentication is done using the appropriate authenticator object:
  319. # see @@authenticators for more information on plugging in your own
  320. # authenticator.
  321. #
  322. # For example:
  323. #
  324. # imap.authenticate('LOGIN', user, password)
  325. #
  326. # A Net::IMAP::NoResponseError is raised if authentication fails.
  327. def authenticate(auth_type, *args)
  328. auth_type = auth_type.upcase
  329. unless @@authenticators.has_key?(auth_type)
  330. raise ArgumentError,
  331. format('unknown auth type - "%s"', auth_type)
  332. end
  333. authenticator = @@authenticators[auth_type].new(*args)
  334. send_command("AUTHENTICATE", auth_type) do |resp|
  335. if resp.instance_of?(ContinuationRequest)
  336. data = authenticator.process(resp.data.text.unpack("m")[0])
  337. s = [data].pack("m").gsub(/\n/, "")
  338. send_string_data(s)
  339. put_string(CRLF)
  340. end
  341. end
  342. end
  343. # Sends a LOGIN command to identify the client and carries
  344. # the plaintext +password+ authenticating this +user+. Note
  345. # that, unlike calling #authenticate() with an +auth_type+
  346. # of "LOGIN", #login() does *not* use the login authenticator.
  347. #
  348. # A Net::IMAP::NoResponseError is raised if authentication fails.
  349. def login(user, password)
  350. send_command("LOGIN", user, password)
  351. end
  352. # Sends a SELECT command to select a +mailbox+ so that messages
  353. # in the +mailbox+ can be accessed.
  354. #
  355. # After you have selected a mailbox, you may retrieve the
  356. # number of items in that mailbox from @responses["EXISTS"][-1],
  357. # and the number of recent messages from @responses["RECENT"][-1].
  358. # Note that these values can change if new messages arrive
  359. # during a session; see #add_response_handler() for a way of
  360. # detecting this event.
  361. #
  362. # A Net::IMAP::NoResponseError is raised if the mailbox does not
  363. # exist or is for some reason non-selectable.
  364. def select(mailbox)
  365. synchronize do
  366. @responses.clear
  367. send_command("SELECT", mailbox)
  368. end
  369. end
  370. # Sends a EXAMINE command to select a +mailbox+ so that messages
  371. # in the +mailbox+ can be accessed. Behaves the same as #select(),
  372. # except that the selected +mailbox+ is identified as read-only.
  373. #
  374. # A Net::IMAP::NoResponseError is raised if the mailbox does not
  375. # exist or is for some reason non-examinable.
  376. def examine(mailbox)
  377. synchronize do
  378. @responses.clear
  379. send_command("EXAMINE", mailbox)
  380. end
  381. end
  382. # Sends a CREATE command to create a new +mailbox+.
  383. #
  384. # A Net::IMAP::NoResponseError is raised if a mailbox with that name
  385. # cannot be created.
  386. def create(mailbox)
  387. send_command("CREATE", mailbox)
  388. end
  389. # Sends a DELETE command to remove the +mailbox+.
  390. #
  391. # A Net::IMAP::NoResponseError is raised if a mailbox with that name
  392. # cannot be deleted, either because it does not exist or because the
  393. # client does not have permission to delete it.
  394. def delete(mailbox)
  395. send_command("DELETE", mailbox)
  396. end
  397. # Sends a RENAME command to change the name of the +mailbox+ to
  398. # +newname+.
  399. #
  400. # A Net::IMAP::NoResponseError is raised if a mailbox with the
  401. # name +mailbox+ cannot be renamed to +newname+ for whatever
  402. # reason; for instance, because +mailbox+ does not exist, or
  403. # because there is already a mailbox with the name +newname+.
  404. def rename(mailbox, newname)
  405. send_command("RENAME", mailbox, newname)
  406. end
  407. # Sends a SUBSCRIBE command to add the specified +mailbox+ name to
  408. # the server's set of "active" or "subscribed" mailboxes as returned
  409. # by #lsub().
  410. #
  411. # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
  412. # subscribed to, for instance because it does not exist.
  413. def subscribe(mailbox)
  414. send_command("SUBSCRIBE", mailbox)
  415. end
  416. # Sends a UNSUBSCRIBE command to remove the specified +mailbox+ name
  417. # from the server's set of "active" or "subscribed" mailboxes.
  418. #
  419. # A Net::IMAP::NoResponseError is raised if +mailbox+ cannot be
  420. # unsubscribed from, for instance because the client is not currently
  421. # subscribed to it.
  422. def unsubscribe(mailbox)
  423. send_command("UNSUBSCRIBE", mailbox)
  424. end
  425. # Sends a LIST command, and returns a subset of names from
  426. # the complete set of all names available to the client.
  427. # +refname+ provides a context (for instance, a base directory
  428. # in a directory-based mailbox hierarchy). +mailbox+ specifies
  429. # a mailbox or (via wildcards) mailboxes under that context.
  430. # Two wildcards may be used in +mailbox+: '*', which matches
  431. # all characters *including* the hierarchy delimiter (for instance,
  432. # '/' on a UNIX-hosted directory-based mailbox hierarchy); and '%',
  433. # which matches all characters *except* the hierarchy delimiter.
  434. #
  435. # If +refname+ is empty, +mailbox+ is used directly to determine
  436. # which mailboxes to match. If +mailbox+ is empty, the root
  437. # name of +refname+ and the hierarchy delimiter are returned.
  438. #
  439. # The return value is an array of +Net::IMAP::MailboxList+. For example:
  440. #
  441. # imap.create("foo/bar")
  442. # imap.create("foo/baz")
  443. # p imap.list("", "foo/%")
  444. # #=> [#<Net::IMAP::MailboxList attr=[:Noselect], delim="/", name="foo/">, \\
  445. # #<Net::IMAP::MailboxList attr=[:Noinferiors, :Marked], delim="/", name="foo/bar">, \\
  446. # #<Net::IMAP::MailboxList attr=[:Noinferiors], delim="/", name="foo/baz">]
  447. def list(refname, mailbox)
  448. synchronize do
  449. send_command("LIST", refname, mailbox)
  450. return @responses.delete("LIST")
  451. end
  452. end
  453. # Sends the GETQUOTAROOT command along with specified +mailbox+.
  454. # This command is generally available to both admin and user.
  455. # If mailbox exists, returns an array containing objects of
  456. # Net::IMAP::MailboxQuotaRoot and Net::IMAP::MailboxQuota.
  457. def getquotaroot(mailbox)
  458. synchronize do
  459. send_command("GETQUOTAROOT", mailbox)
  460. result = []
  461. result.concat(@responses.delete("QUOTAROOT"))
  462. result.concat(@responses.delete("QUOTA"))
  463. return result
  464. end
  465. end
  466. # Sends the GETQUOTA command along with specified +mailbox+.
  467. # If this mailbox exists, then an array containing a
  468. # Net::IMAP::MailboxQuota object is returned. This
  469. # command generally is only available to server admin.
  470. def getquota(mailbox)
  471. synchronize do
  472. send_command("GETQUOTA", mailbox)
  473. return @responses.delete("QUOTA")
  474. end
  475. end
  476. # Sends a SETQUOTA command along with the specified +mailbox+ and
  477. # +quota+. If +quota+ is nil, then quota will be unset for that
  478. # mailbox. Typically one needs to be logged in as server admin
  479. # for this to work. The IMAP quota commands are described in
  480. # [RFC-2087].
  481. def setquota(mailbox, quota)
  482. if quota.nil?
  483. data = '()'
  484. else
  485. data = '(STORAGE ' + quota.to_s + ')'
  486. end
  487. send_command("SETQUOTA", mailbox, RawData.new(data))
  488. end
  489. # Sends the SETACL command along with +mailbox+, +user+ and the
  490. # +rights+ that user is to have on that mailbox. If +rights+ is nil,
  491. # then that user will be stripped of any rights to that mailbox.
  492. # The IMAP ACL commands are described in [RFC-2086].
  493. def setacl(mailbox, user, rights)
  494. if rights.nil?
  495. send_command("SETACL", mailbox, user, "")
  496. else
  497. send_command("SETACL", mailbox, user, rights)
  498. end
  499. end
  500. # Send the GETACL command along with specified +mailbox+.
  501. # If this mailbox exists, an array containing objects of
  502. # Net::IMAP::MailboxACLItem will be returned.
  503. def getacl(mailbox)
  504. synchronize do
  505. send_command("GETACL", mailbox)
  506. return @responses.delete("ACL")[-1]
  507. end
  508. end
  509. # Sends a LSUB command, and returns a subset of names from the set
  510. # of names that the user has declared as being "active" or
  511. # "subscribed". +refname+ and +mailbox+ are interpreted as
  512. # for #list().
  513. # The return value is an array of +Net::IMAP::MailboxList+.
  514. def lsub(refname, mailbox)
  515. synchronize do
  516. send_command("LSUB", refname, mailbox)
  517. return @responses.delete("LSUB")
  518. end
  519. end
  520. # Sends a STATUS command, and returns the status of the indicated
  521. # +mailbox+. +attr+ is a list of one or more attributes that
  522. # we are request the status of. Supported attributes include:
  523. #
  524. # MESSAGES:: the number of messages in the mailbox.
  525. # RECENT:: the number of recent messages in the mailbox.
  526. # UNSEEN:: the number of unseen messages in the mailbox.
  527. #
  528. # The return value is a hash of attributes. For example:
  529. #
  530. # p imap.status("inbox", ["MESSAGES", "RECENT"])
  531. # #=> {"RECENT"=>0, "MESSAGES"=>44}
  532. #
  533. # A Net::IMAP::NoResponseError is raised if status values
  534. # for +mailbox+ cannot be returned, for instance because it
  535. # does not exist.
  536. def status(mailbox, attr)
  537. synchronize do
  538. send_command("STATUS", mailbox, attr)
  539. return @responses.delete("STATUS")[-1].attr
  540. end
  541. end
  542. # Sends a APPEND command to append the +message+ to the end of
  543. # the +mailbox+. The optional +flags+ argument is an array of
  544. # flags to initially passing to the new message. The optional
  545. # +date_time+ argument specifies the creation time to assign to the
  546. # new message; it defaults to the current time.
  547. # For example:
  548. #
  549. # imap.append("inbox", <<EOF.gsub(/\n/, "\r\n"), [:Seen], Time.now)
  550. # Subject: hello
  551. # From: shugo@ruby-lang.org
  552. # To: shugo@ruby-lang.org
  553. #
  554. # hello world
  555. # EOF
  556. #
  557. # A Net::IMAP::NoResponseError is raised if the mailbox does
  558. # not exist (it is not created automatically), or if the flags,
  559. # date_time, or message arguments contain errors.
  560. def append(mailbox, message, flags = nil, date_time = nil)
  561. args = []
  562. if flags
  563. args.push(flags)
  564. end
  565. args.push(date_time) if date_time
  566. args.push(Literal.new(message))
  567. send_command("APPEND", mailbox, *args)
  568. end
  569. # Sends a CHECK command to request a checkpoint of the currently
  570. # selected mailbox. This performs implementation-specific
  571. # housekeeping, for instance, reconciling the mailbox's
  572. # in-memory and on-disk state.
  573. def check
  574. send_command("CHECK")
  575. end
  576. # Sends a CLOSE command to close the currently selected mailbox.
  577. # The CLOSE command permanently removes from the mailbox all
  578. # messages that have the \Deleted flag set.
  579. def close
  580. send_command("CLOSE")
  581. end
  582. # Sends a EXPUNGE command to permanently remove from the currently
  583. # selected mailbox all messages that have the \Deleted flag set.
  584. def expunge
  585. synchronize do
  586. send_command("EXPUNGE")
  587. return @responses.delete("EXPUNGE")
  588. end
  589. end
  590. # Sends a SEARCH command to search the mailbox for messages that
  591. # match the given searching criteria, and returns message sequence
  592. # numbers. +keys+ can either be a string holding the entire
  593. # search string, or a single-dimension array of search keywords and
  594. # arguments. The following are some common search criteria;
  595. # see [IMAP] section 6.4.4 for a full list.
  596. #
  597. # <message set>:: a set of message sequence numbers. ',' indicates
  598. # an interval, ':' indicates a range. For instance,
  599. # '2,10:12,15' means "2,10,11,12,15".
  600. #
  601. # BEFORE <date>:: messages with an internal date strictly before
  602. # <date>. The date argument has a format similar
  603. # to 8-Aug-2002.
  604. #
  605. # BODY <string>:: messages that contain <string> within their body.
  606. #
  607. # CC <string>:: messages containing <string> in their CC field.
  608. #
  609. # FROM <string>:: messages that contain <string> in their FROM field.
  610. #
  611. # NEW:: messages with the \Recent, but not the \Seen, flag set.
  612. #
  613. # NOT <search-key>:: negate the following search key.
  614. #
  615. # OR <search-key> <search-key>:: "or" two search keys together.
  616. #
  617. # ON <date>:: messages with an internal date exactly equal to <date>,
  618. # which has a format similar to 8-Aug-2002.
  619. #
  620. # SINCE <date>:: messages with an internal date on or after <date>.
  621. #
  622. # SUBJECT <string>:: messages with <string> in their subject.
  623. #
  624. # TO <string>:: messages with <string> in their TO field.
  625. #
  626. # For example:
  627. #
  628. # p imap.search(["SUBJECT", "hello", "NOT", "NEW"])
  629. # #=> [1, 6, 7, 8]
  630. def search(keys, charset = nil)
  631. return search_internal("SEARCH", keys, charset)
  632. end
  633. # As for #search(), but returns unique identifiers.
  634. def uid_search(keys, charset = nil)
  635. return search_internal("UID SEARCH", keys, charset)
  636. end
  637. # Sends a FETCH command to retrieve data associated with a message
  638. # in the mailbox. The +set+ parameter is a number or an array of
  639. # numbers or a Range object. The number is a message sequence
  640. # number. +attr+ is a list of attributes to fetch; see the
  641. # documentation for Net::IMAP::FetchData for a list of valid
  642. # attributes.
  643. # The return value is an array of Net::IMAP::FetchData. For example:
  644. #
  645. # p imap.fetch(6..8, "UID")
  646. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"UID"=>98}>, \\
  647. # #<Net::IMAP::FetchData seqno=7, attr={"UID"=>99}>, \\
  648. # #<Net::IMAP::FetchData seqno=8, attr={"UID"=>100}>]
  649. # p imap.fetch(6, "BODY[HEADER.FIELDS (SUBJECT)]")
  650. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"BODY[HEADER.FIELDS (SUBJECT)]"=>"Subject: test\r\n\r\n"}>]
  651. # data = imap.uid_fetch(98, ["RFC822.SIZE", "INTERNALDATE"])[0]
  652. # p data.seqno
  653. # #=> 6
  654. # p data.attr["RFC822.SIZE"]
  655. # #=> 611
  656. # p data.attr["INTERNALDATE"]
  657. # #=> "12-Oct-2000 22:40:59 +0900"
  658. # p data.attr["UID"]
  659. # #=> 98
  660. def fetch(set, attr)
  661. return fetch_internal("FETCH", set, attr)
  662. end
  663. # As for #fetch(), but +set+ contains unique identifiers.
  664. def uid_fetch(set, attr)
  665. return fetch_internal("UID FETCH", set, attr)
  666. end
  667. # Sends a STORE command to alter data associated with messages
  668. # in the mailbox, in particular their flags. The +set+ parameter
  669. # is a number or an array of numbers or a Range object. Each number
  670. # is a message sequence number. +attr+ is the name of a data item
  671. # to store: 'FLAGS' means to replace the message's flag list
  672. # with the provided one; '+FLAGS' means to add the provided flags;
  673. # and '-FLAGS' means to remove them. +flags+ is a list of flags.
  674. #
  675. # The return value is an array of Net::IMAP::FetchData. For example:
  676. #
  677. # p imap.store(6..8, "+FLAGS", [:Deleted])
  678. # #=> [#<Net::IMAP::FetchData seqno=6, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
  679. # #<Net::IMAP::FetchData seqno=7, attr={"FLAGS"=>[:Seen, :Deleted]}>, \\
  680. # #<Net::IMAP::FetchData seqno=8, attr={"FLAGS"=>[:Seen, :Deleted]}>]
  681. def store(set, attr, flags)
  682. return store_internal("STORE", set, attr, flags)
  683. end
  684. # As for #store(), but +set+ contains unique identifiers.
  685. def uid_store(set, attr, flags)
  686. return store_internal("UID STORE", set, attr, flags)
  687. end
  688. # Sends a COPY command to copy the specified message(s) to the end
  689. # of the specified destination +mailbox+. The +set+ parameter is
  690. # a number or an array of numbers or a Range object. The number is
  691. # a message sequence number.
  692. def copy(set, mailbox)
  693. copy_internal("COPY", set, mailbox)
  694. end
  695. # As for #copy(), but +set+ contains unique identifiers.
  696. def uid_copy(set, mailbox)
  697. copy_internal("UID COPY", set, mailbox)
  698. end
  699. # Sends a SORT command to sort messages in the mailbox.
  700. # Returns an array of message sequence numbers. For example:
  701. #
  702. # p imap.sort(["FROM"], ["ALL"], "US-ASCII")
  703. # #=> [1, 2, 3, 5, 6, 7, 8, 4, 9]
  704. # p imap.sort(["DATE"], ["SUBJECT", "hello"], "US-ASCII")
  705. # #=> [6, 7, 8, 1]
  706. #
  707. # See [SORT-THREAD-EXT] for more details.
  708. def sort(sort_keys, search_keys, charset)
  709. return sort_internal("SORT", sort_keys, search_keys, charset)
  710. end
  711. # As for #sort(), but returns an array of unique identifiers.
  712. def uid_sort(sort_keys, search_keys, charset)
  713. return sort_internal("UID SORT", sort_keys, search_keys, charset)
  714. end
  715. # Adds a response handler. For example, to detect when
  716. # the server sends us a new EXISTS response (which normally
  717. # indicates new messages being added to the mail box),
  718. # you could add the following handler after selecting the
  719. # mailbox.
  720. #
  721. # imap.add_response_handler { |resp|
  722. # if resp.kind_of?(Net::IMAP::UntaggedResponse) and resp.name == "EXISTS"
  723. # puts "Mailbox now has #{resp.data} messages"
  724. # end
  725. # }
  726. #
  727. def add_response_handler(handler = Proc.new)
  728. @response_handlers.push(handler)
  729. end
  730. # Removes the response handler.
  731. def remove_response_handler(handler)
  732. @response_handlers.delete(handler)
  733. end
  734. # As for #search(), but returns message sequence numbers in threaded
  735. # format, as a Net::IMAP::ThreadMember tree. The supported algorithms
  736. # are:
  737. #
  738. # ORDEREDSUBJECT:: split into single-level threads according to subject,
  739. # ordered by date.
  740. # REFERENCES:: split into threads by parent/child relationships determined
  741. # by which message is a reply to which.
  742. #
  743. # Unlike #search(), +charset+ is a required argument. US-ASCII
  744. # and UTF-8 are sample values.
  745. #
  746. # See [SORT-THREAD-EXT] for more details.
  747. def thread(algorithm, search_keys, charset)
  748. return thread_internal("THREAD", algorithm, search_keys, charset)
  749. end
  750. # As for #thread(), but returns unique identifiers instead of
  751. # message sequence numbers.
  752. def uid_thread(algorithm, search_keys, charset)
  753. return thread_internal("UID THREAD", algorithm, search_keys, charset)
  754. end
  755. # Decode a string from modified UTF-7 format to UTF-8.
  756. #
  757. # UTF-7 is a 7-bit encoding of Unicode [UTF7]. IMAP uses a
  758. # slightly modified version of this to encode mailbox names
  759. # containing non-ASCII characters; see [IMAP] section 5.1.3.
  760. #
  761. # Net::IMAP does _not_ automatically encode and decode
  762. # mailbox names to and from utf7.
  763. def self.decode_utf7(s)
  764. return s.gsub(/&(.*?)-/n) {
  765. if $1.empty?
  766. "&"
  767. else
  768. base64 = $1.tr(",", "/")
  769. x = base64.length % 4
  770. if x > 0
  771. base64.concat("=" * (4 - x))
  772. end
  773. u16tou8(base64.unpack("m")[0])
  774. end
  775. }
  776. end
  777. # Encode a string from UTF-8 format to modified UTF-7.
  778. def self.encode_utf7(s)
  779. return s.gsub(/(&)|([^\x20-\x25\x27-\x7e]+)/n) { |x|
  780. if $1
  781. "&-"
  782. else
  783. base64 = [u8tou16(x)].pack("m")
  784. "&" + base64.delete("=\n").tr("/", ",") + "-"
  785. end
  786. }
  787. end
  788. private
  789. CRLF = "\r\n" # :nodoc:
  790. PORT = 143 # :nodoc:
  791. @@debug = false
  792. @@authenticators = {}
  793. # Creates a new Net::IMAP object and connects it to the specified
  794. # +port+ (143 by default) on the named +host+. If +usessl+ is true,
  795. # then an attempt will
  796. # be made to use SSL (now TLS) to connect to the server. For this
  797. # to work OpenSSL [OSSL] and the Ruby OpenSSL [RSSL]
  798. # extensions need to be installed. The +certs+ parameter indicates
  799. # the path or file containing the CA cert of the server, and the
  800. # +verify+ parameter is for the OpenSSL verification callback.
  801. #
  802. # The most common errors are:
  803. #
  804. # Errno::ECONNREFUSED:: connection refused by +host+ or an intervening
  805. # firewall.
  806. # Errno::ETIMEDOUT:: connection timed out (possibly due to packets
  807. # being dropped by an intervening firewall).
  808. # Errno::ENETUNREACH:: there is no route to that network.
  809. # SocketError:: hostname not known or other socket error.
  810. # Net::IMAP::ByeResponseError:: we connected to the host, but they
  811. # immediately said goodbye to us.
  812. def initialize(host, port = PORT, usessl = false, certs = nil, verify = false)
  813. super()
  814. @host = host
  815. @port = port
  816. @tag_prefix = "RUBY"
  817. @tagno = 0
  818. @parser = ResponseParser.new
  819. @sock = TCPSocket.open(host, port)
  820. if usessl
  821. unless defined?(OpenSSL)
  822. raise "SSL extension not installed"
  823. end
  824. @usessl = true
  825. # verify the server.
  826. context = SSLContext::new()
  827. context.ca_file = certs if certs && FileTest::file?(certs)
  828. context.ca_path = certs if certs && FileTest::directory?(certs)
  829. context.verify_mode = VERIFY_PEER if verify
  830. if defined?(VerifyCallbackProc)
  831. context.verify_callback = VerifyCallbackProc
  832. end
  833. @sock = SSLSocket.new(@sock, context)
  834. @sock.connect # start ssl session.
  835. else
  836. @usessl = false
  837. end
  838. @responses = Hash.new([].freeze)
  839. @tagged_responses = {}
  840. @response_handlers = []
  841. @response_arrival = new_cond
  842. @continuation_request = nil
  843. @logout_command_tag = nil
  844. @debug_output_bol = true
  845. @greeting = get_response
  846. if @greeting.name == "BYE"
  847. @sock.close
  848. raise ByeResponseError, @greeting.raw_data
  849. end
  850. @client_thread = Thread.current
  851. @receiver_thread = Thread.start {
  852. receive_responses
  853. }
  854. end
  855. def receive_responses
  856. while true
  857. begin
  858. resp = get_response
  859. rescue Exception
  860. @sock.close
  861. @client_thread.raise($!)
  862. break
  863. end
  864. break unless resp
  865. begin
  866. synchronize do
  867. case resp
  868. when TaggedResponse
  869. @tagged_responses[resp.tag] = resp
  870. @response_arrival.broadcast
  871. if resp.tag == @logout_command_tag
  872. return
  873. end
  874. when UntaggedResponse
  875. record_response(resp.name, resp.data)
  876. if resp.data.instance_of?(ResponseText) &&
  877. (code = resp.data.code)
  878. record_response(code.name, code.data)
  879. end
  880. if resp.name == "BYE" && @logout_command_tag.nil?
  881. @sock.close
  882. raise ByeResponseError, resp.raw_data
  883. end
  884. when ContinuationRequest
  885. @continuation_request = resp
  886. @response_arrival.broadcast
  887. end
  888. @response_handlers.each do |handler|
  889. handler.call(resp)
  890. end
  891. end
  892. rescue Exception
  893. @client_thread.raise($!)
  894. end
  895. end
  896. end
  897. def get_tagged_response(tag)
  898. until @tagged_responses.key?(tag)
  899. @response_arrival.wait
  900. end
  901. return pick_up_tagged_response(tag)
  902. end
  903. def pick_up_tagged_response(tag)
  904. resp = @tagged_responses.delete(tag)
  905. case resp.name
  906. when /\A(?:NO)\z/ni
  907. raise NoResponseError, resp.data.text
  908. when /\A(?:BAD)\z/ni
  909. raise BadResponseError, resp.data.text
  910. else
  911. return resp
  912. end
  913. end
  914. def get_response
  915. buff = ""
  916. while true
  917. s = @sock.gets(CRLF)
  918. break unless s
  919. buff.concat(s)
  920. if /\{(\d+)\}\r\n/n =~ s
  921. s = @sock.read($1.to_i)
  922. buff.concat(s)
  923. else
  924. break
  925. end
  926. end
  927. return nil if buff.length == 0
  928. if @@debug
  929. $stderr.print(buff.gsub(/^/n, "S: "))
  930. end
  931. return @parser.parse(buff)
  932. end
  933. def record_response(name, data)
  934. unless @responses.has_key?(name)
  935. @responses[name] = []
  936. end
  937. @responses[name].push(data)
  938. end
  939. def send_command(cmd, *args, &block)
  940. synchronize do
  941. tag = Thread.current[:net_imap_tag] = generate_tag
  942. put_string(tag + " " + cmd)
  943. args.each do |i|
  944. put_string(" ")
  945. send_data(i)
  946. end
  947. put_string(CRLF)
  948. if cmd == "LOGOUT"
  949. @logout_command_tag = tag
  950. end
  951. if block
  952. add_response_handler(block)
  953. end
  954. begin
  955. return get_tagged_response(tag)
  956. ensure
  957. if block
  958. remove_response_handler(block)
  959. end
  960. end
  961. end
  962. end
  963. def generate_tag
  964. @tagno += 1
  965. return format("%s%04d", @tag_prefix, @tagno)
  966. end
  967. def put_string(str)
  968. @sock.print(str)
  969. if @@debug
  970. if @debug_output_bol
  971. $stderr.print("C: ")
  972. end
  973. $stderr.print(str.gsub(/\n(?!\z)/n, "\nC: "))
  974. if /\r\n\z/n.match(str)
  975. @debug_output_bol = true
  976. else
  977. @debug_output_bol = false
  978. end
  979. end
  980. end
  981. def send_data(data)
  982. case data
  983. when nil
  984. put_string("NIL")
  985. when String
  986. send_string_data(data)
  987. when Integer
  988. send_number_data(data)
  989. when Array
  990. send_list_data(data)
  991. when Time
  992. send_time_data(data)
  993. when Symbol
  994. send_symbol_data(data)
  995. else
  996. data.send_data(self)
  997. end
  998. end
  999. def send_string_data(str)
  1000. case str
  1001. when ""
  1002. put_string('""')
  1003. when /[\x80-\xff\r\n]/n
  1004. # literal
  1005. send_literal(str)
  1006. when /[(){ \x00-\x1f\x7f%*"\\]/n
  1007. # quoted string
  1008. send_quoted_string(str)
  1009. else
  1010. put_string(str)
  1011. end
  1012. end
  1013. def send_quoted_string(str)
  1014. put_string('"' + str.gsub(/["\\]/n, "\\\\\\&") + '"')
  1015. end
  1016. def send_literal(str)
  1017. put_string("{" + str.length.to_s + "}" + CRLF)
  1018. while @continuation_request.nil? &&
  1019. !@tagged_responses.key?(Thread.current[:net_imap_tag])
  1020. @response_arrival.wait
  1021. end
  1022. if @continuation_request.nil?
  1023. pick_up_tagged_response(Thread.current[:net_imap_tag])
  1024. raise ResponseError.new("expected continuation request")
  1025. end
  1026. @continuation_request = nil
  1027. put_string(str)
  1028. end
  1029. def send_number_data(num)
  1030. if num < 0 || num >= 4294967296
  1031. raise DataFormatError, num.to_s
  1032. end
  1033. put_string(num.to_s)
  1034. end
  1035. def send_list_data(list)
  1036. put_string("(")
  1037. first = true
  1038. list.each do |i|
  1039. if first
  1040. first = false
  1041. else
  1042. put_string(" ")
  1043. end
  1044. send_data(i)
  1045. end
  1046. put_string(")")
  1047. end
  1048. DATE_MONTH = %w(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
  1049. def send_time_data(time)
  1050. t = time.dup.gmtime
  1051. s = format('"%2d-%3s-%4d %02d:%02d:%02d +0000"',
  1052. t.day, DATE_MONTH[t.month - 1], t.year,
  1053. t.hour, t.min, t.sec)
  1054. put_string(s)
  1055. end
  1056. def send_symbol_data(symbol)
  1057. put_string("\\" + symbol.to_s)
  1058. end
  1059. def search_internal(cmd, keys, charset)
  1060. if keys.instance_of?(String)
  1061. keys = [RawData.new(keys)]
  1062. else
  1063. normalize_searching_criteria(keys)
  1064. end
  1065. synchronize do
  1066. if charset
  1067. send_command(cmd, "CHARSET", charset, *keys)
  1068. else
  1069. send_command(cmd, *keys)
  1070. end
  1071. return @responses.delete("SEARCH")[-1]
  1072. end
  1073. end
  1074. def fetch_internal(cmd, set, attr)
  1075. if attr.instance_of?(String)
  1076. attr = RawData.new(attr)
  1077. end
  1078. synchronize do
  1079. @responses.delete("FETCH")
  1080. send_command(cmd, MessageSet.new(set), attr)
  1081. return @responses.delete("FETCH")
  1082. end
  1083. end
  1084. def store_internal(cmd, set, attr, flags)
  1085. if attr.instance_of?(String)
  1086. attr = RawData.new(attr)
  1087. end
  1088. synchronize do
  1089. @responses.delete("FETCH")
  1090. send_command(cmd, MessageSet.new(set), attr, flags)
  1091. return @responses.delete("FETCH")
  1092. end
  1093. end
  1094. def copy_internal(cmd, set, mailbox)
  1095. send_command(cmd, MessageSet.new(set), mailbox)
  1096. end
  1097. def sort_internal(cmd, sort_keys, search_keys, charset)
  1098. if search_keys.instance_of?(String)
  1099. search_keys = [RawData.new(search_keys)]
  1100. else
  1101. normalize_searching_criteria(search_keys)
  1102. end
  1103. normalize_searching_criteria(search_keys)
  1104. synchronize do
  1105. send_command(cmd, sort_keys, charset, *search_keys)
  1106. return @responses.delete("SORT")[-1]
  1107. end
  1108. end
  1109. def thread_internal(cmd, algorithm, search_keys, charset)
  1110. if search_keys.instance_of?(String)
  1111. search_keys = [RawData.new(search_keys)]
  1112. else
  1113. normalize_searching_criteria(search_keys)
  1114. end
  1115. normalize_searching_criteria(search_keys)
  1116. send_command(cmd, algorithm, charset, *search_keys)
  1117. return @responses.delete("THREAD")[-1]
  1118. end
  1119. def normalize_searching_criteria(keys)
  1120. keys.collect! do |i|
  1121. case i
  1122. when -1, Range, Array
  1123. MessageSet.new(i)
  1124. else
  1125. i
  1126. end
  1127. end
  1128. end
  1129. def self.u16tou8(s)
  1130. len = s.length
  1131. if len < 2
  1132. return ""
  1133. end
  1134. buf = ""
  1135. i = 0
  1136. while i < len
  1137. c = s[i] << 8 | s[i + 1]
  1138. i += 2
  1139. if c == 0xfeff
  1140. next
  1141. elsif c < 0x0080
  1142. buf.concat(c)
  1143. elsif c < 0x0800
  1144. b2 = c & 0x003f
  1145. b1 = c >> 6
  1146. buf.concat(b1 | 0xc0)
  1147. buf.concat(b2 | 0x80)
  1148. elsif c >= 0xdc00 && c < 0xe000
  1149. raise DataFormatError, "invalid surrogate detected"
  1150. elsif c >= 0xd800 && c < 0xdc00
  1151. if i + 2 > len
  1152. raise DataFormatError, "invalid surrogate detected"
  1153. end
  1154. low = s[i] << 8 | s[i + 1]
  1155. i += 2
  1156. if low < 0xdc00 || low > 0xdfff
  1157. raise DataFormatError, "invalid surrogate detected"
  1158. end
  1159. c = (((c & 0x03ff)) << 10 | (low & 0x03ff)) + 0x10000
  1160. b4 = c & 0x003f
  1161. b3 = (c >> 6) & 0x003f
  1162. b2 = (c >> 12) & 0x003f
  1163. b1 = c >> 18;
  1164. buf.concat(b1 | 0xf0)
  1165. buf.concat(b2 | 0x80)
  1166. buf.concat(b3 | 0x80)
  1167. buf.concat(b4 | 0x80)
  1168. else # 0x0800-0xffff
  1169. b3 = c & 0x003f
  1170. b2 = (c >> 6) & 0x003f
  1171. b1 = c >> 12
  1172. buf.concat(b1 | 0xe0)
  1173. buf.concat(b2 | 0x80)
  1174. buf.concat(b3 | 0x80)
  1175. end
  1176. end
  1177. return buf
  1178. end
  1179. private_class_method :u16tou8
  1180. def self.u8tou16(s)
  1181. len = s.length
  1182. buf = ""
  1183. i = 0
  1184. while i < len
  1185. c = s[i]
  1186. if (c & 0x80) == 0
  1187. buf.concat(0x00)
  1188. buf.concat(c)
  1189. i += 1
  1190. elsif (c & 0xe0) == 0xc0 &&
  1191. len >= 2 &&
  1192. (s[i + 1] & 0xc0) == 0x80
  1193. if c == 0xc0 || c == 0xc1
  1194. raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
  1195. end
  1196. u = ((c & 0x1f) << 6) | (s[i + 1] & 0x3f)
  1197. buf.concat(u >> 8)
  1198. buf.concat(u & 0x00ff)
  1199. i += 2
  1200. elsif (c & 0xf0) == 0xe0 &&
  1201. i + 2 < len &&
  1202. (s[i + 1] & 0xc0) == 0x80 &&
  1203. (s[i + 2] & 0xc0) == 0x80
  1204. if c == 0xe0 && s[i + 1] < 0xa0
  1205. raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
  1206. end
  1207. u = ((c & 0x0f) << 12) | ((s[i + 1] & 0x3f) << 6) | (s[i + 2] & 0x3f)
  1208. # surrogate chars
  1209. if u >= 0xd800 && u <= 0xdfff
  1210. raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
  1211. end
  1212. buf.concat(u >> 8)
  1213. buf.concat(u & 0x00ff)
  1214. i += 3
  1215. elsif (c & 0xf8) == 0xf0 &&
  1216. i + 3 < len &&
  1217. (s[i + 1] & 0xc0) == 0x80 &&
  1218. (s[i + 2] & 0xc0) == 0x80 &&
  1219. (s[i + 3] & 0xc0) == 0x80
  1220. if c == 0xf0 && s[i + 1] < 0x90
  1221. raise DataFormatError, format("non-shortest UTF-8 sequence (%02x)", c)
  1222. end
  1223. u = ((c & 0x07) << 18) | ((s[i + 1] & 0x3f) << 12) |
  1224. ((s[i + 2] & 0x3f) << 6) | (s[i + 3] & 0x3f)
  1225. if u < 0x10000
  1226. buf.concat(u >> 8)
  1227. buf.concat(u & 0x00ff)
  1228. elsif u < 0x110000
  1229. high = ((u - 0x10000) >> 10) | 0xd800
  1230. low = (u & 0x03ff) | 0xdc00
  1231. buf.concat(high >> 8)
  1232. buf.concat(high & 0x00ff)
  1233. buf.concat(low >> 8)
  1234. buf.concat(low & 0x00ff)
  1235. else
  1236. raise DataFormatError, format("none-UTF-16 char detected (%04x)", u)
  1237. end
  1238. i += 4
  1239. else
  1240. raise DataFormatError, format("illegal UTF-8 sequence (%02x)", c)
  1241. end
  1242. end
  1243. return buf
  1244. end
  1245. private_class_method :u8tou16
  1246. class RawData # :nodoc:
  1247. def send_data(imap)
  1248. imap.send(:put_string, @data)
  1249. end
  1250. private
  1251. def initialize(data)
  1252. @data = data
  1253. end
  1254. end
  1255. class Atom # :nodoc:
  1256. def send_data(imap)
  1257. imap.send(:put_string, @data)
  1258. end
  1259. private
  1260. def initialize(data)
  1261. @data = data
  1262. end
  1263. end
  1264. class QuotedString # :nodoc:
  1265. def send_data(imap)
  1266. imap.send(:send_quoted_string, @data)
  1267. end
  1268. private
  1269. def initialize(data)
  1270. @data = data
  1271. end
  1272. end
  1273. class Literal # :nodoc:
  1274. def send_data(imap)
  1275. imap.send(:send_literal, @data)
  1276. end
  1277. private
  1278. def initialize(data)
  1279. @data = data
  1280. end
  1281. end
  1282. class MessageSet # :nodoc:
  1283. def send_data(imap)
  1284. imap.send(:put_string, format_internal(@data))
  1285. end
  1286. private
  1287. def initialize(data)
  1288. @data = data
  1289. end
  1290. def format_internal(data)
  1291. case data
  1292. when "*"
  1293. return data
  1294. when Integer
  1295. ensure_nz_number(data)
  1296. if data == -1
  1297. return "*"
  1298. else
  1299. return data.to_s
  1300. end
  1301. when Range
  1302. return format_internal(data.first) +
  1303. ":" + format_internal(data.last)
  1304. when Array
  1305. return data.collect {|i| format_internal(i)}.join(",")
  1306. when ThreadMember
  1307. return data.seqno.to_s +
  1308. ":" + data.children.collect {|i| format_internal(i).join(",")}
  1309. else
  1310. raise DataFormatError, data.inspect
  1311. end
  1312. end
  1313. def ensure_nz_number(num)
  1314. if num < -1 || num == 0 || num >= 4294967296
  1315. msg = "nz_number must be non-zero unsigned 32-bit integer: " +
  1316. num.inspect
  1317. raise DataFormatError, msg
  1318. end
  1319. end
  1320. end
  1321. # Net::IMAP::ContinuationRequest represents command continuation requests.
  1322. #
  1323. # The command continuation request response is indicated by a "+" token
  1324. # instead of a tag. This form of response indicates that the server is
  1325. # ready to accept the continuation of a command from the client. The
  1326. # remainder of this response is a line of text.
  1327. #
  1328. # continue_req ::= "+" SPACE (resp_text / base64)
  1329. #
  1330. # ==== Fields:
  1331. #
  1332. # data:: Returns the data (Net::IMAP::ResponseText).
  1333. #
  1334. # raw_data:: Returns the raw data string.
  1335. ContinuationRequest = Struct.new(:data, :raw_data)
  1336. # Net::IMAP::UntaggedResponse represents untagged responses.
  1337. #
  1338. # Data transmitted by the server to the client and status responses
  1339. # that do not indicate command completion are prefixed with the token
  1340. # "*", and are called untagged responses.
  1341. #
  1342. # response_data ::= "*" SPACE (resp_cond_state / resp_cond_bye /
  1343. # mailbox_data / message_data / capability_data)
  1344. #
  1345. # ==== Fields:
  1346. #
  1347. # name:: Returns the name such as "FLAGS", "LIST", "FETCH"....
  1348. #
  1349. # data:: Returns the data such as an array of flag symbols,
  1350. # a ((<Net::IMAP::MailboxList>)) object....
  1351. #
  1352. # raw_data:: Returns the raw data string.
  1353. UntaggedResponse = Struct.new(:name, :data, :raw_data)
  1354. # Net::IMAP::TaggedResponse represents tagged responses.
  1355. #
  1356. # The server completion result response indicates the success or
  1357. # failure of the operation. It is tagged with the same tag as the
  1358. # client command which began the operation.
  1359. #
  1360. # response_tagged ::= tag SPACE resp_cond_state CRLF
  1361. #
  1362. # tag ::= 1*<any ATOM_CHAR except "+">
  1363. #
  1364. # resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text
  1365. #
  1366. # ==== Fields:
  1367. #
  1368. # tag:: Returns the tag.
  1369. #
  1370. # name:: Returns the name. the name is one of "OK", "NO", "BAD".
  1371. #
  1372. # data:: Returns the data. See ((<Net::IMAP::ResponseText>)).
  1373. #
  1374. # raw_data:: Returns the raw data string.
  1375. #
  1376. TaggedResponse = Struct.new(:tag, :name, :data, :raw_data)
  1377. # Net::IMAP::ResponseText represents texts of responses.
  1378. # The text may be prefixed by the response code.
  1379. #
  1380. # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text)
  1381. # ;; text SHOULD NOT begin with "[" or "="
  1382. #
  1383. # ==== Fields:
  1384. #
  1385. # code:: Returns the response code. See ((<Net::IMAP::ResponseCode>)).
  1386. #
  1387. # text:: Returns the text.
  1388. #
  1389. ResponseText = Struct.new(:code, :text)
  1390. #
  1391. # Net::IMAP::ResponseCode represents response codes.
  1392. #
  1393. # resp_text_code ::= "ALERT" / "PARSE" /
  1394. # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" /
  1395. # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
  1396. # "UIDVALIDITY" SPACE nz_number /
  1397. # "UNSEEN" SPACE nz_number /
  1398. # atom [SPACE 1*<any TEXT_CHAR except "]">]
  1399. #
  1400. # ==== Fields:
  1401. #
  1402. # name:: Returns the name such as "ALERT", "PERMANENTFLAGS", "UIDVALIDITY"....
  1403. #
  1404. # data:: Returns the data if it exists.
  1405. #
  1406. ResponseCode = Struct.new(:name, :data)
  1407. # Net::IMAP::MailboxList represents contents of the LIST response.
  1408. #
  1409. # mailbox_list ::= "(" #("\Marked" / "\Noinferiors" /
  1410. # "\Noselect" / "\Unmarked" / flag_extension) ")"
  1411. # SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox
  1412. #
  1413. # ==== Fields:
  1414. #
  1415. # attr:: Returns the name attributes. Each name attribute is a symbol
  1416. # capitalized by String#capitalize, such as :Noselect (not :NoSelect).
  1417. #
  1418. # delim:: Returns the hierarchy delimiter
  1419. #
  1420. # name:: Returns the mailbox name.
  1421. #
  1422. MailboxList = Struct.new(:attr, :delim, :name)
  1423. # Net::IMAP::MailboxQuota represents contents of GETQUOTA response.
  1424. # This object can also be a response to GETQUOTAROOT. In the syntax
  1425. # specification below, the delimiter used with the "#" construct is a
  1426. # single space (SPACE).
  1427. #
  1428. # quota_list ::= "(" #quota_resource ")"
  1429. #
  1430. # quota_resource ::= atom SPACE number SPACE number
  1431. #
  1432. # quota_response ::= "QUOTA" SPACE astring SPACE quota_list
  1433. #
  1434. # ==== Fields:
  1435. #
  1436. # mailbox:: The mailbox with the associated quota.
  1437. #
  1438. # usage:: Current storage usage of mailbox.
  1439. #
  1440. # quota:: Quota limit imposed on mailbox.
  1441. #
  1442. MailboxQuota = Struct.new(:mailbox, :usage, :quota)
  1443. # Net::IMAP::MailboxQuotaRoot represents part of the GETQUOTAROOT
  1444. # response. (GETQUOTAROOT can also return Net::IMAP::MailboxQuota.)
  1445. #
  1446. # quotaroot_response ::= "QUOTAROOT" SPACE astring *(SPACE astring)
  1447. #
  1448. # ==== Fields:
  1449. #
  1450. # mailbox:: The mailbox with the associated quota.
  1451. #
  1452. # quotaroots:: Zero or more quotaroots that effect the quota on the
  1453. # specified mailbox.
  1454. #
  1455. MailboxQuotaRoot = Struct.new(:mailbox, :quotaroots)
  1456. # Net::IMAP::MailboxACLItem represents response from GETACL.
  1457. #
  1458. # acl_data ::= "ACL" SPACE mailbox *(SPACE identifier SPACE rights)
  1459. #
  1460. # identifier ::= astring
  1461. #
  1462. # rights ::= astring
  1463. #
  1464. # ==== Fields:
  1465. #
  1466. # user:: Login name that has certain rights to the mailbox
  1467. # that was specified with the getacl command.
  1468. #
  1469. # rights:: The access rights the indicated user has to the
  1470. # mailbox.
  1471. #
  1472. MailboxACLItem = Struct.new(:user, :rights)
  1473. # Net::IMAP::StatusData represents contents of the STATUS response.
  1474. #
  1475. # ==== Fields:
  1476. #
  1477. # mailbox:: Returns the mailbox name.
  1478. #
  1479. # attr:: Returns a hash. Each key is one of "MESSAGES", "RECENT", "UIDNEXT",
  1480. # "UIDVALIDITY", "UNSEEN". Each value is a number.
  1481. #
  1482. StatusData = Struct.new(:mailbox, :attr)
  1483. # Net::IMAP::FetchData represents contents of the FETCH response.
  1484. #
  1485. # ==== Fields:
  1486. #
  1487. # seqno:: Returns the message sequence number.
  1488. # (Note: not the unique identifier, even for the UID command response.)
  1489. #
  1490. # attr:: Returns a hash. Each key is a data item name, and each value is
  1491. # its value.
  1492. #
  1493. # The current data items are:
  1494. #
  1495. # [BODY]
  1496. # A form of BODYSTRUCTURE without extension data.
  1497. # [BODY[<section>]<<origin_octet>>]
  1498. # A string expressing the body contents of the specified section.
  1499. # [BODYSTRUCTURE]
  1500. # An object that describes the [MIME-IMB] body structure of a message.
  1501. # See Net::IMAP::BodyTypeBasic, Net::IMAP::BodyTypeText,
  1502. # Net::IMAP::BodyTypeMessage, Net::IMAP::BodyTypeMultipart.
  1503. # [ENVELOPE]
  1504. # A Net::IMAP::Envelope object that describes the envelope
  1505. # structure of a message.
  1506. # [FLAGS]
  1507. # A array of flag symbols that are set for this message. flag symbols
  1508. # are capitalized by String#capitalize.
  1509. # [INTERNALDATE]
  1510. # A string representing the internal date of the message.
  1511. # [RFC822]
  1512. # Equivalent to BODY[].
  1513. # [RFC822.HEADER]
  1514. # Equivalent to BODY.PEEK[HEADER].
  1515. # [RFC822.SIZE]
  1516. # A number expressing the [RFC-822] size of the message.
  1517. # [RFC822.TEXT]
  1518. # Equivalent to BODY[TEXT].
  1519. # [UID]
  1520. # A number expressing the unique identifier of the message.
  1521. #
  1522. FetchData = Struct.new(:seqno, :attr)
  1523. # Net::IMAP::Envelope represents envelope structures of messages.
  1524. #
  1525. # ==== Fields:
  1526. #
  1527. # date:: Returns a string that represents the date.
  1528. #
  1529. # subject:: Returns a string that represents the subject.
  1530. #
  1531. # from:: Returns an array of Net::IMAP::Address that represents the from.
  1532. #
  1533. # sender:: Returns an array of Net::IMAP::Address that represents the sender.
  1534. #
  1535. # reply_to:: Returns an array of Net::IMAP::Address that represents the reply-to.
  1536. #
  1537. # to:: Returns an array of Net::IMAP::Address that represents the to.
  1538. #
  1539. # cc:: Returns an array of Net::IMAP::Address that represents the cc.
  1540. #
  1541. # bcc:: Returns an array of Net::IMAP::Address that represents the bcc.
  1542. #
  1543. # in_reply_to:: Returns a string that represents the in-reply-to.
  1544. #
  1545. # message_id:: Returns a string that represents the message-id.
  1546. #
  1547. Envelope = Struct.new(:date, :subject, :from, :sender, :reply_to,
  1548. :to, :cc, :bcc, :in_reply_to, :message_id)
  1549. #
  1550. # Net::IMAP::Address represents electronic mail addresses.
  1551. #
  1552. # ==== Fields:
  1553. #
  1554. # name:: Returns the phrase from [RFC-822] mailbox.
  1555. #
  1556. # route:: Returns the route from [RFC-822] route-addr.
  1557. #
  1558. # mailbox:: nil indicates end of [RFC-822] group.
  1559. # If non-nil and host is nil, returns [RFC-822] group name.
  1560. # Otherwise, returns [RFC-822] local-part
  1561. #
  1562. # host:: nil indicates [RFC-822] group syntax.
  1563. # Otherwise, returns [RFC-822] domain name.
  1564. #
  1565. Address = Struct.new(:name, :route, :mailbox, :host)
  1566. #
  1567. # Net::IMAP::ContentDisposition represents Content-Disposition fields.
  1568. #
  1569. # ==== Fields:
  1570. #
  1571. # dsp_type:: Returns the disposition type.
  1572. #
  1573. # param:: Returns a hash that represents parameters of the Content-Disposition
  1574. # field.
  1575. #
  1576. ContentDisposition = Struct.new(:dsp_type, :param)
  1577. # Net::IMAP::ThreadMember represents a thread-node returned
  1578. # by Net::IMAP#thread
  1579. #
  1580. # ==== Fields:
  1581. #
  1582. # seqno:: The sequence number of this message.
  1583. #
  1584. # children:: an array of Net::IMAP::ThreadMember objects for mail
  1585. # items that are children of this in the thread.
  1586. #
  1587. ThreadMember = Struct.new(:seqno, :children)
  1588. # Net::IMAP::BodyTypeBasic represents basic body structures of messages.
  1589. #
  1590. # ==== Fields:
  1591. #
  1592. # media_type:: Returns the content media type name as defined in [MIME-IMB].
  1593. #
  1594. # subtype:: Returns the content subtype name as defined in [MIME-IMB].
  1595. #
  1596. # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
  1597. #
  1598. # content_id:: Returns a string giving the content id as defined in [MIME-IMB].
  1599. #
  1600. # description:: Returns a string giving the content description as defined in
  1601. # [MIME-IMB].
  1602. #
  1603. # encoding:: Returns a string giving the content transfer encoding as defined in
  1604. # [MIME-IMB].
  1605. #
  1606. # size:: Returns a number giving the size of the body in octets.
  1607. #
  1608. # md5:: Returns a string giving the body MD5 value as defined in [MD5].
  1609. #
  1610. # disposition:: Returns a Net::IMAP::ContentDisposition object giving
  1611. # the content disposition.
  1612. #
  1613. # language:: Returns a string or an array of strings giving the body
  1614. # language value as defined in [LANGUAGE-TAGS].
  1615. #
  1616. # extension:: Returns extension data.
  1617. #
  1618. # multipart?:: Returns false.
  1619. #
  1620. class BodyTypeBasic < Struct.new(:media_type, :subtype,
  1621. :param, :content_id,
  1622. :description, :encoding, :size,
  1623. :md5, :disposition, :language,
  1624. :extension)
  1625. def multipart?
  1626. return false
  1627. end
  1628. # Obsolete: use +subtype+ instead. Calling this will
  1629. # generate a warning message to +stderr+, then return
  1630. # the value of +subtype+.
  1631. def media_subtype
  1632. $stderr.printf("warning: media_subtype is obsolete.\n")
  1633. $stderr.printf(" use subtype instead.\n")
  1634. return subtype
  1635. end
  1636. end
  1637. # Net::IMAP::BodyTypeText represents TEXT body structures of messages.
  1638. #
  1639. # ==== Fields:
  1640. #
  1641. # lines:: Returns the size of the body in text lines.
  1642. #
  1643. # And Net::IMAP::BodyTypeText has all fields of Net::IMAP::BodyTypeBasic.
  1644. #
  1645. class BodyTypeText < Struct.new(:media_type, :subtype,
  1646. :param, :content_id,
  1647. :description, :encoding, :size,
  1648. :lines,
  1649. :md5, :disposition, :language,
  1650. :extension)
  1651. def multipart?
  1652. return false
  1653. end
  1654. # Obsolete: use +subtype+ instead. Calling this will
  1655. # generate a warning message to +stderr+, then return
  1656. # the value of +subtype+.
  1657. def media_subtype
  1658. $stderr.printf("warning: media_subtype is obsolete.\n")
  1659. $stderr.printf(" use subtype instead.\n")
  1660. return subtype
  1661. end
  1662. end
  1663. # Net::IMAP::BodyTypeMessage represents MESSAGE/RFC822 body structures of messages.
  1664. #
  1665. # ==== Fields:
  1666. #
  1667. # envelope:: Returns a Net::IMAP::Envelope giving the envelope structure.
  1668. #
  1669. # body:: Returns an object giving the body structure.
  1670. #
  1671. # And Net::IMAP::BodyTypeMessage has all methods of Net::IMAP::BodyTypeText.
  1672. #
  1673. class BodyTypeMessage < Struct.new(:media_type, :subtype,
  1674. :param, :content_id,
  1675. :description, :encoding, :size,
  1676. :envelope, :body, :lines,
  1677. :md5, :disposition, :language,
  1678. :extension)
  1679. def multipart?
  1680. return false
  1681. end
  1682. # Obsolete: use +subtype+ instead. Calling this will
  1683. # generate a warning message to +stderr+, then return
  1684. # the value of +subtype+.
  1685. def media_subtype
  1686. $stderr.printf("warning: media_subtype is obsolete.\n")
  1687. $stderr.printf(" use subtype instead.\n")
  1688. return subtype
  1689. end
  1690. end
  1691. # Net::IMAP::BodyTypeMultipart represents multipart body structures
  1692. # of messages.
  1693. #
  1694. # ==== Fields:
  1695. #
  1696. # media_type:: Returns the content media type name as defined in [MIME-IMB].
  1697. #
  1698. # subtype:: Returns the content subtype name as defined in [MIME-IMB].
  1699. #
  1700. # parts:: Returns multiple parts.
  1701. #
  1702. # param:: Returns a hash that represents parameters as defined in [MIME-IMB].
  1703. #
  1704. # disposition:: Returns a Net::IMAP::ContentDisposition object giving
  1705. # the content disposition.
  1706. #
  1707. # language:: Returns a string or an array of strings giving the body
  1708. # language value as defined in [LANGUAGE-TAGS].
  1709. #
  1710. # extension:: Returns extension data.
  1711. #
  1712. # multipart?:: Returns true.
  1713. #
  1714. class BodyTypeMultipart < Struct.new(:media_type, :subtype,
  1715. :parts,
  1716. :param, :disposition, :language,
  1717. :extension)
  1718. def multipart?
  1719. return true
  1720. end
  1721. # Obsolete: use +subtype+ instead. Calling this will
  1722. # generate a warning message to +stderr+, then return
  1723. # the value of +subtype+.
  1724. def media_subtype
  1725. $stderr.printf("warning: media_subtype is obsolete.\n")
  1726. $stderr.printf(" use subtype instead.\n")
  1727. return subtype
  1728. end
  1729. end
  1730. class ResponseParser # :nodoc:
  1731. def parse(str)
  1732. @str = str
  1733. @pos = 0
  1734. @lex_state = EXPR_BEG
  1735. @token = nil
  1736. return response
  1737. end
  1738. private
  1739. EXPR_BEG = :EXPR_BEG
  1740. EXPR_DATA = :EXPR_DATA
  1741. EXPR_TEXT = :EXPR_TEXT
  1742. EXPR_RTEXT = :EXPR_RTEXT
  1743. EXPR_CTEXT = :EXPR_CTEXT
  1744. T_SPACE = :SPACE
  1745. T_NIL = :NIL
  1746. T_NUMBER = :NUMBER
  1747. T_ATOM = :ATOM
  1748. T_QUOTED = :QUOTED
  1749. T_LPAR = :LPAR
  1750. T_RPAR = :RPAR
  1751. T_BSLASH = :BSLASH
  1752. T_STAR = :STAR
  1753. T_LBRA = :LBRA
  1754. T_RBRA = :RBRA
  1755. T_LITERAL = :LITERAL
  1756. T_PLUS = :PLUS
  1757. T_PERCENT = :PERCENT
  1758. T_CRLF = :CRLF
  1759. T_EOF = :EOF
  1760. T_TEXT = :TEXT
  1761. BEG_REGEXP = /\G(?:\
  1762. (?# 1: SPACE )( +)|\
  1763. (?# 2: NIL )(NIL)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
  1764. (?# 3: NUMBER )(\d+)(?=[\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+])|\
  1765. (?# 4: ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\\[\]+]+)|\
  1766. (?# 5: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
  1767. (?# 6: LPAR )(\()|\
  1768. (?# 7: RPAR )(\))|\
  1769. (?# 8: BSLASH )(\\)|\
  1770. (?# 9: STAR )(\*)|\
  1771. (?# 10: LBRA )(\[)|\
  1772. (?# 11: RBRA )(\])|\
  1773. (?# 12: LITERAL )\{(\d+)\}\r\n|\
  1774. (?# 13: PLUS )(\+)|\
  1775. (?# 14: PERCENT )(%)|\
  1776. (?# 15: CRLF )(\r\n)|\
  1777. (?# 16: EOF )(\z))/ni
  1778. DATA_REGEXP = /\G(?:\
  1779. (?# 1: SPACE )( )|\
  1780. (?# 2: NIL )(NIL)|\
  1781. (?# 3: NUMBER )(\d+)|\
  1782. (?# 4: QUOTED )"((?:[^\x00\r\n"\\]|\\["\\])*)"|\
  1783. (?# 5: LITERAL )\{(\d+)\}\r\n|\
  1784. (?# 6: LPAR )(\()|\
  1785. (?# 7: RPAR )(\)))/ni
  1786. TEXT_REGEXP = /\G(?:\
  1787. (?# 1: TEXT )([^\x00\r\n]*))/ni
  1788. RTEXT_REGEXP = /\G(?:\
  1789. (?# 1: LBRA )(\[)|\
  1790. (?# 2: TEXT )([^\x00\r\n]*))/ni
  1791. CTEXT_REGEXP = /\G(?:\
  1792. (?# 1: TEXT )([^\x00\r\n\]]*))/ni
  1793. Token = Struct.new(:symbol, :value)
  1794. def response
  1795. token = lookahead
  1796. case token.symbol
  1797. when T_PLUS
  1798. result = continue_req
  1799. when T_STAR
  1800. result = response_untagged
  1801. else
  1802. result = response_tagged
  1803. end
  1804. match(T_CRLF)
  1805. match(T_EOF)
  1806. return result
  1807. end
  1808. def continue_req
  1809. match(T_PLUS)
  1810. match(T_SPACE)
  1811. return ContinuationRequest.new(resp_text, @str)
  1812. end
  1813. def response_untagged
  1814. match(T_STAR)
  1815. match(T_SPACE)
  1816. token = lookahead
  1817. if token.symbol == T_NUMBER
  1818. return numeric_response
  1819. elsif token.symbol == T_ATOM
  1820. case token.value
  1821. when /\A(?:OK|NO|BAD|BYE|PREAUTH)\z/ni
  1822. return response_cond
  1823. when /\A(?:FLAGS)\z/ni
  1824. return flags_response
  1825. when /\A(?:LIST|LSUB)\z/ni
  1826. return list_response
  1827. when /\A(?:QUOTA)\z/ni
  1828. return getquota_response
  1829. when /\A(?:QUOTAROOT)\z/ni
  1830. return getquotaroot_response
  1831. when /\A(?:ACL)\z/ni
  1832. return getacl_response
  1833. when /\A(?:SEARCH|SORT)\z/ni
  1834. return search_response
  1835. when /\A(?:THREAD)\z/ni
  1836. return thread_response
  1837. when /\A(?:STATUS)\z/ni
  1838. return status_response
  1839. when /\A(?:CAPABILITY)\z/ni
  1840. return capability_response
  1841. else
  1842. return text_response
  1843. end
  1844. else
  1845. parse_error("unexpected token %s", token.symbol)
  1846. end
  1847. end
  1848. def response_tagged
  1849. tag = atom
  1850. match(T_SPACE)
  1851. token = match(T_ATOM)
  1852. name = token.value.upcase
  1853. match(T_SPACE)
  1854. return TaggedResponse.new(tag, name, resp_text, @str)
  1855. end
  1856. def response_cond
  1857. token = match(T_ATOM)
  1858. name = token.value.upcase
  1859. match(T_SPACE)
  1860. return UntaggedResponse.new(name, resp_text, @str)
  1861. end
  1862. def numeric_response
  1863. n = number
  1864. match(T_SPACE)
  1865. token = match(T_ATOM)
  1866. name = token.value.upcase
  1867. case name
  1868. when "EXISTS", "RECENT", "EXPUNGE"
  1869. return UntaggedResponse.new(name, n, @str)
  1870. when "FETCH"
  1871. shift_token
  1872. match(T_SPACE)
  1873. data = FetchData.new(n, msg_att)
  1874. return UntaggedResponse.new(name, data, @str)
  1875. end
  1876. end
  1877. def msg_att
  1878. match(T_LPAR)
  1879. attr = {}
  1880. while true
  1881. token = lookahead
  1882. case token.symbol
  1883. when T_RPAR
  1884. shift_token
  1885. break
  1886. when T_SPACE
  1887. shift_token
  1888. token = lookahead
  1889. end
  1890. case token.value
  1891. when /\A(?:ENVELOPE)\z/ni
  1892. name, val = envelope_data
  1893. when /\A(?:FLAGS)\z/ni
  1894. name, val = flags_data
  1895. when /\A(?:INTERNALDATE)\z/ni
  1896. name, val = internaldate_data
  1897. when /\A(?:RFC822(?:\.HEADER|\.TEXT)?)\z/ni
  1898. name, val = rfc822_text
  1899. when /\A(?:RFC822\.SIZE)\z/ni
  1900. name, val = rfc822_size
  1901. when /\A(?:BODY(?:STRUCTURE)?)\z/ni
  1902. name, val = body_data
  1903. when /\A(?:UID)\z/ni
  1904. name, val = uid_data
  1905. else
  1906. parse_error("unknown attribute `%s'", token.value)
  1907. end
  1908. attr[name] = val
  1909. end
  1910. return attr
  1911. end
  1912. def envelope_data
  1913. token = match(T_ATOM)
  1914. name = token.value.upcase
  1915. match(T_SPACE)
  1916. return name, envelope
  1917. end
  1918. def envelope
  1919. @lex_state = EXPR_DATA
  1920. token = lookahead
  1921. if token.symbol == T_NIL
  1922. shift_token
  1923. result = nil
  1924. else
  1925. match(T_LPAR)
  1926. date = nstring
  1927. match(T_SPACE)
  1928. subject = nstring
  1929. match(T_SPACE)
  1930. from = address_list
  1931. match(T_SPACE)
  1932. sender = address_list
  1933. match(T_SPACE)
  1934. reply_to = address_list
  1935. match(T_SPACE)
  1936. to = address_list
  1937. match(T_SPACE)
  1938. cc = address_list
  1939. match(T_SPACE)
  1940. bcc = address_list
  1941. match(T_SPACE)
  1942. in_reply_to = nstring
  1943. match(T_SPACE)
  1944. message_id = nstring
  1945. match(T_RPAR)
  1946. result = Envelope.new(date, subject, from, sender, reply_to,
  1947. to, cc, bcc, in_reply_to, message_id)
  1948. end
  1949. @lex_state = EXPR_BEG
  1950. return result
  1951. end
  1952. def flags_data
  1953. token = match(T_ATOM)
  1954. name = token.value.upcase
  1955. match(T_SPACE)
  1956. return name, flag_list
  1957. end
  1958. def internaldate_data
  1959. token = match(T_ATOM)
  1960. name = token.value.upcase
  1961. match(T_SPACE)
  1962. token = match(T_QUOTED)
  1963. return name, token.value
  1964. end
  1965. def rfc822_text
  1966. token = match(T_ATOM)
  1967. name = token.value.upcase
  1968. match(T_SPACE)
  1969. return name, nstring
  1970. end
  1971. def rfc822_size
  1972. token = match(T_ATOM)
  1973. name = token.value.upcase
  1974. match(T_SPACE)
  1975. return name, number
  1976. end
  1977. def body_data
  1978. token = match(T_ATOM)
  1979. name = token.value.upcase
  1980. token = lookahead
  1981. if token.symbol == T_SPACE
  1982. shift_token
  1983. return name, body
  1984. end
  1985. name.concat(section)
  1986. token = lookahead
  1987. if token.symbol == T_ATOM
  1988. name.concat(token.value)
  1989. shift_token
  1990. end
  1991. match(T_SPACE)
  1992. data = nstring
  1993. return name, data
  1994. end
  1995. def body
  1996. @lex_state = EXPR_DATA
  1997. token = lookahead
  1998. if token.symbol == T_NIL
  1999. shift_token
  2000. result = nil
  2001. else
  2002. match(T_LPAR)
  2003. token = lookahead
  2004. if token.symbol == T_LPAR
  2005. result = body_type_mpart
  2006. else
  2007. result = body_type_1part
  2008. end
  2009. match(T_RPAR)
  2010. end
  2011. @lex_state = EXPR_BEG
  2012. return result
  2013. end
  2014. def body_type_1part
  2015. token = lookahead
  2016. case token.value
  2017. when /\A(?:TEXT)\z/ni
  2018. return body_type_text
  2019. when /\A(?:MESSAGE)\z/ni
  2020. return body_type_msg
  2021. else
  2022. return body_type_basic
  2023. end
  2024. end
  2025. def body_type_basic
  2026. mtype, msubtype = media_type
  2027. token = lookahead
  2028. if token.symbol == T_RPAR
  2029. return BodyTypeBasic.new(mtype, msubtype)
  2030. end
  2031. match(T_SPACE)
  2032. param, content_id, desc, enc, size = body_fields
  2033. md5, disposition, language, extension = body_ext_1part
  2034. return BodyTypeBasic.new(mtype, msubtype,
  2035. param, content_id,
  2036. desc, enc, size,
  2037. md5, disposition, language, extension)
  2038. end
  2039. def body_type_text
  2040. mtype, msubtype = media_type
  2041. match(T_SPACE)
  2042. param, content_id, desc, enc, size = body_fields
  2043. match(T_SPACE)
  2044. lines = number
  2045. md5, disposition, language, extension = body_ext_1part
  2046. return BodyTypeText.new(mtype, msubtype,
  2047. param, content_id,
  2048. desc, enc, size,
  2049. lines,
  2050. md5, disposition, language, extension)
  2051. end
  2052. def body_type_msg
  2053. mtype, msubtype = media_type
  2054. match(T_SPACE)
  2055. param, content_id, desc, enc, size = body_fields
  2056. match(T_SPACE)
  2057. env = envelope
  2058. match(T_SPACE)
  2059. b = body
  2060. match(T_SPACE)
  2061. lines = number
  2062. md5, disposition, language, extension = body_ext_1part
  2063. return BodyTypeMessage.new(mtype, msubtype,
  2064. param, content_id,
  2065. desc, enc, size,
  2066. env, b, lines,
  2067. md5, disposition, language, extension)
  2068. end
  2069. def body_type_mpart
  2070. parts = []
  2071. while true
  2072. token = lookahead
  2073. if token.symbol == T_SPACE
  2074. shift_token
  2075. break
  2076. end
  2077. parts.push(body)
  2078. end
  2079. mtype = "MULTIPART"
  2080. msubtype = case_insensitive_string
  2081. param, disposition, language, extension = body_ext_mpart
  2082. return BodyTypeMultipart.new(mtype, msubtype, parts,
  2083. param, disposition, language,
  2084. extension)
  2085. end
  2086. def media_type
  2087. mtype = case_insensitive_string
  2088. match(T_SPACE)
  2089. msubtype = case_insensitive_string
  2090. return mtype, msubtype
  2091. end
  2092. def body_fields
  2093. param = body_fld_param
  2094. match(T_SPACE)
  2095. content_id = nstring
  2096. match(T_SPACE)
  2097. desc = nstring
  2098. match(T_SPACE)
  2099. enc = case_insensitive_string
  2100. match(T_SPACE)
  2101. size = number
  2102. return param, content_id, desc, enc, size
  2103. end
  2104. def body_fld_param
  2105. token = lookahead
  2106. if token.symbol == T_NIL
  2107. shift_token
  2108. return nil
  2109. end
  2110. match(T_LPAR)
  2111. param = {}
  2112. while true
  2113. token = lookahead
  2114. case token.symbol
  2115. when T_RPAR
  2116. shift_token
  2117. break
  2118. when T_SPACE
  2119. shift_token
  2120. end
  2121. name = case_insensitive_string
  2122. match(T_SPACE)
  2123. val = string
  2124. param[name] = val
  2125. end
  2126. return param
  2127. end
  2128. def body_ext_1part
  2129. token = lookahead
  2130. if token.symbol == T_SPACE
  2131. shift_token
  2132. else
  2133. return nil
  2134. end
  2135. md5 = nstring
  2136. token = lookahead
  2137. if token.symbol == T_SPACE
  2138. shift_token
  2139. else
  2140. return md5
  2141. end
  2142. disposition = body_fld_dsp
  2143. token = lookahead
  2144. if token.symbol == T_SPACE
  2145. shift_token
  2146. else
  2147. return md5, disposition
  2148. end
  2149. language = body_fld_lang
  2150. token = lookahead
  2151. if token.symbol == T_SPACE
  2152. shift_token
  2153. else
  2154. return md5, disposition, language
  2155. end
  2156. extension = body_extensions
  2157. return md5, disposition, language, extension
  2158. end
  2159. def body_ext_mpart
  2160. token = lookahead
  2161. if token.symbol == T_SPACE
  2162. shift_token
  2163. else
  2164. return nil
  2165. end
  2166. param = body_fld_param
  2167. token = lookahead
  2168. if token.symbol == T_SPACE
  2169. shift_token
  2170. else
  2171. return param
  2172. end
  2173. disposition = body_fld_dsp
  2174. match(T_SPACE)
  2175. language = body_fld_lang
  2176. token = lookahead
  2177. if token.symbol == T_SPACE
  2178. shift_token
  2179. else
  2180. return param, disposition, language
  2181. end
  2182. extension = body_extensions
  2183. return param, disposition, language, extension
  2184. end
  2185. def body_fld_dsp
  2186. token = lookahead
  2187. if token.symbol == T_NIL
  2188. shift_token
  2189. return nil
  2190. end
  2191. match(T_LPAR)
  2192. dsp_type = case_insensitive_string
  2193. match(T_SPACE)
  2194. param = body_fld_param
  2195. match(T_RPAR)
  2196. return ContentDisposition.new(dsp_type, param)
  2197. end
  2198. def body_fld_lang
  2199. token = lookahead
  2200. if token.symbol == T_LPAR
  2201. shift_token
  2202. result = []
  2203. while true
  2204. token = lookahead
  2205. case token.symbol
  2206. when T_RPAR
  2207. shift_token
  2208. return result
  2209. when T_SPACE
  2210. shift_token
  2211. end
  2212. result.push(case_insensitive_string)
  2213. end
  2214. else
  2215. lang = nstring
  2216. if lang
  2217. return lang.upcase
  2218. else
  2219. return lang
  2220. end
  2221. end
  2222. end
  2223. def body_extensions
  2224. result = []
  2225. while true
  2226. token = lookahead
  2227. case token.symbol
  2228. when T_RPAR
  2229. return result
  2230. when T_SPACE
  2231. shift_token
  2232. end
  2233. result.push(body_extension)
  2234. end
  2235. end
  2236. def body_extension
  2237. token = lookahead
  2238. case token.symbol
  2239. when T_LPAR
  2240. shift_token
  2241. result = body_extensions
  2242. match(T_RPAR)
  2243. return result
  2244. when T_NUMBER
  2245. return number
  2246. else
  2247. return nstring
  2248. end
  2249. end
  2250. def section
  2251. str = ""
  2252. token = match(T_LBRA)
  2253. str.concat(token.value)
  2254. token = match(T_ATOM, T_NUMBER, T_RBRA)
  2255. if token.symbol == T_RBRA
  2256. str.concat(token.value)
  2257. return str
  2258. end
  2259. str.concat(token.value)
  2260. token = lookahead
  2261. if token.symbol == T_SPACE
  2262. shift_token
  2263. str.concat(token.value)
  2264. token = match(T_LPAR)
  2265. str.concat(token.value)
  2266. while true
  2267. token = lookahead
  2268. case token.symbol
  2269. when T_RPAR
  2270. str.concat(token.value)
  2271. shift_token
  2272. break
  2273. when T_SPACE
  2274. shift_token
  2275. str.concat(token.value)
  2276. end
  2277. str.concat(format_string(astring))
  2278. end
  2279. end
  2280. token = match(T_RBRA)
  2281. str.concat(token.value)
  2282. return str
  2283. end
  2284. def format_string(str)
  2285. case str
  2286. when ""
  2287. return '""'
  2288. when /[\x80-\xff\r\n]/n
  2289. # literal
  2290. return "{" + str.length.to_s + "}" + CRLF + str
  2291. when /[(){ \x00-\x1f\x7f%*"\\]/n
  2292. # quoted string
  2293. return '"' + str.gsub(/["\\]/n, "\\\\\\&") + '"'
  2294. else
  2295. # atom
  2296. return str
  2297. end
  2298. end
  2299. def uid_data
  2300. token = match(T_ATOM)
  2301. name = token.value.upcase
  2302. match(T_SPACE)
  2303. return name, number
  2304. end
  2305. def text_response
  2306. token = match(T_ATOM)
  2307. name = token.value.upcase
  2308. match(T_SPACE)
  2309. @lex_state = EXPR_TEXT
  2310. token = match(T_TEXT)
  2311. @lex_state = EXPR_BEG
  2312. return UntaggedResponse.new(name, token.value)
  2313. end
  2314. def flags_response
  2315. token = match(T_ATOM)
  2316. name = token.value.upcase
  2317. match(T_SPACE)
  2318. return UntaggedResponse.new(name, flag_list, @str)
  2319. end
  2320. def list_response
  2321. token = match(T_ATOM)
  2322. name = token.value.upcase
  2323. match(T_SPACE)
  2324. return UntaggedResponse.new(name, mailbox_list, @str)
  2325. end
  2326. def mailbox_list
  2327. attr = flag_list
  2328. match(T_SPACE)
  2329. token = match(T_QUOTED, T_NIL)
  2330. if token.symbol == T_NIL
  2331. delim = nil
  2332. else
  2333. delim = token.value
  2334. end
  2335. match(T_SPACE)
  2336. name = astring
  2337. return MailboxList.new(attr, delim, name)
  2338. end
  2339. def getquota_response
  2340. # If quota never established, get back
  2341. # `NO Quota root does not exist'.
  2342. # If quota removed, get `()' after the
  2343. # folder spec with no mention of `STORAGE'.
  2344. token = match(T_ATOM)
  2345. name = token.value.upcase
  2346. match(T_SPACE)
  2347. mailbox = astring
  2348. match(T_SPACE)
  2349. match(T_LPAR)
  2350. token = lookahead
  2351. case token.symbol
  2352. when T_RPAR
  2353. shift_token
  2354. data = MailboxQuota.new(mailbox, nil, nil)
  2355. return UntaggedResponse.new(name, data, @str)
  2356. when T_ATOM
  2357. shift_token
  2358. match(T_SPACE)
  2359. token = match(T_NUMBER)
  2360. usage = token.value
  2361. match(T_SPACE)
  2362. token = match(T_NUMBER)
  2363. quota = token.value
  2364. match(T_RPAR)
  2365. data = MailboxQuota.new(mailbox, usage, quota)
  2366. return UntaggedResponse.new(name, data, @str)
  2367. else
  2368. parse_error("unexpected token %s", token.symbol)
  2369. end
  2370. end
  2371. def getquotaroot_response
  2372. # Similar to getquota, but only admin can use getquota.
  2373. token = match(T_ATOM)
  2374. name = token.value.upcase
  2375. match(T_SPACE)
  2376. mailbox = astring
  2377. quotaroots = []
  2378. while true
  2379. token = lookahead
  2380. break unless token.symbol == T_SPACE
  2381. shift_token
  2382. quotaroots.push(astring)
  2383. end
  2384. data = MailboxQuotaRoot.new(mailbox, quotaroots)
  2385. return UntaggedResponse.new(name, data, @str)
  2386. end
  2387. def getacl_response
  2388. token = match(T_ATOM)
  2389. name = token.value.upcase
  2390. match(T_SPACE)
  2391. mailbox = astring
  2392. data = []
  2393. token = lookahead
  2394. if token.symbol == T_SPACE
  2395. shift_token
  2396. while true
  2397. token = lookahead
  2398. case token.symbol
  2399. when T_CRLF
  2400. break
  2401. when T_SPACE
  2402. shift_token
  2403. end
  2404. user = astring
  2405. match(T_SPACE)
  2406. rights = astring
  2407. ##XXX data.push([user, rights])
  2408. data.push(MailboxACLItem.new(user, rights))
  2409. end
  2410. end
  2411. return UntaggedResponse.new(name, data, @str)
  2412. end
  2413. def search_response
  2414. token = match(T_ATOM)
  2415. name = token.value.upcase
  2416. token = lookahead
  2417. if token.symbol == T_SPACE
  2418. shift_token
  2419. data = []
  2420. while true
  2421. token = lookahead
  2422. case token.symbol
  2423. when T_CRLF
  2424. break
  2425. when T_SPACE
  2426. shift_token
  2427. end
  2428. data.push(number)
  2429. end
  2430. else
  2431. data = []
  2432. end
  2433. return UntaggedResponse.new(name, data, @str)
  2434. end
  2435. def thread_response
  2436. token = match(T_ATOM)
  2437. name = token.value.upcase
  2438. token = lookahead
  2439. if token.symbol == T_SPACE
  2440. threads = []
  2441. while true
  2442. shift_token
  2443. token = lookahead
  2444. case token.symbol
  2445. when T_LPAR
  2446. threads << thread_branch(token)
  2447. when T_CRLF
  2448. break
  2449. end
  2450. end
  2451. else
  2452. # no member
  2453. threads = []
  2454. end
  2455. return UntaggedResponse.new(name, threads, @str)
  2456. end
  2457. def thread_branch(token)
  2458. rootmember = nil
  2459. lastmember = nil
  2460. while true
  2461. shift_token # ignore first T_LPAR
  2462. token = lookahead
  2463. case token.symbol
  2464. when T_NUMBER
  2465. # new member
  2466. newmember = ThreadMember.new(number, [])
  2467. if rootmember.nil?
  2468. rootmember = newmember
  2469. else
  2470. lastmember.children << newmember
  2471. end
  2472. lastmember = newmember
  2473. when T_SPACE
  2474. # do nothing
  2475. when T_LPAR
  2476. if rootmember.nil?
  2477. # dummy member
  2478. lastmember = rootmember = ThreadMember.new(nil, [])
  2479. end
  2480. lastmember.children << thread_branch(token)
  2481. when T_RPAR
  2482. break
  2483. end
  2484. end
  2485. return rootmember
  2486. end
  2487. def status_response
  2488. token = match(T_ATOM)
  2489. name = token.value.upcase
  2490. match(T_SPACE)
  2491. mailbox = astring
  2492. match(T_SPACE)
  2493. match(T_LPAR)
  2494. attr = {}
  2495. while true
  2496. token = lookahead
  2497. case token.symbol
  2498. when T_RPAR
  2499. shift_token
  2500. break
  2501. when T_SPACE
  2502. shift_token
  2503. end
  2504. token = match(T_ATOM)
  2505. key = token.value.upcase
  2506. match(T_SPACE)
  2507. val = number
  2508. attr[key] = val
  2509. end
  2510. data = StatusData.new(mailbox, attr)
  2511. return UntaggedResponse.new(name, data, @str)
  2512. end
  2513. def capability_response
  2514. token = match(T_ATOM)
  2515. name = token.value.upcase
  2516. match(T_SPACE)
  2517. data = []
  2518. while true
  2519. token = lookahead
  2520. case token.symbol
  2521. when T_CRLF
  2522. break
  2523. when T_SPACE
  2524. shift_token
  2525. end
  2526. data.push(atom.upcase)
  2527. end
  2528. return UntaggedResponse.new(name, data, @str)
  2529. end
  2530. def resp_text
  2531. @lex_state = EXPR_RTEXT
  2532. token = lookahead
  2533. if token.symbol == T_LBRA
  2534. code = resp_text_code
  2535. else
  2536. code = nil
  2537. end
  2538. token = match(T_TEXT)
  2539. @lex_state = EXPR_BEG
  2540. return ResponseText.new(code, token.value)
  2541. end
  2542. def resp_text_code
  2543. @lex_state = EXPR_BEG
  2544. match(T_LBRA)
  2545. token = match(T_ATOM)
  2546. name = token.value.upcase
  2547. case name
  2548. when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n
  2549. result = ResponseCode.new(name, nil)
  2550. when /\A(?:PERMANENTFLAGS)\z/n
  2551. match(T_SPACE)
  2552. result = ResponseCode.new(name, flag_list)
  2553. when /\A(?:UIDVALIDITY|UIDNEXT|UNSEEN)\z/n
  2554. match(T_SPACE)
  2555. result = ResponseCode.new(name, number)
  2556. else
  2557. match(T_SPACE)
  2558. @lex_state = EXPR_CTEXT
  2559. token = match(T_TEXT)
  2560. @lex_state = EXPR_BEG
  2561. result = ResponseCode.new(name, token.value)
  2562. end
  2563. match(T_RBRA)
  2564. @lex_state = EXPR_RTEXT
  2565. return result
  2566. end
  2567. def address_list
  2568. token = lookahead
  2569. if token.symbol == T_NIL
  2570. shift_token
  2571. return nil
  2572. else
  2573. result = []
  2574. match(T_LPAR)
  2575. while true
  2576. token = lookahead
  2577. case token.symbol
  2578. when T_RPAR
  2579. shift_token
  2580. break
  2581. when T_SPACE
  2582. shift_token
  2583. end
  2584. result.push(address)
  2585. end
  2586. return result
  2587. end
  2588. end
  2589. ADDRESS_REGEXP = /\G\
  2590. (?# 1: NAME )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
  2591. (?# 2: ROUTE )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
  2592. (?# 3: MAILBOX )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)") \
  2593. (?# 4: HOST )(?:NIL|"((?:[^\x80-\xff\x00\r\n"\\]|\\["\\])*)")\
  2594. \)/ni
  2595. def address
  2596. match(T_LPAR)
  2597. if @str.index(ADDRESS_REGEXP, @pos)
  2598. # address does not include literal.
  2599. @pos = $~.end(0)
  2600. name = $1
  2601. route = $2
  2602. mailbox = $3
  2603. host = $4
  2604. for s in [name, route, mailbox, host]
  2605. if s
  2606. s.gsub!(/\\(["\\])/n, "\\1")
  2607. end
  2608. end
  2609. else
  2610. name = nstring
  2611. match(T_SPACE)
  2612. route = nstring
  2613. match(T_SPACE)
  2614. mailbox = nstring
  2615. match(T_SPACE)
  2616. host = nstring
  2617. match(T_RPAR)
  2618. end
  2619. return Address.new(name, route, mailbox, host)
  2620. end
  2621. # def flag_list
  2622. # result = []
  2623. # match(T_LPAR)
  2624. # while true
  2625. # token = lookahead
  2626. # case token.symbol
  2627. # when T_RPAR
  2628. # shift_token
  2629. # break
  2630. # when T_SPACE
  2631. # shift_token
  2632. # end
  2633. # result.push(flag)
  2634. # end
  2635. # return result
  2636. # end
  2637. # def flag
  2638. # token = lookahead
  2639. # if token.symbol == T_BSLASH
  2640. # shift_token
  2641. # token = lookahead
  2642. # if token.symbol == T_STAR
  2643. # shift_token
  2644. # return token.value.intern
  2645. # else
  2646. # return atom.intern
  2647. # end
  2648. # else
  2649. # return atom
  2650. # end
  2651. # end
  2652. FLAG_REGEXP = /\
  2653. (?# FLAG )\\([^\x80-\xff(){ \x00-\x1f\x7f%"\\]+)|\
  2654. (?# ATOM )([^\x80-\xff(){ \x00-\x1f\x7f%*"\\]+)/n
  2655. def flag_list
  2656. if @str.index(/\(([^)]*)\)/ni, @pos)
  2657. @pos = $~.end(0)
  2658. return $1.scan(FLAG_REGEXP).collect { |flag, atom|
  2659. atom || flag.capitalize.intern
  2660. }
  2661. else
  2662. parse_error("invalid flag list")
  2663. end
  2664. end
  2665. def nstring
  2666. token = lookahead
  2667. if token.symbol == T_NIL
  2668. shift_token
  2669. return nil
  2670. else
  2671. return string
  2672. end
  2673. end
  2674. def astring
  2675. token = lookahead
  2676. if string_token?(token)
  2677. return string
  2678. else
  2679. return atom
  2680. end
  2681. end
  2682. def string
  2683. token = lookahead
  2684. if token.symbol == T_NIL
  2685. shift_token
  2686. return nil
  2687. end
  2688. token = match(T_QUOTED, T_LITERAL)
  2689. return token.value
  2690. end
  2691. STRING_TOKENS = [T_QUOTED, T_LITERAL, T_NIL]
  2692. def string_token?(token)
  2693. return STRING_TOKENS.include?(token.symbol)
  2694. end
  2695. def case_insensitive_string
  2696. token = lookahead
  2697. if token.symbol == T_NIL
  2698. shift_token
  2699. return nil
  2700. end
  2701. token = match(T_QUOTED, T_LITERAL)
  2702. return token.value.upcase
  2703. end
  2704. def atom
  2705. result = ""
  2706. while true
  2707. token = lookahead
  2708. if atom_token?(token)
  2709. result.concat(token.value)
  2710. shift_token
  2711. else
  2712. if result.empty?
  2713. parse_error("unexpected token %s", token.symbol)
  2714. else
  2715. return result
  2716. end
  2717. end
  2718. end
  2719. end
  2720. ATOM_TOKENS = [
  2721. T_ATOM,
  2722. T_NUMBER,
  2723. T_NIL,
  2724. T_LBRA,
  2725. T_RBRA,
  2726. T_PLUS
  2727. ]
  2728. def atom_token?(token)
  2729. return ATOM_TOKENS.include?(token.symbol)
  2730. end
  2731. def number
  2732. token = lookahead
  2733. if token.symbol == T_NIL
  2734. shift_token
  2735. return nil
  2736. end
  2737. token = match(T_NUMBER)
  2738. return token.value.to_i
  2739. end
  2740. def nil_atom
  2741. match(T_NIL)
  2742. return nil
  2743. end
  2744. def match(*args)
  2745. token = lookahead
  2746. unless args.include?(token.symbol)
  2747. parse_error('unexpected token %s (expected %s)',
  2748. token.symbol.id2name,
  2749. args.collect {|i| i.id2name}.join(" or "))
  2750. end
  2751. shift_token
  2752. return token
  2753. end
  2754. def lookahead
  2755. unless @token
  2756. @token = next_token
  2757. end
  2758. return @token
  2759. end
  2760. def shift_token
  2761. @token = nil
  2762. end
  2763. def next_token
  2764. case @lex_state
  2765. when EXPR_BEG
  2766. if @str.index(BEG_REGEXP, @pos)
  2767. @pos = $~.end(0)
  2768. if $1
  2769. return Token.new(T_SPACE, $+)
  2770. elsif $2
  2771. return Token.new(T_NIL, $+)
  2772. elsif $3
  2773. return Token.new(T_NUMBER, $+)
  2774. elsif $4
  2775. return Token.new(T_ATOM, $+)
  2776. elsif $5
  2777. return Token.new(T_QUOTED,
  2778. $+.gsub(/\\(["\\])/n, "\\1"))
  2779. elsif $6
  2780. return Token.new(T_LPAR, $+)
  2781. elsif $7
  2782. return Token.new(T_RPAR, $+)
  2783. elsif $8
  2784. return Token.new(T_BSLASH, $+)
  2785. elsif $9
  2786. return Token.new(T_STAR, $+)
  2787. elsif $10
  2788. return Token.new(T_LBRA, $+)
  2789. elsif $11
  2790. return Token.new(T_RBRA, $+)
  2791. elsif $12
  2792. len = $+.to_i
  2793. val = @str[@pos, len]
  2794. @pos += len
  2795. return Token.new(T_LITERAL, val)
  2796. elsif $13
  2797. return Token.new(T_PLUS, $+)
  2798. elsif $14
  2799. return Token.new(T_PERCENT, $+)
  2800. elsif $15
  2801. return Token.new(T_CRLF, $+)
  2802. elsif $16
  2803. return Token.new(T_EOF, $+)
  2804. else
  2805. parse_error("[Net::IMAP BUG] BEG_REGEXP is invalid")
  2806. end
  2807. else
  2808. @str.index(/\S*/n, @pos)
  2809. parse_error("unknown token - %s", $&.dump)
  2810. end
  2811. when EXPR_DATA
  2812. if @str.index(DATA_REGEXP, @pos)
  2813. @pos = $~.end(0)
  2814. if $1
  2815. return Token.new(T_SPACE, $+)
  2816. elsif $2
  2817. return Token.new(T_NIL, $+)
  2818. elsif $3
  2819. return Token.new(T_NUMBER, $+)
  2820. elsif $4
  2821. return Token.new(T_QUOTED,
  2822. $+.gsub(/\\(["\\])/n, "\\1"))
  2823. elsif $5
  2824. len = $+.to_i
  2825. val = @str[@pos, len]
  2826. @pos += len
  2827. return Token.new(T_LITERAL, val)
  2828. elsif $6
  2829. return Token.new(T_LPAR, $+)
  2830. elsif $7
  2831. return Token.new(T_RPAR, $+)
  2832. else
  2833. parse_error("[Net::IMAP BUG] DATA_REGEXP is invalid")
  2834. end
  2835. else
  2836. @str.index(/\S*/n, @pos)
  2837. parse_error("unknown token - %s", $&.dump)
  2838. end
  2839. when EXPR_TEXT
  2840. if @str.index(TEXT_REGEXP, @pos)
  2841. @pos = $~.end(0)
  2842. if $1
  2843. return Token.new(T_TEXT, $+)
  2844. else
  2845. parse_error("[Net::IMAP BUG] TEXT_REGEXP is invalid")
  2846. end
  2847. else
  2848. @str.index(/\S*/n, @pos)
  2849. parse_error("unknown token - %s", $&.dump)
  2850. end
  2851. when EXPR_RTEXT
  2852. if @str.index(RTEXT_REGEXP, @pos)
  2853. @pos = $~.end(0)
  2854. if $1
  2855. return Token.new(T_LBRA, $+)
  2856. elsif $2
  2857. return Token.new(T_TEXT, $+)
  2858. else
  2859. parse_error("[Net::IMAP BUG] RTEXT_REGEXP is invalid")
  2860. end
  2861. else
  2862. @str.index(/\S*/n, @pos)
  2863. parse_error("unknown token - %s", $&.dump)
  2864. end
  2865. when EXPR_CTEXT
  2866. if @str.index(CTEXT_REGEXP, @pos)
  2867. @pos = $~.end(0)
  2868. if $1
  2869. return Token.new(T_TEXT, $+)
  2870. else
  2871. parse_error("[Net::IMAP BUG] CTEXT_REGEXP is invalid")
  2872. end
  2873. else
  2874. @str.index(/\S*/n, @pos) #/
  2875. parse_error("unknown token - %s", $&.dump)
  2876. end
  2877. else
  2878. parse_error("illegal @lex_state - %s", @lex_state.inspect)
  2879. end
  2880. end
  2881. def parse_error(fmt, *args)
  2882. if IMAP.debug
  2883. $stderr.printf("@str: %s\n", @str.dump)
  2884. $stderr.printf("@pos: %d\n", @pos)
  2885. $stderr.printf("@lex_state: %s\n", @lex_state)
  2886. if @token
  2887. $stderr.printf("@token.symbol: %s\n", @token.symbol)
  2888. $stderr.printf("@token.value: %s\n", @token.value.inspect)
  2889. end
  2890. end
  2891. raise ResponseParseError, format(fmt, *args)
  2892. end
  2893. end
  2894. # Authenticator for the "LOGIN" authentication type. See
  2895. # #authenticate().
  2896. class LoginAuthenticator
  2897. def process(data)
  2898. case @state
  2899. when STATE_USER
  2900. @state = STATE_PASSWORD
  2901. return @user
  2902. when STATE_PASSWORD
  2903. return @password
  2904. end
  2905. end
  2906. private
  2907. STATE_USER = :USER
  2908. STATE_PASSWORD = :PASSWORD
  2909. def initialize(user, password)
  2910. @user = user
  2911. @password = password
  2912. @state = STATE_USER
  2913. end
  2914. end
  2915. add_authenticator "LOGIN", LoginAuthenticator
  2916. # Authenticator for the "CRAM-MD5" authentication type. See
  2917. # #authenticate().
  2918. class CramMD5Authenticator
  2919. def process(challenge)
  2920. digest = hmac_md5(challenge, @password)
  2921. return @user + " " + digest
  2922. end
  2923. private
  2924. def initialize(user, password)
  2925. @user = user
  2926. @password = password
  2927. end
  2928. def hmac_md5(text, key)
  2929. if key.length > 64
  2930. key = Digest::MD5.digest(key)
  2931. end
  2932. k_ipad = key + "\0" * (64 - key.length)
  2933. k_opad = key + "\0" * (64 - key.length)
  2934. for i in 0..63
  2935. k_ipad[i] ^= 0x36
  2936. k_opad[i] ^= 0x5c
  2937. end
  2938. digest = Digest::MD5.digest(k_ipad + text)
  2939. return Digest::MD5.hexdigest(k_opad + digest)
  2940. end
  2941. end
  2942. add_authenticator "CRAM-MD5", CramMD5Authenticator
  2943. # Superclass of IMAP errors.
  2944. class Error < StandardError
  2945. end
  2946. # Error raised when data is in the incorrect format.
  2947. class DataFormatError < Error
  2948. end
  2949. # Error raised when a response from the server is non-parseable.
  2950. class ResponseParseError < Error
  2951. end
  2952. # Superclass of all errors used to encapsulate "fail" responses
  2953. # from the server.
  2954. class ResponseError < Error
  2955. end
  2956. # Error raised upon a "NO" response from the server, indicating
  2957. # that the client command could not be completed successfully.
  2958. class NoResponseError < ResponseError
  2959. end
  2960. # Error raised upon a "BAD" response from the server, indicating
  2961. # that the client command violated the IMAP protocol, or an internal
  2962. # server failure has occurred.
  2963. class BadResponseError < ResponseError
  2964. end
  2965. # Error raised upon a "BYE" response from the server, indicating
  2966. # that the client is not being allowed to login, or has been timed
  2967. # out due to inactivity.
  2968. class ByeResponseError < ResponseError
  2969. end
  2970. end
  2971. end
  2972. if __FILE__ == $0
  2973. # :enddoc:
  2974. require "getoptlong"
  2975. $stdout.sync = true
  2976. $port = nil
  2977. $user = ENV["USER"] || ENV["LOGNAME"]
  2978. $auth = "login"
  2979. $ssl = false
  2980. def usage
  2981. $stderr.print <<EOF
  2982. usage: #{$0} [options] <host>
  2983. --help print this message
  2984. --port=PORT specifies port
  2985. --user=USER specifies user
  2986. --auth=AUTH specifies auth type
  2987. --ssl use ssl
  2988. EOF
  2989. end
  2990. def get_password
  2991. print "password: "
  2992. system("stty", "-echo")
  2993. begin
  2994. return gets.chop
  2995. ensure
  2996. system("stty", "echo")
  2997. print "\n"
  2998. end
  2999. end
  3000. def get_command
  3001. printf("%s@%s> ", $user, $host)
  3002. if line = gets
  3003. return line.strip.split(/\s+/)
  3004. else
  3005. return nil
  3006. end
  3007. end
  3008. parser = GetoptLong.new
  3009. parser.set_options(['--debug', GetoptLong::NO_ARGUMENT],
  3010. ['--help', GetoptLong::NO_ARGUMENT],
  3011. ['--port', GetoptLong::REQUIRED_ARGUMENT],
  3012. ['--user', GetoptLong::REQUIRED_ARGUMENT],
  3013. ['--auth', GetoptLong::REQUIRED_ARGUMENT],
  3014. ['--ssl', GetoptLong::NO_ARGUMENT])
  3015. begin
  3016. parser.each_option do |name, arg|
  3017. case name
  3018. when "--port"
  3019. $port = arg
  3020. when "--user"
  3021. $user = arg
  3022. when "--auth"
  3023. $auth = arg
  3024. when "--ssl"
  3025. $ssl = true
  3026. when "--debug"
  3027. Net::IMAP.debug = true
  3028. when "--help"
  3029. usage
  3030. exit(1)
  3031. end
  3032. end
  3033. rescue
  3034. usage
  3035. exit(1)
  3036. end
  3037. $host = ARGV.shift
  3038. unless $host
  3039. usage
  3040. exit(1)
  3041. end
  3042. $port ||= $ssl ? 993 : 143
  3043. imap = Net::IMAP.new($host, $port, $ssl)
  3044. begin
  3045. password = get_password
  3046. imap.authenticate($auth, $user, password)
  3047. while true
  3048. cmd, *args = get_command
  3049. break unless cmd
  3050. begin
  3051. case cmd
  3052. when "list"
  3053. for mbox in imap.list("", args[0] || "*")
  3054. if mbox.attr.include?(Net::IMAP::NOSELECT)
  3055. prefix = "!"
  3056. elsif mbox.attr.include?(Net::IMAP::MARKED)
  3057. prefix = "*"
  3058. else
  3059. prefix = " "
  3060. end
  3061. print prefix, mbox.name, "\n"
  3062. end
  3063. when "select"
  3064. imap.select(args[0] || "inbox")
  3065. print "ok\n"
  3066. when "close"
  3067. imap.close
  3068. print "ok\n"
  3069. when "summary"
  3070. unless messages = imap.responses["EXISTS"][-1]
  3071. puts "not selected"
  3072. next
  3073. end
  3074. if messages > 0
  3075. for data in imap.fetch(1..-1, ["ENVELOPE"])
  3076. print data.seqno, ": ", data.attr["ENVELOPE"].subject, "\n"
  3077. end
  3078. else
  3079. puts "no message"
  3080. end
  3081. when "fetch"
  3082. if args[0]
  3083. data = imap.fetch(args[0].to_i, ["RFC822.HEADER", "RFC822.TEXT"])[0]
  3084. puts data.attr["RFC822.HEADER"]
  3085. puts data.attr["RFC822.TEXT"]
  3086. else
  3087. puts "missing argument"
  3088. end
  3089. when "logout", "exit", "quit"
  3090. break
  3091. when "help", "?"
  3092. print <<EOF
  3093. list [pattern] list mailboxes
  3094. select [mailbox] select mailbox
  3095. close close mailbox
  3096. summary display summary
  3097. fetch [msgno] display message
  3098. logout logout
  3099. help, ? display help message
  3100. EOF
  3101. else
  3102. print "unknown command: ", cmd, "\n"
  3103. end
  3104. rescue Net::IMAP::Error
  3105. puts $!
  3106. end
  3107. end
  3108. ensure
  3109. imap.logout
  3110. imap.disconnect
  3111. end
  3112. end